Skip to content

Commit

Permalink
Configure default user/password through conf.d
Browse files Browse the repository at this point in the history
This commit stops the operator configuring the default username and
password for the cluster through environment variables. Instead, these
credentials are configured through `/etc/rabbitmq/conf.d/`. This is for
two reasons:

* RabbitMQ is moving away from configuration via env vars
* The operator should not be coupled to functionality in the entrypoint script in the docker-library/rabbitmq Docker image, as this prevents other RabbitMQ images being used

This effectively removes support for RabbitMQ images pre 3.8.4, where
the ability to configure RabbitMQ via sysctl-style `*.conf` files in
the conf.d directory.
  • Loading branch information
coro committed Sep 16, 2020
1 parent 34d3c4c commit c39b820
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 137 deletions.
10 changes: 3 additions & 7 deletions controllers/rabbitmqcluster_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1218,19 +1218,15 @@ var _ = Describe("RabbitmqclusterController", func() {
},
},
corev1.Volume{
Name: "rabbitmq-admin",
Name: "rabbitmq-confd",
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
DefaultMode: &defaultMode,
SecretName: "rabbitmq-sts-override-rabbitmq-admin",
Items: []corev1.KeyToPath{
{
Key: "username",
Path: "username",
},
{
Key: "password",
Path: "password",
Key: "default_user.conf",
Path: "default_user.conf",
},
},
},
Expand Down
43 changes: 39 additions & 4 deletions internal/resource/admin_secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@
package resource

import (
"bytes"
"encoding/base64"

rabbitmqv1beta1 "github.com/rabbitmq/cluster-operator/api/v1beta1"
"github.com/rabbitmq/cluster-operator/internal/metadata"
"gopkg.in/ini.v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
Expand All @@ -31,6 +35,31 @@ func (builder *RabbitmqResourceBuilder) AdminSecret() *AdminSecretBuilder {
}
}

func generateDefaultUserConf(username, password []byte) ([]byte, error) {

ini.PrettySection = false // Remove trailing new line because default_user.conf has only a default section.
cfg, err := ini.Load([]byte{})
if err != nil {
return nil, err
}
defaultSection := cfg.Section("")

if _, err := defaultSection.NewKey("default_user", string(username)); err != nil {
return nil, err
}

if _, err := defaultSection.NewKey("default_pass", string(password)); err != nil {
return nil, err
}

var userConfBuffer bytes.Buffer
if cfg.WriteTo(&userConfBuffer); err != nil {
return nil, err
}

return userConfBuffer.Bytes(), nil
}

func (builder *AdminSecretBuilder) UpdateRequiresStsRestart() bool {
return false
}
Expand All @@ -43,12 +72,17 @@ func (builder *AdminSecretBuilder) Update(object runtime.Object) error {
}

