diff --git a/api/bases/cinder.openstack.org_cinderapis.yaml b/api/bases/cinder.openstack.org_cinderapis.yaml index 104c6d12..9c46944d 100644 --- a/api/bases/cinder.openstack.org_cinderapis.yaml +++ b/api/bases/cinder.openstack.org_cinderapis.yaml @@ -52,6 +52,14 @@ spec: spec: description: CinderAPISpec defines the desired state of CinderAPI properties: + auth: + description: Auth - Parameters related to authentication + properties: + applicationCredentialSecret: + description: ApplicationCredentialSecret - Secret containing Application + Credential ID and Secret + type: string + type: object containerImage: description: ContainerImage - Cinder Container Image URL (will be set to environmental default if empty) diff --git a/api/bases/cinder.openstack.org_cinders.yaml b/api/bases/cinder.openstack.org_cinders.yaml index bfa1b22f..75dbe25c 100644 --- a/api/bases/cinder.openstack.org_cinders.yaml +++ b/api/bases/cinder.openstack.org_cinders.yaml @@ -57,6 +57,14 @@ spec: description: CinderAPI - Spec definition for the API service of this Cinder deployment properties: + auth: + description: Auth - Parameters related to authentication + properties: + applicationCredentialSecret: + description: ApplicationCredentialSecret - Secret containing + Application Credential ID and Secret + type: string + type: object containerImage: description: ContainerImage - Cinder Container Image URL (will be set to environmental default if empty) diff --git a/api/go.mod b/api/go.mod index 497a7ed7..dd3a4f91 100644 --- a/api/go.mod +++ b/api/go.mod @@ -4,6 +4,7 @@ go 1.24.4 require ( github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20251223124749-eedb97238c5f + github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20251206133124-593df0a7a9e1 github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251230215914-6ba873b49a35 github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20251230215914-6ba873b49a35 golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 @@ -17,7 +18,6 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect - github.com/evanphx/json-patch v5.9.11+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect @@ -32,6 +32,7 @@ require ( github.com/google/go-cmp v0.7.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gophercloud/gophercloud/v2 v2.8.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -39,6 +40,8 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/openshift/api v3.9.0+incompatible // indirect + github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20251122131503-b76943960b6c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect @@ -94,3 +97,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-20251211085602-3e1a3e022c81 diff --git a/api/go.sum b/api/go.sum index 75f1cbf3..e0acad44 100644 --- a/api/go.sum +++ b/api/go.sum @@ -1,3 +1,5 @@ +github.com/Deydra71/keystone-operator/api v0.0.0-20251211085602-3e1a3e022c81 h1:plax+NFgJJL1SrERyXAnf3jOHRhLTtBlJ2oc7d84EoU= +github.com/Deydra71/keystone-operator/api v0.0.0-20251211085602-3e1a3e022c81/go.mod h1:b98Jl8eyUw8V07l9YiuQnoMlnWC748oV8IhXH15NCC4= 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= @@ -48,6 +50,8 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gophercloud/gophercloud/v2 v2.8.0 h1:of2+8tT6+FbEYHfYC8GBu8TXJNsXYSNm9KuvpX7Neqo= +github.com/gophercloud/gophercloud/v2 v2.8.0/go.mod h1:Ki/ILhYZr/5EPebrPL9Ej+tUg4lqx71/YH2JWVeU+Qk= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -78,10 +82,14 @@ github.com/onsi/ginkgo/v2 v2.27.3 h1:ICsZJ8JoYafeXFFlFAG75a7CxMsJHwgKwtO+82SE9L8 github.com/onsi/ginkgo/v2 v2.27.3/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/onsi/gomega v1.38.3 h1:eTX+W6dobAYfFeGC2PV6RwXRu/MyT+cQguijutvkpSM= github.com/onsi/gomega v1.38.3/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= +github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e h1:E1OdwSpqWuDPCedyUt0GEdoAE+r5TXy7YS21yNEo+2U= +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.20251223124749-eedb97238c5f h1:xcCGJ/g5vvbWhtEJCbv8UeBneI5yrMawm+CXRsJrJZo= github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20251223124749-eedb97238c5f/go.mod h1:ex8ou6/3ms6ovR+CMXD6XhTlNakm1GhB6UZgagVRNW8= github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251230215914-6ba873b49a35 h1:pF3mJ3nwq6r4qwom+rEWZNquZpcQW/iftHlJ1KPIDsk= github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251230215914-6ba873b49a35/go.mod h1:kycZyoe7OZdW1HUghr2nI3N7wSJtNahXf6b/ypD14f4= +github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20251122131503-b76943960b6c h1:l7FO+XoQRnD4aT5p/JXVY2uezQLdC7D50KrwrTmzCfg= +github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20251122131503-b76943960b6c/go.mod h1:zOX7Y05keiSppIvLabuyh42QHBMhCcoskAtxFRbwXKo= github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20251230215914-6ba873b49a35 h1:8WZYfCt1VJHa5sJRX0UhpmoXud/fn8LHQhXsakdYXuQ= github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20251230215914-6ba873b49a35/go.mod h1:H0aQANk8iJPRhS2Bg9n6cYb/IHF0Cks9g7+uZG04Rhk= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= diff --git a/api/v1beta1/cinder_webhook.go b/api/v1beta1/cinder_webhook.go index 20e94d09..368fcb52 100644 --- a/api/v1beta1/cinder_webhook.go +++ b/api/v1beta1/cinder_webhook.go @@ -27,6 +27,7 @@ import ( "golang.org/x/exp/maps" topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" + keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" "github.com/openstack-k8s-operators/lib-common/modules/common/service" "github.com/openstack-k8s-operators/lib-common/modules/common/util" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -106,6 +107,10 @@ func (r *Cinder) Default() { // This is required, as the loop variable is a by-value copy r.Spec.CinderVolumes[index] = cinderVolume } + // Default ApplicationCredentialSecret to standard AC secret name if not specified + if r.Spec.CinderAPI.Auth.ApplicationCredentialSecret == "" { + r.Spec.CinderAPI.Auth.ApplicationCredentialSecret = keystonev1.GetACSecretName("cinder") + } r.Spec.CinderSpecBase.Default() } diff --git a/api/v1beta1/cinderapi_types.go b/api/v1beta1/cinderapi_types.go index 2ebea369..033d8a64 100644 --- a/api/v1beta1/cinderapi_types.go +++ b/api/v1beta1/cinderapi_types.go @@ -43,6 +43,11 @@ type CinderAPITemplateCore struct { // +operator-sdk:csv:customresourcedefinitions:type=spec // TLS - Parameters related to the TLS TLS tls.API `json:"tls,omitempty"` + + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // Auth - Parameters related to authentication + Auth AuthSpec `json:"auth,omitempty"` } // CinderAPITemplate defines the input parameters for the Cinder API service @@ -61,6 +66,14 @@ type APIOverrideSpec struct { Service map[service.Endpoint]service.RoutedOverrideSpec `json:"service,omitempty"` } +// AuthSpec defines authentication parameters +type AuthSpec struct { + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // ApplicationCredentialSecret - Secret containing Application Credential ID and Secret + ApplicationCredentialSecret string `json:"applicationCredentialSecret,omitempty"` +} + // CinderAPISpec defines the desired state of CinderAPI type CinderAPISpec struct { // Common input parameters for all Cinder services diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 5cb37ef8..426ca395 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -50,6 +50,21 @@ func (in *APIOverrideSpec) DeepCopy() *APIOverrideSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthSpec) DeepCopyInto(out *AuthSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthSpec. +func (in *AuthSpec) DeepCopy() *AuthSpec { + if in == nil { + return nil + } + out := new(AuthSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Cinder) DeepCopyInto(out *Cinder) { *out = *in @@ -267,6 +282,7 @@ func (in *CinderAPITemplateCore) DeepCopyInto(out *CinderAPITemplateCore) { } in.Override.DeepCopyInto(&out.Override) in.TLS.DeepCopyInto(&out.TLS) + out.Auth = in.Auth } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CinderAPITemplateCore. diff --git a/config/crd/bases/cinder.openstack.org_cinderapis.yaml b/config/crd/bases/cinder.openstack.org_cinderapis.yaml index 104c6d12..9c46944d 100644 --- a/config/crd/bases/cinder.openstack.org_cinderapis.yaml +++ b/config/crd/bases/cinder.openstack.org_cinderapis.yaml @@ -52,6 +52,14 @@ spec: spec: description: CinderAPISpec defines the desired state of CinderAPI properties: + auth: + description: Auth - Parameters related to authentication + properties: + applicationCredentialSecret: + description: ApplicationCredentialSecret - Secret containing Application + Credential ID and Secret + type: string + type: object containerImage: description: ContainerImage - Cinder Container Image URL (will be set to environmental default if empty) diff --git a/config/crd/bases/cinder.openstack.org_cinders.yaml b/config/crd/bases/cinder.openstack.org_cinders.yaml index bfa1b22f..75dbe25c 100644 --- a/config/crd/bases/cinder.openstack.org_cinders.yaml +++ b/config/crd/bases/cinder.openstack.org_cinders.yaml @@ -57,6 +57,14 @@ spec: description: CinderAPI - Spec definition for the API service of this Cinder deployment properties: + auth: + description: Auth - Parameters related to authentication + properties: + applicationCredentialSecret: + description: ApplicationCredentialSecret - Secret containing + Application Credential ID and Secret + type: string + type: object containerImage: description: ContainerImage - Cinder Container Image URL (will be set to environmental default if empty) diff --git a/go.mod b/go.mod index fccab1da..b479edff 100644 --- a/go.mod +++ b/go.mod @@ -143,3 +143,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-20251211085602-3e1a3e022c81 diff --git a/go.sum b/go.sum index ea1c75b2..2903e9c9 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/Deydra71/keystone-operator/api v0.0.0-20251211085602-3e1a3e022c81 h1:plax+NFgJJL1SrERyXAnf3jOHRhLTtBlJ2oc7d84EoU= +github.com/Deydra71/keystone-operator/api v0.0.0-20251211085602-3e1a3e022c81/go.mod h1:b98Jl8eyUw8V07l9YiuQnoMlnWC748oV8IhXH15NCC4= 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/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 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.20251223124749-eedb97238c5f h1:xcCGJ/g5vvbWhtEJCbv8UeBneI5yrMawm+CXRsJrJZo= github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20251223124749-eedb97238c5f/go.mod h1:ex8ou6/3ms6ovR+CMXD6XhTlNakm1GhB6UZgagVRNW8= -github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20251206133124-593df0a7a9e1 h1:qcgbrF9c0axkaDcFGfIA2wGz8bkaxPuXHj3mdKAyz6M= -github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20251206133124-593df0a7a9e1/go.mod h1:0XsZ6Fc4hTV6a/BBP8+jiH8LR+IP5z9aStdPTDHALNk= github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251230215914-6ba873b49a35 h1:pF3mJ3nwq6r4qwom+rEWZNquZpcQW/iftHlJ1KPIDsk= github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251230215914-6ba873b49a35/go.mod h1:kycZyoe7OZdW1HUghr2nI3N7wSJtNahXf6b/ypD14f4= github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20251122131503-b76943960b6c h1:l7FO+XoQRnD4aT5p/JXVY2uezQLdC7D50KrwrTmzCfg= diff --git a/internal/controller/cinder_controller.go b/internal/controller/cinder_controller.go index 7d166dfb..b4bbdd9f 100644 --- a/internal/controller/cinder_controller.go +++ b/internal/controller/cinder_controller.go @@ -251,6 +251,7 @@ const ( tlsAPIInternalField = ".spec.tls.api.internal.secretName" tlsAPIPublicField = ".spec.tls.api.public.secretName" topologyField = ".spec.topologyRef.Name" + authAppCredSecretField = ".spec.auth.applicationCredentialSecret" // #nosec G101 ) var ( @@ -265,6 +266,7 @@ var ( tlsAPIInternalField, tlsAPIPublicField, topologyField, + authAppCredSecretField, } ) @@ -1092,6 +1094,26 @@ func (r *CinderReconciler) generateServiceConfigs( templateParameters["ServicePassword"] = string(ospSecret.Data[instance.Spec.PasswordSelectors.Service]) templateParameters["KeystoneInternalURL"] = keystoneInternalURL templateParameters["KeystonePublicURL"] = keystonePublicURL + + // Check for Application Credentials + Log := r.GetLogger(ctx) + if instance.Spec.CinderAPI.Auth.ApplicationCredentialSecret != "" { + secret := &corev1.Secret{} + key := types.NamespacedName{Namespace: instance.Namespace, Name: instance.Spec.CinderAPI.Auth.ApplicationCredentialSecret} + if err := h.GetClient().Get(ctx, key, secret); err != nil { + if !k8s_errors.IsNotFound(err) { + Log.Error(err, "Failed to get ApplicationCredential secret", "secret", key) + } + } else { + acID, okID := secret.Data[keystonev1.ACIDSecretKey] + acSecret, okSecret := secret.Data[keystonev1.ACSecretSecretKey] + if okID && len(acID) > 0 && okSecret && len(acSecret) > 0 { + templateParameters["ApplicationCredentialID"] = string(acID) + templateParameters["ApplicationCredentialSecret"] = string(acSecret) + Log.Info("Using ApplicationCredentials auth", "secret", key) + } + } + } templateParameters["TransportURL"] = transportURLSecretData templateParameters["DatabaseConnection"] = fmt.Sprintf("mysql+pymysql://%s:%s@%s/%s?read_default_file=/etc/my.cnf", databaseAccount.Spec.UserName, diff --git a/internal/controller/cinderapi_controller.go b/internal/controller/cinderapi_controller.go index 8a2fd181..ac78fd3f 100644 --- a/internal/controller/cinderapi_controller.go +++ b/internal/controller/cinderapi_controller.go @@ -338,6 +338,18 @@ func (r *CinderAPIReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Man return err } + // index authAppCredSecretField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &cinderv1beta1.CinderAPI{}, authAppCredSecretField, func(rawObj client.Object) []string { + // Extract the application credential secret name from the spec, if one is provided + cr := rawObj.(*cinderv1beta1.CinderAPI) + if cr.Spec.Auth.ApplicationCredentialSecret == "" { + return nil + } + return []string{cr.Spec.Auth.ApplicationCredentialSecret} + }); err != nil { + return err + } + return ctrl.NewControllerManagedBy(mgr). For(&cinderv1beta1.CinderAPI{}). Owns(&keystonev1.KeystoneService{}). @@ -899,6 +911,15 @@ func (r *CinderAPIReconciler) reconcileNormal(ctx context.Context, instance *cin // normal reconcile tasks // + // Verify Application Credential secret if available (optional) + acSecretName := keystonev1.GetACSecretName(cinder.ServiceName) + acSecret := types.NamespacedName{Namespace: instance.Namespace, Name: acSecretName} + acHash, _, err := secret.VerifySecret(ctx, acSecret, []string{keystonev1.ACIDSecretKey, keystonev1.ACSecretSecretKey}, helper.GetClient(), 0) + if err == nil && acHash != "" { + // AC secret exists and is valid - add to configVars for hash tracking + configVars[acSecretName] = env.SetValue(acHash) + } + // // create hash over all the different input resources to identify if any those changed // and a restart/recreate is required. diff --git a/templates/cinder/config/00-global-defaults.conf b/templates/cinder/config/00-global-defaults.conf index af4912a3..75398796 100644 --- a/templates/cinder/config/00-global-defaults.conf +++ b/templates/cinder/config/00-global-defaults.conf @@ -76,12 +76,18 @@ auth_url = {{ .KeystoneInternalURL }} memcached_servers = {{ .MemcachedServers }} memcache_pool_dead_retry = 10 memcache_pool_conn_get_timeout = 2 +{{ if (index . "ApplicationCredentialID") -}} +auth_type = v3applicationcredential +application_credential_id = {{ .ApplicationCredentialID }} +application_credential_secret = {{ .ApplicationCredentialSecret }} +{{ else -}} auth_type = password +username = {{ .ServiceUser }} +password = {{ .ServicePassword }} project_domain_name = Default user_domain_name = Default project_name = service -username = {{ .ServiceUser }} -password = {{ .ServicePassword }} +{{ end -}} service_token_roles_required = true interface = internal {{if (index . "MemcachedAuthCert")}} @@ -93,20 +99,32 @@ memcache_tls_enabled = true [nova] interface = internal -auth_type = password auth_url = {{ .KeystoneInternalURL }} +{{ if (index . "ApplicationCredentialID") -}} +auth_type = v3applicationcredential +application_credential_id = {{ .ApplicationCredentialID }} +application_credential_secret = {{ .ApplicationCredentialSecret }} +{{ else -}} +auth_type = password username = {{ .ServiceUser }} password = {{ .ServicePassword }} user_domain_name = Default project_name = service project_domain_name = Default +{{ end -}} [service_user] send_service_user_token = True auth_url = {{ .KeystoneInternalURL }} +{{ if (index . "ApplicationCredentialID") -}} +auth_type = v3applicationcredential +application_credential_id = {{ .ApplicationCredentialID }} +application_credential_secret = {{ .ApplicationCredentialSecret }} +{{ else -}} auth_type = password +username = {{ .ServiceUser }} +password = {{ .ServicePassword }} project_domain_name = Default user_domain_name = Default project_name = service -username = {{ .ServiceUser }} -password = {{ .ServicePassword }} +{{ end -}} diff --git a/test/functional/cinder_controller_test.go b/test/functional/cinder_controller_test.go index 3c23ac91..c9392259 100644 --- a/test/functional/cinder_controller_test.go +++ b/test/functional/cinder_controller_test.go @@ -29,6 +29,7 @@ import ( corev1 "k8s.io/api/core/v1" k8s_errors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" @@ -37,9 +38,11 @@ import ( "github.com/openstack-k8s-operators/cinder-operator/internal/cinder" 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" mariadb_test "github.com/openstack-k8s-operators/mariadb-operator/api/test/helpers" + mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" ) var _ = Describe("Cinder controller", func() { @@ -1805,4 +1808,129 @@ var _ = Describe("Cinder Webhook", func() { var statusError *k8s_errors.StatusError Expect(errors.As(err, &statusError)).To(BeFalse()) }) + + When("An ApplicationCredential is created for Cinder", func() { + var ( + acName string + acSecretName string + servicePasswordSecret string + passwordSelector string + ) + BeforeEach(func() { + servicePasswordSecret = "ac-test-osp-secret" //nolint:gosec // G101 + passwordSelector = "CinderPassword" + + DeferCleanup(k8sClient.Delete, ctx, + CreateCinderMessageBusSecret( + cinderTest.Instance.Namespace, + cinderTest.RabbitmqSecretName, + ), + ) + DeferCleanup(k8sClient.Delete, ctx, + CreateCinderSecret( + cinderTest.Instance.Namespace, servicePasswordSecret)) + + acName = fmt.Sprintf("ac-%s", cinder.ServiceName) + acSecretName = acName + "-secret" + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: cinderTest.Instance.Namespace, + Name: acSecretName, + }, + Data: map[string][]byte{ + keystonev1.ACIDSecretKey: []byte("test-ac-id"), + keystonev1.ACSecretSecretKey: []byte("test-ac-secret"), + }, + } + DeferCleanup(k8sClient.Delete, ctx, secret) + Expect(k8sClient.Create(ctx, secret)).To(Succeed()) + + // Create Cinder using the service password secret + spec := GetDefaultCinderSpec() + spec["secret"] = servicePasswordSecret + DeferCleanup(th.DeleteInstance, CreateCinder(cinderTest.Instance, spec)) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + cinderTest.Instance.Namespace, + GetCinder(cinderTest.Instance).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}})) + DeferCleanup(keystone.DeleteKeystoneAPI, + keystone.CreateKeystoneAPI(cinderTest.Instance.Namespace), + ) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(cinderTest.Instance.Namespace, MemcachedInstance, memcachedv1.MemcachedSpec{})) + infra.SimulateMemcachedReady(cinderTest.CinderMemcached) + + // Create MariaDB account and database + acc, accSecret := mariadb.CreateMariaDBAccountAndSecret(cinderTest.Database, mariadbv1.MariaDBAccountSpec{}) + DeferCleanup(k8sClient.Delete, ctx, acc) + DeferCleanup(k8sClient.Delete, ctx, accSecret) + mariadb.CreateMariaDBDatabase(cinderTest.Database.Namespace, cinderTest.Database.Name, mariadbv1.MariaDBDatabaseSpec{}) + DeferCleanup(k8sClient.Delete, ctx, mariadb.GetMariaDBDatabase(cinderTest.Database)) + + ac := &keystonev1.KeystoneApplicationCredential{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: cinderTest.Instance.Namespace, + Name: acName, + }, + Spec: keystonev1.KeystoneApplicationCredentialSpec{ + UserName: cinder.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(cinderTest.CinderTransportURL) + mariadb.SimulateMariaDBAccountCompleted(cinderTest.Database) + mariadb.SimulateMariaDBDatabaseCompleted(cinderTest.Database) + + th.SimulateJobSuccess(cinderTest.CinderDBSync) + + keystone.SimulateKeystoneEndpointReady(cinderTest.CinderKeystoneEndpoint) + }) + + It("should render ApplicationCredential auth in 00-global-defaults.conf", func() { + keystone.SimulateKeystoneEndpointReady(cinderTest.CinderKeystoneEndpoint) + + Eventually(func(g Gomega) { + cfgSecret := th.GetSecret(cinderTest.CinderConfigSecret) + g.Expect(cfgSecret).NotTo(BeNil()) + + conf := string(cfgSecret.Data["00-global-defaults.conf"]) + + // AC auth is configured + g.Expect(conf).To(ContainSubstring("auth_type = v3applicationcredential")) + g.Expect(conf).To(ContainSubstring("application_credential_id = test-ac-id")) + g.Expect(conf).To(ContainSubstring("application_credential_secret = test-ac-secret")) + + // Password auth fields should not be present + g.Expect(conf).NotTo(ContainSubstring("auth_type = password")) + g.Expect(conf).NotTo(ContainSubstring("username = cinder")) + g.Expect(conf).NotTo(ContainSubstring("project_name = service")) + }, timeout, interval).Should(Succeed()) + }) + }) })