Skip to content

Commit bde81dc

Browse files
committed
Application Credential support
Signed-off-by: Veronika Fisarova <vfisarov@redhat.com>
1 parent d5deee7 commit bde81dc

6 files changed

Lines changed: 193 additions & 8 deletions

File tree

api/go.mod

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

9898
replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e //allow-merging
99+
100+
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
@@ -144,3 +144,5 @@ replace k8s.io/component-base => k8s.io/component-base v0.31.14 //allow-merging
144144
replace github.com/rabbitmq/cluster-operator/v2 => github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec //allow-merging
145145

146146
replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e //allow-merging
147+
148+
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=
@@ -124,8 +126,6 @@ github.com/openstack-k8s-operators/horizon-operator/api v0.6.1-0.20251125145341-
124126
github.com/openstack-k8s-operators/horizon-operator/api v0.6.1-0.20251125145341-8bc80a35f9c5/go.mod h1:NHffNXXDFrfJx/+htPjd8WvDRMPqddRJThBqujwlFc8=
125127
github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20251205192058-5cfbada0ab96 h1:hPgCcrbRHBPfngaEPe6coaCtcauMolI71lfcLdinrKI=
126128
github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20251205192058-5cfbada0ab96/go.mod h1:ZuglN7IqXfIo75WcJwe0NLHhu82Fs3k/5IXptqnO1H4=
127-
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20251128160419-8b3a77972a77 h1:XzVPjfzxDJwgW8sNGv9K577Ui2mb6Mp3sDItuDmTv9E=
128-
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20251128160419-8b3a77972a77/go.mod h1:b98Jl8eyUw8V07l9YiuQnoMlnWC748oV8IhXH15NCC4=
129129
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251122131503-b76943960b6c h1:wM8qXCB5mQwSosCvtaydzuXitWVVKBHTzH0A2znQ+Jg=
130130
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251122131503-b76943960b6c/go.mod h1:+Me0raWPPdz8gRi9D4z1khmvUgS9vIKAVC8ckg1yJZU=
131131
github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20251122131503-b76943960b6c h1:l7FO+XoQRnD4aT5p/JXVY2uezQLdC7D50KrwrTmzCfg=

internal/controller/glanceapi_controller.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,42 @@ func (r *GlanceAPIReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Man
378378
return nil
379379
}
380380

