Skip to content

Commit f721c2e

Browse files
authored
Merge pull request #709 from dlaw4608/user_password
Keystone: User CreateOpts for password field
2 parents 2fe2021 + c77882f commit f721c2e

33 files changed

Lines changed: 325 additions & 24 deletions

api/v1alpha1/user_types.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ type UserResourceSpec struct {
4242
// enabled defines whether a user is enabled or disabled
4343
// +optional
4444
Enabled *bool `json:"enabled,omitempty"`
45+
46+
// passwordRef is a reference to a Secret containing the password
47+
// for this user. The Secret must contain a key named "password".
48+
// +required
49+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="passwordRef is immutable"
50+
PasswordRef KubernetesNameRef `json:"passwordRef,omitempty"`
4551
}
4652

4753
// UserFilter defines an existing resource by its properties
@@ -81,4 +87,9 @@ type UserResourceStatus struct {
8187
// enabled defines whether a user is enabled or disabled
8288
// +optional
8389
Enabled bool `json:"enabled,omitempty"`
90+
91+
// passwordExpiresAt is the timestamp at which the user's password expires.
92+
// +kubebuilder:validation:MaxLength:=1024
93+
// +optional
94+
PasswordExpiresAt string `json:"passwordExpiresAt,omitempty"`
8495
}

cmd/models-schema/zz_generated.openapi.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/openstack.k-orc.cloud_users.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,18 @@ spec:
186186
minLength: 1
187187
pattern: ^[^,]+$
188188
type: string
189+
passwordRef:
190+
description: |-
191+
passwordRef is a reference to a Secret containing the password
192+
for this user. The Secret must contain a key named "password".
193+
maxLength: 253
194+
minLength: 1
195+
type: string
196+
x-kubernetes-validations:
197+
- message: passwordRef is immutable
198+
rule: self == oldSelf
199+
required:
200+
- passwordRef
189201
type: object
190202
required:
191203
- cloudCredentialsRef
@@ -313,6 +325,11 @@ spec:
313325
not be unique.
314326
maxLength: 1024
315327
type: string
328+
passwordExpiresAt:
329+
description: passwordExpiresAt is the timestamp at which the user's
330+
password expires.
331+
maxLength: 1024
332+
type: string
316333
type: object
317334
type: object
318335
required:

config/samples/openstack_v1alpha1_user.yaml

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,35 @@
11
---
22
apiVersion: openstack.k-orc.cloud/v1alpha1
3+
kind: Domain
4+
metadata:
5+
name: user-sample
6+
spec:
7+
cloudCredentialsRef:
8+
cloudName: openstack-admin
9+
secretName: openstack-clouds
10+
managementPolicy: managed
11+
resource: {}
12+
---
13+
apiVersion: openstack.k-orc.cloud/v1alpha1
14+
kind: Project
15+
metadata:
16+
name: user-sample
17+
spec:
18+
cloudCredentialsRef:
19+
cloudName: openstack-admin
20+
secretName: openstack-clouds
21+
managementPolicy: managed
22+
resource: {}
23+
---
24+
apiVersion: v1
25+
kind: Secret
26+
metadata:
27+
name: user-sample
28+
type: Opaque
29+
stringData:
30+
password: "TestPassword"
31+
---
32+
apiVersion: openstack.k-orc.cloud/v1alpha1
333
kind: User
434
metadata:
535
name: user-sample
@@ -9,4 +39,9 @@ spec:
939
secretName: openstack-clouds
1040
managementPolicy: managed
1141
resource:
12-
description: Sample User
42+
name: user-sample
43+
description: User sample
44+
domainRef: user-sample
45+
defaultProjectRef: user-sample
46+
enabled: true
47+
passwordRef: user-sample

internal/controllers/user/actuator.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,26 @@ func (actuator userActuator) CreateResource(ctx context.Context, obj orcObjectPT
135135
defaultProjectID = ptr.Deref(project.Status.ID, "")
136136
}
137137
}
138+
139+
var password string
140+
{
141+
secret, secretReconcileStatus := dependency.FetchDependency(
142+
ctx, actuator.k8sClient, obj.Namespace,
143+
&resource.PasswordRef, "Secret",
144+
func(*corev1.Secret) bool { return true },
145+
)
146+
reconcileStatus = reconcileStatus.WithReconcileStatus(secretReconcileStatus)
147+
if secretReconcileStatus == nil {
148+
passwordBytes, ok := secret.Data["password"]
149+
if !ok {
150+
reconcileStatus = reconcileStatus.WithReconcileStatus(
151+
progress.NewReconcileStatus().WithProgressMessage("Password secret does not contain \"password\" key"))
152+
} else {
153+
password = string(passwordBytes)
154+
}
155+
}
156+
}
157+
138158
if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule {
139159
return nil, reconcileStatus
140160
}
@@ -144,6 +164,7 @@ func (actuator userActuator) CreateResource(ctx context.Context, obj orcObjectPT
144164
DomainID: domainID,
145165
Enabled: resource.Enabled,
146166
DefaultProjectID: defaultProjectID,
167+
Password: password,
147168
}
148169

