Skip to content

Commit 986cba8

Browse files
committed
Application Credential support
Signed-off-by: Veronika Fisarova <[email protected]>
1 parent 539c82c commit 986cba8

File tree

7 files changed

+209
-7
lines changed

7 files changed

+209
-7
lines changed

api/go.mod

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

9797
replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e //allow-merging
98+
99+
replace github.com/openstack-k8s-operators/keystone-operator/api => github.com/Deydra71/keystone-operator/api v0.0.0-20251103091514-244e15fe5d63

controllers/cinder_controller.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,6 +1031,19 @@ func (r *CinderReconciler) generateServiceConfigs(
10311031
templateParameters["ServicePassword"] = string(ospSecret.Data[instance.Spec.PasswordSelectors.Service])
10321032
templateParameters["KeystoneInternalURL"] = keystoneInternalURL
10331033
templateParameters["KeystonePublicURL"] = keystonePublicURL
1034+
1035+
// Check for Application Credentials
1036+
acSecretName := keystonev1.GetACSecretName("cinder")
1037+
acSecret, _, err := secret.GetSecret(ctx, h, acSecretName, instance.Namespace)
1038+
if err == nil && acSecret != nil {
1039+
// AC secret exists - add AC auth parameters
1040+
if acID, ok := acSecret.Data["AC_ID"]; ok {
1041+
templateParameters["ApplicationCredentialID"] = string(acID)
1042+
}
1043+
if acSecret, ok := acSecret.Data["AC_SECRET"]; ok {
1044+
templateParameters["ApplicationCredentialSecret"] = string(acSecret)
1045+
}
1046+
}
10341047
templateParameters["TransportURL"] = transportURLSecretData
10351048
templateParameters["DatabaseConnection"] = fmt.Sprintf("mysql+pymysql://%s:%s@%s/%s?read_default_file=/etc/my.cnf",
10361049
databaseAccount.Spec.UserName,

controllers/cinderapi_controller.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,42 @@ func (r *CinderAPIReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Man
338338
return err
339339
}
340340

341+
// Application Credential secret watching function
342+
acSecretFn := func(_ context.Context, o client.Object) []reconcile.Request {
343+
name := o.GetName()
344+
ns := o.GetNamespace()
345+
result := []reconcile.Request{}
346+
347+
// Only handle Secret objects
348+
if _, isSecret := o.(*corev1.Secret); !isSecret {
349+
return nil
350+
}
351+
352+
// Check if this is a cinder AC secret by name pattern (ac-cinder-secret)
353+
expectedSecretName := keystonev1.GetACSecretName("cinder")
354+
if name == expectedSecretName {
355+
// get all CinderAPI CRs in this namespace
356+
cinderAPIs := &cinderv1beta1.CinderAPIList{}
357+
listOpts := []client.ListOption{
358+
client.InNamespace(ns),
359+
}
360+
if err := r.List(context.Background(), cinderAPIs, listOpts...); err != nil {
361+
return nil
362+
}
363+
364+
// Enqueue reconcile for all cinder API instances
365+
for _, cr := range cinderAPIs.Items {
366+
objKey := client.ObjectKey{
367+
Namespace: ns,
368+
Name: cr.Name,
369+
}
370+
result = append(result, reconcile.Request{NamespacedName: objKey})
371+
}
372+
}
373+
374+
return result
375+
}
376+
341377
return ctrl.NewControllerManagedBy(mgr).
342378
For(&cinderv1beta1.CinderAPI{}).
343379
Owns(&keystonev1.KeystoneService{}).
@@ -352,6 +388,8 @@ func (r *CinderAPIReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Man
352388
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
353389
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
354390
).
391+
Watches(&corev1.Secret{},
392+
handler.EnqueueRequestsFromMapFunc(acSecretFn)).
355393
Watches(&topologyv1.Topology{},
356394
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
357395
builder.WithPredicates(predicate.GenerationChangedPredicate{})).
@@ -701,6 +739,11 @@ func (r *CinderAPIReconciler) reconcileNormal(ctx context.Context, instance *cin
701739
return ctrlResult, nil
702740
}
703741

742+
// Check for Application Credentials
743+
if ctrlResult, err = keystonev1.VerifyApplicationCredentialsForService(ctx, r.Client, instance.Namespace, "cinder", &configVars, cinder.NormalDuration); err != nil || ctrlResult.RequeueAfter > 0 {
744+
return ctrlResult, err
745+
}
746+
704747
//
705748
// check for required Transport URL and config secrets
706749
//

go.mod

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

118118
replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e //allow-merging
119+
120+
replace github.com/openstack-k8s-operators/keystone-operator/api => github.com/Deydra71/keystone-operator/api v0.0.0-20251103091514-244e15fe5d63

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-20251103091514-244e15fe5d63 h1:ug2YPMQJ/+0ifOjFyaPx1YtX0zsVnL02pB2ngacYviw=
2+
github.com/Deydra71/keystone-operator/api v0.0.0-20251103091514-244e15fe5d63/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.20251021145236-2b84ec9fd9bb h1:wToXqX7AS1JV3Kna7RcJfkRart8rSGun2biKNfyY6Zg=

templates/cinder/config/00-global-defaults.conf

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,18 @@ auth_url = {{ .KeystoneInternalURL }}
7676
memcached_servers = {{ .MemcachedServers }}
7777
memcache_pool_dead_retry = 10
7878
memcache_pool_conn_get_timeout = 2
79+
{{ if (index . "ApplicationCredentialID") -}}
80+
auth_type = v3applicationcredential
81+
application_credential_id = {{ .ApplicationCredentialID }}
82+
application_credential_secret = {{ .ApplicationCredentialSecret }}
83+
{{ else -}}
7984
auth_type = password
85+
username = {{ .ServiceUser }}
86+
password = {{ .ServicePassword }}
87+
{{ end -}}
8088
project_domain_name = Default
8189
user_domain_name = Default
8290
project_name = service
83-
username = {{ .ServiceUser }}
84-
password = {{ .ServicePassword }}
8591
service_token_roles_required = true
8692
interface = internal
8793
{{if (index . "MemcachedAuthCert")}}
@@ -93,20 +99,32 @@ memcache_tls_enabled = true
9399

94100
[nova]
95101
interface = internal
102+
{{ if (index . "ApplicationCredentialID") -}}
103+
auth_type = v3applicationcredential
104+
application_credential_id = {{ .ApplicationCredentialID }}
105+
application_credential_secret = {{ .ApplicationCredentialSecret }}
106+
{{ else -}}
96107
auth_type = password
97-
auth_url = {{ .KeystoneInternalURL }}
98108
username = {{ .ServiceUser }}
99109
password = {{ .ServicePassword }}
110+
{{ end -}}
111+
auth_url = {{ .KeystoneInternalURL }}
100112
user_domain_name = Default
101113
project_name = service
102114
project_domain_name = Default
103115

104116
[service_user]
105117
send_service_user_token = True
106118
auth_url = {{ .KeystoneInternalURL }}
119+
{{ if (index . "ApplicationCredentialID") -}}
120+
auth_type = v3applicationcredential
121+
application_credential_id = {{ .ApplicationCredentialID }}
122+
application_credential_secret = {{ .ApplicationCredentialSecret }}
123+
{{ else -}}
107124
auth_type = password
125+
username = {{ .ServiceUser }}
126+
password = {{ .ServicePassword }}
127+
{{ end -}}
108128
project_domain_name = Default
109129
user_domain_name = Default
110130
project_name = service
111-
username = {{ .ServiceUser }}
112-
password = {{ .ServicePassword }}

test/functional/cinder_controller_test.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929

3030
corev1 "k8s.io/api/core/v1"
3131
k8s_errors "k8s.io/apimachinery/pkg/api/errors"
32+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3233
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3334
"k8s.io/apimachinery/pkg/types"
3435
"k8s.io/utils/ptr"
@@ -37,9 +38,11 @@ import (
3738
"github.com/openstack-k8s-operators/cinder-operator/pkg/cinder"
3839
memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1"
3940
topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1"
41+
keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1"
4042
condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition"
4143
util "github.com/openstack-k8s-operators/lib-common/modules/common/util"
4244
mariadb_test "github.com/openstack-k8s-operators/mariadb-operator/api/test/helpers"
45+
mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1"
4346
)
4447

4548
var _ = Describe("Cinder controller", func() {
@@ -1744,4 +1747,125 @@ var _ = Describe("Cinder Webhook", func() {
17441747
return instance, fmt.Sprintf("cinderVolumes[%s].topologyRef", instance)
17451748
}),
17461749
)
1750+
1751+
When("An ApplicationCredential is created for Cinder", func() {
1752+
var (
1753+
acName string
1754+
acSecretName string
1755+
servicePasswordSecret string
1756+
passwordSelector string
1757+
)
1758+
BeforeEach(func() {
1759+
servicePasswordSecret = "ac-test-osp-secret" //nolint:gosec // G101
1760+
passwordSelector = "CinderPassword"
1761+
1762+
DeferCleanup(k8sClient.Delete, ctx,
1763+
CreateCinderMessageBusSecret(
1764+
cinderTest.Instance.Namespace,
1765+
cinderTest.RabbitmqSecretName,
1766+
),
1767+
)
1768+
DeferCleanup(k8sClient.Delete, ctx,
1769+
CreateCinderSecret(
1770+
cinderTest.Instance.Namespace, servicePasswordSecret))
1771+
// Create Cinder using the service password secret
1772+
spec := GetDefaultCinderSpec()
1773+
spec["secret"] = servicePasswordSecret
1774+
DeferCleanup(th.DeleteInstance, CreateCinder(cinderTest.Instance, spec))
1775+
DeferCleanup(
1776+
mariadb.DeleteDBService,
1777+
mariadb.CreateDBService(
1778+
cinderTest.Instance.Namespace,
1779+
GetCinder(cinderTest.Instance).Spec.DatabaseInstance,
1780+
corev1.ServiceSpec{
1781+
Ports: []corev1.ServicePort{{Port: 3306}}}))
1782+
DeferCleanup(keystone.DeleteKeystoneAPI,
1783+
keystone.CreateKeystoneAPI(cinderTest.Instance.Namespace),
1784+
)
1785+
DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(cinderTest.Instance.Namespace, MemcachedInstance, memcachedv1.MemcachedSpec{}))
1786+
infra.SimulateMemcachedReady(cinderTest.CinderMemcached)
1787+
1788+
// Create MariaDB account and database
1789+
acc, accSecret := mariadb.CreateMariaDBAccountAndSecret(cinderTest.Database, mariadbv1.MariaDBAccountSpec{})
1790+
DeferCleanup(k8sClient.Delete, ctx, acc)
1791+
DeferCleanup(k8sClient.Delete, ctx, accSecret)
1792+
mariadb.CreateMariaDBDatabase(cinderTest.Database.Namespace, cinderTest.Database.Name, mariadbv1.MariaDBDatabaseSpec{})
1793+
DeferCleanup(k8sClient.Delete, ctx, mariadb.GetMariaDBDatabase(cinderTest.Database))
1794+
1795+
acName = fmt.Sprintf("ac-%s", cinder.ServiceName)
1796+
acSecretName = acName + "-secret"
1797+
secret := &corev1.Secret{
1798+
ObjectMeta: metav1.ObjectMeta{
1799+
Namespace: cinderTest.Instance.Namespace,
1800+
Name: acSecretName,
1801+
},
1802+
Data: map[string][]byte{
1803+
"AC_ID": []byte("test-ac-id"),
1804+
"AC_SECRET": []byte("test-ac-secret"),
1805+
},
1806+
}
1807+
DeferCleanup(k8sClient.Delete, ctx, secret)
1808+
Expect(k8sClient.Create(ctx, secret)).To(Succeed())
1809+
1810+
ac := &keystonev1.KeystoneApplicationCredential{
1811+
ObjectMeta: metav1.ObjectMeta{
1812+
Namespace: cinderTest.Instance.Namespace,
1813+
Name: acName,
1814+
},
1815+
Spec: keystonev1.KeystoneApplicationCredentialSpec{
1816+
UserName: cinder.ServiceName,
1817+
Secret: servicePasswordSecret,
1818+
PasswordSelector: passwordSelector,
1819+
Roles: []string{"admin", "member"},
1820+
AccessRules: []keystonev1.ACRule{{Service: "identity", Method: "POST", Path: "/auth/tokens"}},
1821+
ExpirationDays: 30,
1822+
GracePeriodDays: 5,
1823+
},
1824+
}
1825+
DeferCleanup(k8sClient.Delete, ctx, ac)
1826+
Expect(k8sClient.Create(ctx, ac)).To(Succeed())
1827+
1828+
fetched := &keystonev1.KeystoneApplicationCredential{}
1829+
key := types.NamespacedName{Namespace: ac.Namespace, Name: ac.Name}
1830+
Expect(k8sClient.Get(ctx, key, fetched)).To(Succeed())
1831+
1832+
fetched.Status.SecretName = acSecretName
1833+
now := metav1.Now()
1834+
readyCond := condition.Condition{
1835+
Type: condition.ReadyCondition,
1836+
Status: corev1.ConditionTrue,
1837+
Reason: condition.ReadyReason,
1838+
Message: condition.ReadyMessage,
1839+
LastTransitionTime: now,
1840+
}
1841+
fetched.Status.Conditions = condition.Conditions{readyCond}
1842+
Expect(k8sClient.Status().Update(ctx, fetched)).To(Succeed())
1843+
1844+
infra.SimulateTransportURLReady(cinderTest.CinderTransportURL)
1845+
mariadb.SimulateMariaDBAccountCompleted(cinderTest.Database)
1846+
mariadb.SimulateMariaDBDatabaseCompleted(cinderTest.Database)
1847+
1848+
th.SimulateJobSuccess(cinderTest.CinderDBSync)
1849+
1850+
keystone.SimulateKeystoneEndpointReady(cinderTest.CinderKeystoneEndpoint)
1851+
})
1852+
1853+
It("should render ApplicationCredential auth in 00-global-defaults.conf", func() {
1854+
keystone.SimulateKeystoneEndpointReady(cinderTest.CinderKeystoneEndpoint)
1855+
1856+
Eventually(func(g Gomega) {
1857+
cfgSecret := th.GetSecret(cinderTest.CinderConfigSecret)
1858+
g.Expect(cfgSecret).NotTo(BeNil())
1859+
1860+
conf := string(cfgSecret.Data["00-global-defaults.conf"])
1861+
1862+
g.Expect(conf).To(ContainSubstring(
1863+
"application_credential_id = test-ac-id"),
1864+
)
1865+
g.Expect(conf).To(ContainSubstring(
1866+
"application_credential_secret = test-ac-secret"),
1867+
)
1868+
}, timeout, interval).Should(Succeed())
1869+
})
1870+
})
17471871
})

0 commit comments

Comments
 (0)