Skip to content

Commit 83054d6

Browse files
committed
Add AppCred types and controller support
Signed-off-by: Veronika Fisarova <[email protected]>
1 parent ed8154b commit 83054d6

16 files changed

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

api/v1beta1/conditions.go

Lines changed: 15 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,16 @@ 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 created"
126+
127+
// KeystoneApplicationCredentialReadyErrorMessage
128+
KeystoneApplicationCredentialReadyErrorMessage = "ApplicationCredential error occurred: %s"
114129
)

api/v1beta1/keystoneapi.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,112 @@ 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+
if ospSecretName == "" {
223+
return "", ctrl.Result{}, fmt.Errorf("secret name is empty, cannot retrieve password for selector %q", passwordSelector)
224+
}
225+
if passwordSelector == "" {
226+
return "", ctrl.Result{}, fmt.Errorf("password selector is empty, cannot retrieve password from secret %q", ospSecretName)
227+
}
228+
data, res, err := secret.GetDataFromSecret(
229+
ctx,
230+
h,
231+
ospSecretName,
232+
10*time.Second,
233+
passwordSelector,
234+
)
235+
if err != nil {
236+
return "", ctrl.Result{}, fmt.Errorf("failed to get %q from Secret/%s: %w", passwordSelector, ospSecretName, err)
237+
}
238+
if res != (ctrl.Result{}) {
239+
return "", res, nil
240+
}
241+
if data == "" {
242+
return "", ctrl.Result{}, fmt.Errorf("password selector %q in secret %q is empty", passwordSelector, ospSecretName)
243+
}
244+
return data, ctrl.Result{}, nil
245+
}
246+
141247
// GetScopedAdminServiceClient - get a scoped admin serviceClient for the keystoneAPI instance
142248
func GetScopedAdminServiceClient(
143249
ctx context.Context,

0 commit comments

Comments
 (0)