Skip to content

Commit 3c7806d

Browse files
committed
Add Application Credential API types, AC-controller support and helper functions for service operators.
Signed-off-by: Veronika Fisarova <vfisarov@redhat.com>
1 parent 3f15791 commit 3c7806d

16 files changed

+2061
-1
lines changed
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
---
2+
apiVersion: apiextensions.k8s.io/v1
3+
kind: CustomResourceDefinition
4+
metadata:
5+
annotations:
6+
controller-gen.kubebuilder.io/version: v0.18.0
7+
name: keystoneapplicationcredentials.keystone.openstack.org
8+
spec:
9+
group: keystone.openstack.org
10+
names:
11+
kind: KeystoneApplicationCredential
12+
listKind: KeystoneApplicationCredentialList
13+
plural: keystoneapplicationcredentials
14+
shortNames:
15+
- appcred
16+
singular: keystoneapplicationcredential
17+
scope: Namespaced
18+
versions:
19+
- additionalPrinterColumns:
20+
- description: Keystone ApplicationCredential ID
21+
jsonPath: .status.acID
22+
name: ACID
23+
type: string
24+
- description: Secret holding ApplicationCredential secret
25+
jsonPath: .status.secretName
26+
name: SecretName
27+
type: string
28+
- description: Last rotation time
29+
format: date-time
30+
jsonPath: .status.lastRotated
31+
name: LastRotated
32+
type: string
33+
- description: When rotation becomes eligible
34+
format: date-time
35+
jsonPath: .status.rotationEligibleAt
36+
name: RotationEligible
37+
type: string
38+
- description: Status
39+
jsonPath: .status.conditions[0].status
40+
name: Status
41+
type: string
42+
- description: Message
43+
jsonPath: .status.conditions[0].message
44+
name: Message
45+
type: string
46+
name: v1beta1
47+
schema:
48+
openAPIV3Schema:
49+
description: KeystoneApplicationCredential is the Schema for the applicationcredentials
50+
API
51+
properties:
52+
apiVersion:
53+
description: |-
54+
APIVersion defines the versioned schema of this representation of an object.
55+
Servers should convert recognized schemas to the latest internal value, and
56+
may reject unrecognized values.
57+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
58+
type: string
59+
kind:
60+
description: |-
61+
Kind is a string value representing the REST resource this object represents.
62+
Servers may infer this from the endpoint the client submits requests to.
63+
Cannot be updated.
64+
In CamelCase.
65+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
66+
type: string
67+
metadata:
68+
type: object
69+
spec:
70+
description: KeystoneApplicationCredentialSpec defines what the user can
71+
set
72+
properties:
73+
accessRules:
74+
description: AccessRules defines which services the ApplicationCredential
75+
is permitted to access
76+
items:
77+
description: ACRule defines an access rule for an ApplicationCredential
78+
properties:
79+
method:
80+
description: Method is the HTTP verb to allow
81+
enum:
82+
- GET
83+
- HEAD
84+
- POST
85+
- PUT
86+
- PATCH
87+
- DELETE
88+
type: string
89+
path:
90+
description: Path is the API path to allow
91+
minLength: 1
92+
type: string
93+
service:
94+
description: Service is the OpenStack service type
95+
minLength: 1
96+
type: string
97+
required:
98+
- method
99+
- path
100+
- service
101+
type: object
102+
type: array
103+
expirationDays:
104+
default: 365
105+
description: ExpirationDays sets the lifetime in days for the ApplicationCredential
106+
minimum: 2
107+
type: integer
108+
gracePeriodDays:
109+
default: 182
110+
description: GracePeriodDays sets how many days before expiration
111+
the ApplicationCredential should be rotated
112+
minimum: 1
113+
type: integer
114+
passwordSelector:
115+
description: PasswordSelector for extracting the service password
116+
minLength: 1
117+
type: string
118+
roles:
119+
description: Roles to assign to the ApplicationCredential
120+
items:
121+
type: string
122+
minItems: 1
123+
type: array
124+
secret:
125+
description: Secret containing service user password
126+
minLength: 1
127+
type: string
128+
unrestricted:
129+
default: false
130+
description: Unrestricted indicates whether the ApplicationCredential
131+
may be used to create or destroy other credentials or trusts
132+
type: boolean
133+
userName:
134+
description: UserName - the Keystone user under which this ApplicationCredential
135+
is created
136+
type: string
137+
required:
138+
- passwordSelector
139+
- roles
140+
- secret
141+
- userName
142+
type: object
143+
x-kubernetes-validations:
144+
- message: gracePeriodDays must be smaller than expirationDays
145+
rule: self.gracePeriodDays < self.expirationDays
146+
status:
147+
description: KeystoneApplicationCredentialStatus defines the observed
148+
state
149+
properties:
150+
acID:
151+
description: ACID - the ID in Keystone for this ApplicationCredential
152+
type: string
153+
conditions:
154+
description: Conditions
155+
items:
156+
description: Condition defines an observation of a API resource
157+
operational state.
158+
properties:
159+
lastTransitionTime:
160+
description: |-
161+
Last time the condition transitioned from one status to another.
162+
This should be when the underlying condition changed. If that is not known, then using the time when
163+
the API field changed is acceptable.
164+
format: date-time
165+
type: string
166+
message:
167+
description: A human readable message indicating details about
168+
the transition.
169+
type: string
170+
reason:
171+
description: The reason for the condition's last transition
172+
in CamelCase.
173+
type: string
174+
severity:
175+
description: |-
176+
Severity provides a classification of Reason code, so the current situation is immediately
177+
understandable and could act accordingly.
178+
It is meant for situations where Status=False and it should be indicated if it is just
179+
informational, warning (next reconciliation might fix it) or an error (e.g. DB create issue
180+
and no actions to automatically resolve the issue can/should be done).
181+
For conditions where Status=Unknown or Status=True the Severity should be SeverityNone.
182+
type: string
183+
status:
184+
description: Status of the condition, one of True, False, Unknown.
185+
type: string
186+
type:
187+
description: Type of condition in CamelCase.
188+
type: string
189+
required:
190+
- lastTransitionTime
191+
- status
192+
- type
193+
type: object
194+
type: array
195+
createdAt:
196+
description: CreatedAt - timestap of creation
197+
format: date-time
198+
type: string
199+
expiresAt:
200+
description: ExpiresAt - time of validity expiration
201+
format: date-time
202+
type: string
203+
lastRotated:
204+
description: LastRotated - timestamp when credentials were last rotated
205+
format: date-time
206+
type: string
207+
observedGeneration:
208+
description: ObservedGeneration - the most recent generation observed
209+
for this ApplicationCredential.
210+
format: int64
211+
type: integer
212+
rotationEligibleAt:
213+
description: |-
214+
RotationEligibleAt indicates when rotation becomes eligible (start of grace period window).
215+
Computed as ExpiresAt - GracePeriodDays. The AC can be rotated after this timestamp.
216+
format: date-time
217+
type: string
218+
secretName:
219+
description: SecretName - name of the k8s Secret storing the ApplicationCredential
220+
secret
221+
type: string
222+
securityHash:
223+
description: |-
224+
SecurityHash tracks the hash of security-critical spec fields (roles, accessRules, unrestricted).
225+
Used to detect when these fields change and trigger immediate rotation.
226+
type: string
227+
type: object
228+
type: object
229+
served: true
230+
storage: true
231+
subresources:
232+
status: {}

