Skip to content

Commit dc860c6

Browse files
vlsiclaude
andcommitted
feat: Add plain text secret support for AWS Secrets Manager
AWS Secrets Manager natively supports plain text secrets, but the plugin was forcing JSON parsing which caused plain text secrets to fail. This change allows retrieving plain text secrets using the SecretString key, while maintaining full backward compatibility with existing JSON and binary secret handling. Changes: - Modified GetSecrets to gracefully handle non-JSON secret strings - Added SecretString key for plain text secret access - Added comprehensive test coverage for plain text scenarios - Updated documentation with usage examples Fixes #710 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent b046a7d commit dc860c6

3 files changed

Lines changed: 117 additions & 3 deletions

File tree

docs/backends.md

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,22 @@ stringData:
312312
type: Opaque
313313
```
314314

315+
###### Plain Text Secrets
316+
317+
AWS Secrets Manager can store plain text (non-JSON) secrets. To retrieve the entire plain text value, use `SecretString` as the key:
318+
319+
```yaml
320+
apiVersion: v1
321+
kind: Secret
322+
metadata:
323+
name: aws-example
324+
stringData:
325+
sample-secret: <path:test-plaintext-secret#SecretString>
326+
type: Opaque
327+
```
328+
329+
**Note**: If you need to access the secret value as a single string, use `#SecretString`. If the secret contains JSON, you can access its elements with `#keyName` (e.g., `<path:test-secret#username>`).
330+
315331
###### Versioned secrets
316332

317333
```yaml
@@ -357,9 +373,11 @@ stringData:
357373
type: Opaque
358374
```
359375

360-
###### Retrieving of binary data
376+
###### Retrieving Binary and Plain Text Data
377+
378+
AWS Secrets Manager supports three types of secret values: JSON objects, plain text strings, and binary data.
361379

362-
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:
380+
**For binary data**, use `SecretBinary` as the key:
363381

364382
```yaml
365383
apiVersion: v1
@@ -371,6 +389,31 @@ stringData:
371389
type: Opaque
372390
```
373391

392+
**For plain text (non-JSON) strings**, use `SecretString` as the key:
393+
394+
```yaml
395+
apiVersion: v1
396+
kind: Secret
397+
metadata:
398+
name: aws-example
399+
stringData:
400+
sample-secret: <path:arn:aws:secretsmanager:<REGION>:<ACCOUNT_NUMBER>:<SECRET_ID>#SecretString>
401+
type: Opaque
402+
```
403+
404+
**For JSON secrets**, specify the individual key you want to retrieve:
405+
406+
```yaml
407+
apiVersion: v1
408+
kind: Secret
409+
metadata:
410+
name: aws-example
411+
stringData:
412+
username: <path:arn:aws:secretsmanager:<REGION>:<ACCOUNT_NUMBER>:<SECRET_ID>#username>
413+
password: <path:arn:aws:secretsmanager:<REGION>:<ACCOUNT_NUMBER>:<SECRET_ID>#password>
414+
type: Opaque
415+
```
416+
374417
**NOTE**
375418
For cross account access there is the need to configure the correct permissions between accounts, please check:
376419
https://aws.amazon.com/premiumsupport/knowledge-center/secrets-manager-share-between-accounts

pkg/backends/awssecretsmanager.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,11 @@ func (a *AWSSecretsManager) GetSecrets(path string, version string, annotations
7777
if result.SecretString != nil {
7878
err := json.Unmarshal([]byte(*result.SecretString), &dat)
7979
if err != nil {
80-
return nil, err
80+
// If JSON unmarshal fails, treat as plain text
81+
utils.VerboseToStdErr("Get plain text value for %v", path)
82+
dat = make(map[string]interface{})
83+
dat["SecretString"] = *result.SecretString
84+
return dat, nil
8185
}
8286
} else if result.SecretBinary != nil {
8387
utils.VerboseToStdErr("Get binary value for %v", path)

pkg/backends/awssecretsmanager_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ func (m *mockSecretsManagerClient) GetSecretValue(ctx context.Context, input *se
2727
}
2828
case "test-binary":
2929
data.SecretBinary = []byte("binary-data")
30+
case "test-plaintext":
31+
string := "This is a plain text secret, not JSON"
32+
data.SecretString = &string
33+
case "test-empty-plaintext":
34+
string := ""
35+
data.SecretString = &string
36+
case "test-invalid-json":
37+
string := "{incomplete json"
38+
data.SecretString = &string
3039
}
3140

3241
return data, nil
@@ -92,6 +101,64 @@ func TestAWSSecretManagerGetSecrets(t *testing.T) {
92101
t.Errorf("expected: %v, got: %v.", expected, data)
93102
}
94103
})
104+
105+
t.Run("Get plain text secret", func(t *testing.T) {
106+
data, err := sm.GetSecrets("test-plaintext", "", map[string]string{})
107+
if err != nil {
108+
t.Fatalf("expected 0 errors but got: %s", err)
109+
}
110+
111+
expected := map[string]interface{}{
112+
"SecretString": "This is a plain text secret, not JSON",
113+
}
114+
115+
if !reflect.DeepEqual(expected, data) {
116+
t.Errorf("expected: %v, got: %v.", expected, data)
117+
}
118+
})
119+
120+
t.Run("Get individual plain text secret", func(t *testing.T) {
121+
secret, err := sm.GetIndividualSecret("test-plaintext", "SecretString", "", map[string]string{})
122+
if err != nil {
123+
t.Fatalf("expected 0 errors but got: %s", err)
124+
}
125+
126+
expected := "This is a plain text secret, not JSON"
127+
128+
if !reflect.DeepEqual(expected, secret) {
129+
t.Errorf("expected: %s, got: %s.", expected, secret)
130+
}
131+
})
132+
133+
t.Run("Get empty plain text secret", func(t *testing.T) {
134+
data, err := sm.GetSecrets("test-empty-plaintext", "", map[string]string{})
135+
if err != nil {
136+
t.Fatalf("expected 0 errors but got: %s", err)
137+
}
138+
139+
expected := map[string]interface{}{
140+
"SecretString": "",
141+
}
142+
143+
if !reflect.DeepEqual(expected, data) {
144+
t.Errorf("expected: %v, got: %v.", expected, data)
145+
}
146+
})
147+
148+
t.Run("Get invalid JSON as plain text", func(t *testing.T) {
149+
data, err := sm.GetSecrets("test-invalid-json", "", map[string]string{})
150+
if err != nil {
151+
t.Fatalf("expected 0 errors but got: %s", err)
152+
}
153+
154+
expected := map[string]interface{}{
155+
"SecretString": "{incomplete json",
156+
}
157+
158+
if !reflect.DeepEqual(expected, data) {
159+
t.Errorf("expected: %v, got: %v.", expected, data)
160+
}
161+
})
95162
}
96163

97164
func TestAWSSecretManagerEmptyIfNoSecret(t *testing.T) {

0 commit comments

Comments
 (0)