Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 48 additions & 2 deletions docs/backends.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: <path:test-plaintext-secret#SecretString>
# Or JSON secret as raw string
json-as-string: <path:test-json-secret#SecretString>
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., `<path:test-secret#username>`).

###### Versioned secrets

```yaml
Expand Down Expand Up @@ -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 `<key>` part to `SecretBinary` to retrieve binary data:
**For binary data**, use `SecretBinary` as the key:

```yaml
apiVersion: v1
Expand All @@ -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: <path:arn:aws:secretsmanager:<REGION>:<ACCOUNT_NUMBER>:<SECRET_ID>#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: <path:arn:aws:secretsmanager:<REGION>:<ACCOUNT_NUMBER>:<SECRET_ID>#username>
password: <path:arn:aws:secretsmanager:<REGION>:<ACCOUNT_NUMBER>:<SECRET_ID>#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
Expand Down
8 changes: 7 additions & 1 deletion pkg/backends/awssecretsmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{})
Expand Down
89 changes: 87 additions & 2 deletions pkg/backends/awssecretsmanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down