diff --git a/docs/backends.md b/docs/backends.md index d59e1d63..37ae20db 100644 --- a/docs/backends.md +++ b/docs/backends.md @@ -312,6 +312,25 @@ stringData: type: Opaque ``` +###### Plain Text Secrets + +AWS Secrets Manager can store plain text (non-JSON) secrets. To retrieve the entire secret value as a raw string (whether it's plain text or JSON), use `SecretString` as the key: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: aws-example +stringData: + # Plain text secret + sample-secret: + # Or JSON secret as raw string + json-as-string: +type: Opaque +``` + +**Note**: Use `#SecretString` to retrieve the raw secret value as a single string. If the secret contains JSON and you want to access individual elements, use `#keyName` (e.g., ``). + ###### Versioned secrets ```yaml @@ -357,9 +376,11 @@ stringData: type: Opaque ``` -###### Retrieving of binary data +###### Retrieving Binary and Plain Text Data + +AWS Secrets Manager supports three types of secret values: JSON objects, plain text strings, and binary data. -Since there is no way to set a key for binary type in AWS Secret Manager, set the `` part to `SecretBinary` to retrieve binary data: +**For binary data**, use `SecretBinary` as the key: ```yaml apiVersion: v1 @@ -371,6 +392,31 @@ stringData: type: Opaque ``` +**For plain text (non-JSON) strings**, use `SecretString` as the key: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: aws-example +stringData: + sample-secret: ::#SecretString> +type: Opaque +``` + +**For JSON secrets**, specify the individual key you want to retrieve: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: aws-example +stringData: + username: ::#username> + password: ::#password> +type: Opaque +``` + **NOTE** For cross account access there is the need to configure the correct permissions between accounts, please check: https://aws.amazon.com/premiumsupport/knowledge-center/secrets-manager-share-between-accounts diff --git a/pkg/backends/awssecretsmanager.go b/pkg/backends/awssecretsmanager.go index 08efb77c..f26ac2b4 100644 --- a/pkg/backends/awssecretsmanager.go +++ b/pkg/backends/awssecretsmanager.go @@ -77,8 +77,14 @@ func (a *AWSSecretsManager) GetSecrets(path string, version string, annotations if result.SecretString != nil { err := json.Unmarshal([]byte(*result.SecretString), &dat) if err != nil { - return nil, err + // If JSON unmarshal fails, treat as plain text + utils.VerboseToStdErr("Get plain text value for %v", path) + dat = make(map[string]interface{}) + dat["SecretString"] = *result.SecretString + return dat, nil } + // Always include SecretString to allow retrieving raw JSON value + dat["SecretString"] = *result.SecretString } else if result.SecretBinary != nil { utils.VerboseToStdErr("Get binary value for %v", path) dat = make(map[string]interface{}) diff --git a/pkg/backends/awssecretsmanager_test.go b/pkg/backends/awssecretsmanager_test.go index 53c39e30..c79fc3d6 100644 --- a/pkg/backends/awssecretsmanager_test.go +++ b/pkg/backends/awssecretsmanager_test.go @@ -27,6 +27,18 @@ func (m *mockSecretsManagerClient) GetSecretValue(ctx context.Context, input *se } case "test-binary": data.SecretBinary = []byte("binary-data") + case "test-plaintext": + string := "This is a plain text secret, not JSON" + data.SecretString = &string + case "test-empty-plaintext": + string := "" + data.SecretString = &string + case "test-invalid-json": + string := "{incomplete json" + data.SecretString = &string + case "test-json-as-string": + string := "{\"username\":\"admin\",\"password\":\"secret123\"}" + data.SecretString = &string } return data, nil @@ -42,7 +54,8 @@ func TestAWSSecretManagerGetSecrets(t *testing.T) { } expected := map[string]interface{}{ - "test-secret": "current-value", + "test-secret": "current-value", + "SecretString": "{\"test-secret\":\"current-value\"}", } if !reflect.DeepEqual(expected, data) { @@ -70,7 +83,8 @@ func TestAWSSecretManagerGetSecrets(t *testing.T) { } expected := map[string]interface{}{ - "test-secret": "previous-value", + "test-secret": "previous-value", + "SecretString": "{\"test-secret\":\"previous-value\"}", } if !reflect.DeepEqual(expected, data) { @@ -92,6 +106,77 @@ func TestAWSSecretManagerGetSecrets(t *testing.T) { t.Errorf("expected: %v, got: %v.", expected, data) } }) + + t.Run("Get plain text secret", func(t *testing.T) { + data, err := sm.GetSecrets("test-plaintext", "", map[string]string{}) + if err != nil { + t.Fatalf("expected 0 errors but got: %s", err) + } + + expected := map[string]interface{}{ + "SecretString": "This is a plain text secret, not JSON", + } + + if !reflect.DeepEqual(expected, data) { + t.Errorf("expected: %v, got: %v.", expected, data) + } + }) + + t.Run("Get individual plain text secret", func(t *testing.T) { + secret, err := sm.GetIndividualSecret("test-plaintext", "SecretString", "", map[string]string{}) + if err != nil { + t.Fatalf("expected 0 errors but got: %s", err) + } + + expected := "This is a plain text secret, not JSON" + + if !reflect.DeepEqual(expected, secret) { + t.Errorf("expected: %s, got: %s.", expected, secret) + } + }) + + t.Run("Get empty plain text secret", func(t *testing.T) { + data, err := sm.GetSecrets("test-empty-plaintext", "", map[string]string{}) + if err != nil { + t.Fatalf("expected 0 errors but got: %s", err) + } + + expected := map[string]interface{}{ + "SecretString": "", + } + + if !reflect.DeepEqual(expected, data) { + t.Errorf("expected: %v, got: %v.", expected, data) + } + }) + + t.Run("Get invalid JSON as plain text", func(t *testing.T) { + data, err := sm.GetSecrets("test-invalid-json", "", map[string]string{}) + if err != nil { + t.Fatalf("expected 0 errors but got: %s", err) + } + + expected := map[string]interface{}{ + "SecretString": "{incomplete json", + } + + if !reflect.DeepEqual(expected, data) { + t.Errorf("expected: %v, got: %v.", expected, data) + } + }) + + t.Run("Get valid JSON secret as raw string using SecretString key", func(t *testing.T) { + secret, err := sm.GetIndividualSecret("test-json-as-string", "SecretString", "", map[string]string{}) + if err != nil { + t.Fatalf("expected 0 errors but got: %s", err) + } + + expected := "{\"username\":\"admin\",\"password\":\"secret123\"}" + + if !reflect.DeepEqual(expected, secret) { + t.Errorf("expected: %s, got: %s.", expected, secret) + } + }) } func TestAWSSecretManagerEmptyIfNoSecret(t *testing.T) {