Skip to content

Commit a067b61

Browse files
committed
Read password hash from User's importCredentialSecret
If `passwordHash` field is set, then `password` field is ignored and the resulting credential secret will contain only the hash. A passwordless user is created if the hash is an empty string.
1 parent 16f09c3 commit a067b61

File tree

2 files changed

+70
-33
lines changed

2 files changed

+70
-33
lines changed

controllers/user_controller.go

Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,18 @@ const (
4040
// +kubebuilder:rbac:groups=rabbitmq.com,resources=users/status,verbs=get;update;patch
4141
// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create
4242

43+
// UserCredentials describes the credentials that can be provided in ImportCredentialsSecret for a User.
44+
// If the secret is not provided, a random username and password will be generated.
45+
type UserCredentials struct {
46+
// Must be present if ImportCredentialsSecret is provided.
47+
Username string
48+
// If PasswordHash is an empty string, a passwordless user is created.
49+
// If PasswordHash is nil, Password is used instead.
50+
PasswordHash *string
51+
// If Password is empty and PasswordHash is nil, a random password is generated.
52+
Password string
53+
}
54+
4355
type UserReconciler struct {
4456
client.Client
4557
Scheme *runtime.Scheme
@@ -48,21 +60,31 @@ type UserReconciler struct {
4860
func (r *UserReconciler) declareCredentials(ctx context.Context, user *topology.User) (string, error) {
4961
logger := ctrl.LoggerFrom(ctx)
5062

51-
username, password, err := r.generateCredentials(ctx, user)
63+
credentials, err := r.generateCredentials(ctx, user)
5264
if err != nil {
5365
logger.Error(err, "failed to generate credentials")
5466
return "", err
5567
}
56-
// Password wasn't in the provided input secret we need to generate a random one
57-
if password == "" {
58-
password, err = internal.RandomEncodedString(24)
68+
// Neither PasswordHash nor Password wasn't in the provided input secret we need to generate a random password
69+
if credentials.PasswordHash == nil && credentials.Password == "" {
70+
credentials.Password, err = internal.RandomEncodedString(24)
5971
if err != nil {
6072
return "", fmt.Errorf("failed to generate random password: %w", err)
6173
}
62-
6374
}
6475

65-
logger.Info("Credentials generated for User", "user", user.Name, "generatedUsername", username)
76+
logger.Info("Credentials generated for User", "user", user.Name, "generatedUsername", credentials.Username)
77+
78+
credentialSecretData := map[string][]byte{
79+
"username": []byte(credentials.Username),
80+
}
81+
if credentials.PasswordHash != nil {
82+
// Create `passwordHash` field only if necessary, to distinguish between an unset hash and an empty one
83+
credentialSecretData["passwordHash"] = []byte(*credentials.PasswordHash)
84+
} else {
85+
// Store password in the credential secret only if it will be used
86+
credentialSecretData["password"] = []byte(credentials.Password)
87+
}
6688

6789
credentialSecret := corev1.Secret{
6890
ObjectMeta: metav1.ObjectMeta{
@@ -72,10 +94,7 @@ func (r *UserReconciler) declareCredentials(ctx context.Context, user *topology.
7294
Type: corev1.SecretTypeOpaque,
7395
// The format of the generated Secret conforms to the Provisioned Service
7496
// type Spec. For more information, see https://k8s-service-bindings.github.io/spec/#provisioned-service.
75-
Data: map[string][]byte{
76-
"username": []byte(username),
77-
"password": []byte(password),
78-
},
97+
Data: credentialSecretData,
7998
}
8099

81100
var operationResult controllerutil.OperationResult
@@ -102,10 +121,10 @@ func (r *UserReconciler) declareCredentials(ctx context.Context, user *topology.
102121
}
103122

104123
logger.Info("Successfully declared credentials secret", "secret", credentialSecret.Name, "namespace", credentialSecret.Namespace)
105-
return username, nil
124+
return credentials.Username, nil
106125
}
107126

108-
func (r *UserReconciler) generateCredentials(ctx context.Context, user *topology.User) (string, string, error) {
127+
func (r *UserReconciler) generateCredentials(ctx context.Context, user *topology.User) (UserCredentials, error) {
109128
logger := ctrl.LoggerFrom(ctx)
110129

111130
var err error
@@ -117,37 +136,48 @@ func (r *UserReconciler) generateCredentials(ctx context.Context, user *topology
117136
return r.importCredentials(ctx, user.Spec.ImportCredentialsSecret.Name, user.Namespace)
118137
}
119138

120-
username, err := internal.RandomEncodedString(24)
139+
credentials := UserCredentials{}
140+
141+
credentials.Username, err = internal.RandomEncodedString(24)
121142
if err != nil {
122-
return "", "", fmt.Errorf("failed to generate random username: %w", err)
143+
return credentials, fmt.Errorf("failed to generate random username: %w", err)
123144
}
124-
password, err := internal.RandomEncodedString(24)
145+
credentials.Password, err = internal.RandomEncodedString(24)
125146
if err != nil {
126-
return "", "", fmt.Errorf("failed to generate random password: %w", err)
147+
return credentials, fmt.Errorf("failed to generate random password: %w", err)
127148
}
128-
return username, password, nil
149+
return credentials, nil
129150
}
130151

131-
func (r *UserReconciler) importCredentials(ctx context.Context, secretName, secretNamespace string) (string, string, error) {
152+
func (r *UserReconciler) importCredentials(ctx context.Context, secretName, secretNamespace string) (UserCredentials, error) {
132153
logger := ctrl.LoggerFrom(ctx)
133154
logger.Info("Importing user credentials from provided Secret", "secretName", secretName, "secretNamespace", secretNamespace)
134155

156+
var credentials UserCredentials
135157
var credentialsSecret corev1.Secret
158+
136159
err := r.Client.Get(ctx, types.NamespacedName{Name: secretName, Namespace: secretNamespace}, &credentialsSecret)
137160
if err != nil {
138-
return "", "", fmt.Errorf("could not find password secret %s in namespace %s; Err: %w", secretName, secretNamespace, err)
161+
return credentials, fmt.Errorf("could not find password secret %s in namespace %s; Err: %w", secretName, secretNamespace, err)
139162
}
163+
140164
username, ok := credentialsSecret.Data["username"]
141165
if !ok {
142-
return "", "", fmt.Errorf("could not find username key in credentials secret: %s", credentialsSecret.Name)
166+
return credentials, fmt.Errorf("could not find username key in credentials secret: %s", credentialsSecret.Name)
143167
}
144-
password, ok := credentialsSecret.Data["password"]
145-
if !ok {
146-
return string(username), "", nil
168+
credentials.Username = string(username)
169+
170+
password := credentialsSecret.Data["password"]
171+
credentials.Password = string(password)
172+
173+
passwordHash, ok := credentialsSecret.Data["passwordHash"]
174+
if ok {
175+
credentials.PasswordHash = new(string)
176+
*credentials.PasswordHash = string(passwordHash)
147177
}
148178

149179
logger.Info("Retrieved credentials from Secret", "secretName", secretName, "retrievedUsername", string(username))
150-
return string(username), string(password), nil
180+
return credentials, nil
151181
}
152182

153183
func (r *UserReconciler) setUserStatus(ctx context.Context, user *topology.User, username string) error {

internal/user_settings.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,20 @@ func GenerateUserSettings(credentials *corev1.Secret, tags []topology.UserTag) (
2222
if !ok {
2323
return rabbithole.UserSettings{}, fmt.Errorf("could not find username in credentials secret %s", credentials.Name)
2424
}
25-
password, ok := credentials.Data["password"]
25+
26+
passwordHash, ok := credentials.Data["passwordHash"]
2627
if !ok {
27-
return rabbithole.UserSettings{}, fmt.Errorf("could not find password in credentials secret %s", credentials.Name)
28+
// Use password as a fallback
29+
password, ok := credentials.Data["password"]
30+
if !ok {
31+
return rabbithole.UserSettings{}, fmt.Errorf("could not find passwordHash or password in credentials secret %s", credentials.Name)
32+
}
33+
// To avoid sending raw passwords over the wire, compute a password hash using a random salt
34+
// and use this in the UserSettings instead.
35+
// For more information on this hashing algorithm, see
36+
// https://www.rabbitmq.com/passwords.html#computing-password-hash.
37+
passwordHashStr := rabbithole.Base64EncodedSaltedPasswordHashSHA512(string(password))
38+
passwordHash = []byte(passwordHashStr)
2839
}
2940

3041
var userTagStrings []string
@@ -33,13 +44,9 @@ func GenerateUserSettings(credentials *corev1.Secret, tags []topology.UserTag) (
3344
}
3445

3546
return rabbithole.UserSettings{
36-
Name: string(username),
37-
Tags: userTagStrings,
38-
// To avoid sending raw passwords over the wire, compute a password hash using a random salt
39-
// and use this in the UserSettings instead.
40-
// For more information on this hashing algorithm, see
41-
// https://www.rabbitmq.com/passwords.html#computing-password-hash.
42-
PasswordHash: rabbithole.Base64EncodedSaltedPasswordHashSHA512(string(password)),
47+
Name: string(username),
48+
Tags: userTagStrings,
49+
PasswordHash: string(passwordHash),
4350
HashingAlgorithm: rabbithole.HashingAlgorithmSHA512,
4451
}, nil
4552
}

0 commit comments

Comments
 (0)