api/v1beta1/conditions.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ const (
3535

3636
// KeystoneServiceOSUserReadyCondition Status=True condition which indicates if the service user got created in the keystone instance is ready/was successful
3737
KeystoneServiceOSUserReadyCondition condition.Type = "KeystoneServiceOSUserReady"
38+
39+
// KeystoneApplicationCredentialReadyCondition Status=True condition which indicates if the ApplicationCredential has been created and is ready
40+
KeystoneApplicationCredentialReadyCondition condition.Type = "KeystoneApplicationCredentialReady"
3841
)
3942

4043
// Common Messages used by API objects.
@@ -111,4 +114,19 @@ const (
111114

112115
// KeystoneServiceOSUserReadyErrorMessage
113116
KeystoneServiceOSUserReadyErrorMessage = "Keystone Service user error occured %s"
117+
118+
//
119+
// KeystoneApplicationCredentialReady condition messages
120+
//
121+
// KeystoneApplicationCredentialReadyInitMessage
122+
KeystoneApplicationCredentialReadyInitMessage = "ApplicationCredential not yet created"
123+
124+
// KeystoneApplicationCredentialReadyMessage
125+
KeystoneApplicationCredentialReadyMessage = "ApplicationCredential ready"
126+
127+
// KeystoneApplicationCredentialWaitingMessage
128+
KeystoneApplicationCredentialWaitingMessage = "ApplicationCredential waiting for secret %s to be available"
129+
130+
// KeystoneApplicationCredentialReadyErrorMessage
131+
KeystoneApplicationCredentialReadyErrorMessage = "ApplicationCredential error occurred: %s"
114132
)

