Skip to content

Commit 3b64e91

Browse files
Akrogclaude
andcommitted
MCP: Use Keystone Application Credentials
Initial implementation of the MCP deployment uses the credentials from the `openstackclient` pod, which means that we are not the owners of that secret, just the copy we make in the `openstack-lightspeed` namespace, so those credentials could be removed/deleted and that would break our `openstack-cli` tool. In this patch we change the credentials and we leverage the `KeystoneApplicationCredential` CR to get our own credentials. Since OpenStack can be deployed on any namespace we are forced to have permissions to access any secret in the deployment. Alternatively we could change how the operator is deployed altogether and do a per-namespace deployment so only those secrets can be accessed. That is currently out of scope of this effort. Credential Rotation is handled by the code as well. Jira: OSPRH-27075 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d7551a2 commit 3b64e91

66 files changed

Lines changed: 1560 additions & 326 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

api/v1beta1/conditions.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ const (
5959
// OpenStackLightspeedMCPServerWaitingOpenStack
6060
OpenStackLightspeedMCPServerWaitingOpenStack = "MCP server deployed, waiting for OpenStackControlPlane to become ready"
6161

62+
// OpenStackLightspeedMCPServerCreatingUser
63+
OpenStackLightspeedMCPServerCreatingUser = "Creating OpenStack service user"
64+
65+
// OpenStackLightspeedMCPServerWaitingAC
66+
OpenStackLightspeedMCPServerWaitingAC = "Waiting for application credential secret"
67+
6268
// OpenStackLightspeedMCPServerDisabledMessage
6369
OpenStackLightspeedMCPServerDisabledMessage = "RHOSO MCP server is disabled (rhoso_mcps feature flag not set)"
6470

api/v1beta1/openstacklightspeed_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,11 @@ type OpenStackLightspeedStatus struct {
213213
// OpenStackReady indicates whether an OpenStackControlPlane was detected and
214214
// is ready. When true, the OpenStack MCP tools are included in lightspeed-stack config.
215215
OpenStackReady bool `json:"openStackReady,omitempty"`
216+
217+
// +optional
218+
// ApplicationCredentialSecret is the name of the current AC secret in the
219+
// OpenStack namespace. Tracked for rotation detection.
220+
ApplicationCredentialSecret string `json:"applicationCredentialSecret,omitempty"`
216221
}
217222

218223
// +kubebuilder:object:root=true

bundle/manifests/lightspeed.openstack.org_openstacklightspeeds.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,11 @@ spec:
187187
status:
188188
description: OpenStackLightspeedStatus defines the observed state of OpenStackLightspeed
189189
properties:
190+
applicationCredentialSecret:
191+
description: |-
192+
ApplicationCredentialSecret is the name of the current AC secret in the
193+
OpenStack namespace. Tracked for rotation detection.
194+
type: string
190195
conditions:
191196
description: Conditions
192197
items:

bundle/manifests/openstack-lightspeed-operator.clusterserviceversion.yaml

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ metadata:
2525
]
2626
capabilities: Basic Install
2727
categories: AI/Machine Learning
28-
createdAt: "2026-07-13T09:32:13Z"
28+
createdAt: "2026-07-13T15:45:52Z"
2929
description: AI-powered virtual assistant for Red Hat OpenStack Services on OpenShift
3030
features.operators.openshift.io/cnf: "false"
3131
features.operators.openshift.io/cni: "false"
@@ -169,9 +169,26 @@ spec:
169169
- ""
170170
resources:
171171
- configmaps
172+
verbs:
173+
- get
174+
- apiGroups:
175+
- ""
176+
resources:
172177
- secrets
173178
verbs:
179+
- create
180+
- get
181+
- update
182+
- apiGroups:
183+
- ""
184+
resourceNames:
185+
- lightspeed-password
186+
resources:
187+
- secrets
188+
verbs:
189+
- delete
174190
- get
191+
- update
175192
- apiGroups:
176193
- ""
177194
resourceNames:
@@ -216,6 +233,24 @@ spec:
216233
- get
217234
- list
218235
- watch
236+
- apiGroups:
237+
- keystone.openstack.org
238+
resources:
239+
- keystoneapplicationcredentials
240+
verbs:
241+
- create
242+
- delete
243+
- get
244+
- list
245+
- patch
246+
- update
247+
- watch
248+
- apiGroups:
249+
- keystone.openstack.org
250+
resources:
251+
- keystoneapplicationcredentials/status
252+
verbs:
253+
- get
219254
- apiGroups:
220255
- lightspeed.openstack.org
221256
resources:

cmd/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,5 +259,10 @@ func getDynamicWatchCRDs() map[schema.GroupVersionKind]*atomic.Bool {
259259
Version: "v1beta1",
260260
Kind: "OpenStackControlPlane",
261261
}: new(atomic.Bool),
262+
{
263+
Group: "keystone.openstack.org",
264+
Version: "v1beta1",
265+
Kind: "KeystoneApplicationCredential",
266+
}: new(atomic.Bool),
262267
}
263268
}

config/crd/bases/lightspeed.openstack.org_openstacklightspeeds.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,11 @@ spec:
187187
status:
188188
description: OpenStackLightspeedStatus defines the observed state of OpenStackLightspeed
189189
properties:
190+
applicationCredentialSecret:
191+
description: |-
192+
ApplicationCredentialSecret is the name of the current AC secret in the
193+
OpenStack namespace. Tracked for rotation detection.
194+
type: string
190195
conditions:
191196
description: Conditions
192197
items:

