Skip to content

Commit f7cba7b

Browse files
committed
Application Credential support
Adds the end-to-end support for consuming Keystone ApplicationCredentials (AC) in the Octavia operator, enabling Octavia API, Housekeeping, Workers and Healthmanager pods to use AC-based authentication when available. Signed-off-by: Veronika Fisarova <[email protected]>
1 parent 74a4a7e commit f7cba7b

File tree

8 files changed

+268
-15
lines changed

8 files changed

+268
-15
lines changed

api/go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,5 @@ replace k8s.io/component-base => k8s.io/component-base v0.31.13 //allow-merging
104104
replace github.com/rabbitmq/cluster-operator/v2 => github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec //allow-merging
105105

106106
replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e //allow-merging
107+
108+
replace github.com/openstack-k8s-operators/keystone-operator/api => github.com/Deydra71/keystone-operator/api v0.0.0-20251105080148-59c1e577e327

controllers/amphoracontroller_controller.go

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,19 @@ func (r *OctaviaAmphoraControllerReconciler) reconcileNormal(ctx context.Context
299299
// Handle secrets
300300
secretsVars := make(map[string]env.Setter)
301301

302+
// Verify Application Credentials if available
303+
ctrlResult, err := keystonev1.VerifyApplicationCredentialsForService(
304+
ctx,
305+
r.Client,
306+
instance.Namespace,
307+
octavia.ServiceName,
308+
&secretsVars,
309+
10*time.Second,
310+
)
311+
if (err != nil || ctrlResult != ctrl.Result{}) {
312+
return ctrlResult, err
313+
}
314+
302315
defaultFlavorID, err := amphoracontrollers.EnsureFlavors(ctx, instance, &Log, helper)
303316
if err != nil {
304317
Log.Info(fmt.Sprintf("Cannot define flavors: %s", err))
@@ -388,7 +401,7 @@ func (r *OctaviaAmphoraControllerReconciler) reconcileNormal(ctx context.Context
388401
instance.Status.Conditions.MarkTrue(condition.ServiceConfigReadyCondition, condition.ServiceConfigReadyMessage)
389402

390403
// Handle service update
391-
ctrlResult, err := r.reconcileUpdate(ctx)
404+
ctrlResult, err = r.reconcileUpdate(ctx)
392405
if err != nil {
393406
return ctrlResult, err
394407
} else if (ctrlResult != ctrl.Result{}) {
@@ -704,6 +717,16 @@ func (r *OctaviaAmphoraControllerReconciler) generateServiceSecrets(
704717
templateParameters["Password"] = servicePassword
705718
templateParameters["KeystoneInternalURL"] = keystoneInternalURL
706719
templateParameters["KeystonePublicURL"] = keystonePublicURL
720+
721+
// Check for Application Credentials
722+
if acData, err := keystonev1.GetApplicationCredentialFromSecret(ctx, helper.GetClient(), instance.Namespace, octavia.ServiceName); err != nil {
723+
Log.Error(err, "Failed to get ApplicationCredential for service", "service", octavia.ServiceName)
724+
return err
725+
} else if acData != nil {
726+
templateParameters["ApplicationCredentialID"] = acData.ID
727+
templateParameters["ApplicationCredentialSecret"] = acData.Secret
728+
Log.Info("Using ApplicationCredentials auth", "service", octavia.ServiceName)
729+
}
707730
templateParameters["ServiceRoleName"] = spec.Role
708731
templateParameters["LbMgmtNetworkId"] = templateVars.LbMgmtNetworkID
709732
templateParameters["LbSecurityGroupId"] = templateVars.LbSecurityGroupID
@@ -857,6 +880,42 @@ func (r *OctaviaAmphoraControllerReconciler) SetupWithManager(mgr ctrl.Manager)
857880
return nil
858881
}
859882

883+
// Application Credential secret watching function
884+
acSecretFn := func(_ context.Context, o client.Object) []reconcile.Request {
885+
name := o.GetName()
886+
ns := o.GetNamespace()
887+
result := []reconcile.Request{}
888+
889+
// Only handle Secret objects
890+
if _, isSecret := o.(*corev1.Secret); !isSecret {
891+
return nil
892+
}
893+
894+
// Check if this is an octavia AC secret by name pattern (ac-octavia-secret)
895+
expectedSecretName := keystonev1.GetACSecretName(octavia.ServiceName)
896+
if name == expectedSecretName {
897+
// get all OctaviaAmphoraController CRs in this namespace
898+
amphoraControllers := &octaviav1.OctaviaAmphoraControllerList{}
899+
listOpts := []client.ListOption{
900+
client.InNamespace(ns),
901+
}
902+
if err := r.List(context.Background(), amphoraControllers, listOpts...); err != nil {
903+
return nil
904+
}
905+
906+
// Enqueue reconcile for all amphora controller instances
907+
for _, cr := range amphoraControllers.Items {
908+
objKey := client.ObjectKey{
909+
Namespace: ns,
910+
Name: cr.Name,
911+
}
912+
result = append(result, reconcile.Request{NamespacedName: objKey})
913+
}
914+
}
915+
916+
return result
917+
}
918+
860919
return ctrl.NewControllerManagedBy(mgr).
861920
For(&octaviav1.OctaviaAmphoraController{}).
862921
Owns(&corev1.Service{}).
@@ -871,6 +930,8 @@ func (r *OctaviaAmphoraControllerReconciler) SetupWithManager(mgr ctrl.Manager)
871930
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
872931
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
873932
).
933+
Watches(&corev1.Secret{},
934+
handler.EnqueueRequestsFromMapFunc(acSecretFn)).
874935
Watches(&topologyv1.Topology{},
875936
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
876937
builder.WithPredicates(predicate.GenerationChangedPredicate{})).

controllers/octaviaapi_controller.go

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,42 @@ func (r *OctaviaAPIReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Ma
301301
return err
302302
}
303303

304+
// Application Credential secret watching function
305+
acSecretFn := func(_ context.Context, o client.Object) []reconcile.Request {
306+
name := o.GetName()
307+
ns := o.GetNamespace()
308+
result := []reconcile.Request{}
309+
310+
// Only handle Secret objects
311+
if _, isSecret := o.(*corev1.Secret); !isSecret {
312+
return nil
313+
}
314+
315+
// Check if this is an octavia AC secret by name pattern (ac-octavia-secret)
316+
expectedSecretName := keystonev1.GetACSecretName(octavia.ServiceName)
317+
if name == expectedSecretName {
318+
// get all OctaviaAPI CRs in this namespace
319+
octaviaAPIs := &octaviav1.OctaviaAPIList{}
320+
listOpts := []client.ListOption{
321+
client.InNamespace(ns),
322+
}
323+
if err := r.List(context.Background(), octaviaAPIs, listOpts...); err != nil {
324+
return nil
325+
}
326+
327+
// Enqueue reconcile for all octavia API instances
328+
for _, cr := range octaviaAPIs.Items {
329+
objKey := client.ObjectKey{
330+
Namespace: ns,
331+
Name: cr.Name,
332+
}
333+
result = append(result, reconcile.Request{NamespacedName: objKey})
334+
}
335+
}
336+
337+
return result
338+
}
339+
304340
return ctrl.NewControllerManagedBy(mgr).
305341
For(&octaviav1.OctaviaAPI{}).
306342
Owns(&keystonev1.KeystoneService{}).
@@ -316,6 +352,8 @@ func (r *OctaviaAPIReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Ma
316352
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
317353
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
318354
).
355+
Watches(&corev1.Secret{},
356+
handler.EnqueueRequestsFromMapFunc(acSecretFn)).
319357
Watches(&topologyv1.Topology{},
320358
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
321359
builder.WithPredicates(predicate.GenerationChangedPredicate{})).
@@ -650,6 +688,19 @@ func (r *OctaviaAPIReconciler) reconcileNormal(ctx context.Context, instance *oc
650688
// Secrets
651689
secretsVars := make(map[string]env.Setter)
652690

691+
// Verify Application Credentials if available
692+
ctrlResult, err := keystonev1.VerifyApplicationCredentialsForService(
693+
ctx,
694+
r.Client,
695+
instance.Namespace,
696+
octavia.ServiceName,
697+
&secretsVars,
698+
10*time.Second,
699+
)
700+
if (err != nil || ctrlResult != ctrl.Result{}) {
701+
return ctrlResult, err
702+
}
703+
653704
//
654705
// TLS input validation
655706
//
@@ -722,7 +773,7 @@ func (r *OctaviaAPIReconciler) reconcileNormal(ctx context.Context, instance *oc
722773
// - %-scripts secret holding scripts to e.g. bootstrap the service
723774
// - %-config secret holding minimal octavia config required to get the service up, user can add additional files to be added to the service
724775
//
725-
err := r.generateServiceSecrets(ctx, instance, helper, &secretsVars)
776+
err = r.generateServiceSecrets(ctx, instance, helper, &secretsVars)
726777
if err != nil {
727778
instance.Status.Conditions.Set(condition.FalseCondition(
728779
condition.ServiceConfigReadyCondition,
@@ -794,7 +845,7 @@ func (r *OctaviaAPIReconciler) reconcileNormal(ctx context.Context, instance *oc
794845
}
795846

796847
// Handle service init
797-
ctrlResult, err := r.reconcileInit(ctx, instance, helper, serviceLabels)
848+
ctrlResult, err = r.reconcileInit(ctx, instance, helper, serviceLabels)
798849
if err != nil {
799850
return ctrlResult, err
800851
} else if (ctrlResult != ctrl.Result{}) {
@@ -1105,6 +1156,17 @@ func (r *OctaviaAPIReconciler) generateServiceSecrets(
11051156
templateParameters["TenantDomainName"] = instance.Spec.TenantDomainName
11061157
templateParameters["KeystoneInternalURL"] = keystoneInternalURL
11071158
templateParameters["KeystonePublicURL"] = keystonePublicURL
1159+
1160+
// Check for Application Credentials
1161+
if acData, err := keystonev1.GetApplicationCredentialFromSecret(ctx, h.GetClient(), instance.Namespace, octavia.ServiceName); err != nil {
1162+
Log.Error(err, "Failed to get ApplicationCredential for service", "service", octavia.ServiceName)
1163+
return err
1164+
} else if acData != nil {
1165+
templateParameters["ApplicationCredentialID"] = acData.ID
1166+
templateParameters["ApplicationCredentialSecret"] = acData.Secret
1167+
Log.Info("Using ApplicationCredentials auth", "service", octavia.ServiceName)
1168+
}
1169+
11081170
templateParameters["NBConnection"], err = nbCluster.GetInternalEndpoint()
11091171
if err != nil {
11101172
return err

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,5 @@ replace k8s.io/component-base => k8s.io/component-base v0.31.13 //allow-merging
120120
replace github.com/rabbitmq/cluster-operator/v2 => github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec //allow-merging
121121

122122
replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e //allow-merging
123+
124+
replace github.com/openstack-k8s-operators/keystone-operator/api => github.com/Deydra71/keystone-operator/api v0.0.0-20251105080148-59c1e577e327

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-20251105080148-59c1e577e327 h1:Czf2Y2e7S4l6aXChDtdh+b5RKtIA+3HQtG9z9jZ80Lc=
2+
github.com/Deydra71/keystone-operator/api v0.0.0-20251105080148-59c1e577e327/go.mod h1:FMFoO4MjEQ85JpdLtDHxYSZxvJ9KzHua+HdKhpl0KRI=
13
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
24
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
35
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -100,8 +102,6 @@ github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e h1:E1OdwSpqWuDPCedyU
100102
github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e/go.mod h1:Shkl4HanLwDiiBzakv+con/aMGnVE2MAGvoKp5oyYUo=
101103
github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20251030184102-82d2cbaafd35 h1:QFFGu93A+XCvDUxZIgfBE4gB5hEdVQAIw+E8dF1kP/E=
102104
github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20251030184102-82d2cbaafd35/go.mod h1:qq8BCRxTEmLRriUsQ4HeDUzqltWg32MQPDTMhgbBGK4=
103-
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20251027074845-ed8154b20ad1 h1:QohvX44nxoV2GwvvOURGXYyDuCn4SCrnwubTKJtzehY=
104-
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20251027074845-ed8154b20ad1/go.mod h1:FMFoO4MjEQ85JpdLtDHxYSZxvJ9KzHua+HdKhpl0KRI=
105105
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251027074416-ab5c045dbe00 h1:Xih6tYYqiDVllo4fDGHqTPL+M2biO5YLOUmbiTqrW/I=
106106
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251027074416-ab5c045dbe00/go.mod h1:PMoNILOdQ1Ij7DyrKgljN6RAiq8pFM2AGsUb6mcxe98=
107107
github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20251027074416-ab5c045dbe00 h1:YwkGrTpeeAq9bk09u9Hp96BEZb8X3XgnMfoyxypelVM=

templates/octaviaamphoracontroller/config/octavia.conf

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,18 @@ controller_ip_port_list={{ .ControllerIPList }}
1515
[keystone_authtoken]
1616
www_authenticate_uri={{ .KeystonePublicURL }}
1717
auth_url={{ .KeystoneInternalURL }}
18+
{{ if (index . "ApplicationCredentialID") -}}
19+
auth_type=v3applicationcredential
20+
application_credential_id={{ .ApplicationCredentialID }}
21+
application_credential_secret={{ .ApplicationCredentialSecret }}
22+
{{- else -}}
23+
auth_type=password
1824
username={{ .ServiceUser }}
1925
password={{ .Password }}
26+
user_domain_name=Default
27+
{{- end }}
2028
project_name={{ .TenantName }}
2129
project_domain_name={{ .TenantDomainName }}
22-
user_domain_name=Default
23-
auth_type=password
2430
# memcache_use_advanced_pool=True
2531
# memcached_servers=FIXMEhost1:11211
2632
# region_name=regionOne
@@ -73,10 +79,16 @@ disable_local_log_storage=False
7379
[service_auth]
7480
project_name={{ .TenantName }}
7581
project_domain_name={{ .TenantDomainName }}
76-
user_domain_name=Default
77-
password={{ .Password }}
78-
username=octavia
82+
{{ if (index . "ApplicationCredentialID") -}}
83+
auth_type=v3applicationcredential
84+
application_credential_id={{ .ApplicationCredentialID }}
85+
application_credential_secret={{ .ApplicationCredentialSecret }}
86+
{{- else -}}
7987
auth_type=password
88+
username=octavia
89+
password={{ .Password }}
90+
user_domain_name=Default
91+
{{- end }}
8092
auth_url={{ .KeystoneInternalURL }}/v3
8193
region_name=regionOne
8294

templates/octaviaapi/config/octavia.conf

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,18 @@ stats_update_threads=4
1919
[keystone_authtoken]
2020
www_authenticate_uri={{ .KeystonePublicURL }}
2121
auth_url={{ .KeystoneInternalURL }}
22+
{{ if (index . "ApplicationCredentialID") -}}
23+
auth_type=v3applicationcredential
24+
application_credential_id={{ .ApplicationCredentialID }}
25+
application_credential_secret={{ .ApplicationCredentialSecret }}
26+
{{- else -}}
27+
auth_type=password
2228
username={{ .ServiceUser }}
2329
password={{ .Password }}
30+
user_domain_name=Default
31+
{{- end }}
2432
project_name={{ .TenantName }}
2533
project_domain_name={{ .TenantDomainName }}
26-
user_domain_name=Default
27-
auth_type=password
2834
# memcache_use_advanced_pool=True
2935
# memcached_servers=FIXMEhost1:11211
3036
# region_name=regionOne
@@ -75,10 +81,16 @@ disable_local_log_storage=False
7581
[service_auth]
7682
project_name={{ .TenantName }}
7783
project_domain_name={{ .TenantDomainName }}
78-
user_domain_name=Default
79-
password={{ .Password }}
80-
username=octavia
84+
{{ if (index . "ApplicationCredentialID") -}}
85+
auth_type=v3applicationcredential
86+
application_credential_id={{ .ApplicationCredentialID }}
87+
application_credential_secret={{ .ApplicationCredentialSecret }}
88+
{{- else -}}
8189
auth_type=password
90+
username=octavia
91+
password={{ .Password }}
92+
user_domain_name=Default
93+
{{- end }}
8294
auth_url={{ .KeystoneInternalURL }}/v3
8395
region_name=regionOne
8496

0 commit comments

Comments
 (0)