From 71591eba87c9fc1086dd530860d077d07b80a69c Mon Sep 17 00:00:00 2001 From: Gavin Lam Date: Mon, 27 Nov 2023 22:20:24 -0500 Subject: [PATCH] feat: support immutable secrets (#574) * add spec.template.immutable field to the SealedSecrets CRD * enable controller to unseal immutable secrets * enable kubeseal to seal immutable secrets Signed-off-by: Gavin Lam --- README.md | 4 +++- .../crds/bitnami.com_sealedsecrets.yaml | 4 ++++ integration/controller_test.go | 11 ++++++++++- .../sealedsecrets/v1alpha1/sealedsecret_expansion.go | 4 +++- pkg/apis/sealedsecrets/v1alpha1/types.go | 7 +++++++ schema-v1alpha1.yaml | 3 +++ 6 files changed, 30 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d8a7db1aea..8d1ffdd2b5 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ In particular, the annotations and labels of a `SealedSecret` resource are not t To capture this distinction, the `SealedSecret` object has a `template` section which encodes all the fields you want the controller to put in the unsealed `Secret`. -This includes metadata such as labels or annotations, but also things like the `type` of the secret. +This includes metadata such as labels or annotations, but also things like `type` and `immutable` fields of the secret. ```yaml apiVersion: bitnami.com/v1alpha1 @@ -131,6 +131,7 @@ spec: .dockerconfigjson: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEq..... template: type: kubernetes.io/dockerconfigjson + immutable: true # this is an example of labels and annotations that will be added to the output secret metadata: labels: @@ -158,6 +159,7 @@ metadata: name: mysecret uid: 5caff6a0-c9ac-11e9-881e-42010aac003e type: kubernetes.io/dockerconfigjson +immutable: true data: .dockerconfigjson: ewogICJjcmVk... ``` diff --git a/helm/sealed-secrets/crds/bitnami.com_sealedsecrets.yaml b/helm/sealed-secrets/crds/bitnami.com_sealedsecrets.yaml index f37b5a7354..44b6cf4f64 100644 --- a/helm/sealed-secrets/crds/bitnami.com_sealedsecrets.yaml +++ b/helm/sealed-secrets/crds/bitnami.com_sealedsecrets.yaml @@ -91,6 +91,10 @@ spec: description: Used to facilitate programmatic handling of secret data. type: string + immutable: + description: 'Immutable, if set to true, ensures that data stored in the Secret cannot be updated (only object metadata can be modified). + If not set to true, the field can be modified at any time. Defaulted to nil.' + type: boolean type: object required: - encryptedData diff --git a/integration/controller_test.go b/integration/controller_test.go index 108752b174..f555b9dd68 100644 --- a/integration/controller_test.go +++ b/integration/controller_test.go @@ -73,6 +73,10 @@ func getSecretType(s *v1.Secret) v1.SecretType { return s.Type } +func getSecretImmutable(s *v1.Secret) bool { + return *s.Immutable +} + func fetchKeys(ctx context.Context, c corev1.SecretsGetter) (map[string]*rsa.PrivateKey, []*x509.Certificate, error) { list, err := c.Secrets(*controllerNs).List(ctx, metav1.ListOptions{ LabelSelector: keySelector, @@ -555,7 +559,7 @@ var _ = Describe("create", func() { }) }) - Describe("Custom Secret Type", func() { + Describe("Custom template fields", func() { BeforeEach(func() { label := fmt.Sprintf("%s/%s", s.Namespace, s.Name) ciphertext, err := crypto.HybridEncrypt(rand.Reader, pubKey, []byte("{\"auths\": {\"https://index.docker.io/v1/\": {\"auth\": \"c3R...zE2\"}}}"), []byte(label)) @@ -564,6 +568,8 @@ var _ = Describe("create", func() { ss.Spec.EncryptedData[".dockerconfigjson"] = base64.StdEncoding.EncodeToString(ciphertext) delete(ss.Spec.EncryptedData, "foo") ss.Spec.Template.Type = "kubernetes.io/dockerconfigjson" + ss.Spec.Template.Immutable = new(bool) + *ss.Spec.Template.Immutable = true }) It("should produce expected Secret", func() { expected := map[string][]byte{ @@ -577,6 +583,9 @@ var _ = Describe("create", func() { Eventually(func() (*v1.Secret, error) { return c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{}) }, Timeout, PollingInterval).Should(WithTransform(getSecretType, Equal(expectedType))) + Eventually(func() (*v1.Secret, error) { + return c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{}) + }, Timeout, PollingInterval).Should(WithTransform(getSecretImmutable, Equal(true))) Eventually(func() (*ssv1alpha1.SealedSecret, error) { return ssc.BitnamiV1alpha1().SealedSecrets(ns).Get(context.Background(), secretName, metav1.GetOptions{}) }, Timeout, PollingInterval).ShouldNot(WithTransform(getStatus, BeNil())) diff --git a/pkg/apis/sealedsecrets/v1alpha1/sealedsecret_expansion.go b/pkg/apis/sealedsecrets/v1alpha1/sealedsecret_expansion.go index 6c1b7459d7..ad5f2bf593 100644 --- a/pkg/apis/sealedsecrets/v1alpha1/sealedsecret_expansion.go +++ b/pkg/apis/sealedsecrets/v1alpha1/sealedsecret_expansion.go @@ -208,7 +208,8 @@ func NewSealedSecret(codecs runtimeserializer.CodecFactory, pubKey *rsa.PublicKe Spec: SealedSecretSpec{ Template: SecretTemplateSpec{ // ObjectMeta copied below - Type: secret.Type, + Type: secret.Type, + Immutable: secret.Immutable, }, EncryptedData: map[string]string{}, }, @@ -266,6 +267,7 @@ func (s *SealedSecret) Unseal(codecs runtimeserializer.CodecFactory, privKeys ma if s.Spec.Data == nil { s.Spec.Template.ObjectMeta.DeepCopyInto(&secret.ObjectMeta) secret.Type = s.Spec.Template.Type + secret.Immutable = s.Spec.Template.Immutable secret.Data = map[string][]byte{} data := map[string]string{} diff --git a/pkg/apis/sealedsecrets/v1alpha1/types.go b/pkg/apis/sealedsecrets/v1alpha1/types.go index 9375498ff7..3b2ba7eb24 100644 --- a/pkg/apis/sealedsecrets/v1alpha1/types.go +++ b/pkg/apis/sealedsecrets/v1alpha1/types.go @@ -51,6 +51,13 @@ type SecretTemplateSpec struct { // +optional Type apiv1.SecretType `json:"type,omitempty" protobuf:"bytes,3,opt,name=type,casttype=SecretType"` + // Immutable, if set to true, ensures that data stored in the Secret cannot + // be updated (only object metadata can be modified). + // If not set to true, the field can be modified at any time. + // Defaulted to nil. + // +optional + Immutable *bool `json:"immutable,omitempty" protobuf:"varint,5,opt,name=immutable"` + // Keys that should be templated using decrypted data. // +optional // +nullable diff --git a/schema-v1alpha1.yaml b/schema-v1alpha1.yaml index 283f950fed..913f300946 100644 --- a/schema-v1alpha1.yaml +++ b/schema-v1alpha1.yaml @@ -55,6 +55,9 @@ openAPIV3Schema: type: description: Used to facilitate programmatic handling of secret data. type: string + immutable: + description: 'Immutable, if set to true, ensures that data stored in the Secret cannot be updated (only object metadata can be modified). If not set to true, the field can be modified at any time. Defaulted to nil.' + type: boolean type: object required: - encryptedData