Skip to content

Commit 0cd688b

Browse files
committed
Application Credential support
Adds the end-to-end support for consuming Keystone ApplicationCredentials (AC) in the Placement operator, enabling Placement pod to use AC-based authentication when available. Signed-off-by: Veronika Fisarova <vfisarov@redhat.com>
1 parent 1dd2c48 commit 0cd688b

6 files changed

Lines changed: 118 additions & 7 deletions

File tree

api/go.mod

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

9595
replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e //allow-merging
96+
97+
replace github.com/openstack-k8s-operators/keystone-operator/api => github.com/Deydra71/keystone-operator/api v0.0.0-20251211085602-3e1a3e022c81

go.mod

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

144144
replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e //allow-merging
145+
146+
replace github.com/openstack-k8s-operators/keystone-operator/api => github.com/Deydra71/keystone-operator/api v0.0.0-20251211085602-3e1a3e022c81

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-20251211085602-3e1a3e022c81 h1:plax+NFgJJL1SrERyXAnf3jOHRhLTtBlJ2oc7d84EoU=
2+
github.com/Deydra71/keystone-operator/api v0.0.0-20251211085602-3e1a3e022c81/go.mod h1:b98Jl8eyUw8V07l9YiuQnoMlnWC748oV8IhXH15NCC4=
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/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
@@ -120,8 +122,6 @@ github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e h1:E1OdwSpqWuDPCedyU
120122
github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e/go.mod h1:Shkl4HanLwDiiBzakv+con/aMGnVE2MAGvoKp5oyYUo=
121123
github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20251205192058-5cfbada0ab96 h1:hPgCcrbRHBPfngaEPe6coaCtcauMolI71lfcLdinrKI=
122124
github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20251205192058-5cfbada0ab96/go.mod h1:ZuglN7IqXfIo75WcJwe0NLHhu82Fs3k/5IXptqnO1H4=
123-
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20251128160419-8b3a77972a77 h1:XzVPjfzxDJwgW8sNGv9K577Ui2mb6Mp3sDItuDmTv9E=
124-
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20251128160419-8b3a77972a77/go.mod h1:b98Jl8eyUw8V07l9YiuQnoMlnWC748oV8IhXH15NCC4=
125125
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251122131503-b76943960b6c h1:wM8qXCB5mQwSosCvtaydzuXitWVVKBHTzH0A2znQ+Jg=
126126
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251122131503-b76943960b6c/go.mod h1:+Me0raWPPdz8gRi9D4z1khmvUgS9vIKAVC8ckg1yJZU=
127127
github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20251103072528-9eb684fef4ef h1:Ql4G7sRHpqWFGwXypN7MorDGUWv4jz5n34ayzVt3R9E=

internal/controller/placementapi_controller.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,41 @@ var allWatchFields = []string{
859859
topologyField,
860860
}
861861

862+
// Application Credential secret watching function
863+
func (r *PlacementAPIReconciler) acSecretFn(_ context.Context, o client.Object) []reconcile.Request {
864+
name := o.GetName()
865+
ns := o.GetNamespace()
866+
result := []reconcile.Request{}
867+
868+
// Only handle Secret objects
869+
if _, isSecret := o.(*corev1.Secret); !isSecret {
870+
return nil
871+
}
872+
873+
// Check if this is a placement AC secret by name pattern (ac-placement-secret)
874+
expectedSecretName := keystonev1.GetACSecretName(placement.ServiceName)
875+
if name == expectedSecretName {
876+
// get all PlacementAPI CRs in this namespace
877+
placementAPIs := &placementv1.PlacementAPIList{}
878+
listOpts := []client.ListOption{
879+
client.InNamespace(ns),
880+
}
881+
if err := r.List(context.Background(), placementAPIs, listOpts...); err != nil {
882+
return nil
883+
}
884+
885+
for _, cr := range placementAPIs.Items {
886+
result = append(result, reconcile.Request{
887+
NamespacedName: types.NamespacedName{
888+
Namespace: cr.Namespace,
889+
Name: cr.Name,
890+
},
891+
})
892+
}
893+
}
894+
return result
895+
}
896+
862897
// SetupWithManager sets up the controller with the Manager.
863898
func (r *PlacementAPIReconciler) SetupWithManager(mgr ctrl.Manager) error {
864899
// index passwordSecretField
@@ -940,6 +975,8 @@ func (r *PlacementAPIReconciler) SetupWithManager(mgr ctrl.Manager) error {
940975
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
941976
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
942977
).
978+
Watches(&corev1.Secret{},
979+
handler.EnqueueRequestsFromMapFunc(r.acSecretFn)).
943980
Watches(&topologyv1.Topology{},
944981
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
945982
builder.WithPredicates(predicate.GenerationChangedPredicate{})).
@@ -1378,6 +1415,18 @@ func (r *PlacementAPIReconciler) generateServiceConfigMaps(
13781415
),
13791416
}
13801417

