Skip to content

Commit 591aa82

Browse files
committed
Application Credential support
Adds the end-to-end support for consuming Keystone ApplicationCredentials (AC) in the Neutron operator, enabling Neutron pod to use AC-based authentication when available. Signed-off-by: Veronika Fisarova <[email protected]>
1 parent c4a3d79 commit 591aa82

File tree

6 files changed

+138
-8
lines changed

6 files changed

+138
-8
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-20251110074936-69e69f698212

controllers/neutronapi_controller.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,41 @@ var allWatchFields = []string{
245245
topologyField,
246246
}
247247

248+
// Application Credential secret watching function
249+
func (r *NeutronAPIReconciler) acSecretFn(_ context.Context, o client.Object) []reconcile.Request {
250+
name := o.GetName()
251+
ns := o.GetNamespace()
252+
result := []reconcile.Request{}
253+
254+
// Only handle Secret objects
255+
if _, isSecret := o.(*corev1.Secret); !isSecret {
256+
return nil
257+
}
258+
259+
// Check if this is a neutron AC secret by name pattern (ac-neutron-secret)
260+
expectedSecretName := keystonev1.GetACSecretName(neutronapi.ServiceName)
261+
if name == expectedSecretName {
262+
// get all NeutronAPI CRs in this namespace
263+
neutronAPIs := &neutronv1beta1.NeutronAPIList{}
264+
listOpts := []client.ListOption{
265+
client.InNamespace(ns),
266+
}
267+
if err := r.List(context.Background(), neutronAPIs, listOpts...); err != nil {
268+
return nil
269+
}
270+
271+
for _, cr := range neutronAPIs.Items {
272+
result = append(result, reconcile.Request{
273+
NamespacedName: types.NamespacedName{
274+
Namespace: cr.Namespace,
275+
Name: cr.Name,
276+
},
277+
})
278+
}
279+
}
280+
return result
281+
}
282+
248283
// SetupWithManager -
249284
func (r *NeutronAPIReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {
250285
// index passwordSecretField
@@ -341,6 +376,8 @@ func (r *NeutronAPIReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Ma
341376
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
342377
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
343378
).
379+
Watches(&corev1.Secret{},
380+
handler.EnqueueRequestsFromMapFunc(r.acSecretFn)).
344381
Watches(&topologyv1.Topology{},
345382
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
346383
builder.WithPredicates(predicate.GenerationChangedPredicate{})).
@@ -501,6 +538,11 @@ func (r *NeutronAPIReconciler) reconcileInit(
501538
// Create Secrets required as input for the Service and calculate an overall hash of hashes
502539
//
503540

541+
// Verify Application Credentials if available
542+
if result, err := keystonev1.VerifyApplicationCredentialsForService(ctx, r.Client, instance.Namespace, neutronapi.ServiceName, &secretVars, 10*time.Second); err != nil || result.RequeueAfter > 0 {
543+
return result, err
544+
}
545+
504546
//
505547
// create Secret required for neutronapi and dbsync input. It contains minimal neutron config required
506548
// to get the service up, user can add additional files to be added to the service.
@@ -1854,6 +1896,18 @@ func (r *NeutronAPIReconciler) generateServiceSecrets(
18541896
servicePassword := string(ospSecret.Data[instance.Spec.PasswordSelectors.Service])
18551897
templateParameters["ServicePassword"] = servicePassword
18561898

1899+
templateParameters["UseApplicationCredentials"] = false
1900+
// Try to get Application Credential for this service
1901+
if acData, err := keystonev1.GetApplicationCredentialFromSecret(ctx, r.Client, instance.Namespace, neutronapi.ServiceName); err != nil {
1902+
h.GetLogger().Error(err, "Failed to get ApplicationCredential for service", "service", neutronapi.ServiceName)
1903+
return err
1904+
} else if acData != nil {
1905+
templateParameters["UseApplicationCredentials"] = true
1906+
templateParameters["ACID"] = acData.ID
1907+
templateParameters["ACSecret"] = acData.Secret
1908+
h.GetLogger().Info("Using ApplicationCredentials auth", "service", neutronapi.ServiceName)
1909+
}
1910+
18571911
// Database
18581912
databaseAccount := db.GetAccount()
18591913
dbSecret := db.GetSecret()

go.mod

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

119119
replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e //allow-merging
120+
121+
replace github.com/openstack-k8s-operators/keystone-operator/api => github.com/Deydra71/keystone-operator/api v0.0.0-20251110074936-69e69f698212

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-20251110074936-69e69f698212 h1:m/OIYRcJvoKnfhoJCAJDwN54cnu495rgQAngyGPTKvc=
2+
github.com/Deydra71/keystone-operator/api v0.0.0-20251110074936-69e69f698212/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/neutronapi/config/01-neutron.conf

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,18 @@ auth_url = {{ .KeystoneInternalURL }}
5858
memcached_servers={{ .MemcachedServers }}
5959
memcache_pool_dead_retry = 10
6060
memcache_pool_conn_get_timeout = 2
61+
{{ if .UseApplicationCredentials -}}
62+
auth_type = v3applicationcredential
63+
application_credential_id = {{ .ACID }}
64+
application_credential_secret = {{ .ACSecret }}
65+
{{ else -}}
6166
auth_type = password
67+
username = {{ .ServiceUser }}
68+
password = {{ .ServicePassword }}
69+
{{- end }}
6270
project_domain_name = Default
6371
user_domain_name = Default
6472
project_name = service
65-
username = {{ .ServiceUser }}
66-
password = {{ .ServicePassword }}
6773
interface = internal
6874
{{if (index . "MemcachedAuthCert")}}
6975
memcache_tls_certfile = {{ .MemcachedAuthCert }}
@@ -74,25 +80,37 @@ memcache_tls_enabled = true
7480

7581
[nova]
7682
auth_url = {{ .KeystoneInternalURL }}
83+
{{ if .UseApplicationCredentials -}}
84+
auth_type = v3applicationcredential
85+
application_credential_id = {{ .ACID }}
86+
application_credential_secret = {{ .ACSecret }}
87+
{{ else -}}
7788
auth_type = password
89+
username = {{ .ServiceUser }}
90+
password = {{ .ServicePassword }}
91+
{{- end }}
7892
project_domain_name = Default
7993
user_domain_name = Default
8094
region_name = regionOne
8195
project_name = service
82-
username = {{ .ServiceUser }}
8396
endpoint_type = internal
84-
password = {{ .ServicePassword }}
8597

8698
[placement]
8799
auth_url = {{ .KeystoneInternalURL }}
100+
{{ if .UseApplicationCredentials -}}
101+
auth_type = v3applicationcredential
102+
application_credential_id = {{ .ACID }}
103+
application_credential_secret = {{ .ACSecret }}
104+
{{ else -}}
88105
auth_type = password
106+
username = {{ .ServiceUser }}
107+
password = {{ .ServicePassword }}
108+
{{- end }}
89109
project_domain_name = Default
90110
user_domain_name = Default
91111
region_name = regionOne
92112
project_name = service
93-
username = {{ .ServiceUser }}
94113
endpoint_type = internal
95-
password = {{ .ServicePassword }}
96114

97115
[oslo_concurrency]
98116
lock_path = /var/lib/neutron/tmp

test/functional/neutronapi_controller_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1957,6 +1957,60 @@ func getNeutronAPIControllerSuite(ml2MechanismDrivers []string) func() {
19571957
},
19581958
).Spec.Template.Spec.Containers[0].Env, "CONFIG_HASH", "")
19591959
})
1960+
1961+
When("an ApplicationCredential is created for Neutron", func() {
1962+
BeforeEach(func() {
1963+
DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec))
1964+
DeferCleanup(k8sClient.Delete, ctx, CreateNeutronAPISecret(namespace, SecretName))
1965+
DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec))
1966+
infra.SimulateMemcachedReady(memcachedName)
1967+
DeferCleanup(
1968+
mariadb.DeleteDBService,
1969+
mariadb.CreateDBService(
1970+
namespace,
1971+
GetNeutronAPI(neutronAPIName).Spec.DatabaseInstance,
1972+
corev1.ServiceSpec{
1973+
Ports: []corev1.ServicePort{{Port: 3306}},
1974+
},
1975+
),
1976+
)
1977+
SimulateTransportURLReady(apiTransportURLName)
1978+
mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: GetNeutronAPI(neutronAPIName).Spec.DatabaseAccount})
1979+
mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: neutronapi.DatabaseCRName})
1980+
1981+
if isOVNEnabled {
1982+
DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace))
1983+
}
1984+
DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(namespace))
1985+
1986+
// Create AC secret
1987+
acSecretName := fmt.Sprintf("ac-%s-secret", neutronapi.ServiceName)
1988+
acSecret := th.CreateSecret(
1989+
types.NamespacedName{Namespace: neutronAPIName.Namespace, Name: acSecretName},
1990+
map[string][]byte{
1991+
"AC_ID": []byte("test-ac-id"),
1992+
"AC_SECRET": []byte("test-ac-secret"),
1993+
},
1994+
)
1995+
DeferCleanup(th.DeleteInstance, acSecret)
1996+
})
1997+
1998+
It("should render ApplicationCredential auth in 01-neutron.conf", func() {
1999+
configSecretName := types.NamespacedName{
2000+
Namespace: neutronAPIName.Namespace,
2001+
Name: fmt.Sprintf("%s-config", neutronAPIName.Name),
2002+
}
2003+
2004+
Eventually(func(g Gomega) {
2005+
configSecret := th.GetSecret(configSecretName)
2006+
g.Expect(configSecret.Data).ShouldNot(BeNil())
2007+
2008+
conf := string(configSecret.Data["01-neutron.conf"])
2009+
g.Expect(conf).To(ContainSubstring("application_credential_id = test-ac-id"))
2010+
g.Expect(conf).To(ContainSubstring("application_credential_secret = test-ac-secret"))
2011+
}, timeout, interval).Should(Succeed())
2012+
})
2013+
})
19602014
}
19612015
}
19622016

0 commit comments

Comments
 (0)