Skip to content

Commit d9a3d2e

Browse files
committed
Application Credential support
Signed-off-by: Veronika Fisarova <[email protected]>
1 parent b924833 commit d9a3d2e

File tree

11 files changed

+343
-31
lines changed

11 files changed

+343
-31
lines changed

api/go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,5 @@ require (
7676
// mschuppert: map to latest commit from release-4.16 tag
7777
// must consistent within modules and service operators
7878
replace github.com/openshift/api => github.com/openshift/api v0.0.0-20240830023148-b7d0481c9094 //allow-merging
79+
80+
replace github.com/openstack-k8s-operators/keystone-operator/api => github.com/Deydra71/keystone-operator/api v0.0.0-20250718124530-83b0609d1c8c

config/rbac/role.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ rules:
1616
- patch
1717
- update
1818
- watch
19+
- apiGroups:
20+
- ""
21+
resources:
22+
- secrets
23+
verbs:
24+
- get
25+
- list
26+
- watch
1927
- apiGroups:
2028
- ""
2129
resources:
@@ -211,6 +219,14 @@ rules:
211219
- get
212220
- list
213221
- watch
222+
- apiGroups:
223+
- keystone.openstack.org
224+
resources:
225+
- keystoneapplicationcredentials
226+
verbs:
227+
- get
228+
- list
229+
- watch
214230
- apiGroups:
215231
- keystone.openstack.org
216232
resources:

controllers/barbican_common.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,20 @@ import (
2424

2525
barbicanv1beta1 "github.com/openstack-k8s-operators/barbican-operator/api/v1beta1"
2626
topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1"
27+
keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1"
2728
"github.com/openstack-k8s-operators/lib-common/modules/common/condition"
2829
"github.com/openstack-k8s-operators/lib-common/modules/common/env"
2930
"github.com/openstack-k8s-operators/lib-common/modules/common/helper"
3031
"github.com/openstack-k8s-operators/lib-common/modules/common/secret"
3132
"github.com/openstack-k8s-operators/lib-common/modules/common/util"
33+
corev1 "k8s.io/api/core/v1"
3234
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
35+
"k8s.io/apimachinery/pkg/types"
36+
"sigs.k8s.io/controller-runtime/pkg/builder"
3337
"sigs.k8s.io/controller-runtime/pkg/client"
38+
"sigs.k8s.io/controller-runtime/pkg/handler"
39+
"sigs.k8s.io/controller-runtime/pkg/predicate"
40+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
3441
)
3542

3643
type conditionUpdater interface {
@@ -146,3 +153,51 @@ func GenerateSecretStoreTemplateMap(
146153
}
147154
return tempMap, nil
148155
}
156+
157+
// AddACWatches adds KeystoneApplicationCredential + Secret watches to the passed controller builder
158+
func AddACWatches(b *builder.Builder) *builder.Builder {
159+
const (
160+
acPrefix = "ac-"
161+
acSecSuffix = "-secret"
162+
)
163+
164+
acMap := handler.MapFunc(func(_ context.Context, obj client.Object) []reconcile.Request {
165+
name := obj.GetName()
166+
ns := obj.GetNamespace()
167+
168+
// must begin with "ac-"
169+
if !strings.HasPrefix(name, acPrefix) {
170+
return nil
171+
}
172+
trim := strings.TrimPrefix(name, acPrefix)
173+
// for Secrets also strip "-secret"
174+
if _, isSecret := obj.(*corev1.Secret); isSecret {
175+
if !strings.HasSuffix(trim, acSecSuffix) {
176+
return nil
177+
}
178+
trim = strings.TrimSuffix(trim, acSecSuffix)
179+
}
180+
181+
// enqueue reconcile only for barbican API controller
182+
svc := trim
183+
return []reconcile.Request{
184+
{NamespacedName: types.NamespacedName{Namespace: ns, Name: svc + "-api"}},
185+
}
186+
})
187+
188+
// watch the AC CR
189+
b = b.Watches(
190+
&keystonev1.KeystoneApplicationCredential{},
191+
handler.EnqueueRequestsFromMapFunc(acMap),
192+
builder.WithPredicates(predicate.GenerationChangedPredicate{}),
193+
)
194+
195+
// watch the AC kube Secret
196+
b = b.Watches(
197+
&corev1.Secret{},
198+
handler.EnqueueRequestsFromMapFunc(acMap),
199+
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
200+
)
201+
202+
return b
203+
}

controllers/barbican_controller.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ func (r *BarbicanReconciler) GetLogger(ctx context.Context) logr.Logger {
9999
//+kubebuilder:rbac:groups=keystone.openstack.org,resources=keystoneapis,verbs=get;list;watch;
100100
//+kubebuilder:rbac:groups=keystone.openstack.org,resources=keystoneservices,verbs=get;list;watch;create;update;patch;delete;
101101
//+kubebuilder:rbac:groups=keystone.openstack.org,resources=keystoneendpoints,verbs=get;list;watch;create;update;patch;delete;
102+
//+kubebuilder:rbac:groups=keystone.openstack.org,resources=keystoneapplicationcredentials,verbs=get;list;watch
102103
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete;
103104
//+kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;update;patch;delete;
104105
//+kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch;create;update;patch;delete;
@@ -113,6 +114,7 @@ func (r *BarbicanReconciler) GetLogger(ctx context.Context) logr.Logger {
113114

114115
// service account, role, rolebinding
115116
//+kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch;create;update;patch
117+
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch
116118
//+kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=roles,verbs=get;list;watch;create;update;patch
117119
//+kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=rolebindings,verbs=get;list;watch;create;update;patch
118120
//+kubebuilder:rbac:groups="security.openshift.io",resourceNames=anyuid,resources=securitycontextconstraints,verbs=use
@@ -705,6 +707,49 @@ func (r *BarbicanReconciler) generateServiceConfig(
705707
"EnableSecureRBAC": instance.Spec.BarbicanAPI.EnableSecureRBAC,
706708
}
707709

710+
templateParameters["UseApplicationCredentials"] = false
711+
// Fetch AC CR
712+
ac := &keystonev1.KeystoneApplicationCredential{}
713+
acName := types.NamespacedName{Namespace: instance.Namespace, Name: fmt.Sprintf("ac-%s", barbican.ServiceName)}
714+
// Look up the AC
715+
if err := r.Client.Get(ctx, acName, ac); err == nil {
716+
// Look up the AC secret
717+
secret := &corev1.Secret{}
718+
secName := types.NamespacedName{Namespace: ac.Namespace, Name: ac.Status.SecretName}
719+
if err := r.Client.Get(ctx, secName, secret); err != nil {
720+
if k8s_errors.IsNotFound(err) {
721+
return fmt.Errorf("%w: ApplicationCredential Secret %s", util.ErrNotFound, secName)
722+
}
723+
Log.Error(err, "Failed to fetch ApplicationCredential Secret", "secret", secName)
724+
return err
725+
}
726+
727+
// Validate required AC secret keys exist and are not empty
728+
acID, acIDExists := secret.Data["AC_ID"]
729+
acSecret, acSecretExists := secret.Data["AC_SECRET"]
730+
731+
if !acIDExists {
732+
return fmt.Errorf("%w: field AC_ID not found in ApplicationCredential Secret %s", util.ErrFieldNotFound, secName)
733+
}
734+
if !acSecretExists {
735+
return fmt.Errorf("%w: field AC_SECRET not found in ApplicationCredential Secret %s", util.ErrFieldNotFound, secName)
736+
}
737+
if len(acID) == 0 {
738+
return fmt.Errorf("%w: field AC_ID is empty in ApplicationCredential Secret %s", util.ErrFieldNotFound, secName)
739+
}
740+
if len(acSecret) == 0 {
741+
return fmt.Errorf("%w: field AC_SECRET is empty in ApplicationCredential Secret %s", util.ErrFieldNotFound, secName)
742+
}
743+
744+
templateParameters["UseApplicationCredentials"] = true
745+
templateParameters["ACID"] = string(acID)
746+
templateParameters["ACSecret"] = string(acSecret)
747+
Log.Info("Using ApplicationCredentials auth", "AC", secret.Name)
748+
} else if !k8s_errors.IsNotFound(err) {
749+
Log.Error(err, "Failed to fetch ApplicationCredential CR", "ac", acName)
750+
return err
751+
}
752+
708753
// To avoid a json parsing error in kolla files, we always need to set PKCS11ClientDataPath
709754
// This gets overridden in the PKCS11 section below if needed.
710755
templateParameters["PKCS11ClientDataPath"] = barbicanv1beta1.DefaultPKCS11ClientDataPath

controllers/barbicanapi_controller.go

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,11 @@ func (r *BarbicanAPIReconciler) reconcileNormal(ctx context.Context, instance *b
617617

618618
Log.Info(fmt.Sprintf("[API] Got secrets '%s'", instance.Name))
619619

620+
// Verify Application Credentials if available
621+
if res, err := r.verifyApplicationCredentials(ctx, helper, instance, &configVars); err != nil || res.RequeueAfter > 0 {
622+
return res, err
623+
}
624+
620625
//
621626
// TLS input validation
622627
//
@@ -994,7 +999,7 @@ func (r *BarbicanAPIReconciler) SetupWithManager(mgr ctrl.Manager) error {
994999
return err
9951000
}
9961001

997-
return ctrl.NewControllerManagedBy(mgr).
1002+
b := ctrl.NewControllerManagedBy(mgr).
9981003
For(&barbicanv1beta1.BarbicanAPI{}).
9991004
Owns(&corev1.Service{}).
10001005
Owns(&corev1.Secret{}).
@@ -1007,8 +1012,10 @@ func (r *BarbicanAPIReconciler) SetupWithManager(mgr ctrl.Manager) error {
10071012
).
10081013
Watches(&topologyv1.Topology{},
10091014
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
1010-
builder.WithPredicates(predicate.GenerationChangedPredicate{})).
1011-
Complete(r)
1015+
builder.WithPredicates(predicate.GenerationChangedPredicate{}),
1016+
)
1017+
b = AddACWatches(b)
1018+
return b.Complete(r)
10121019
}
10131020

10141021
func (r *BarbicanAPIReconciler) findObjectsForSrc(ctx context.Context, src client.Object) []reconcile.Request {
@@ -1044,3 +1051,89 @@ func (r *BarbicanAPIReconciler) findObjectsForSrc(ctx context.Context, src clien
10441051

10451052
return requests
10461053
}
1054+
1055+
// verifyApplicationCredentials handles Application Credentials validation
1056+
// It only uses AC if it's in a complete/ready state, otherwise continues with password auth
1057+
func (r *BarbicanAPIReconciler) verifyApplicationCredentials(
1058+
ctx context.Context,
1059+
_ *helper.Helper,
1060+
instance *barbicanv1beta1.BarbicanAPI,
1061+
configVars *map[string]env.Setter,
1062+
) (ctrl.Result, error) {
1063+
log := r.GetLogger(ctx)
1064+
1065+
// Check for Application Credential - only use it if it's fully ready
1066+
acName := fmt.Sprintf("ac-%s", barbican.ServiceName)
1067+
ac := &keystonev1.KeystoneApplicationCredential{}
1068+
1069+
if err := r.Client.Get(ctx, client.ObjectKey{Namespace: instance.Namespace, Name: acName}, ac); err == nil {
1070+
// AC CR exists - check if it's in ready state
1071+
if r.isACReady(ctx, ac) {
1072+
// AC is ready - add it to configVars for hash tracking
1073+
secretKey := types.NamespacedName{Namespace: instance.Namespace, Name: ac.Status.SecretName}
1074+
hash, res, err := secret.VerifySecret(
1075+
ctx,
1076+
secretKey,
1077+
[]string{"AC_ID", "AC_SECRET"},
1078+
r.Client,
1079+
10*time.Second,
1080+
)
1081+
if err != nil {
1082+
log.Info("ApplicationCredential secret verification failed, continuing with password auth", "error", err.Error())
1083+
} else if res.RequeueAfter > 0 {
1084+
return res, nil
1085+
} else {
1086+
// AC is ready and verified - add to configVars for change tracking
1087+
(*configVars)["secret-"+ac.Status.SecretName] = env.SetValue(hash)
1088+
log.Info("Using ApplicationCredential authentication")
1089+
}
1090+
} else {
1091+
// AC exists but not ready - wait for it
1092+
log.Info("ApplicationCredential exists but not ready, waiting")
1093+
instance.Status.Conditions.Set(condition.FalseCondition(
1094+
condition.InputReadyCondition,
1095+
condition.RequestedReason,
1096+
condition.SeverityInfo,
1097+
condition.InputReadyWaitingMessage))
1098+
return ctrl.Result{RequeueAfter: time.Duration(10) * time.Second}, nil
1099+
}
1100+
} else if !k8s_errors.IsNotFound(err) {
1101+
return ctrl.Result{}, err
1102+
}
1103+
1104+
return ctrl.Result{}, nil
1105+
}
1106+
1107+
// isACReady checks if ApplicationCredential is in a ready state with all required components
1108+
func (r *BarbicanAPIReconciler) isACReady(ctx context.Context, ac *keystonev1.KeystoneApplicationCredential) bool {
1109+
log := r.GetLogger(ctx)
1110+
1111+
// Check if AC has completed setup (secret name is populated)
1112+
if ac.Status.SecretName == "" {
1113+
log.V(1).Info("AC not ready: SecretName not populated", "ac", ac.Name)
1114+
return false
1115+
}
1116+
1117+
secret := &corev1.Secret{}
1118+
secretKey := types.NamespacedName{Namespace: ac.Namespace, Name: ac.Status.SecretName}
1119+
if err := r.Client.Get(ctx, secretKey, secret); err != nil {
1120+
log.V(1).Info("AC not ready: Secret not found", "secret", secretKey, "error", err)
1121+
return false
1122+
}
1123+
1124+
acID, acIDExists := secret.Data["AC_ID"]
1125+
acSecret, acSecretExists := secret.Data["AC_SECRET"]
1126+
1127+
if !acIDExists || !acSecretExists {
1128+
log.V(1).Info("AC not ready: Missing required fields", "secret", secretKey)
1129+
return false
1130+
}
1131+
1132+
if len(acID) == 0 || len(acSecret) == 0 {
1133+
log.V(1).Info("AC not ready: Empty required fields", "secret", secretKey)
1134+
return false
1135+
}
1136+
1137+
log.V(1).Info("AC is ready", "secret", secretKey)
1138+
return true
1139+
}

controllers/barbicankeystonelistener_controller.go

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ import (
2727
"github.com/openstack-k8s-operators/barbican-operator/pkg/barbican"
2828
"github.com/openstack-k8s-operators/barbican-operator/pkg/barbicankeystonelistener"
2929
topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1"
30-
31-
// keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1"
3230
"github.com/openstack-k8s-operators/lib-common/modules/common"
3331
"github.com/openstack-k8s-operators/lib-common/modules/common/condition"
3432
"github.com/openstack-k8s-operators/lib-common/modules/common/deployment"
@@ -337,23 +335,7 @@ func (r *BarbicanKeystoneListenerReconciler) reconcileDelete(ctx context.Context
337335
Log := r.GetLogger(ctx)
338336
Log.Info(fmt.Sprintf("Reconciling Service '%s' delete", instance.Name))
339337

340-
// Remove the finalizer from our KeystoneEndpoint CR
341-
//keystoneEndpoint, err := keystonev1.GetKeystoneEndpointWithName(ctx, helper, instance.Name, instance.Namespace)
342-
//if err != nil && !k8s_errors.IsNotFound(err) {
343-
// return ctrl.Result{}, err
344-
//}
345-
346-
/*
347-
if err == nil {
348-
if controllerutil.RemoveFinalizer(keystoneEndpoint, helper.GetFinalizer()) {
349-
err = r.Update(ctx, keystoneEndpoint)
350-
if err != nil && !k8s_errors.IsNotFound(err) {
351-
return ctrl.Result{}, err
352-
}
353-
util.LogForObject(helper, "Removed finalizer from our KeystoneEndpoint", instance)
354-
}
355-
}
356-
*/
338+
// KeystoneListener doesn't create KeystoneEndpoint, so no cleanup needed
357339

358340
// Remove finalizer on the Topology CR
359341
if ctrlResult, err := topologyv1.EnsureDeletedTopologyRef(
@@ -704,7 +686,7 @@ func (r *BarbicanKeystoneListenerReconciler) SetupWithManager(mgr ctrl.Manager)
704686
return err
705687
}
706688

707-
return ctrl.NewControllerManagedBy(mgr).
689+
b := ctrl.NewControllerManagedBy(mgr).
708690
For(&barbicanv1beta1.BarbicanKeystoneListener{}).
709691
// Owns(&corev1.Service{}).
710692
// Owns(&corev1.Secret{}).
@@ -717,8 +699,10 @@ func (r *BarbicanKeystoneListenerReconciler) SetupWithManager(mgr ctrl.Manager)
717699
).
718700
Watches(&topologyv1.Topology{},
719701
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
720-
builder.WithPredicates(predicate.GenerationChangedPredicate{})).
721-
Complete(r)
702+
builder.WithPredicates(predicate.GenerationChangedPredicate{}),
703+
)
704+
return b.Complete(r)
705+
722706
}
723707

724708
func (r *BarbicanKeystoneListenerReconciler) findObjectsForSrc(ctx context.Context, src client.Object) []reconcile.Request {

controllers/barbicanworker_controller.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -726,7 +726,7 @@ func (r *BarbicanWorkerReconciler) SetupWithManager(mgr ctrl.Manager) error {
726726
return err
727727
}
728728

729-
return ctrl.NewControllerManagedBy(mgr).
729+
b := ctrl.NewControllerManagedBy(mgr).
730730
For(&barbicanv1beta1.BarbicanWorker{}).
731731
// Owns(&corev1.Service{}).
732732
// Owns(&corev1.Secret{}).
@@ -739,8 +739,9 @@ func (r *BarbicanWorkerReconciler) SetupWithManager(mgr ctrl.Manager) error {
739739
).
740740
Watches(&topologyv1.Topology{},
741741
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
742-
builder.WithPredicates(predicate.GenerationChangedPredicate{})).
743-
Complete(r)
742+
builder.WithPredicates(predicate.GenerationChangedPredicate{}),
743+
)
744+
return b.Complete(r)
744745
}
745746

746747
func (r *BarbicanWorkerReconciler) findObjectsForSrc(ctx context.Context, src client.Object) []reconcile.Request {

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,5 @@ replace github.com/openshift/api => github.com/openshift/api v0.0.0-202408300231
9292

9393
// custom RabbitmqClusterSpecCore for OpenStackControlplane (v2.6.0_patches_tag)
9494
replace github.com/rabbitmq/cluster-operator/v2 => github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250717122149-12f70b7f3d8d //allow-merging
95+
96+
replace github.com/openstack-k8s-operators/keystone-operator/api => github.com/Deydra71/keystone-operator/api v0.0.0-20250718124530-83b0609d1c8c

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
github.com/Deydra71/keystone-operator/api v0.0.0-20250718124530-83b0609d1c8c h1:283YBuEmIfplwstu3oQvYWATABVCV7QS1jQztP7teFg=
2+
github.com/Deydra71/keystone-operator/api v0.0.0-20250718124530-83b0609d1c8c/go.mod h1:0/8UQBzOyJYqHjarBfm2Rsw4xbQ8tywtKxSbTFbLbO0=
13
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
24
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
35
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
@@ -80,8 +82,6 @@ github.com/openshift/api v0.0.0-20240830023148-b7d0481c9094 h1:J1wuGhVxpsHykZBa6
8082
github.com/openshift/api v0.0.0-20240830023148-b7d0481c9094/go.mod h1:CxgbWAlvu2iQB0UmKTtRu1YfepRg1/vJ64n2DlIEVz4=
8183
github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20250802211136-f5a38d83e342 h1:YnvkneTck4ir4kEbwuSCZFgKG/Iwy75PCyVjI1+r1oY=
8284
github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20250802211136-f5a38d83e342/go.mod h1:Dv8qpmBIQy3Jv/EyQnOyc0w61X8vyfxpjcIQONP5CwY=
83-
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20250802061907-896a24e4fc36 h1:Z9aVKpwjQq1m8WKm/pOctbYHxYm6YKV+5tOPFE+13qQ=
84-
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20250802061907-896a24e4fc36/go.mod h1:Xic8tNbumSe5WFoA8MlNp1jRJpUtGHzGYyJKMilOqx8=
8585
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20250730071847-837b07f8d72f h1:DW8aNjEtDFrWiZ6vWuOXwdRB4eBD0n+bA9foQkOEx6U=
8686
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20250730071847-837b07f8d72f/go.mod h1:P+7F1wiwZUxOy4myYXFyc/uBtGATDFpk3yAllXe1Vzk=
8787
github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20250730071847-837b07f8d72f h1:nGYLHcpM7EjiSzN4bmiLZbxty9u0k0Qzvkqn+1s1TF0=

0 commit comments

Comments
 (0)