func (builder *AdminSecretBuilder) Build() (runtime.Object, error) {
username, err := randomEncodedString(24)
username, err := randomBytes(24)
if err != nil {
return nil, err
}

password, err := randomBytes(24)
if err != nil {
return nil, err
}

password, err := randomEncodedString(24)
defaultUserConf, err := generateDefaultUserConf(username, password)
if err != nil {
return nil, err
}
Expand All @@ -60,8 +94,9 @@ func (builder *AdminSecretBuilder) Build() (runtime.Object, error) {
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{
"username": []byte(username),
"password": []byte(password),
"username": []byte(base64.URLEncoding.EncodeToString(username)),
"password": []byte(base64.URLEncoding.EncodeToString(password)),
"default_user.conf": []byte(base64.URLEncoding.EncodeToString(defaultUserConf)),
},
}, nil
}
118 changes: 70 additions & 48 deletions internal/resource/admin_secret_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ package resource_test
import (
b64 "encoding/base64"

"gopkg.in/ini.v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"

Expand Down Expand Up @@ -44,41 +45,62 @@ var _ = Describe("AdminSecret", func() {
})

Context("Build with defaults", func() {
BeforeEach(func() {
It("creates the necessary admin secret", func() {
var decodedUsername []byte
var decodedPassword []byte
var err error

obj, err := adminSecretBuilder.Build()
Expect(err).NotTo(HaveOccurred())
secret = obj.(*corev1.Secret)
})

It("creates the secret with correct name and namespace", func() {
Expect(secret.Name).To(Equal(instance.ChildResourceName("admin")))
Expect(secret.Namespace).To(Equal("a namespace"))
})
By("creating the secret with correct name and namespace", func() {
Expect(secret.Name).To(Equal(instance.ChildResourceName("admin")))
Expect(secret.Namespace).To(Equal("a namespace"))
})

It("creates a 'opaque' secret ", func() {
Expect(secret.Type).To(Equal(corev1.SecretTypeOpaque))
})
By("creating a 'opaque' secret ", func() {
Expect(secret.Type).To(Equal(corev1.SecretTypeOpaque))
})

It("creates a rabbitmq username that is base64 encoded and 24 characters in length", func() {
username, ok := secret.Data["username"]
Expect(ok).NotTo(BeFalse())
decodedUsername, err := b64.URLEncoding.DecodeString(string(username))
Expect(err).NotTo(HaveOccurred())
Expect(len(decodedUsername)).To(Equal(24))
By("creating a rabbitmq username that is base64 encoded and 24 characters in length", func() {
username, ok := secret.Data["username"]
Expect(ok).NotTo(BeFalse())
decodedUsername, err = b64.URLEncoding.DecodeString(string(username))
Expect(err).NotTo(HaveOccurred())
Expect(len(decodedUsername)).To(Equal(24))

})
})

It("creates a rabbitmq password that is base64 encoded and 24 characters in length", func() {
password, ok := secret.Data["password"]
Expect(ok).NotTo(BeFalse())
decodedPassword, err := b64.URLEncoding.DecodeString(string(password))
Expect(err).NotTo(HaveOccurred())
Expect(len(decodedPassword)).To(Equal(24))
By("creating a rabbitmq password that is base64 encoded and 24 characters in length", func() {
password, ok := secret.Data["password"]
Expect(ok).NotTo(BeFalse())
decodedPassword, err = b64.URLEncoding.DecodeString(string(password))
Expect(err).NotTo(HaveOccurred())
Expect(len(decodedPassword)).To(Equal(24))
})

By("creating a default_user.conf file that contains the correct sysctl config format to be parsed by RabbitMQ", func() {
defaultUserConf, ok := secret.Data["default_user.conf"]
Expect(ok).NotTo(BeFalse())

decodedDefaultUserConf, err := b64.URLEncoding.DecodeString(string(defaultUserConf))
Expect(err).NotTo(HaveOccurred())

cfg, err := ini.Load(decodedDefaultUserConf)
Expect(err).NotTo(HaveOccurred())

Expect(cfg.Section("").HasKey("default_user")).To(BeTrue())
Expect(cfg.Section("").HasKey("default_pass")).To(BeTrue())

Expect(cfg.Section("").Key("default_user").Value()).To(Equal(string(decodedUsername)))
Expect(cfg.Section("").Key("default_pass").Value()).To(Equal(string(decodedPassword)))
})
})
})

Context("Update with instance labels", func() {
BeforeEach(func() {
It("Updates the secret", func() {
instance = rabbitmqv1beta1.RabbitmqCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "rabbit-labelled",
Expand All @@ -102,26 +124,26 @@ var _ = Describe("AdminSecret", func() {
}
err := adminSecretBuilder.Update(secret)
Expect(err).NotTo(HaveOccurred())
})

It("adds new labels from the CR", func() {
testLabels(secret.Labels)
})
By("adding new labels from the CR", func() {
testLabels(secret.Labels)
})

It("restores the default labels", func() {
labels := secret.Labels
Expect(labels["app.kubernetes.io/name"]).To(Equal(instance.Name))
Expect(labels["app.kubernetes.io/component"]).To(Equal("rabbitmq"))
Expect(labels["app.kubernetes.io/part-of"]).To(Equal("rabbitmq"))
})
By("restoring the default labels", func() {
labels := secret.Labels
Expect(labels["app.kubernetes.io/name"]).To(Equal(instance.Name))
Expect(labels["app.kubernetes.io/component"]).To(Equal("rabbitmq"))
Expect(labels["app.kubernetes.io/part-of"]).To(Equal("rabbitmq"))
})

It("deletes the labels that are removed from the CR", func() {
Expect(secret.Labels).NotTo(HaveKey("this-was-the-previous-label"))
By("deleting the labels that are removed from the CR", func() {
Expect(secret.Labels).NotTo(HaveKey("this-was-the-previous-label"))
})
})
})

Context("Update with instance annotations", func() {
BeforeEach(func() {
It("updates the secret with the annotations", func() {
instance = rabbitmqv1beta1.RabbitmqCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "rabbit-labelled",
Expand Down Expand Up @@ -150,19 +172,19 @@ var _ = Describe("AdminSecret", func() {
}
err := adminSecretBuilder.Update(secret)
Expect(err).NotTo(HaveOccurred())
})

It("updates secret annotations on admin secret", func() {
expectedAnnotations := map[string]string{
"my-annotation": "i-like-this",
"i-was-here-already": "please-dont-delete-me",
"im-here-to-stay.kubernetes.io": "for-a-while",
"kubernetes.io/name": "should-stay",
"kubectl.kubernetes.io/name": "should-stay",
"k8s.io/name": "should-stay",
}

Expect(secret.Annotations).To(Equal(expectedAnnotations))
By("updating secret annotations on admin secret", func() {
expectedAnnotations := map[string]string{
"my-annotation": "i-like-this",
"i-was-here-already": "please-dont-delete-me",
"im-here-to-stay.kubernetes.io": "for-a-while",
"kubernetes.io/name": "should-stay",
"kubectl.kubernetes.io/name": "should-stay",
"k8s.io/name": "should-stay",
}

Expect(secret.Annotations).To(Equal(expectedAnnotations))
})
})
})
})
12 changes: 10 additions & 2 deletions internal/resource/erlang_cookie.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,18 @@ func (builder *ErlangCookieBuilder) Update(object runtime.Object) error {
return nil
}

func randomEncodedString(dataLen int) (string, error) {
func randomBytes(dataLen int) ([]byte, error) {
randomBytes := make([]byte, dataLen)
if _, err := rand.Read(randomBytes); err != nil {
return nil, err
}
return randomBytes, nil
}

func randomEncodedString(dataLen int) (string, error) {
generatedBytes, err := randomBytes(dataLen)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(randomBytes), nil
return base64.URLEncoding.EncodeToString(generatedBytes), nil
}
58 changes: 28 additions & 30 deletions internal/resource/statefulset.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,24 +258,6 @@ func (builder *StatefulSetBuilder) podTemplateSpec(annotations, labels map[strin
terminationGracePeriod := defaultGracePeriodTimeoutSeconds

volumes := []corev1.Volume{
{
Name: "rabbitmq-admin",
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: builder.Instance.ChildResourceName(AdminSecretName),
Items: []corev1.KeyToPath{
{
Key: "username",
Path: "username",
},
{
Key: "password",
Path: "password",
},
},
},
},
},
{
Name: "server-conf",
VolumeSource: corev1.VolumeSource{
Expand All @@ -302,6 +284,20 @@ func (builder *StatefulSetBuilder) podTemplateSpec(annotations, labels map[strin
EmptyDir: &corev1.EmptyDirVolumeSource{},
},
},
{
Name: "rabbitmq-confd",
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: builder.Instance.ChildResourceName(AdminSecretName),
Items: []corev1.KeyToPath{
{
Key: "default_user.conf",
Path: "default_user.conf",
},
},
},
},
},
{
Name: "rabbitmq-erlang-cookie",
VolumeSource: corev1.VolumeSource{
Expand Down Expand Up @@ -378,10 +374,6 @@ func (builder *StatefulSetBuilder) podTemplateSpec(annotations, labels map[strin
}

rabbitmqContainerVolumeMounts := []corev1.VolumeMount{
{
Name: "rabbitmq-admin",
MountPath: "/opt/rabbitmq-secret/",
},
{
Name: "persistence",
MountPath: "/var/lib/rabbitmq/mnesia/",
Expand All @@ -390,6 +382,10 @@ func (builder *StatefulSetBuilder) podTemplateSpec(annotations, labels map[strin
Name: "rabbitmq-etc",
MountPath: "/etc/rabbitmq/",
},
{
Name: "rabbitmq-confd",
MountPath: "/etc/rabbitmq/conf.d/",
},
{
Name: "rabbitmq-erlang-cookie",
MountPath: "/var/lib/rabbitmq/",
Expand Down Expand Up @@ -509,6 +505,8 @@ func (builder *StatefulSetBuilder) podTemplateSpec(annotations, labels map[strin
"&& chown 999:999 /etc/rabbitmq/advanced.config ; " +
"cp /tmp/rabbitmq/rabbitmq-env.conf /etc/rabbitmq/rabbitmq-env.conf " +
"&& chown 999:999 /etc/rabbitmq/rabbitmq-env.conf ; " +
"cp /tmp/rabbitmq-admin/default_user.conf /etc/rabbitmq/conf.d/default_user.conf " +
"&& chown 999:999 /etc/rabbitmq/conf.d/*.conf ; " +
"cp /tmp/erlang-cookie-secret/.erlang.cookie /var/lib/rabbitmq/.erlang.cookie " +
"&& chown 999:999 /var/lib/rabbitmq/.erlang.cookie " +
"&& chmod 600 /var/lib/rabbitmq/.erlang.cookie ; " +
Expand All @@ -535,10 +533,18 @@ func (builder *StatefulSetBuilder) podTemplateSpec(annotations, labels map[strin
Name: "plugins-conf",
MountPath: "/tmp/rabbitmq-plugins/",
},
{
Name: "rabbitmq-admin",
MountPath: "/tmp/rabbitmq-admin/",
},
{
Name: "rabbitmq-etc",
MountPath: "/etc/rabbitmq/",
},
{
Name: "rabbitmq-confd",
MountPath: "/etc/rabbitmq/conf.d/",
},
{
Name: "rabbitmq-erlang-cookie",
MountPath: "/var/lib/rabbitmq/",
Expand All @@ -561,14 +567,6 @@ func (builder *StatefulSetBuilder) podTemplateSpec(annotations, labels map[strin
Resources: *builder.Instance.Spec.Resources,
Image: builder.Instance.Spec.Image,
Env: []corev1.EnvVar{
{
Name: "RABBITMQ_DEFAULT_PASS_FILE",
Value: "/opt/rabbitmq-secret/password",
},
{
Name: "RABBITMQ_DEFAULT_USER_FILE",
Value: "/opt/rabbitmq-secret/username",
},
{
Name: "MY_POD_NAME",
ValueFrom: &corev1.EnvVarSource{
Expand Down
Loading

0 comments on commit c39b820

Please sign in to comment.