1418+
templateParameters["UseApplicationCredentials"] = false
1419+
// Try to get Application Credential for this service
1420+
if acData, err := keystonev1.GetApplicationCredentialFromSecret(ctx, r.Client, instance.Namespace, placement.ServiceName); err != nil {
1421+
h.GetLogger().Error(err, "Failed to get ApplicationCredential for service", "service", placement.ServiceName)
1422+
return err
1423+
} else if acData != nil {
1424+
templateParameters["UseApplicationCredentials"] = true
1425+
templateParameters["ACID"] = acData.ID
1426+
templateParameters["ACSecret"] = acData.Secret
1427+
h.GetLogger().Info("Using ApplicationCredentials auth", "service", placement.ServiceName)
1428+
}
1429+
13811430
// create httpd vhost template parameters
13821431
httpdVhostConfig := map[string]any{}
13831432
for _, endpt := range []service.Endpoint{service.EndpointInternal, service.EndpointPublic} {

templates/placementapi/config/placement.conf

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,20 @@ connection = {{ .DatabaseConnection }}
1515
auth_strategy = keystone
1616

1717
[keystone_authtoken]
18-
project_domain_name = Default
19-
user_domain_name = Default
20-
project_name = service
21-
username = {{ .ServiceUser }}
22-
password = {{ .PlacementPassword }}
2318
www_authenticate_uri = {{ .KeystonePublicURL }}
2419
auth_url = {{ .KeystoneInternalURL }}
20+
{{ if .UseApplicationCredentials -}}
21+
auth_type = v3applicationcredential
22+
application_credential_id = {{ .ACID }}
23+
application_credential_secret = {{ .ACSecret }}
24+
{{ else -}}
2525
auth_type = password
26+
username = {{ .ServiceUser }}
27+
password = {{ .PlacementPassword }}
28+
user_domain_name = Default
29+
project_domain_name = Default
30+
project_name = service
31+
{{- end }}
2632
interface = internal
2733

2834
[oslo_policy]

test/functional/placementapi_controller_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1438,4 +1438,56 @@ var _ = Describe("PlacementAPI reconfiguration", func() {
14381438

14391439
})
14401440

1441+
When("an ApplicationCredential is created for Placement", func() {
1442+
BeforeEach(func() {
1443+
DeferCleanup(th.DeleteInstance, CreatePlacementAPI(names.PlacementAPIName, GetDefaultPlacementAPISpec()))
1444+
DeferCleanup(
1445+
k8sClient.Delete, ctx, CreatePlacementAPISecret(names.Namespace, SecretName))
1446+
keystoneAPIName := keystone.CreateKeystoneAPI(names.Namespace)
1447+
DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName)
1448+
1449+
DeferCleanup(
1450+
mariadb.DeleteDBService,
1451+
mariadb.CreateDBService(
1452+
names.Namespace,
1453+
GetDefaultPlacementAPISpec()["databaseInstance"].(string),
1454+
corev1.ServiceSpec{
1455+
Ports: []corev1.ServicePort{{Port: 3306}},
1456+
},
1457+
),
1458+
)
1459+
mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName)
1460+
mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount)
1461+
1462+
// Create AC secret
1463+
acSecretName := fmt.Sprintf("ac-%s-secret", placement.ServiceName)
1464+
acSecret := th.CreateSecret(
1465+
types.NamespacedName{Namespace: names.Namespace, Name: acSecretName},
1466+
map[string][]byte{
1467+
"AC_ID": []byte("test-ac-id"),
1468+
"AC_SECRET": []byte("test-ac-secret"),
1469+
},
1470+
)
1471+
DeferCleanup(th.DeleteInstance, acSecret)
1472+
})
1473+
1474+
It("should render ApplicationCredential auth in placement.conf", func() {
1475+
configSecret := th.GetSecret(names.ConfigMapName)
1476+
conf := configSecret.Data["placement.conf"]
1477+
1478+
// AC auth is configured
1479+
Expect(conf).To(ContainSubstring("auth_type = v3applicationcredential"))
1480+
Expect(conf).To(ContainSubstring("application_credential_id = test-ac-id"))
1481+
Expect(conf).To(ContainSubstring("application_credential_secret = test-ac-secret"))
1482+
1483+
// Password auth fields should not be present
1484+
Expect(conf).NotTo(ContainSubstring("auth_type = password"))
1485+
Expect(conf).NotTo(ContainSubstring("username ="))
1486+
Expect(conf).NotTo(ContainSubstring("password ="))
1487+
Expect(conf).NotTo(ContainSubstring("project_name ="))
1488+
Expect(conf).NotTo(ContainSubstring("user_domain_name ="))
1489+
Expect(conf).NotTo(ContainSubstring("project_domain_name ="))
1490+
})
1491+
})
1492+
14411493
})

0 commit comments

Comments
 (0)