Skip to content

Commit bcad4d2

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

16 files changed

+2003
-1
lines changed
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
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 a additional access rule for an ApplicationCredential
78+
properties:
79+
method:
80+
description: Method is the HTTP verb to allow (defaults to all
81+
if empty)
82+
type: string
83+
path:
84+
description: Path is the API path to allow
85+
type: string
86+
service:
87+
description: Service is the OpenStack service type
88+
type: string
89+
type: object
90+
type: array
91+
expirationDays:
92+
default: 365
93+
description: ExpirationDays sets the lifetime in days for the ApplicationCredential
94+
minimum: 2
95+
type: integer
96+
gracePeriodDays:
97+
default: 182
98+
description: GracePeriodDays sets how many days before expiration
99+
the ApplicationCredential should be rotated
100+
minimum: 1
101+
type: integer
102+
passwordSelector:
103+
description: PasswordSelector for extracting the service password
104+
type: string
105+
roles:
106+
description: Roles to assign to the ApplicationCredential
107+
items:
108+
type: string
109+
minItems: 1
110+
type: array
111+
secret:
112+
description: Secret containing service user password
113+
type: string
114+
unrestricted:
115+
default: false
116+
description: Unrestricted indicates whether the ApplicationCredential
117+
may be used to create or destroy other credentials or trusts
118+
type: boolean
119+
userName:
120+
description: UserName - the Keystone user under which this ApplicationCredential
121+
is created
122+
type: string
123+
required:
124+
- passwordSelector
125+
- roles
126+
- secret
127+
- userName
128+
type: object
129+
x-kubernetes-validations:
130+
- message: gracePeriodDays must be smaller than expirationDays
131+
rule: self.gracePeriodDays < self.expirationDays
132+
status:
133+
description: KeystoneApplicationCredentialStatus defines the observed
134+
state
135+
properties:
136+
acID:
137+
description: ACID - the ID in Keystone for this ApplicationCredential
138+
type: string
139+
conditions:
140+
description: Conditions
141+
items:
142+
description: Condition defines an observation of a API resource
143+
operational state.
144+
properties:
145+
lastTransitionTime:
146+
description: |-
147+
Last time the condition transitioned from one status to another.
148+
This should be when the underlying condition changed. If that is not known, then using the time when
149+
the API field changed is acceptable.
150+
format: date-time
151+
type: string
152+
message:
153+
description: A human readable message indicating details about
154+
the transition.
155+
type: string
156+
reason:
157+
description: The reason for the condition's last transition
158+
in CamelCase.
159+
type: string
160+
severity:
161+
description: |-
162+
Severity provides a classification of Reason code, so the current situation is immediately
163+
understandable and could act accordingly.
164+
It is meant for situations where Status=False and it should be indicated if it is just
165+
informational, warning (next reconciliation might fix it) or an error (e.g. DB create issue
166+
and no actions to automatically resolve the issue can/should be done).
167+
For conditions where Status=Unknown or Status=True the Severity should be SeverityNone.
168+
type: string
169+
status:
170+
description: Status of the condition, one of True, False, Unknown.
171+
type: string
172+
type:
173+
description: Type of condition in CamelCase.
174+
type: string
175+
required:
176+
- lastTransitionTime
177+
- status
178+
- type
179+
type: object
180+
type: array
181+
createdAt:
182+
description: CreatedAt - timestap of creation
183+
format: date-time
184+
type: string
185+
expiresAt:
186+
description: ExpiresAt - time of validity expiration
187+
format: date-time
188+
type: string
189+
lastRotated:
190+
description: LastRotated - timestamp when credentials were last rotated
191+
format: date-time
192+
type: string
193+
rotationEligibleAt:
194+
description: |-
195+
RotationEligibleAt indicates when rotation becomes eligible (start of grace period window).
196+
Computed as ExpiresAt - GracePeriodDays. The AC can be rotated after this timestamp.
197+
format: date-time
198+
type: string
199+
secretName:
200+
description: SecretName - name of the k8s Secret storing the ApplicationCredential
201+
secret
202+
type: string
203+
type: object
204+
type: object
205+
served: true
206+
storage: true
207+
subresources:
208+
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)