149170
osResource, err := actuator.osClient.CreateUser(ctx, createOpts)

internal/controllers/user/controller.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"errors"
2222

23+
corev1 "k8s.io/api/core/v1"
2324
ctrl "sigs.k8s.io/controller-runtime"
2425
"sigs.k8s.io/controller-runtime/pkg/builder"
2526
"sigs.k8s.io/controller-runtime/pkg/controller"
@@ -86,6 +87,17 @@ var domainImportDependency = dependency.NewDependency[*orcv1alpha1.UserList, *or
8687
},
8788
)
8889

90+
var passwordDependency = dependency.NewDependency[*orcv1alpha1.UserList, *corev1.Secret](
91+
"spec.resource.passwordRef",
92+
func(user *orcv1alpha1.User) []string {
93+
resource := user.Spec.Resource
94+
if resource == nil {
95+
return nil
96+
}
97+
return []string{string(resource.PasswordRef)}
98+
},
99+
)
100+
89101
// SetupWithManager sets up the controller with the Manager.
90102
func (c userReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
91103
log := ctrl.LoggerFrom(ctx)
@@ -106,8 +118,14 @@ func (c userReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctr
106118
return err
107119
}
108120

121+
passwordWatchEventHandler, err := passwordDependency.WatchEventHandler(log, k8sClient)
122+
if err != nil {
123+
return err
124+
}
125+
109126
builder := ctrl.NewControllerManagedBy(mgr).
110127
WithOptions(options).
128+
For(&orcv1alpha1.User{}).
111129
Watches(&orcv1alpha1.Domain{}, domainWatchEventHandler,
112130
builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Domain{})),
113131
).
@@ -118,12 +136,20 @@ func (c userReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctr
118136
Watches(&orcv1alpha1.Domain{}, domainImportWatchEventHandler,
119137
builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Domain{})),
120138
).
121-
For(&orcv1alpha1.User{})
139+
// XXX: This is a general watch on secrets. A general watch on secrets
140+
// is undesirable because:
141+
// - It requires problematic RBAC
142+
// - Secrets are arbitrarily large, and we don't want to cache their contents
143+
//
144+
// These will require separate solutions. For the latter we should
145+
// probably use a MetadataOnly watch on secrets.
146+
Watches(&corev1.Secret{}, passwordWatchEventHandler)
122147

123148
if err := errors.Join(
124149
domainDependency.AddToManager(ctx, mgr),
125150
projectDependency.AddToManager(ctx, mgr),
126151
domainImportDependency.AddToManager(ctx, mgr),
152+
passwordDependency.AddToManager(ctx, mgr),
127153
credentialsDependency.AddToManager(ctx, mgr),
128154
credentials.AddCredentialsWatch(log, mgr.GetClient(), builder, credentialsDependency),
129155
); err != nil {

internal/controllers/user/status.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ limitations under the License.
1717
package user
1818

1919
import (
20+
"time"
21+
2022
"github.com/go-logr/logr"
2123
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2224

@@ -62,5 +64,9 @@ func (userStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResou
6264
resourceStatus.WithDefaultProjectID(osResource.DefaultProjectID)
6365
}
6466

67+
if !osResource.PasswordExpiresAt.IsZero() {
68+
resourceStatus.WithPasswordExpiresAt(osResource.PasswordExpiresAt.Format(time.RFC3339))
69+
}
70+
6571
statusApply.WithResource(resourceStatus)
6672
}

internal/controllers/user/tests/user-create-full/00-assert.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,5 @@ assertAll:
3535
- celExpr: "user.status.id != ''"
3636
- celExpr: "user.status.resource.domainID == domain.status.id"
3737
- celExpr: "user.status.resource.defaultProjectID == project.status.id"
38+
# passwordExpiresAt depends on the Keystone security_compliance
39+
# configuration and is not asserted here.

internal/controllers/user/tests/user-create-full/00-create-resource.yaml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ spec:
2121
managementPolicy: managed
2222
resource: {}
2323
---
24+
apiVersion: v1
25+
kind: Secret
26+
metadata:
27+
name: user-create-full
28+
type: Opaque
29+
stringData:
30+
password: "TestPassword"
31+
---
2432
apiVersion: openstack.k-orc.cloud/v1alpha1
2533
kind: User
2634
metadata:
@@ -35,4 +43,5 @@ spec:
3543
description: User from "create full" test
3644
domainRef: user-create-full
3745
defaultProjectRef: user-create-full
38-
enabled: true
46+
enabled: true
47+
passwordRef: user-create-full

internal/controllers/user/tests/user-create-minimal/00-assert.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,6 @@ assertAll:
2727
- celExpr: "!has(user.status.resource.description)"
2828
- celExpr: "user.status.resource.domainID == 'default'"
2929
- celExpr: "!has(user.status.resource.defaultProjectID)"
30+
# passwordExpiresAt depends on the Keystone security_compliance
31+
# configuration and is not asserted here.
32+

0 commit comments

Comments
 (0)