From 09e4eb34ac52809711e142f52db8ae8738c94e07 Mon Sep 17 00:00:00 2001 From: Veronika Fisarova Date: Mon, 10 Nov 2025 12:27:20 +0100 Subject: [PATCH] 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 --- api/go.mod | 2 + controllers/placementapi_controller.go | 54 +++++++++++++++++++ go.mod | 2 + go.sum | 4 +- templates/placementapi/config/placement.conf | 16 ++++-- .../placementapi_controller_test.go | 42 +++++++++++++++ 6 files changed, 113 insertions(+), 7 deletions(-) diff --git a/api/go.mod b/api/go.mod index aaf114a1..4ec4d68e 100644 --- a/api/go.mod +++ b/api/go.mod @@ -94,3 +94,5 @@ replace k8s.io/component-base => k8s.io/component-base v0.31.13 //allow-merging replace github.com/rabbitmq/cluster-operator/v2 => github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec //allow-merging replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e //allow-merging + +replace github.com/openstack-k8s-operators/keystone-operator/api => github.com/Deydra71/keystone-operator/api v0.0.0-20251110074936-69e69f698212 diff --git a/controllers/placementapi_controller.go b/controllers/placementapi_controller.go index a0bdb2c8..2c2276b0 100644 --- a/controllers/placementapi_controller.go +++ b/controllers/placementapi_controller.go @@ -365,6 +365,11 @@ func (r *PlacementAPIReconciler) Reconcile(ctx context.Context, req ctrl.Request return result, nil } + // Verify Application Credentials if available + if result, err := keystonev1.VerifyApplicationCredentialsForService(ctx, r.Client, instance.Namespace, placement.ServiceName, &configMapVars, 10*time.Second); err != nil || result.RequeueAfter > 0 { + return result, err + } + err = r.generateServiceConfigMaps(ctx, h, instance, secret, &configMapVars, db) if err != nil { instance.Status.Conditions.Set(condition.FalseCondition( @@ -859,6 +864,41 @@ var allWatchFields = []string{ topologyField, } +// Application Credential secret watching function +func (r *PlacementAPIReconciler) acSecretFn(_ context.Context, o client.Object) []reconcile.Request { + name := o.GetName() + ns := o.GetNamespace() + result := []reconcile.Request{} + + // Only handle Secret objects + if _, isSecret := o.(*corev1.Secret); !isSecret { + return nil + } + + // Check if this is a placement AC secret by name pattern (ac-placement-secret) + expectedSecretName := keystonev1.GetACSecretName(placement.ServiceName) + if name == expectedSecretName { + // get all PlacementAPI CRs in this namespace + placementAPIs := &placementv1.PlacementAPIList{} + listOpts := []client.ListOption{ + client.InNamespace(ns), + } + if err := r.List(context.Background(), placementAPIs, listOpts...); err != nil { + return nil + } + + for _, cr := range placementAPIs.Items { + result = append(result, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: cr.Namespace, + Name: cr.Name, + }, + }) + } + } + return result +} + // SetupWithManager sets up the controller with the Manager. func (r *PlacementAPIReconciler) SetupWithManager(mgr ctrl.Manager) error { // index passwordSecretField @@ -940,6 +980,8 @@ func (r *PlacementAPIReconciler) SetupWithManager(mgr ctrl.Manager) error { handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc), builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), ). + Watches(&corev1.Secret{}, + handler.EnqueueRequestsFromMapFunc(r.acSecretFn)). Watches(&topologyv1.Topology{}, handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc), builder.WithPredicates(predicate.GenerationChangedPredicate{})). @@ -1378,6 +1420,18 @@ func (r *PlacementAPIReconciler) generateServiceConfigMaps( ), } + templateParameters["UseApplicationCredentials"] = false + // Try to get Application Credential for this service + if acData, err := keystonev1.GetApplicationCredentialFromSecret(ctx, r.Client, instance.Namespace, placement.ServiceName); err != nil { + h.GetLogger().Error(err, "Failed to get ApplicationCredential for service", "service", placement.ServiceName) + return err + } else if acData != nil { + templateParameters["UseApplicationCredentials"] = true + templateParameters["ACID"] = acData.ID + templateParameters["ACSecret"] = acData.Secret + h.GetLogger().Info("Using ApplicationCredentials auth", "service", placement.ServiceName) + } + // create httpd vhost template parameters httpdVhostConfig := map[string]any{} for _, endpt := range []service.Endpoint{service.EndpointInternal, service.EndpointPublic} { diff --git a/go.mod b/go.mod index 29277032..d7d5f4b5 100644 --- a/go.mod +++ b/go.mod @@ -115,3 +115,5 @@ replace k8s.io/component-base => k8s.io/component-base v0.31.13 //allow-merging replace github.com/rabbitmq/cluster-operator/v2 => github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec //allow-merging replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e //allow-merging + +replace github.com/openstack-k8s-operators/keystone-operator/api => github.com/Deydra71/keystone-operator/api v0.0.0-20251110074936-69e69f698212 diff --git a/go.sum b/go.sum index 10396416..ed50f256 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/Deydra71/keystone-operator/api v0.0.0-20251110074936-69e69f698212 h1:m/OIYRcJvoKnfhoJCAJDwN54cnu495rgQAngyGPTKvc= +github.com/Deydra71/keystone-operator/api v0.0.0-20251110074936-69e69f698212/go.mod h1:FMFoO4MjEQ85JpdLtDHxYSZxvJ9KzHua+HdKhpl0KRI= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= 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 github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e/go.mod h1:Shkl4HanLwDiiBzakv+con/aMGnVE2MAGvoKp5oyYUo= github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20251030184102-82d2cbaafd35 h1:QFFGu93A+XCvDUxZIgfBE4gB5hEdVQAIw+E8dF1kP/E= github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20251030184102-82d2cbaafd35/go.mod h1:qq8BCRxTEmLRriUsQ4HeDUzqltWg32MQPDTMhgbBGK4= -github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20251027074845-ed8154b20ad1 h1:QohvX44nxoV2GwvvOURGXYyDuCn4SCrnwubTKJtzehY= -github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20251027074845-ed8154b20ad1/go.mod h1:FMFoO4MjEQ85JpdLtDHxYSZxvJ9KzHua+HdKhpl0KRI= github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251027074416-ab5c045dbe00 h1:Xih6tYYqiDVllo4fDGHqTPL+M2biO5YLOUmbiTqrW/I= github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251027074416-ab5c045dbe00/go.mod h1:PMoNILOdQ1Ij7DyrKgljN6RAiq8pFM2AGsUb6mcxe98= github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20251021145236-2b84ec9fd9bb h1:wToXqX7AS1JV3Kna7RcJfkRart8rSGun2biKNfyY6Zg= diff --git a/templates/placementapi/config/placement.conf b/templates/placementapi/config/placement.conf index 2cdbd2f8..7e9db877 100644 --- a/templates/placementapi/config/placement.conf +++ b/templates/placementapi/config/placement.conf @@ -15,14 +15,20 @@ connection = {{ .DatabaseConnection }} auth_strategy = keystone [keystone_authtoken] -project_domain_name = Default -user_domain_name = Default -project_name = service -username = {{ .ServiceUser }} -password = {{ .PlacementPassword }} www_authenticate_uri = {{ .KeystonePublicURL }} auth_url = {{ .KeystoneInternalURL }} +{{ if .UseApplicationCredentials -}} +auth_type = v3applicationcredential +application_credential_id = {{ .ACID }} +application_credential_secret = {{ .ACSecret }} +{{ else -}} auth_type = password +username = {{ .ServiceUser }} +password = {{ .PlacementPassword }} +{{- end }} +user_domain_name = Default +project_domain_name = Default +project_name = service interface = internal [oslo_policy] diff --git a/tests/functional/placementapi_controller_test.go b/tests/functional/placementapi_controller_test.go index 4adefbb4..e37c9dc5 100644 --- a/tests/functional/placementapi_controller_test.go +++ b/tests/functional/placementapi_controller_test.go @@ -1438,4 +1438,46 @@ var _ = Describe("PlacementAPI reconfiguration", func() { }) + When("an ApplicationCredential is created for Placement", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreatePlacementAPI(names.PlacementAPIName, GetDefaultPlacementAPISpec())) + DeferCleanup( + k8sClient.Delete, ctx, CreatePlacementAPISecret(names.Namespace, SecretName)) + keystoneAPIName := keystone.CreateKeystoneAPI(names.Namespace) + DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName) + + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + names.Namespace, + GetDefaultPlacementAPISpec()["databaseInstance"].(string), + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + + // Create AC secret + acSecretName := fmt.Sprintf("ac-%s-secret", placement.ServiceName) + acSecret := th.CreateSecret( + types.NamespacedName{Namespace: names.Namespace, Name: acSecretName}, + map[string][]byte{ + "AC_ID": []byte("test-ac-id"), + "AC_SECRET": []byte("test-ac-secret"), + }, + ) + DeferCleanup(th.DeleteInstance, acSecret) + }) + + It("should render ApplicationCredential auth in placement.conf", func() { + configSecret := th.GetSecret(names.ConfigMapName) + conf := configSecret.Data["placement.conf"] + + Expect(conf).To(ContainSubstring("application_credential_id = test-ac-id")) + Expect(conf).To(ContainSubstring("application_credential_secret = test-ac-secret")) + }) + }) + })