Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions api/v1alpha1/postgresuser_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ type PostgresUserSpec struct {
Annotations map[string]string `json:"annotations,omitempty"`
// +optional
Labels map[string]string `json:"labels,omitempty"`
// +optional
// +kubebuilder:validation:Enum=0;4;5;6;7;8;9;10;11;12
// Length of the random suffix appended to the role name.
// Default is 6. Set to 0 to disable the suffix entirely.
// Values 1-3 are not allowed as they provide insufficient uniqueness.
RoleSuffixLength *int `json:"roleSuffixLength,omitempty"`
}

// PostgresUserAWSSpec encapsulates AWS specific configuration toggles.
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,23 @@ spec:
role:
description: Name of the PostgresRole this user will be associated with
type: string
roleSuffixLength:
description: |-
Length of the random suffix appended to the role name.
Default is 6. Set to 0 to disable the suffix entirely.
Values 1-3 are not allowed as they provide insufficient uniqueness.
enum:
- 0
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
type: integer
secretName:
description: Name of the secret to create with user credentials
type: string
Expand Down
17 changes: 17 additions & 0 deletions config/crd/bases/db.movetokube.com_postgresusers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,23 @@ spec:
description: Name of the PostgresRole this user will be associated
with
type: string
roleSuffixLength:
description: |-
Length of the random suffix appended to the role name.
Default is 6. Set to 0 to disable the suffix entirely.
Values 1-3 are not allowed as they provide insufficient uniqueness.
enum:
- 0
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
type: integer
secretName:
description: Name of the secret to create with user credentials
type: string
Expand Down
14 changes: 11 additions & 3 deletions internal/controller/postgresuser_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,17 @@ func (r *PostgresUserReconciler) Reconcile(ctx context.Context, req ctrl.Request
if err != nil {
return r.requeue(ctx, instance, errors.NewInternalError(err))
}
// Create user role
suffix := utils.GetRandomString(6)
role = fmt.Sprintf("%s-%s", instance.Spec.Role, suffix)
// Create user role with configurable suffix length (default: 6)
suffixLength := 6
if instance.Spec.RoleSuffixLength != nil {
suffixLength = *instance.Spec.RoleSuffixLength
}
if suffixLength > 0 {
suffix := utils.GetRandomString(suffixLength)
role = fmt.Sprintf("%s-%s", instance.Spec.Role, suffix)
} else {
role = instance.Spec.Role
}
login, err = r.pg.CreateUserRole(role, password)
if err != nil {
return r.requeue(ctx, instance, errors.NewInternalError(err))
Expand Down
97 changes: 97 additions & 0 deletions internal/controller/postgresuser_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,103 @@ var _ = Describe("PostgresUser Controller", func() {

})
})

Context("Role suffix length configuration", func() {
BeforeEach(func() {
initClient(postgresDB, nil, false)
})

AfterEach(func() {
// Clean up any created secrets
secretList := &corev1.SecretList{}
Expect(cl.List(ctx, secretList, client.InNamespace(namespace))).To(Succeed())
for _, secret := range secretList.Items {
Expect(cl.Delete(ctx, &secret)).To(Succeed())
}
})

It("should use default suffix length of 6 when not specified", func() {
// Create user without roleSuffixLength specified
user := postgresUser.DeepCopy()
Expect(cl.Create(ctx, user)).To(Succeed())

var capturedRole string
pg.EXPECT().GetDefaultDatabase().Return("postgres").AnyTimes()
pg.EXPECT().CreateUserRole(gomock.Any(), gomock.Any()).DoAndReturn(
func(role, password string) (string, error) {
capturedRole = role
// Should have format: roleName-XXXXXX (6 char suffix)
Expect(role).To(HavePrefix(roleName + "-"))
Expect(len(role)).To(Equal(len(roleName) + 1 + 6)) // role + "-" + 6 chars
return role, nil
})
pg.EXPECT().GrantRole(gomock.Any(), gomock.Any()).Return(nil)
pg.EXPECT().AlterDefaultLoginRole(gomock.Any(), gomock.Any()).Return(nil)

err := runReconcile(rp, ctx, req)
Expect(err).NotTo(HaveOccurred())

foundUser := &dbv1alpha1.PostgresUser{}
err = cl.Get(ctx, types.NamespacedName{Name: name, Namespace: namespace}, foundUser)
Expect(err).NotTo(HaveOccurred())
Expect(foundUser.Status.PostgresRole).To(Equal(capturedRole))
})

It("should use custom suffix length when specified", func() {
// Create user with custom roleSuffixLength of 4
user := postgresUser.DeepCopy()
suffixLen := 4
user.Spec.RoleSuffixLength = &suffixLen
Expect(cl.Create(ctx, user)).To(Succeed())

var capturedRole string
pg.EXPECT().GetDefaultDatabase().Return("postgres").AnyTimes()
pg.EXPECT().CreateUserRole(gomock.Any(), gomock.Any()).DoAndReturn(
func(role, password string) (string, error) {
capturedRole = role
// Should have format: roleName-XXXX (4 char suffix)
Expect(role).To(HavePrefix(roleName + "-"))
Expect(len(role)).To(Equal(len(roleName) + 1 + 4)) // role + "-" + 4 chars
return role, nil
})
pg.EXPECT().GrantRole(gomock.Any(), gomock.Any()).Return(nil)
pg.EXPECT().AlterDefaultLoginRole(gomock.Any(), gomock.Any()).Return(nil)

err := runReconcile(rp, ctx, req)
Expect(err).NotTo(HaveOccurred())

foundUser := &dbv1alpha1.PostgresUser{}
err = cl.Get(ctx, types.NamespacedName{Name: name, Namespace: namespace}, foundUser)
Expect(err).NotTo(HaveOccurred())
Expect(foundUser.Status.PostgresRole).To(Equal(capturedRole))
})

It("should use exact role name when suffix length is 0", func() {
// Create user with roleSuffixLength of 0 (disable suffix)
user := postgresUser.DeepCopy()
suffixLen := 0
user.Spec.RoleSuffixLength = &suffixLen
Expect(cl.Create(ctx, user)).To(Succeed())

pg.EXPECT().GetDefaultDatabase().Return("postgres").AnyTimes()
pg.EXPECT().CreateUserRole(gomock.Any(), gomock.Any()).DoAndReturn(
func(role, password string) (string, error) {
// Should be exactly the role name, no suffix
Expect(role).To(Equal(roleName))
return role, nil
})
pg.EXPECT().GrantRole(gomock.Any(), gomock.Any()).Return(nil)
pg.EXPECT().AlterDefaultLoginRole(gomock.Any(), gomock.Any()).Return(nil)

err := runReconcile(rp, ctx, req)
Expect(err).NotTo(HaveOccurred())

foundUser := &dbv1alpha1.PostgresUser{}
err = cl.Get(ctx, types.NamespacedName{Name: name, Namespace: namespace}, foundUser)
Expect(err).NotTo(HaveOccurred())
Expect(foundUser.Status.PostgresRole).To(Equal(roleName))
})
})
})

Context("IAM authentication", func() {
Expand Down