api/v1beta1/keystoneapi.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,103 @@ func GetAdminServiceClient(
138138
return os, ctrlResult, nil
139139
}
140140

141+
// GetUserServiceClient - returns an *openstack.OpenStack object scoped as the given service user
142+
func GetUserServiceClient(
143+
ctx context.Context,
144+
h *helper.Helper,
145+
keystoneAPI *KeystoneAPI,
146+
userName string,
147+
secretName string,
148+
passwordSelector string,
149+
) (*openstack.OpenStack, ctrl.Result, error) {
150+
151+
authURL, err := keystoneAPI.GetEndpoint(endpoint.EndpointInternal)
152+
if err != nil {
153+
return nil, ctrl.Result{}, err
154+
}
155+
156+
parsedAuthURL, err := url.Parse(authURL)
157+
if err != nil {
158+
return nil, ctrl.Result{}, err
159+
}
160+
161+
tlsConfig := &openstack.TLSConfig{}
162+
if parsedAuthURL.Scheme == "https" && keystoneAPI.Spec.TLS.CaBundleSecretName != "" {
163+
caCert, ctrlResult, err := secret.GetDataFromSecret(
164+
ctx,
165+
h,
166+
keystoneAPI.Spec.TLS.CaBundleSecretName,
167+
10*time.Second,
168+
tls.InternalCABundleKey)
169+
if err != nil {
170+
return nil, ctrlResult, err
171+
}
172+
if (ctrlResult != ctrl.Result{}) {
173+
return nil, ctrlResult,
174+
fmt.Errorf("CABundleSecret %s not found",
175+
keystoneAPI.Spec.TLS.CaBundleSecretName)
176+
}
177+
178+
tlsConfig = &openstack.TLSConfig{
179+
CACerts: []string{caCert},
180+
}
181+
}
182+
183+
password, res, err := getPasswordFromOSPSecret(ctx, h, secretName, passwordSelector)
184+
if err != nil {
185+
return nil, ctrl.Result{}, fmt.Errorf("failed to get password from osp-secret for user %q: %w", userName, err)
186+
}
187+
if res != (ctrl.Result{}) {
188+
return nil, res, nil
189+
}
190+
191+
scope := &gophercloud.AuthScope{
192+
ProjectName: "service",
193+
DomainName: "Default",
194+
}
195+
196+
osClient, err := openstack.NewOpenStack(
197+
ctx,
198+
h.GetLogger(),
199+
openstack.AuthOpts{
200+
AuthURL: authURL,
201+
Username: userName,
202+
Password: password,
203+
TenantName: "service",
204+
DomainName: "Default",
205+
Region: keystoneAPI.Spec.Region,
206+
TLS: tlsConfig,
207+
Scope: scope,
208+
},
209+
)
210+
if err != nil {
211+
return nil, ctrl.Result{}, err
212+
}
213+
214+
return osClient, ctrl.Result{}, nil
215+
}
216+
217+
func getPasswordFromOSPSecret(
218+
ctx context.Context,
219+
h *helper.Helper,
220+
ospSecretName, passwordSelector string,
221+
) (string, ctrl.Result, error) {
222+
data, res, err := secret.GetDataFromSecret(
223+
ctx,
224+
h,
225+
ospSecretName,
226+
10*time.Second,
227+
passwordSelector,
228+
)
229+
if err != nil {
230+
return "", ctrl.Result{}, err
231+
}
232+
if res != (ctrl.Result{}) {
233+
return "", res, nil
234+
}
235+
return data, ctrl.Result{}, nil
236+
}
237+
141238
// GetScopedAdminServiceClient - get a scoped admin serviceClient for the keystoneAPI instance
142239
func GetScopedAdminServiceClient(
143240
ctx context.Context,

0 commit comments

Comments
 (0)