config/rbac/role.yaml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,26 @@ rules:
88
- ""
99
resources:
1010
- configmaps
11+
verbs:
12+
- get
13+
- apiGroups:
14+
- ""
15+
resources:
1116
- secrets
1217
verbs:
18+
- create
19+
- get
20+
- update
21+
- apiGroups:
22+
- ""
23+
resourceNames:
24+
- lightspeed-password
25+
resources:
26+
- secrets
27+
verbs:
28+
- delete
1329
- get
30+
- update
1431
- apiGroups:
1532
- ""
1633
resourceNames:
@@ -55,6 +72,24 @@ rules:
5572
- get
5673
- list
5774
- watch
75+
- apiGroups:
76+
- keystone.openstack.org
77+
resources:
78+
- keystoneapplicationcredentials
79+
verbs:
80+
- create
81+
- delete
82+
- get
83+
- list
84+
- patch
85+
- update
86+
- watch
87+
- apiGroups:
88+
- keystone.openstack.org
89+
resources:
90+
- keystoneapplicationcredentials/status
91+
verbs:
92+
- get
5893
- apiGroups:
5994
- lightspeed.openstack.org
6095
resources:

go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ require (
1111
github.com/onsi/gomega v1.40.0
1212
github.com/openshift/api v3.9.0+incompatible // from lib-common
1313
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.0
14+
github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.0
1415
github.com/operator-framework/api v0.37.0
1516
k8s.io/api v0.34.2
1617
k8s.io/apiextensions-apiserver v0.34.2
@@ -24,6 +25,8 @@ require (
2425
// must be consistent within modules and service operators
2526
replace github.com/openshift/api => github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e
2627

28+
require k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
29+
2730
require (
2831
cel.dev/expr v0.24.0 // indirect
2932
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
@@ -50,6 +53,7 @@ require (
5053
github.com/google/go-cmp v0.7.0 // indirect
5154
github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 // indirect
5255
github.com/google/uuid v1.6.0 // indirect
56+
github.com/gophercloud/gophercloud v1.14.1 // indirect
5357
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect
5458
github.com/inconshreveable/mousetrap v1.1.0 // indirect
5559
github.com/josharian/intern v1.0.0 // indirect
@@ -104,7 +108,6 @@ require (
104108
k8s.io/component-base v0.34.2 // indirect
105109
k8s.io/klog/v2 v2.130.1 // indirect
106110
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
107-
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
108111
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0 // indirect
109112
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
110113
sigs.k8s.io/randfill v1.0.0 // indirect

go.sum

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 h1:EwtI+Al+DeppwYX2oX
7171
github.com/google/pprof v0.0.0-20260402051712-545e8a4df936/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
7272
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
7373
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
74+
github.com/gophercloud/gophercloud v1.14.1 h1:DTCNaTVGl8/cFu58O1JwWgis9gtISAFONqpMKNg/Vpw=
75+
github.com/gophercloud/gophercloud v1.14.1/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM=
7476
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak=
7577
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII=
7678
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@@ -113,6 +115,8 @@ github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e h1:E1OdwSpqWuDPCedyU
113115
github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e/go.mod h1:Shkl4HanLwDiiBzakv+con/aMGnVE2MAGvoKp5oyYUo=
114116
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.0 h1:2TD4hi+MLt67jKxJUs2tuBKFMxibrLJQqKqhsTMsHeQ=
115117
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.0/go.mod h1:rgpcv2tLD+/vudXx/gpIQSTuRpk4GOxHx84xwfvQalM=
118+
github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.0 h1:8BniQwsPk8qjqoniLFDLnBEJgA0FLOwIrPDv93URiMo=
119+
github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.0/go.mod h1:tfMa+ochq7Dyilq9hQr2CEPfPtsj6IUgMmMqi4CWDmo=
116120
github.com/operator-framework/api v0.37.0 h1:2XCMWitBnumtJTqzip6LQKUwpM2pXVlt3gkpdlkbaCE=
117121
github.com/operator-framework/api v0.37.0/go.mod h1:NZs4vB+Jiamyv3pdPDjZtuC4U7KX0eq4z2r5hKY5fUA=
118122
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -196,6 +200,7 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
196200
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
197201
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
198202
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
203+
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
199204
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
200205
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
201206
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -206,6 +211,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
206211
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
207212
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
208213
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
214+
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
209215
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
210216
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
211217
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
@@ -218,13 +224,18 @@ golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
218224
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
219225
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
220226
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
227+
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
228+
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
229+
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
221230
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
222231
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
223232
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
233+
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
224234
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
225235
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
226236
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
227237
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
238+
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
228239
golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
229240
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=
230241
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
@@ -258,6 +269,7 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSP
258269
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
259270
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
260271
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
272+
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
261273
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
262274
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
263275
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

internal/controller/common.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,15 @@ func OpenStackControlPlaneGVK() schema.GroupVersionKind {
291291
}
292292
}
293293

294+
// KeystoneApplicationCredentialGVK returns the GroupVersionKind for KeystoneApplicationCredential.
295+
func KeystoneApplicationCredentialGVK() schema.GroupVersionKind {
296+
return schema.GroupVersionKind{
297+
Group: KeystoneApplicationCredentialGroup,
298+
Version: KeystoneApplicationCredentialVersion,
299+
Kind: KeystoneApplicationCredentialKind,
300+
}
301+
}
302+
294303
// IsDynamicCRDWatched reports whether a controller-runtime watch is currently
295304
// registered for the given GVK. The flag is reset to false when a CRD is
296305
// removed and its broken informer is cleaned up. This does NOT indicate

0 commit comments

Comments
 (0)