Browse Source

feat: add support to set Type for AWS parameter store (#3576)

Signed-off-by: Victor Santos <vsantos.py@gmail.com>
Victor Santos 1 year ago
parent
commit
dd8c004f47

+ 12 - 0
docs/provider/aws-parameter-store.md

@@ -101,6 +101,18 @@ The SetSecret method for the Parameter Store allows the user to set the value st
 {% include "full-pushsecret.yaml" %}
 ```
 
+#### Additional Metadata for PushSecret
+
+Optionally, it is possible to configure additional options for the parameter such as `Type` and encryption Key. To control this behaviour you can set the following provider's `metadata`:
+
+```yaml
+{% include 'aws-pm-push-secret-with-metadata.yaml' %}
+```
+
+`parameterStoreType` takes three options. `String`, `StringList`, and `SecureString`, where `String` is the _default_.
+
+`parameterStoreKeyID` takes a KMS Key `$ID` or `$ARN` (in case a key source is created in another account) as a string, where `alias/aws/ssm` is the _default_. This property is only used if `parameterStoreType` is set as `SecureString`.
+
 #### Check successful secret sync
 
 To be able to check that the secret has been succesfully synced you can run the following command:

+ 21 - 0
docs/snippets/aws-pm-push-secret-with-metadata.yaml

@@ -0,0 +1,21 @@
+apiVersion: external-secrets.io/v1alpha1
+kind: PushSecret
+metadata:
+  name: pushsecret-example # Customisable
+  namespace: default # Same of the SecretStores
+spec:
+  deletionPolicy: Delete # the provider' secret will be deleted if the PushSecret is deleted
+  refreshInterval: 10s # Refresh interval for which push secret will reconcile
+  secretStoreRefs: # A list of secret stores to push secrets to
+    - name: aws-parameterstore
+      kind: SecretStore
+  selector:
+    secret:
+      name: pokedex-credentials # Source Kubernetes secret to be pushed
+  data:
+    - match:
+        remoteRef:
+          remoteKey: my-first-parameter # Remote reference (where the secret is going to be pushed)
+      metadata:
+        parameterStoreType: "SecureString"
+        parameterStoreKeyID: "bb123123-b2b0-4f60-ac3a-44a13f0e6b6c"

+ 32 - 4
pkg/provider/aws/parameterstore/parameterstore.go

@@ -39,6 +39,16 @@ import (
 	"github.com/external-secrets/external-secrets/pkg/utils"
 )
 
+// Declares metadata information for pushing secrets to AWS Parameter Store.
+const (
+	PushSecretType                 = "parameterStoreType"
+	ParameterStoreTypeString       = "String"
+	ParameterStoreTypeStringList   = "StringList"
+	ParameterStoreTypeSecureString = "SecureString"
+	ParameterStoreKeyID            = "parameterStoreKeyID"
+	PushSecretKeyID                = "keyID"
+)
+
 // https://github.com/external-secrets/external-secrets/issues/644
 var (
 	_               esv1beta1.SecretsClient = &ParameterStore{}
@@ -138,13 +148,27 @@ func (pm *ParameterStore) SecretExists(_ context.Context, _ esv1beta1.PushSecret
 }
 
 func (pm *ParameterStore) PushSecret(ctx context.Context, secret *corev1.Secret, data esv1beta1.PushSecretData) error {
-	parameterType := "String"
-	overwrite := true
-
 	var (
 		value []byte
 		err   error
 	)
+
+	parameterTypeFormat, err := utils.FetchValueFromMetadata(PushSecretType, data.GetMetadata(), ParameterStoreTypeString)
+	if err != nil {
+		return fmt.Errorf("failed to parse metadata: %w", err)
+	}
+
+	parameterKeyIDFormat, err := utils.FetchValueFromMetadata(ParameterStoreKeyID, data.GetMetadata(), PushSecretKeyID)
+	if err != nil {
+		return fmt.Errorf("failed to parse metadata: %w", err)
+	}
+
+	if parameterKeyIDFormat == "keyID" || parameterKeyIDFormat == "" {
+		parameterKeyIDFormat = "alias/aws/ssm"
+	}
+
+	overwrite := true
+
 	key := data.GetSecretKey()
 
 	if key == "" {
@@ -162,10 +186,14 @@ func (pm *ParameterStore) PushSecret(ctx context.Context, secret *corev1.Secret,
 	secretRequest := ssm.PutParameterInput{
 		Name:      &secretName,
 		Value:     &stringValue,
-		Type:      &parameterType,
+		Type:      &parameterTypeFormat,
 		Overwrite: &overwrite,
 	}
 
+	if parameterTypeFormat == "SecureString" {
+		secretRequest.KeyId = &parameterKeyIDFormat
+	}
+
 	secretValue := ssm.GetParameterInput{
 		Name: &secretName,
 	}

+ 66 - 2
pkg/provider/aws/parameterstore/parameterstore_test.go

@@ -26,6 +26,7 @@ import (
 	"github.com/aws/aws-sdk-go/service/ssm"
 	"github.com/google/go-cmp/cmp"
 	corev1 "k8s.io/api/core/v1"
+	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
@@ -306,8 +307,9 @@ func TestPushSecret(t *testing.T) {
 	}
 
 	type args struct {
-		store  *esv1beta1.AWSProvider
-		client fakeps.Client
+		store    *esv1beta1.AWSProvider
+		metadata *apiextensionsv1.JSON
+		client   fakeps.Client
 	}
 
 	type want struct {
@@ -424,11 +426,73 @@ func TestPushSecret(t *testing.T) {
 				err: nil,
 			},
 		},
+		"SetSecretWithValidMetadata": {
+			reason: "test push secret with valid parameterStoreType metadata",
+			args: args{
+				store: makeValidParameterStore().Spec.Provider.AWS,
+				metadata: &apiextensionsv1.JSON{
+					Raw: []byte(`
+					{
+						"parameterStoreType": "SecureString", 
+						"parameterStoreKeyID": "arn:aws:kms:sa-east-1:00000000000:key/bb123123-b2b0-4f60-ac3a-44a13f0e6b6c"
+					}
+					`),
+				},
+				client: fakeps.Client{
+					PutParameterWithContextFn:        fakeps.NewPutParameterWithContextFn(putParameterOutput, nil),
+					GetParameterWithContextFn:        fakeps.NewGetParameterWithContextFn(sameGetParameterOutput, nil),
+					DescribeParametersWithContextFn:  fakeps.NewDescribeParametersWithContextFn(describeParameterOutput, nil),
+					ListTagsForResourceWithContextFn: fakeps.NewListTagsForResourceWithContextFn(validListTagsForResourceOutput, nil),
+				},
+			},
+			want: want{
+				err: nil,
+			},
+		},
+		"SetSecretWithValidMetadataListString": {
+			reason: "test push secret with valid parameterStoreType metadata and unused parameterStoreKeyID",
+			args: args{
+				store: makeValidParameterStore().Spec.Provider.AWS,
+				metadata: &apiextensionsv1.JSON{
+					Raw: []byte(`{"parameterStoreType": "StringList", "parameterStoreKeyID": "alias/aws/ssm"}`),
+				},
+				client: fakeps.Client{
+					PutParameterWithContextFn:        fakeps.NewPutParameterWithContextFn(putParameterOutput, nil),
+					GetParameterWithContextFn:        fakeps.NewGetParameterWithContextFn(sameGetParameterOutput, nil),
+					DescribeParametersWithContextFn:  fakeps.NewDescribeParametersWithContextFn(describeParameterOutput, nil),
+					ListTagsForResourceWithContextFn: fakeps.NewListTagsForResourceWithContextFn(validListTagsForResourceOutput, nil),
+				},
+			},
+			want: want{
+				err: nil,
+			},
+		},
+		"SetSecretWithInvalidMetadata": {
+			reason: "test push secret with invalid metadata structure",
+			args: args{
+				store: makeValidParameterStore().Spec.Provider.AWS,
+				metadata: &apiextensionsv1.JSON{
+					Raw: []byte(`{ fakeMetadataKey: "" }`),
+				},
+				client: fakeps.Client{
+					PutParameterWithContextFn:        fakeps.NewPutParameterWithContextFn(putParameterOutput, nil),
+					GetParameterWithContextFn:        fakeps.NewGetParameterWithContextFn(sameGetParameterOutput, nil),
+					DescribeParametersWithContextFn:  fakeps.NewDescribeParametersWithContextFn(describeParameterOutput, nil),
+					ListTagsForResourceWithContextFn: fakeps.NewListTagsForResourceWithContextFn(validListTagsForResourceOutput, nil),
+				},
+			},
+			want: want{
+				err: fmt.Errorf("failed to parse metadata: failed to parse JSON raw data: invalid character 'f' looking for beginning of object key string"),
+			},
+		},
 	}
 
 	for name, tc := range tests {
 		t.Run(name, func(t *testing.T) {
 			psd := fake.PushSecretData{SecretKey: fakeSecretKey, RemoteKey: "fake-key"}
+			if tc.args.metadata != nil {
+				psd.Metadata = tc.args.metadata
+			}
 			ps := ParameterStore{
 				client: &tc.args.client,
 			}