env-secrets 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +3 -0
- package/README.md +41 -2
- package/__e2e__/README.md +1 -1
- package/__e2e__/aws-cli-help.test.ts +23 -0
- package/__e2e__/aws-exec-args.test.ts +44 -0
- package/__e2e__/aws-get-secrets-args.test.ts +156 -0
- package/__e2e__/aws-output-file-args.test.ts +65 -0
- package/__e2e__/aws-secret-lifecycle.test.ts +257 -0
- package/__e2e__/aws-secret-mutation-args.test.ts +199 -0
- package/__e2e__/utils/aws-e2e-context.ts +50 -0
- package/__e2e__/utils/test-utils.ts +35 -39
- package/__tests__/cli/helpers.test.ts +89 -1
- package/__tests__/index.test.ts +5 -3
- package/__tests__/vaults/secretsmanager-admin.test.ts +43 -0
- package/__tests__/vaults/secretsmanager.test.ts +47 -0
- package/dist/cli/helpers.js +66 -4
- package/dist/index.js +248 -12
- package/dist/vaults/secretsmanager-admin.js +34 -1
- package/dist/vaults/secretsmanager.js +47 -8
- package/docs/AWS.md +55 -3
- package/package.json +5 -4
- package/src/cli/helpers.ts +98 -3
- package/src/index.ts +318 -7
- package/src/vaults/secretsmanager-admin.ts +45 -0
- package/src/vaults/secretsmanager.ts +65 -7
- package/website/docs/cli-reference.mdx +36 -2
- package/website/docs/providers/aws-secrets-manager.mdx +28 -1
- package/__e2e__/index.test.ts +0 -490
package/AGENTS.md
CHANGED
|
@@ -133,6 +133,9 @@ yarn test:unit:coverage # Run tests with coverage
|
|
|
133
133
|
4. Ensure all CI checks pass
|
|
134
134
|
5. Submit a pull request with a clear description
|
|
135
135
|
6. Always request a GitHub Copilot review on every new pull request
|
|
136
|
+
7. After requesting Copilot review, wait 5 minutes and check for review comments
|
|
137
|
+
8. If no Copilot review is present yet, wait another 5 minutes and check again
|
|
138
|
+
9. Create a plan to address Copilot feedback, but evaluate each suggestion critically and do not accept recommendations blindly
|
|
136
139
|
|
|
137
140
|
## Development Environment
|
|
138
141
|
|
package/README.md
CHANGED
|
@@ -103,7 +103,7 @@ env-secrets aws -s my-app-secrets -r us-east-1 -- node app.js
|
|
|
103
103
|
- `-o, --output <file>` (optional): Output secrets to a file instead of injecting into environment variables. File will be created with 0400 permissions and will not overwrite existing files
|
|
104
104
|
- `-- <program-to-run>`: The program to run with the injected environment variables (only used when `-o` is not specified)
|
|
105
105
|
|
|
106
|
-
For `aws secret` management subcommands (`create`, `update`, `list`, `get`, `delete`), use:
|
|
106
|
+
For `aws secret` management subcommands (`create`, `update`, `append`, `remove`, `upsert`/`import`, `list`, `get`, `delete`), use:
|
|
107
107
|
|
|
108
108
|
- `-r, --region <region>` to target a specific region
|
|
109
109
|
- `-p, --profile <profile>` to select credentials profile
|
|
@@ -111,6 +111,9 @@ For `aws secret` management subcommands (`create`, `update`, `list`, `get`, `del
|
|
|
111
111
|
|
|
112
112
|
These options are honored consistently on `aws secret` subcommands.
|
|
113
113
|
|
|
114
|
+
`env-secrets aws -s` is for fetching/injecting secret values into a child process.
|
|
115
|
+
`env-secrets aws secret ...` is for lifecycle management commands (`create`, `update`, `append`, `remove`, `upsert`/`import`, `list`, `get`, `delete`).
|
|
116
|
+
|
|
114
117
|
#### Examples
|
|
115
118
|
|
|
116
119
|
1. **Create a secret using AWS CLI:**
|
|
@@ -217,6 +220,42 @@ env-secrets aws -s my-secret -r us-east-1 -o secrets.env
|
|
|
217
220
|
# Error: File secrets.env already exists and will not be overwritten
|
|
218
221
|
```
|
|
219
222
|
|
|
223
|
+
10. **Load secrets into your current shell session:**
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
# Write export statements to a file
|
|
227
|
+
env-secrets aws -s my-secret -r us-east-1 -o secrets.env
|
|
228
|
+
|
|
229
|
+
# Load into current shell
|
|
230
|
+
source secrets.env
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Note: `env-secrets aws -s ... -- <command>` injects secrets into the spawned child process only.
|
|
234
|
+
To affect your current shell, use file output and `source` it.
|
|
235
|
+
|
|
236
|
+
11. **Upsert secrets from a local env file:**
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
# Supported line formats:
|
|
240
|
+
# export NAME=secret1
|
|
241
|
+
# NAME=secret1
|
|
242
|
+
env-secrets aws secret upsert --file .env --name app/dev --output json
|
|
243
|
+
|
|
244
|
+
# Creates/updates a single secret named app/dev
|
|
245
|
+
# with SecretString like:
|
|
246
|
+
# {"NAME":"secret1"}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
12. **Append/remove keys on an existing JSON secret:**
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
# Add or overwrite one key
|
|
253
|
+
env-secrets aws secret append -n app/dev --key JIRA_EMAIL_TOKEN -v blah --output json
|
|
254
|
+
|
|
255
|
+
# Remove one or more keys
|
|
256
|
+
env-secrets aws secret remove -n app/dev --key API_KEY --key OLD_TOKEN --output json
|
|
257
|
+
```
|
|
258
|
+
|
|
220
259
|
## Security Considerations
|
|
221
260
|
|
|
222
261
|
- 🔐 **Credential Management**: The tool respects AWS credential precedence (environment variables, IAM roles, profiles)
|
|
@@ -500,7 +539,7 @@ npm run test:e2e
|
|
|
500
539
|
npm run test:e2e:coverage
|
|
501
540
|
|
|
502
541
|
# Run specific e2e test
|
|
503
|
-
yarn build && npx jest --config jest.e2e.config.js __e2e__/
|
|
542
|
+
yarn build && npx jest --config jest.e2e.config.js __e2e__/aws-get-secrets-args.test.ts -t "test name"
|
|
504
543
|
```
|
|
505
544
|
|
|
506
545
|
#### E2E Test Features
|
package/__e2e__/README.md
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { cli } from './utils/test-utils';
|
|
2
|
+
|
|
3
|
+
describe('CLI Help Commands', () => {
|
|
4
|
+
test('should show general help', async () => {
|
|
5
|
+
const result = await cli(['-h']);
|
|
6
|
+
expect(result.code).toBe(0);
|
|
7
|
+
expect(result.stdout).toContain('env-secrets');
|
|
8
|
+
expect(result.stdout).toContain('pull secrets from vaults');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test('should show AWS command help', async () => {
|
|
12
|
+
const result = await cli(['aws', '-h']);
|
|
13
|
+
expect(result.code).toBe(0);
|
|
14
|
+
expect(result.stdout).toContain('get secrets from AWS secrets manager');
|
|
15
|
+
expect(result.stdout).toContain('--secret');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('should show version', async () => {
|
|
19
|
+
const result = await cli(['--version']);
|
|
20
|
+
expect(result.code).toBe(0);
|
|
21
|
+
expect(result.stdout).toMatch(/^\d+\.\d+\.\d+/);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { cliWithEnv } from './utils/test-utils';
|
|
2
|
+
import { registerAwsE2eContext } from './utils/aws-e2e-context';
|
|
3
|
+
|
|
4
|
+
describe('AWS Program Execution CLI Args', () => {
|
|
5
|
+
const { createTestSecret, getLocalStackEnv } = registerAwsE2eContext();
|
|
6
|
+
|
|
7
|
+
test('should execute program with injected environment variables', async () => {
|
|
8
|
+
const secret = await createTestSecret({
|
|
9
|
+
name: `test-secret-exec-${Date.now()}`,
|
|
10
|
+
value:
|
|
11
|
+
'{"API_KEY": "secret123", "DATABASE_URL": "postgres://localhost:5432/test"}',
|
|
12
|
+
description: 'Secret for program execution test'
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const result = await cliWithEnv(
|
|
16
|
+
['aws', '-s', secret.prefixedName, 'echo', '$API_KEY'],
|
|
17
|
+
getLocalStackEnv()
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
expect(result.code).toBe(0);
|
|
21
|
+
|
|
22
|
+
const envVars = JSON.parse(result.stdout.trim()) as Record<string, string>;
|
|
23
|
+
expect(envVars.API_KEY).toBe('secret123');
|
|
24
|
+
expect(envVars.DATABASE_URL).toBe('postgres://localhost:5432/test');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('should handle program execution errors gracefully', async () => {
|
|
28
|
+
const secret = await createTestSecret({
|
|
29
|
+
name: `test-secret-error-${Date.now()}`,
|
|
30
|
+
value: '{"API_KEY": "secret123"}',
|
|
31
|
+
description: 'Secret for error handling test'
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const result = await cliWithEnv(
|
|
35
|
+
['aws', '-s', secret.prefixedName, 'nonexistent-command'],
|
|
36
|
+
getLocalStackEnv()
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
expect(result.code).toBe(0);
|
|
40
|
+
|
|
41
|
+
const envVars = JSON.parse(result.stdout.trim()) as Record<string, string>;
|
|
42
|
+
expect(envVars.API_KEY).toBe('secret123');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { cliWithEnv } from './utils/test-utils';
|
|
2
|
+
import { registerAwsE2eContext } from './utils/aws-e2e-context';
|
|
3
|
+
|
|
4
|
+
describe('AWS Get Secret CLI Args', () => {
|
|
5
|
+
const { createTestSecret, getLocalStackEnv } = registerAwsE2eContext();
|
|
6
|
+
|
|
7
|
+
test('should retrieve basic JSON secret', async () => {
|
|
8
|
+
const secret = await createTestSecret({
|
|
9
|
+
name: 'test-secret-basic',
|
|
10
|
+
value:
|
|
11
|
+
'{"API_KEY": "secret123", "DATABASE_URL": "postgres://localhost:5432/test"}',
|
|
12
|
+
description: 'Basic test secret with API key and database URL'
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const result = await cliWithEnv(
|
|
16
|
+
['aws', '-s', secret.prefixedName],
|
|
17
|
+
getLocalStackEnv()
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
expect(result.code).toBe(0);
|
|
21
|
+
expect(result.stderr).toBe('');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('should retrieve simple string secret', async () => {
|
|
25
|
+
const secret = await createTestSecret({
|
|
26
|
+
name: 'test-secret-simple',
|
|
27
|
+
value: 'simple-string-value',
|
|
28
|
+
description: 'Simple string secret'
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const result = await cliWithEnv(
|
|
32
|
+
['aws', '-s', secret.prefixedName],
|
|
33
|
+
getLocalStackEnv()
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
expect(result.code).toBe(0);
|
|
37
|
+
expect(result.stderr).toBe('');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('should retrieve complex JSON secret', async () => {
|
|
41
|
+
const secret = await createTestSecret({
|
|
42
|
+
name: 'test-secret-complex',
|
|
43
|
+
value:
|
|
44
|
+
'{"NESTED": {"KEY": "value"}, "ARRAY": [1, 2, 3], "BOOLEAN": true, "NUMBER": 42}',
|
|
45
|
+
description: 'Complex JSON secret'
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const result = await cliWithEnv(
|
|
49
|
+
['aws', '-s', secret.prefixedName],
|
|
50
|
+
getLocalStackEnv()
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
expect(result.code).toBe(0);
|
|
54
|
+
expect(result.stderr).toBe('');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('should retrieve secret with special characters', async () => {
|
|
58
|
+
const secret = await createTestSecret({
|
|
59
|
+
name: 'test-secret-special-chars',
|
|
60
|
+
value:
|
|
61
|
+
'{"PASSWORD": "p@ssw0rd!#$%", "URL": "https://api.example.com/v1?key=value&other=test"}',
|
|
62
|
+
description: 'Secret with special characters'
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const result = await cliWithEnv(
|
|
66
|
+
['aws', '-s', secret.prefixedName],
|
|
67
|
+
getLocalStackEnv()
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
expect(result.code).toBe(0);
|
|
71
|
+
expect(result.stderr).toBe('');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('should handle non-existent secret', async () => {
|
|
75
|
+
const result = await cliWithEnv(
|
|
76
|
+
['aws', '-s', 'non-existent-secret'],
|
|
77
|
+
getLocalStackEnv()
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
expect(result.code).toBe(0);
|
|
81
|
+
expect(result.stderr).toContain('non-existent-secret not found');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('should work with custom region', async () => {
|
|
85
|
+
const secret = await createTestSecret(
|
|
86
|
+
{
|
|
87
|
+
name: `test-secret-region-${Date.now()}`,
|
|
88
|
+
value:
|
|
89
|
+
'{"API_KEY": "secret123", "DATABASE_URL": "postgres://localhost:5432/test"}',
|
|
90
|
+
description: 'Basic test secret for us-west-2'
|
|
91
|
+
},
|
|
92
|
+
'us-west-2'
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const result = await cliWithEnv(
|
|
96
|
+
['aws', '-s', secret.prefixedName, '-r', 'us-west-2'],
|
|
97
|
+
getLocalStackEnv()
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
expect(result.code).toBe(0);
|
|
101
|
+
expect(result.stderr).toBe('');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('should retrieve secret using default profile', async () => {
|
|
105
|
+
const secret = await createTestSecret({
|
|
106
|
+
name: 'test-secret-profile',
|
|
107
|
+
value:
|
|
108
|
+
'{"API_KEY": "secret123", "DATABASE_URL": "postgres://localhost:5432/test"}',
|
|
109
|
+
description: 'Secret for profile test'
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const result = await cliWithEnv(
|
|
113
|
+
['aws', '-s', secret.prefixedName],
|
|
114
|
+
getLocalStackEnv()
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
expect(result.code).toBe(0);
|
|
118
|
+
expect(result.stderr).toBe('');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('should retrieve secret using custom profile', async () => {
|
|
122
|
+
const secret = await createTestSecret({
|
|
123
|
+
name: 'test-secret-profile-custom',
|
|
124
|
+
value:
|
|
125
|
+
'{"API_KEY": "secret123", "DATABASE_URL": "postgres://localhost:5432/test"}',
|
|
126
|
+
description: 'Secret for custom profile test'
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const result = await cliWithEnv(
|
|
130
|
+
['aws', '-s', secret.prefixedName, '-p', 'env-secrets-test'],
|
|
131
|
+
getLocalStackEnv()
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
expect(result.code).toBe(0);
|
|
135
|
+
expect(result.stderr).toBe('');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('should handle invalid AWS credentials', async () => {
|
|
139
|
+
const result = await cliWithEnv(
|
|
140
|
+
['aws', '-s', 'test-secret-basic'],
|
|
141
|
+
getLocalStackEnv({
|
|
142
|
+
AWS_ACCESS_KEY_ID: 'invalid',
|
|
143
|
+
AWS_SECRET_ACCESS_KEY: 'invalid'
|
|
144
|
+
})
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
expect(result.code).toBe(0);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('should handle missing secret parameter', async () => {
|
|
151
|
+
const result = await cliWithEnv(['aws'], getLocalStackEnv());
|
|
152
|
+
|
|
153
|
+
expect(result.code).toBe(1);
|
|
154
|
+
expect(result.stderr).toContain('Missing required option --secret');
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
cliWithEnv,
|
|
7
|
+
createTempFile,
|
|
8
|
+
cleanupTempFile
|
|
9
|
+
} from './utils/test-utils';
|
|
10
|
+
import { registerAwsE2eContext } from './utils/aws-e2e-context';
|
|
11
|
+
|
|
12
|
+
describe('AWS Output File CLI Args', () => {
|
|
13
|
+
const { createTestSecret, getLocalStackEnv } = registerAwsE2eContext();
|
|
14
|
+
|
|
15
|
+
test('should write secrets to file', async () => {
|
|
16
|
+
const secret = await createTestSecret({
|
|
17
|
+
name: `test-secret-file-${Date.now()}`,
|
|
18
|
+
value:
|
|
19
|
+
'{"API_KEY": "secret123", "DATABASE_URL": "postgres://localhost:5432/test"}',
|
|
20
|
+
description: 'Secret for file output test'
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const tempFile = path.join(
|
|
24
|
+
os.tmpdir(),
|
|
25
|
+
`env-secrets-test-${Date.now()}.env`
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const result = await cliWithEnv(
|
|
29
|
+
['aws', '-s', secret.prefixedName, '-o', tempFile],
|
|
30
|
+
getLocalStackEnv()
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
expect(result.code).toBe(0);
|
|
34
|
+
expect(result.stdout).toContain(`Secrets written to ${tempFile}`);
|
|
35
|
+
|
|
36
|
+
const fileContent = fs.readFileSync(tempFile, 'utf8');
|
|
37
|
+
expect(fileContent).toContain('API_KEY=secret123');
|
|
38
|
+
expect(fileContent).toContain(
|
|
39
|
+
'DATABASE_URL=postgres://localhost:5432/test'
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
cleanupTempFile(tempFile);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('should not overwrite existing file', async () => {
|
|
46
|
+
const tempFile = createTempFile('existing content');
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const result = await cliWithEnv(
|
|
50
|
+
['aws', '-s', 'test-secret-basic', '-o', tempFile],
|
|
51
|
+
getLocalStackEnv()
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
expect(result.code).toBe(1);
|
|
55
|
+
expect(result.stderr).toContain(
|
|
56
|
+
'already exists and will not be overwritten'
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const fileContent = fs.readFileSync(tempFile, 'utf8');
|
|
60
|
+
expect(fileContent).toBe('existing content');
|
|
61
|
+
} finally {
|
|
62
|
+
cleanupTempFile(tempFile);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
cliWithEnv,
|
|
7
|
+
cliWithEnvAndStdin,
|
|
8
|
+
cleanupTempFile
|
|
9
|
+
} from './utils/test-utils';
|
|
10
|
+
import { registerAwsE2eContext } from './utils/aws-e2e-context';
|
|
11
|
+
|
|
12
|
+
describe('AWS Secret Subcommand Lifecycle Args', () => {
|
|
13
|
+
const { createTestSecret, getLocalStackEnv } = registerAwsE2eContext();
|
|
14
|
+
|
|
15
|
+
test('should create, list, get, and delete a secret', async () => {
|
|
16
|
+
const secretName = `e2e-managed-secret-${Date.now()}`;
|
|
17
|
+
|
|
18
|
+
const createResult = await cliWithEnv(
|
|
19
|
+
['aws', 'secret', 'create', '-n', secretName, '-v', 'initial-value'],
|
|
20
|
+
getLocalStackEnv()
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
expect(createResult.code).toBe(0);
|
|
24
|
+
expect(createResult.stdout).toContain(secretName);
|
|
25
|
+
|
|
26
|
+
const listResult = await cliWithEnv(
|
|
27
|
+
['aws', 'secret', 'list', '--prefix', 'e2e-managed-secret-'],
|
|
28
|
+
getLocalStackEnv()
|
|
29
|
+
);
|
|
30
|
+
expect(listResult.code).toBe(0);
|
|
31
|
+
expect(listResult.stdout).toContain(secretName);
|
|
32
|
+
|
|
33
|
+
const getResult = await cliWithEnv(
|
|
34
|
+
['aws', 'secret', 'get', '-n', secretName],
|
|
35
|
+
getLocalStackEnv()
|
|
36
|
+
);
|
|
37
|
+
expect(getResult.code).toBe(0);
|
|
38
|
+
expect(getResult.stdout).toContain(secretName);
|
|
39
|
+
expect(getResult.stdout).not.toContain('initial-value');
|
|
40
|
+
|
|
41
|
+
const deleteResult = await cliWithEnv(
|
|
42
|
+
[
|
|
43
|
+
'aws',
|
|
44
|
+
'secret',
|
|
45
|
+
'delete',
|
|
46
|
+
'-n',
|
|
47
|
+
secretName,
|
|
48
|
+
'--force-delete-without-recovery',
|
|
49
|
+
'--yes'
|
|
50
|
+
],
|
|
51
|
+
getLocalStackEnv()
|
|
52
|
+
);
|
|
53
|
+
expect(deleteResult.code).toBe(0);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('should update secret value from stdin', async () => {
|
|
57
|
+
const secret = await createTestSecret({
|
|
58
|
+
name: `managed-secret-stdin-${Date.now()}`,
|
|
59
|
+
value: 'initial-value',
|
|
60
|
+
description: 'Secret for stdin update test'
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const updateResult = await cliWithEnvAndStdin(
|
|
64
|
+
['aws', 'secret', 'update', '-n', secret.prefixedName, '--value-stdin'],
|
|
65
|
+
getLocalStackEnv(),
|
|
66
|
+
'stdin-updated-value'
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
expect(updateResult.code).toBe(0);
|
|
70
|
+
expect(updateResult.stderr).toBe('');
|
|
71
|
+
|
|
72
|
+
const deleteResult = await cliWithEnv(
|
|
73
|
+
[
|
|
74
|
+
'aws',
|
|
75
|
+
'secret',
|
|
76
|
+
'delete',
|
|
77
|
+
'-n',
|
|
78
|
+
secret.prefixedName,
|
|
79
|
+
'--force-delete-without-recovery',
|
|
80
|
+
'--yes'
|
|
81
|
+
],
|
|
82
|
+
getLocalStackEnv()
|
|
83
|
+
);
|
|
84
|
+
expect(deleteResult.code).toBe(0);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('should create from a single-line env file and retrieve via aws -s', async () => {
|
|
88
|
+
const secretName = `managed-secret-create-single-file-${Date.now()}`;
|
|
89
|
+
const tempFile = path.join(
|
|
90
|
+
os.tmpdir(),
|
|
91
|
+
`env-secrets-create-single-${Date.now()}.env`
|
|
92
|
+
);
|
|
93
|
+
fs.writeFileSync(tempFile, 'GITHUB_PAT=github_pat_single_line');
|
|
94
|
+
|
|
95
|
+
const createResult = await cliWithEnv(
|
|
96
|
+
[
|
|
97
|
+
'aws',
|
|
98
|
+
'secret',
|
|
99
|
+
'create',
|
|
100
|
+
'--file',
|
|
101
|
+
tempFile,
|
|
102
|
+
'--name',
|
|
103
|
+
secretName,
|
|
104
|
+
'--output',
|
|
105
|
+
'json'
|
|
106
|
+
],
|
|
107
|
+
getLocalStackEnv()
|
|
108
|
+
);
|
|
109
|
+
expect(createResult.code).toBe(0);
|
|
110
|
+
|
|
111
|
+
const retrieveResult = await cliWithEnv(
|
|
112
|
+
['aws', '-s', secretName, 'echo', '$GITHUB_PAT'],
|
|
113
|
+
getLocalStackEnv()
|
|
114
|
+
);
|
|
115
|
+
expect(retrieveResult.code).toBe(0);
|
|
116
|
+
const envVars = JSON.parse(retrieveResult.stdout.trim()) as Record<
|
|
117
|
+
string,
|
|
118
|
+
string
|
|
119
|
+
>;
|
|
120
|
+
expect(envVars.GITHUB_PAT).toBe('github_pat_single_line');
|
|
121
|
+
|
|
122
|
+
const deleteResult = await cliWithEnv(
|
|
123
|
+
[
|
|
124
|
+
'aws',
|
|
125
|
+
'secret',
|
|
126
|
+
'delete',
|
|
127
|
+
'-n',
|
|
128
|
+
secretName,
|
|
129
|
+
'--force-delete-without-recovery',
|
|
130
|
+
'--yes'
|
|
131
|
+
],
|
|
132
|
+
getLocalStackEnv()
|
|
133
|
+
);
|
|
134
|
+
expect(deleteResult.code).toBe(0);
|
|
135
|
+
cleanupTempFile(tempFile);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('should create from a multi-line env file and retrieve via aws -s', async () => {
|
|
139
|
+
const secretName = `managed-secret-create-multi-file-${Date.now()}`;
|
|
140
|
+
const tempFile = path.join(
|
|
141
|
+
os.tmpdir(),
|
|
142
|
+
`env-secrets-create-multi-${Date.now()}.env`
|
|
143
|
+
);
|
|
144
|
+
fs.writeFileSync(
|
|
145
|
+
tempFile,
|
|
146
|
+
['GITHUB_PAT=github_pat_multi_line', 'API_URL=https://example.com'].join(
|
|
147
|
+
'\n'
|
|
148
|
+
)
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const createResult = await cliWithEnv(
|
|
152
|
+
[
|
|
153
|
+
'aws',
|
|
154
|
+
'secret',
|
|
155
|
+
'create',
|
|
156
|
+
'--file',
|
|
157
|
+
tempFile,
|
|
158
|
+
'--name',
|
|
159
|
+
secretName,
|
|
160
|
+
'--output',
|
|
161
|
+
'json'
|
|
162
|
+
],
|
|
163
|
+
getLocalStackEnv()
|
|
164
|
+
);
|
|
165
|
+
expect(createResult.code).toBe(0);
|
|
166
|
+
|
|
167
|
+
const retrieveResult = await cliWithEnv(
|
|
168
|
+
['aws', '-s', secretName, 'echo', '$GITHUB_PAT'],
|
|
169
|
+
getLocalStackEnv()
|
|
170
|
+
);
|
|
171
|
+
expect(retrieveResult.code).toBe(0);
|
|
172
|
+
const envVars = JSON.parse(retrieveResult.stdout.trim()) as Record<
|
|
173
|
+
string,
|
|
174
|
+
string
|
|
175
|
+
>;
|
|
176
|
+
expect(envVars.GITHUB_PAT).toBe('github_pat_multi_line');
|
|
177
|
+
expect(envVars.API_URL).toBe('https://example.com');
|
|
178
|
+
|
|
179
|
+
const deleteResult = await cliWithEnv(
|
|
180
|
+
[
|
|
181
|
+
'aws',
|
|
182
|
+
'secret',
|
|
183
|
+
'delete',
|
|
184
|
+
'-n',
|
|
185
|
+
secretName,
|
|
186
|
+
'--force-delete-without-recovery',
|
|
187
|
+
'--yes'
|
|
188
|
+
],
|
|
189
|
+
getLocalStackEnv()
|
|
190
|
+
);
|
|
191
|
+
expect(deleteResult.code).toBe(0);
|
|
192
|
+
cleanupTempFile(tempFile);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test('should require confirmation for delete', async () => {
|
|
196
|
+
const secret = await createTestSecret({
|
|
197
|
+
name: `managed-secret-confirm-${Date.now()}`,
|
|
198
|
+
value: 'value',
|
|
199
|
+
description: 'Secret for delete confirmation test'
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const result = await cliWithEnv(
|
|
203
|
+
['aws', 'secret', 'delete', '-n', secret.prefixedName],
|
|
204
|
+
getLocalStackEnv()
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
expect(result.code).toBe(1);
|
|
208
|
+
expect(result.stderr).toContain('requires --yes confirmation');
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test('should honor region flag for secret list across multiple regions', async () => {
|
|
212
|
+
const secret = await createTestSecret(
|
|
213
|
+
{
|
|
214
|
+
name: `managed-secret-multi-region-${Date.now()}`,
|
|
215
|
+
value: '{"region":"us-west-2"}',
|
|
216
|
+
description: 'Secret used for region isolation test'
|
|
217
|
+
},
|
|
218
|
+
'us-west-2'
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const westResult = await cliWithEnv(
|
|
222
|
+
[
|
|
223
|
+
'aws',
|
|
224
|
+
'secret',
|
|
225
|
+
'list',
|
|
226
|
+
'--prefix',
|
|
227
|
+
secret.prefixedName,
|
|
228
|
+
'-r',
|
|
229
|
+
'us-west-2',
|
|
230
|
+
'--output',
|
|
231
|
+
'json'
|
|
232
|
+
],
|
|
233
|
+
getLocalStackEnv()
|
|
234
|
+
);
|
|
235
|
+
expect(westResult.code).toBe(0);
|
|
236
|
+
const westRows = JSON.parse(westResult.stdout) as Array<{ name: string }>;
|
|
237
|
+
expect(westRows.some((row) => row.name === secret.prefixedName)).toBe(true);
|
|
238
|
+
|
|
239
|
+
const eastResult = await cliWithEnv(
|
|
240
|
+
[
|
|
241
|
+
'aws',
|
|
242
|
+
'secret',
|
|
243
|
+
'list',
|
|
244
|
+
'--prefix',
|
|
245
|
+
secret.prefixedName,
|
|
246
|
+
'-r',
|
|
247
|
+
'us-east-1',
|
|
248
|
+
'--output',
|
|
249
|
+
'json'
|
|
250
|
+
],
|
|
251
|
+
getLocalStackEnv()
|
|
252
|
+
);
|
|
253
|
+
expect(eastResult.code).toBe(0);
|
|
254
|
+
const eastRows = JSON.parse(eastResult.stdout) as Array<{ name: string }>;
|
|
255
|
+
expect(eastRows).toEqual([]);
|
|
256
|
+
});
|
|
257
|
+
});
|