From a1a7e6a68acb9e68b46376dd883c0babf8abd8a4 Mon Sep 17 00:00:00 2001 From: Veronika Fisarova Date: Mon, 3 Nov 2025 11:48:19 +0100 Subject: [PATCH] Application Credential support Signed-off-by: Veronika Fisarova --- api/go.mod | 2 + controllers/manila_controller.go | 11 +++ controllers/manilaapi_controller.go | 42 +++++++++ go.mod | 2 + go.sum | 4 +- templates/manila/config/00-config.conf | 42 +++++++-- test/functional/manila_controller_test.go | 103 ++++++++++++++++++++++ 7 files changed, 195 insertions(+), 11 deletions(-) diff --git a/api/go.mod b/api/go.mod index f3c09322..1331206c 100644 --- a/api/go.mod +++ b/api/go.mod @@ -95,3 +95,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-20251103091514-244e15fe5d63 diff --git a/controllers/manila_controller.go b/controllers/manila_controller.go index 29403978..f6e2f6bd 100644 --- a/controllers/manila_controller.go +++ b/controllers/manila_controller.go @@ -1025,6 +1025,17 @@ func (r *ManilaReconciler) generateServiceConfig( // Check if Quorum Queues are enabled templateParameters["QuorumQueues"] = transportURLQuorumQueues + // Check for Application Credentials + templateParameters["UseApplicationCredentials"] = false + if acData, err := keystonev1.GetApplicationCredentialFromSecret(ctx, r.Client, instance.Namespace, manila.ServiceName); err != nil { + h.GetLogger().Error(err, "Failed to get ApplicationCredential for service", "service", manila.ServiceName) + } else if acData != nil { + templateParameters["UseApplicationCredentials"] = true + templateParameters["ApplicationCredentialID"] = acData.ID + templateParameters["ApplicationCredentialSecret"] = acData.Secret + h.GetLogger().Info("Using ApplicationCredentials auth", "service", manila.ServiceName) + } + // create httpd vhost template parameters httpdVhostConfig := map[string]any{} for _, endpt := range []service.Endpoint{service.EndpointInternal, service.EndpointPublic} { diff --git a/controllers/manilaapi_controller.go b/controllers/manilaapi_controller.go index 9ac6b097..111f685f 100644 --- a/controllers/manilaapi_controller.go +++ b/controllers/manilaapi_controller.go @@ -276,6 +276,41 @@ func (r *ManilaAPIReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Man return nil } + // Application Credential secret watching function + acSecretFn := func(_ 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 manila AC secret by name pattern (ac-manila-secret) + if name == keystonev1.GetACSecretName(manila.ServiceName) { + // get all ManilaAPI CRs in this namespace + manilaAPIs := &manilav1beta1.ManilaAPIList{} + listOpts := []client.ListOption{ + client.InNamespace(ns), + } + if err := r.List(context.Background(), manilaAPIs, listOpts...); err != nil { + return nil + } + + // Enqueue reconcile for all manila API instances + for _, cr := range manilaAPIs.Items { + objKey := client.ObjectKey{ + Namespace: ns, + Name: cr.Name, + } + result = append(result, reconcile.Request{NamespacedName: objKey}) + } + } + + return result + } + // index passwordSecretField if err := mgr.GetFieldIndexer().IndexField(context.Background(), &manilav1beta1.ManilaAPI{}, passwordSecretField, func(rawObj client.Object) []string { // Extract the secret name from the spec, if one is provided @@ -351,6 +386,8 @@ func (r *ManilaAPIReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Man handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc), builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), ). + Watches(&corev1.Secret{}, + handler.EnqueueRequestsFromMapFunc(acSecretFn)). Watches(&topologyv1.Topology{}, handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc), builder.WithPredicates(predicate.GenerationChangedPredicate{})). @@ -725,6 +762,11 @@ func (r *ManilaAPIReconciler) reconcileNormal(ctx context.Context, instance *man } instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage) + // Verify Application Credentials if available + if ctrlResult, err = keystonev1.VerifyApplicationCredentialsForService(ctx, r.Client, instance.Namespace, manila.ServiceName, &configVars, manila.NormalDuration); err != nil || ctrlResult.RequeueAfter > 0 { + return ctrlResult, err + } + // // TLS input validation // diff --git a/go.mod b/go.mod index b5c91c50..64d28285 100644 --- a/go.mod +++ b/go.mod @@ -119,3 +119,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-20251103091514-244e15fe5d63 diff --git a/go.sum b/go.sum index 0a311d09..086544b9 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/Deydra71/keystone-operator/api v0.0.0-20251103091514-244e15fe5d63 h1:ug2YPMQJ/+0ifOjFyaPx1YtX0zsVnL02pB2ngacYviw= +github.com/Deydra71/keystone-operator/api v0.0.0-20251103091514-244e15fe5d63/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/manila/config/00-config.conf b/templates/manila/config/00-config.conf index 81d11ca1..36837dca 100644 --- a/templates/manila/config/00-config.conf +++ b/templates/manila/config/00-config.conf @@ -31,12 +31,18 @@ auth_url = {{ .KeystoneInternalURL }} memcached_servers = {{ .MemcachedServers }} memcache_pool_dead_retry = 10 memcache_pool_conn_get_timeout = 2 +{{ if .UseApplicationCredentials -}} +auth_type = v3applicationcredential +application_credential_id = {{ .ApplicationCredentialID }} +application_credential_secret = {{ .ApplicationCredentialSecret }} +{{ else -}} auth_type = password +username = {{ .ServiceUser }} +password = {{ .ServicePassword }} +{{- end }} project_domain_name = Default user_domain_name = Default project_name = service -username = {{ .ServiceUser }} -password = {{ .ServicePassword }} interface = internal {{if (index . "MemcachedAuthCert")}} memcache_tls_certfile = {{ .MemcachedAuthCert }} @@ -47,19 +53,31 @@ memcache_tls_enabled = true [neutron] auth_url = {{ .KeystoneInternalURL }} -auth_type=password +{{ if .UseApplicationCredentials -}} +auth_type = v3applicationcredential +application_credential_id = {{ .ApplicationCredentialID }} +application_credential_secret = {{ .ApplicationCredentialSecret }} +{{ else -}} +auth_type = password +username = {{ .ServiceUser }} +password = {{ .ServicePassword }} +{{- end }} project_domain_name=Default project_name=service user_domain_name=Default -username = {{ .ServiceUser }} -password = {{ .ServicePassword }} [nova] interface = internal +{{ if .UseApplicationCredentials -}} +auth_type = v3applicationcredential +application_credential_id = {{ .ApplicationCredentialID }} +application_credential_secret = {{ .ApplicationCredentialSecret }} +{{ else -}} auth_type = password -auth_url = {{ .KeystoneInternalURL }} username = {{ .ServiceUser }} password = {{ .ServicePassword }} +{{- end }} +auth_url = {{ .KeystoneInternalURL }} user_domain_name = Default project_name = service project_domain_name = Default @@ -104,12 +122,18 @@ file_event_handler=/var/lib/manila auth_endpoint = {{ .KeystoneInternalURL }} barbican_endpoint_type = internal auth_url = {{ .KeystoneInternalURL }} -auth_type=password +{{ if .UseApplicationCredentials -}} +auth_type = v3applicationcredential +application_credential_id = {{ .ApplicationCredentialID }} +application_credential_secret = {{ .ApplicationCredentialSecret }} +{{ else -}} +auth_type = password +username = {{ .ServiceUser }} +password = {{ .ServicePassword }} +{{- end }} project_domain_name=Default project_name=service user_domain_name=Default -username = {{ .ServiceUser }} -password = {{ .ServicePassword }} [key_manager] backend = barbican diff --git a/test/functional/manila_controller_test.go b/test/functional/manila_controller_test.go index 01b6f1d8..95d3d418 100644 --- a/test/functional/manila_controller_test.go +++ b/test/functional/manila_controller_test.go @@ -27,11 +27,13 @@ import ( . "github.com/openstack-k8s-operators/lib-common/modules/common/test/helpers" mariadb_test "github.com/openstack-k8s-operators/mariadb-operator/api/test/helpers" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/types" memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1" topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" + keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" util "github.com/openstack-k8s-operators/lib-common/modules/common/util" manilav1 "github.com/openstack-k8s-operators/manila-operator/api/v1beta1" @@ -1459,6 +1461,106 @@ var _ = Describe("Manila controller", func() { }) + When("An ApplicationCredential is created for Manila", func() { + var ( + acName string + acSecretName string + servicePasswordSecret string + passwordSelector string + ) + BeforeEach(func() { + servicePasswordSecret = "ac-test-osp-secret" //nolint:gosec // G101 + passwordSelector = "ManilaPassword" + + DeferCleanup(th.DeleteInstance, CreateManilaSecret(manilaTest.Instance.Namespace, servicePasswordSecret)) + DeferCleanup(th.DeleteInstance, CreateManilaMessageBusSecret(manilaTest.Instance.Namespace, manilaTest.RabbitmqSecretName)) + DeferCleanup( + infra.DeleteMemcached, + infra.CreateMemcached(manilaTest.ManilaMemcached.Namespace, manilaTest.MemcachedInstance, memcachedSpec)) + infra.SimulateMemcachedReady(manilaTest.ManilaMemcached) + + spec := GetDefaultManilaSpec() + spec["secret"] = servicePasswordSecret + DeferCleanup(th.DeleteInstance, CreateManila(manilaTest.Instance, spec)) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + manilaTest.ManilaDatabaseName.Namespace, + GetManila(manilaTest.Instance).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(manilaTest.Instance.Namespace)) + + acName = fmt.Sprintf("ac-%s", manila.ServiceName) + acSecretName = acName + "-secret" + secret := &corev1.Secret{} + secret.Name = acSecretName + secret.Namespace = manilaTest.Instance.Namespace + secret.Data = map[string][]byte{ + "AC_ID": []byte("test-ac-id"), + "AC_SECRET": []byte("test-ac-secret"), + } + DeferCleanup(k8sClient.Delete, ctx, secret) + Expect(k8sClient.Create(ctx, secret)).To(Succeed()) + + ac := &keystonev1.KeystoneApplicationCredential{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: manilaTest.Instance.Namespace, + Name: acName, + }, + Spec: keystonev1.KeystoneApplicationCredentialSpec{ + UserName: manila.ServiceName, + Secret: servicePasswordSecret, + PasswordSelector: passwordSelector, + Roles: []string{"admin", "member"}, + AccessRules: []keystonev1.ACRule{{Service: "identity", Method: "POST", Path: "/auth/tokens"}}, + ExpirationDays: 30, + GracePeriodDays: 5, + }, + } + DeferCleanup(k8sClient.Delete, ctx, ac) + Expect(k8sClient.Create(ctx, ac)).To(Succeed()) + + fetched := &keystonev1.KeystoneApplicationCredential{} + key := types.NamespacedName{Namespace: ac.Namespace, Name: ac.Name} + Expect(k8sClient.Get(ctx, key, fetched)).To(Succeed()) + + fetched.Status.SecretName = acSecretName + now := metav1.Now() + readyCond := condition.Condition{ + Type: condition.ReadyCondition, + Status: corev1.ConditionTrue, + Reason: condition.ReadyReason, + Message: condition.ReadyMessage, + LastTransitionTime: now, + } + fetched.Status.Conditions = condition.Conditions{readyCond} + Expect(k8sClient.Status().Update(ctx, fetched)).To(Succeed()) + + infra.SimulateTransportURLReady(manilaTest.ManilaTransportURL) + mariadb.SimulateMariaDBAccountCompleted(manilaTest.ManilaDatabaseAccount) + mariadb.SimulateMariaDBDatabaseCompleted(manilaTest.ManilaDatabaseName) + }) + + It("should render ApplicationCredential auth in 00-config.conf", func() { + // Retrieve the generated config secret + configDataMap := th.GetSecret(manilaTest.ManilaConfigSecret) + + conf := configDataMap.Data["00-config.conf"] + Expect(string(conf)).Should( + ContainSubstring("auth_type = v3applicationcredential")) + Expect(string(conf)).Should( + ContainSubstring("application_credential_id = test-ac-id")) + Expect(string(conf)).Should( + ContainSubstring("application_credential_secret = test-ac-secret")) + Expect(string(conf)).Should( + Not(ContainSubstring("auth_type = password"))) + }) + }) + }) var _ = Describe("Manila Webhook", func() { @@ -1567,4 +1669,5 @@ var _ = Describe("Manila Webhook", func() { return instance, fmt.Sprintf("manilaShares[%s].topologyRef", instance) }), ) + })