Skip to content

Commit 316b4d3

Browse files
Merge pull request #565 from lmiccini/reject-default-user
Reject RabbitMQUser when username matches cluster default user
2 parents 9c35b4c + d7d6d52 commit 316b4d3

2 files changed

Lines changed: 129 additions & 0 deletions

File tree

apis/rabbitmq/v1beta1/rabbitmquser_webhook.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ func (r *RabbitMQUser) ValidateCreate(k8sClient client.Client) (admission.Warnin
112112
}
113113
}
114114

115+
// Validate username doesn't conflict with the cluster's default user
116+
if err := r.validateNotDefaultUser(k8sClient); err != nil {
117+
return nil, err
118+
}
119+
115120
return nil, r.validateUniqueUsername(k8sClient, r.Spec.Username)
116121
}
117122

@@ -363,6 +368,40 @@ func (r *RabbitMQUser) validateUniqueUsername(k8sClient client.Client, username
363368
return nil
364369
}
365370

371+
// validateNotDefaultUser checks that the username doesn't match the cluster's default user
372+
func (r *RabbitMQUser) validateNotDefaultUser(k8sClient client.Client) error {
373+
secretName := fmt.Sprintf("%s-default-user", r.Spec.RabbitmqClusterName)
374+
secret := &corev1.Secret{}
375+
376+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
377+
defer cancel()
378+
379+
if err := k8sClient.Get(ctx,
380+
client.ObjectKey{Name: secretName, Namespace: r.Namespace},
381+
secret); err != nil {
382+
// If the secret doesn't exist, the cluster may not be created yet - skip check
383+
if apierrors.IsNotFound(err) {
384+
return nil
385+
}
386+
return apierrors.NewInternalError(fmt.Errorf("failed to look up default user secret: %w", err))
387+
}
388+
389+
defaultUsername := string(secret.Data["username"])
390+
if r.Spec.Username == defaultUsername {
391+
return apierrors.NewInvalid(
392+
schema.GroupKind{Group: "rabbitmq.openstack.org", Kind: "RabbitMQUser"},
393+
r.Name,
394+
field.ErrorList{
395+
field.Invalid(field.NewPath("spec", "username"), r.Spec.Username,
396+
fmt.Sprintf("username %q conflicts with the default user of cluster %q",
397+
r.Spec.Username, r.Spec.RabbitmqClusterName)),
398+
},
399+
)
400+
}
401+
402+
return nil
403+
}
404+
366405
// validatePermissions validates that permission regex patterns are valid
367406
func (r *RabbitMQUser) validatePermissions() error {
368407
var allErrs field.ErrorList

internal/webhook/rabbitmq/v1beta1/rabbitmquser_webhook_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@ limitations under the License.
1717
package v1beta1
1818

1919
import (
20+
"fmt"
21+
2022
. "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports
2123
. "github.com/onsi/gomega" //revive:disable:dot-imports
2224
rabbitmqv1beta1 "github.com/openstack-k8s-operators/infra-operator/apis/rabbitmq/v1beta1"
25+
corev1 "k8s.io/api/core/v1"
2326
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2427
)
2528

@@ -126,6 +129,93 @@ var _ = Describe("RabbitMQUser webhook", func() {
126129
Expect(err).To(HaveOccurred())
127130
Expect(err.Error()).To(ContainSubstring("invalid character"))
128131
})
132+
133+
It("should reject username matching the cluster's default user", func() {
134+
clusterName := "my-cluster"
135+
defaultUsername := fmt.Sprintf("default_user_%s", clusterName)
136+
137+
// Create the default user secret that the cluster controller would create
138+
secret := &corev1.Secret{
139+
ObjectMeta: metav1.ObjectMeta{
140+
Name: fmt.Sprintf("%s-default-user", clusterName),
141+
Namespace: "default",
142+
},
143+
Data: map[string][]byte{
144+
"username": []byte(defaultUsername),
145+
"password": []byte("secret-password"),
146+
},
147+
}
148+
Expect(k8sClient.Create(ctx, secret)).To(Succeed())
149+
DeferCleanup(func() {
150+
Expect(k8sClient.Delete(ctx, secret)).To(Succeed())
151+
})
152+
153+
user := &rabbitmqv1beta1.RabbitMQUser{
154+
ObjectMeta: metav1.ObjectMeta{
155+
Name: "conflicting-user",
156+
Namespace: "default",
157+
},
158+
Spec: rabbitmqv1beta1.RabbitMQUserSpec{
159+
RabbitmqClusterName: clusterName,
160+
Username: defaultUsername,
161+
},
162+
}
163+
164+
_, err := user.ValidateCreate(k8sClient)
165+
Expect(err).To(HaveOccurred())
166+
Expect(err.Error()).To(ContainSubstring("conflicts with the default user"))
167+
})
168+
169+
It("should accept username not matching the cluster's default user", func() {
170+
clusterName := "other-cluster"
171+
defaultUsername := fmt.Sprintf("default_user_%s", clusterName)
172+
173+
// Create the default user secret
174+
secret := &corev1.Secret{
175+
ObjectMeta: metav1.ObjectMeta{
176+
Name: fmt.Sprintf("%s-default-user", clusterName),
177+
Namespace: "default",
178+
},
179+
Data: map[string][]byte{
180+
"username": []byte(defaultUsername),
181+
"password": []byte("secret-password"),
182+
},
183+
}
184+
Expect(k8sClient.Create(ctx, secret)).To(Succeed())
185+
DeferCleanup(func() {
186+
Expect(k8sClient.Delete(ctx, secret)).To(Succeed())
187+
})
188+
189+
user := &rabbitmqv1beta1.RabbitMQUser{
190+
ObjectMeta: metav1.ObjectMeta{
191+
Name: "non-conflicting-user",
192+
Namespace: "default",
193+
},
194+
Spec: rabbitmqv1beta1.RabbitMQUserSpec{
195+
RabbitmqClusterName: clusterName,
196+
Username: "my-custom-user",
197+
},
198+
}
199+
200+
_, err := user.ValidateCreate(k8sClient)
201+
Expect(err).NotTo(HaveOccurred())
202+
})
203+
204+
It("should accept username when cluster default user secret does not exist", func() {
205+
user := &rabbitmqv1beta1.RabbitMQUser{
206+
ObjectMeta: metav1.ObjectMeta{
207+
Name: "early-user",
208+
Namespace: "default",
209+
},
210+
Spec: rabbitmqv1beta1.RabbitMQUserSpec{
211+
RabbitmqClusterName: "nonexistent-cluster",
212+
Username: "default_user_nonexistent-cluster",
213+
},
214+
}
215+
216+
_, err := user.ValidateCreate(k8sClient)
217+
Expect(err).NotTo(HaveOccurred())
218+
})
129219
})
130220

131221
Context("ValidateUpdate method", func() {

0 commit comments

Comments
 (0)