381+
// Application Credential secret watching function
382+
acSecretFn := func(_ context.Context, o client.Object) []reconcile.Request {
383+
name := o.GetName()
384+
ns := o.GetNamespace()
385+
result := []reconcile.Request{}
386+
387+
// Only handle Secret objects
388+
if _, isSecret := o.(*corev1.Secret); !isSecret {
389+
return nil
390+
}
391+
392+
// Check if this is a glance AC secret by name pattern (ac-glance-secret)
393+
expectedSecretName := keystonev1.GetACSecretName("glance")
394+
if name == expectedSecretName {
395+
// get all GlanceAPI CRs in this namespace
396+
glanceAPIs := &glancev1.GlanceAPIList{}
397+
listOpts := []client.ListOption{
398+
client.InNamespace(ns),
399+
}
400+
if err := r.List(context.Background(), glanceAPIs, listOpts...); err != nil {
401+
return nil
402+
}
403+
404+
// Enqueue reconcile for all glance API instances
405+
for _, cr := range glanceAPIs.Items {
406+
objKey := client.ObjectKey{
407+
Namespace: ns,
408+
Name: cr.Name,
409+
}
410+
result = append(result, reconcile.Request{NamespacedName: objKey})
411+
}
412+
}
413+
414+
return result
415+
}
416+
381417
return ctrl.NewControllerManagedBy(mgr).
382418
For(&glancev1.GlanceAPI{}).
383419
Owns(&keystonev1.KeystoneEndpoint{}).
@@ -393,6 +429,8 @@ func (r *GlanceAPIReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Man
393429
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
394430
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
395431
).
432+
Watches(&corev1.Secret{},
433+
handler.EnqueueRequestsFromMapFunc(acSecretFn)).
396434
Watches(&memcachedv1.Memcached{},
397435
handler.EnqueueRequestsFromMapFunc(memcachedFn)).
398436
Watches(&topologyv1.Topology{},
@@ -1285,6 +1323,17 @@ func (r *GlanceAPIReconciler) generateServiceConfig(
12851323
"Wsgi": wsgi,
12861324
}
12871325

1326+
templateParameters["UseApplicationCredentials"] = false
1327+
// Try to get Application Credential for this service (via keystone api helper)
1328+
if acData, err := keystonev1.GetApplicationCredentialFromSecret(ctx, r.Client, instance.Namespace, glance.ServiceName); err != nil {
1329+
Log.Error(err, "Failed to get ApplicationCredential for service", "service", glance.ServiceName)
1330+
} else if acData != nil {
1331+
templateParameters["UseApplicationCredentials"] = true
1332+
templateParameters["ACID"] = acData.ID
1333+
templateParameters["ACSecret"] = acData.Secret
1334+
Log.Info("Using ApplicationCredentials auth", "service", glance.ServiceName)
1335+
}
1336+
12881337
// (OSPRH-18291)Only set EndpointID parameter when the Endpoint has been
12891338
// created and the associated ID is set in the keystoneapi CR. Because we
12901339
// have the Keystone CR, we get the Region parameter mirrored in its

templates/common/config/00-config.conf

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,18 @@ default_backend=default_backend
4141
[keystone_authtoken]
4242
www_authenticate_uri={{ .KeystonePublicURL }}
4343
auth_url={{ .KeystoneInternalURL }}
44-
auth_type=password
45-
username={{ .ServiceUser }}
44+
{{ if .UseApplicationCredentials -}}
45+
auth_type = v3applicationcredential
46+
application_credential_id = {{ .ACID }}
47+
application_credential_secret = {{ .ACSecret }}
48+
{{ else -}}
49+
auth_type = password
50+
username = {{ .ServiceUser }}
4651
password = {{ .ServicePassword }}
52+
project_domain_name = Default
53+
user_domain_name = Default
54+
project_name = service
55+
{{- end }}
4756
{{ if (index . "MemcachedServers") }}
4857
memcached_servers = {{ .MemcachedServers }}
4958
memcache_pool_dead_retry = 10
@@ -55,12 +64,16 @@ memcache_tls_keyfile = {{ .MemcachedAuthKey }}
5564
memcache_tls_cafile = {{ .MemcachedAuthCa }}
5665
memcache_tls_enabled = true
5766
{{end}}
58-
project_domain_name=Default
59-
user_domain_name=Default
60-
project_name=service
6167

6268
[service_user]
69+
{{ if .UseApplicationCredentials -}}
70+
auth_type = v3applicationcredential
71+
application_credential_id = {{ .ACID }}
72+
application_credential_secret = {{ .ACSecret }}
73+
{{ else -}}
74+
auth_type = password
6375
password = {{ .ServicePassword }}
76+
{{- end }}
6477

6578
[oslo_messaging_notifications]
6679
{{ if (index . "TransportURL") -}}
@@ -94,11 +107,16 @@ filesystem_store_datadir = /var/lib/glance/os_glance_tasks_store/
94107

95108
[oslo_limit]
96109
auth_url={{ .KeystoneInternalURL }}
97-
auth_type = password
110+
auth_type = {{ if .UseApplicationCredentials }}v3applicationcredential{{ else }}password{{ end }}
111+
{{ if .UseApplicationCredentials -}}
112+
application_credential_id = {{ .ACID }}
113+
application_credential_secret = {{ .ACSecret }}
114+
{{ else -}}
98115
username={{ .ServiceUser }}
99116
password = {{ .ServicePassword }}
100117
system_scope = all
101118
user_domain_id = default
119+
{{- end }}
102120
{{ if (index . "EndpointID") -}}
103121
endpoint_id = {{ .EndpointID }}
104122
{{ end -}}

test/functional/glanceapi_controller_test.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ import (
2626
. "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports
2727
. "github.com/onsi/gomega" //revive:disable:dot-imports
2828
glancev1 "github.com/openstack-k8s-operators/glance-operator/api/v1beta1"
29+
"github.com/openstack-k8s-operators/glance-operator/internal/glance"
2930
memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1"
3031
topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1"
32+
keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1"
3133
"github.com/openstack-k8s-operators/lib-common/modules/common/condition"
3234

3335
//revive:disable-next-line:dot-imports
@@ -1452,4 +1454,116 @@ var _ = Describe("Glanceapi controller", func() {
14521454
}, timeout, interval).Should(Succeed())
14531455
})
14541456
})
1457+
1458+
When("An ApplicationCredential is created for Glance", func() {
1459+
var (
1460+
acName string
1461+
acSecretName string
1462+
servicePasswordSecret string
1463+
passwordSelector string
1464+
)
1465+
BeforeEach(func() {
1466+
servicePasswordSecret = "ac-test-osp-secret" //nolint:gosec // G101
1467+
passwordSelector = "GlancePassword"
1468+
1469+
DeferCleanup(k8sClient.Delete, ctx, CreateGlanceSecret(glanceTest.Instance.Namespace, servicePasswordSecret))
1470+
DeferCleanup(k8sClient.Delete, ctx, CreateGlanceMessageBusSecret(glanceTest.Instance.Namespace, glanceTest.RabbitmqSecretName))
1471+
DeferCleanup(th.DeleteInstance, CreateDefaultGlance(glanceTest.Instance))
1472+
DeferCleanup(
1473+
mariadb.DeleteDBService,
1474+
mariadb.CreateDBService(
1475+
glanceTest.Instance.Namespace,
1476+
glanceTest.GlanceDatabaseName.Name,
1477+
corev1.ServiceSpec{
1478+
Ports: []corev1.ServicePort{{Port: 3306}}}))
1479+
mariadb.CreateMariaDBDatabase(glanceTest.GlanceDatabaseName.Namespace, glanceTest.GlanceDatabaseName.Name, mariadbv1.MariaDBDatabaseSpec{})
1480+
DeferCleanup(k8sClient.Delete, ctx, mariadb.GetMariaDBDatabase(glanceTest.GlanceDatabaseName))
1481+
1482+
DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(glanceTest.Instance.Namespace))
1483+
DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(glanceTest.Instance.Namespace, MemcachedInstance, memcachedv1.MemcachedSpec{}))
1484+
infra.SimulateMemcachedReady(glanceTest.GlanceMemcached)
1485+
1486+
// Create AC secret with test credentials
1487+
acName = fmt.Sprintf("ac-%s", glance.ServiceName)
1488+
acSecretName = acName + "-secret"
1489+
acSecret := &corev1.Secret{
1490+
ObjectMeta: metav1.ObjectMeta{
1491+
Namespace: glanceTest.Instance.Namespace,
1492+
Name: acSecretName,
1493+
},
1494+
Data: map[string][]byte{
1495+
"AC_ID": []byte("test-ac-id"),
1496+
"AC_SECRET": []byte("test-ac-secret"),
1497+
},
1498+
}
1499+
DeferCleanup(k8sClient.Delete, ctx, acSecret)
1500+
Expect(k8sClient.Create(ctx, acSecret)).To(Succeed())
1501+
1502+
// Create AC CR
1503+
ac := &keystonev1.KeystoneApplicationCredential{
1504+
ObjectMeta: metav1.ObjectMeta{
1505+
Namespace: glanceTest.Instance.Namespace,
1506+
Name: acName,
1507+
},
1508+
Spec: keystonev1.KeystoneApplicationCredentialSpec{
1509+
UserName: glance.ServiceName,
1510+
Secret: servicePasswordSecret,
1511+
PasswordSelector: passwordSelector,
1512+
Roles: []string{"admin", "member"},
1513+
AccessRules: []keystonev1.ACRule{{Service: "identity", Method: "POST", Path: "/auth/tokens"}},
1514+
ExpirationDays: 30,
1515+
GracePeriodDays: 5,
1516+
},
1517+
}
1518+
DeferCleanup(k8sClient.Delete, ctx, ac)
1519+
Expect(k8sClient.Create(ctx, ac)).To(Succeed())
1520+
1521+
// Simulate AC controller updating the status
1522+
fetched := &keystonev1.KeystoneApplicationCredential{}
1523+
key := types.NamespacedName{Namespace: ac.Namespace, Name: ac.Name}
1524+
Expect(k8sClient.Get(ctx, key, fetched)).To(Succeed())
1525+
1526+
fetched.Status.SecretName = acSecretName
1527+
now := metav1.Now()
1528+
readyCond := condition.Condition{
1529+
Type: condition.ReadyCondition,
1530+
Status: corev1.ConditionTrue,
1531+
Reason: condition.ReadyReason,
1532+
Message: condition.ReadyMessage,
1533+
LastTransitionTime: now,
1534+
}
1535+
fetched.Status.Conditions = condition.Conditions{readyCond}
1536+
Expect(k8sClient.Status().Update(ctx, fetched)).To(Succeed())
1537+
1538+
// Create GlanceAPI using the service password secret
1539+
spec := CreateGlanceAPISpec(GlanceAPITypeInternal)
1540+
spec["secret"] = servicePasswordSecret
1541+
DeferCleanup(th.DeleteInstance, CreateGlanceAPI(glanceTest.GlanceInternal, spec))
1542+
1543+
mariadb.SimulateMariaDBAccountCompleted(glanceTest.GlanceDatabaseAccount)
1544+
mariadb.SimulateMariaDBDatabaseCompleted(glanceTest.GlanceDatabaseName)
1545+
th.SimulateStatefulSetReplicaReady(glanceTest.GlanceInternalStatefulSet)
1546+
1547+
keystone.SimulateKeystoneEndpointReady(glanceTest.GlanceInternal)
1548+
})
1549+
1550+
It("should render ApplicationCredential auth in 00-config.conf", func() {
1551+
keystone.SimulateKeystoneEndpointReady(glanceTest.GlanceInternal)
1552+
1553+
// Wait for the config to be generated and updated with AC auth
1554+
Eventually(func(g Gomega) {
1555+
cfgSecret := th.GetSecret(glanceTest.GlanceInternalConfigMapData)
1556+
g.Expect(cfgSecret).NotTo(BeNil())
1557+
1558+
conf := string(cfgSecret.Data["00-config.conf"])
1559+
1560+
g.Expect(conf).To(ContainSubstring(
1561+
"application_credential_id = test-ac-id"),
1562+
)
1563+
g.Expect(conf).To(ContainSubstring(
1564+
"application_credential_secret = test-ac-secret"),
1565+
)
1566+
}, timeout, interval).Should(Succeed())
1567+
})
1568+
})
14551569
})

0 commit comments

Comments
 (0)