diff --git a/api/v1beta1/rabbitmqcluster_types.go b/api/v1beta1/rabbitmqcluster_types.go index f2aeb61fc..0c24175dd 100644 --- a/api/v1beta1/rabbitmqcluster_types.go +++ b/api/v1beta1/rabbitmqcluster_types.go @@ -16,6 +16,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" k8sresource "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -104,7 +105,8 @@ type RabbitmqClusterSpec struct { // Future secret backends could be Secrets Store CSI Driver. // If not configured, K8s Secrets will be used. type SecretBackend struct { - Vault *VaultSpec `json:"vault,omitempty"` + Vault *VaultSpec `json:"vault,omitempty"` + ExternalSecret v1.LocalObjectReference `json:"externalSecret,omitempty"` } // VaultSpec will add Vault annotations (see https://www.vaultproject.io/docs/platform/k8s/injector/annotations) @@ -443,6 +445,10 @@ func (cluster *RabbitmqCluster) VaultEnabled() bool { return cluster.Spec.SecretBackend.Vault != nil } +func (cluster *RabbitmqCluster) ExternalSecretEnabled() bool { + return cluster.Spec.SecretBackend.ExternalSecret.Name != "" +} + func (cluster *RabbitmqCluster) UsesDefaultUserUpdaterImage() bool { return cluster.VaultEnabled() && cluster.Spec.SecretBackend.Vault.DefaultUserUpdaterImage == nil } diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 73eea12a4..bbdc4e626 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -435,6 +435,7 @@ func (in *SecretBackend) DeepCopyInto(out *SecretBackend) { *out = new(VaultSpec) (*in).DeepCopyInto(*out) } + out.ExternalSecret = in.ExternalSecret } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretBackend. diff --git a/config/crd/bases/rabbitmq.com_rabbitmqclusters.yaml b/config/crd/bases/rabbitmq.com_rabbitmqclusters.yaml index 99323dd4e..95ead99d6 100644 --- a/config/crd/bases/rabbitmq.com_rabbitmqclusters.yaml +++ b/config/crd/bases/rabbitmq.com_rabbitmqclusters.yaml @@ -4022,6 +4022,14 @@ spec: secretBackend: description: Secret backend configuration for the RabbitmqCluster. Enables to fetch default user credentials and certificates from K8s external secret stores. properties: + externalSecret: + description: LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + type: object + x-kubernetes-map-type: atomic vault: description: VaultSpec will add Vault annotations (see https://www.vaultproject.io/docs/platform/k8s/injector/annotations) to RabbitMQ Pods. It requires a Vault Agent Sidecar Injector (https://www.vaultproject.io/docs/platform/k8s/injector) to be installed in the K8s cluster. The injector is a K8s Mutation Webhook Controller that alters RabbitMQ Pod specifications (based on the added Vault annotations) to include Vault Agent containers that render Vault secrets to the volume. properties: diff --git a/controllers/reconcile_status.go b/controllers/reconcile_status.go index 85d01156b..0b9a67344 100644 --- a/controllers/reconcile_status.go +++ b/controllers/reconcile_status.go @@ -2,10 +2,11 @@ package controllers import ( "context" + "reflect" + rabbitmqv1beta1 "github.com/rabbitmq/cluster-operator/api/v1beta1" "github.com/rabbitmq/cluster-operator/internal/resource" corev1 "k8s.io/api/core/v1" - "reflect" ) // reconcileStatus sets status.defaultUser (secret and service reference) and status.binding. @@ -34,8 +35,15 @@ func (r *RabbitmqClusterReconciler) reconcileStatus(ctx context.Context, rmq *ra "password": "password", }, } - binding = &corev1.LocalObjectReference{ - Name: rmq.ChildResourceName(resource.DefaultUserSecretName), + if !rmq.ExternalSecretEnabled() { + binding = &corev1.LocalObjectReference{ + Name: rmq.ChildResourceName(resource.DefaultUserSecretName), + } + } else { + binding = &corev1.LocalObjectReference{ + Name: rmq.Spec.SecretBackend.ExternalSecret.Name, + } + } } diff --git a/docs/api/rabbitmq.com.ref.asciidoc b/docs/api/rabbitmq.com.ref.asciidoc index 59e3500f7..eb183212e 100644 --- a/docs/api/rabbitmq.com.ref.asciidoc +++ b/docs/api/rabbitmq.com.ref.asciidoc @@ -348,6 +348,7 @@ SecretBackend configures a single secret backend. Today, only Vault exists as su |=== | Field | Description | *`vault`* __xref:{anchor_prefix}-github-com-rabbitmq-cluster-operator-api-v1beta1-vaultspec[$$VaultSpec$$]__ | +| *`externalSecret`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#localobjectreference-v1-core[$$LocalObjectReference$$]__ | |=== diff --git a/internal/resource/rabbitmq_resource_builder.go b/internal/resource/rabbitmq_resource_builder.go index 24247338d..4a2eceeba 100644 --- a/internal/resource/rabbitmq_resource_builder.go +++ b/internal/resource/rabbitmq_resource_builder.go @@ -40,7 +40,7 @@ func (builder *RabbitmqResourceBuilder) ResourceBuilders() []ResourceBuilder { builder.RoleBinding(), builder.StatefulSet(), } - if builder.Instance.VaultDefaultUserSecretEnabled() { + if builder.Instance.VaultDefaultUserSecretEnabled() || builder.Instance.ExternalSecretEnabled() { // do not create default-user K8s Secret builders = append(builders[:3], builders[3+1:]...) } diff --git a/internal/resource/statefulset.go b/internal/resource/statefulset.go index 2fb655705..185c0d03d 100644 --- a/internal/resource/statefulset.go +++ b/internal/resource/statefulset.go @@ -419,8 +419,10 @@ func (builder *StatefulSetBuilder) podTemplateSpec(previousPodAnnotations map[st }, } - if !builder.Instance.VaultDefaultUserSecretEnabled() { - appendDefaultUserSecretVolumeProjection(volumes, builder.Instance) + if !builder.Instance.VaultDefaultUserSecretEnabled() && !builder.Instance.ExternalSecretEnabled() { + appendDefaultUserSecretVolumeProjection(volumes, builder.Instance, "") + } else if builder.Instance.ExternalSecretEnabled() { + appendDefaultUserSecretVolumeProjection(volumes, builder.Instance, builder.Instance.Spec.SecretBackend.ExternalSecret.Name) } if builder.Instance.Spec.Rabbitmq.AdvancedConfig != "" || builder.Instance.Spec.Rabbitmq.EnvConfig != "" { @@ -779,14 +781,19 @@ func setupContainer(instance *rabbitmqv1beta1.RabbitmqCluster) corev1.Container return setupContainer } -func appendDefaultUserSecretVolumeProjection(volumes []corev1.Volume, instance *rabbitmqv1beta1.RabbitmqCluster) { +func appendDefaultUserSecretVolumeProjection(volumes []corev1.Volume, instance *rabbitmqv1beta1.RabbitmqCluster, secretName string) { + + if secretName == "" { + secretName = instance.ChildResourceName(DefaultUserSecretName) + } + for _, value := range volumes { if value.Name == "rabbitmq-confd" { value.VolumeSource.Projected.Sources = append(value.VolumeSource.Projected.Sources, corev1.VolumeProjection{ Secret: &corev1.SecretProjection{ LocalObjectReference: corev1.LocalObjectReference{ - Name: instance.ChildResourceName(DefaultUserSecretName), + Name: secretName, }, Items: []corev1.KeyToPath{ { diff --git a/internal/resource/statefulset_test.go b/internal/resource/statefulset_test.go index 21bc2811a..253d56a33 100644 --- a/internal/resource/statefulset_test.go +++ b/internal/resource/statefulset_test.go @@ -869,6 +869,25 @@ var _ = Describe("StatefulSet", func() { Expect(container.Env).To(ConsistOf(requiredEnvVariables)) }) + Context("ExternalSecret", func() { + When("SecretBackend.ExternalSecret is set", func() { + JustBeforeEach(func() { + Expect(stsBuilder.Update(statefulSet)).To(Succeed()) + }) + BeforeEach(func() { + instance.Spec.SecretBackend.ExternalSecret.Name = "my-secret" + }) + + It("does not project default user secret to rabbitmq-confd volume", func() { + rabbitmqConfdVolume := extractVolume(statefulSet.Spec.Template.Spec.Volumes, "rabbitmq-confd") + defaultUserSecret := extractProjectedSecret(rabbitmqConfdVolume, "foo-default-user") + Expect(defaultUserSecret.Secret).To(BeNil()) + }) + + }) + + }) + Context("Vault", func() { BeforeEach(func() { instance.Spec.SecretBackend.Vault = &rabbitmqv1beta1.VaultSpec{