env-secrets 0.3.2 → 0.4.0
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/.codex/rules/cicd.md +170 -0
- package/.codex/rules/linting.md +174 -0
- package/.codex/rules/local-dev-badges.md +93 -0
- package/.codex/rules/local-dev-env.md +271 -0
- package/.codex/rules/local-dev-license.md +104 -0
- package/.codex/rules/local-dev-mcp.md +72 -0
- package/.codex/rules/logging.md +358 -0
- package/.codex/rules/observability.md +25 -0
- package/.codex/rules/testing.md +133 -0
- package/.github/workflows/lint.yaml +7 -8
- package/.github/workflows/release.yml +1 -1
- package/.github/workflows/unittests.yaml +1 -1
- package/AGENTS.md +10 -4
- package/README.md +14 -9
- package/__e2e__/README.md +2 -5
- package/__e2e__/index.test.ts +152 -1
- package/__e2e__/utils/test-utils.ts +61 -1
- package/__tests__/cli/helpers.test.ts +129 -0
- package/__tests__/vaults/aws-config.test.ts +85 -0
- package/__tests__/vaults/secretsmanager-admin.test.ts +312 -0
- package/__tests__/vaults/secretsmanager.test.ts +57 -20
- package/dist/cli/helpers.js +110 -0
- package/dist/index.js +221 -2
- package/dist/vaults/aws-config.js +29 -0
- package/dist/vaults/secretsmanager-admin.js +240 -0
- package/dist/vaults/secretsmanager.js +20 -16
- package/docs/AWS.md +78 -3
- package/eslint.config.js +67 -0
- package/jest.e2e.config.js +1 -0
- package/package.json +23 -13
- package/src/cli/helpers.ts +144 -0
- package/src/index.ts +287 -2
- package/src/vaults/aws-config.ts +51 -0
- package/src/vaults/secretsmanager-admin.ts +352 -0
- package/src/vaults/secretsmanager.ts +32 -20
- package/website/docs/cli-reference.mdx +67 -0
- package/website/docs/examples.mdx +1 -1
- package/website/docs/installation.mdx +1 -1
- package/website/docs/providers/aws-secrets-manager.mdx +32 -0
- package/.eslintignore +0 -4
- package/.eslintrc +0 -18
- package/.lintstagedrc +0 -4
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CreateSecretCommand,
|
|
3
|
+
DeleteSecretCommand,
|
|
4
|
+
DescribeSecretCommand,
|
|
5
|
+
ListSecretsCommand,
|
|
6
|
+
SecretsManagerClient,
|
|
7
|
+
Tag,
|
|
8
|
+
UpdateSecretCommand
|
|
9
|
+
} from '@aws-sdk/client-secrets-manager';
|
|
10
|
+
import { GetCallerIdentityCommand, STSClient } from '@aws-sdk/client-sts';
|
|
11
|
+
import Debug from 'debug';
|
|
12
|
+
import { buildAwsClientConfig } from './aws-config';
|
|
13
|
+
|
|
14
|
+
const debug = Debug('env-secrets:secretsmanager-admin');
|
|
15
|
+
|
|
16
|
+
export interface AwsSecretCommandOptions {
|
|
17
|
+
profile?: string;
|
|
18
|
+
region?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SecretCreateOptions extends AwsSecretCommandOptions {
|
|
22
|
+
name: string;
|
|
23
|
+
value: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
kmsKeyId?: string;
|
|
26
|
+
tags?: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SecretUpdateOptions extends AwsSecretCommandOptions {
|
|
30
|
+
name: string;
|
|
31
|
+
value?: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
kmsKeyId?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface SecretListOptions extends AwsSecretCommandOptions {
|
|
37
|
+
prefix?: string;
|
|
38
|
+
tags?: string[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface SecretDeleteOptions extends AwsSecretCommandOptions {
|
|
42
|
+
name: string;
|
|
43
|
+
recoveryDays?: number;
|
|
44
|
+
forceDeleteWithoutRecovery?: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface SecretSummary {
|
|
48
|
+
name: string;
|
|
49
|
+
arn?: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
lastChangedDate?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface SecretMetadata {
|
|
55
|
+
name?: string;
|
|
56
|
+
arn?: string;
|
|
57
|
+
description?: string;
|
|
58
|
+
kmsKeyId?: string;
|
|
59
|
+
deletedDate?: string;
|
|
60
|
+
lastChangedDate?: string;
|
|
61
|
+
lastAccessedDate?: string;
|
|
62
|
+
createdDate?: string;
|
|
63
|
+
versionIdsToStages?: Record<string, string[]>;
|
|
64
|
+
tags?: Record<string, string>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface AWSLikeError {
|
|
68
|
+
name?: string;
|
|
69
|
+
message?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Allowed characters are documented by AWS Secrets Manager naming rules.
|
|
73
|
+
// See: https://docs.aws.amazon.com/secretsmanager/latest/userguide/reference_limits.html
|
|
74
|
+
const SECRET_NAME_PATTERN = /^[A-Za-z0-9/_+=.@-]+$/;
|
|
75
|
+
|
|
76
|
+
const formatDate = (value?: Date): string | undefined => {
|
|
77
|
+
if (!value) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return value.toISOString();
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const parseTags = (tags?: string[]): Tag[] | undefined => {
|
|
85
|
+
if (!tags || tags.length === 0) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return tags.map((tag) => {
|
|
90
|
+
const parts = tag.split('=');
|
|
91
|
+
if (parts.length < 2) {
|
|
92
|
+
throw new Error(`Invalid tag format: ${tag}. Use key=value.`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const key = parts[0].trim();
|
|
96
|
+
const value = parts.slice(1).join('=').trim();
|
|
97
|
+
|
|
98
|
+
if (!key || !value) {
|
|
99
|
+
throw new Error(`Invalid tag format: ${tag}. Use key=value.`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { Key: key, Value: value };
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const tagsToRecord = (tags?: Tag[]): Record<string, string> | undefined => {
|
|
107
|
+
if (!tags || tags.length === 0) {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const result: Record<string, string> = {};
|
|
112
|
+
for (const tag of tags) {
|
|
113
|
+
if (tag.Key && tag.Value) {
|
|
114
|
+
result[tag.Key] = tag.Value;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const mapAwsError = (error: unknown, secretName?: string): never => {
|
|
122
|
+
const awsError = error as AWSLikeError;
|
|
123
|
+
const secretLabel = secretName ? ` for "${secretName}"` : '';
|
|
124
|
+
|
|
125
|
+
if (awsError?.name === 'AlreadyExistsException') {
|
|
126
|
+
throw new Error(`Secret${secretLabel} already exists.`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (awsError?.name === 'ResourceNotFoundException') {
|
|
130
|
+
throw new Error(`Secret${secretLabel} was not found.`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (awsError?.name === 'InvalidRequestException') {
|
|
134
|
+
throw new Error(
|
|
135
|
+
awsError.message || 'Invalid request to AWS Secrets Manager.'
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (awsError?.name === 'AccessDeniedException') {
|
|
140
|
+
throw new Error(
|
|
141
|
+
awsError.message ||
|
|
142
|
+
'Access denied while calling AWS Secrets Manager. Verify IAM permissions.'
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (awsError?.message) {
|
|
147
|
+
throw new Error(awsError.message);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
throw new Error(String(error));
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const ensureConnected = async (
|
|
154
|
+
clientConfig: ReturnType<typeof buildAwsClientConfig>
|
|
155
|
+
) => {
|
|
156
|
+
const stsClient = new STSClient(clientConfig);
|
|
157
|
+
await stsClient.send(new GetCallerIdentityCommand({}));
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const createClient = async (options: AwsSecretCommandOptions) => {
|
|
161
|
+
const config = buildAwsClientConfig(options);
|
|
162
|
+
debug('Creating AWS clients', {
|
|
163
|
+
hasProfile: Boolean(options.profile),
|
|
164
|
+
region: options.region,
|
|
165
|
+
hasEndpoint: Boolean(config.endpoint)
|
|
166
|
+
});
|
|
167
|
+
await ensureConnected(config);
|
|
168
|
+
return new SecretsManagerClient(config);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export const validateSecretName = (name: string) => {
|
|
172
|
+
if (!SECRET_NAME_PATTERN.test(name)) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
`Invalid secret name "${name}". Use only letters, numbers, and /_+=.@- characters.`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export const createSecret = async (
|
|
180
|
+
options: SecretCreateOptions
|
|
181
|
+
): Promise<{ arn?: string; name?: string; versionId?: string }> => {
|
|
182
|
+
validateSecretName(options.name);
|
|
183
|
+
debug('createSecret called', {
|
|
184
|
+
name: options.name,
|
|
185
|
+
hasTags: !!options.tags?.length
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const client = await createClient(options);
|
|
189
|
+
const tags = parseTags(options.tags);
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
const result = await client.send(
|
|
193
|
+
new CreateSecretCommand({
|
|
194
|
+
Name: options.name,
|
|
195
|
+
Description: options.description,
|
|
196
|
+
SecretString: options.value,
|
|
197
|
+
KmsKeyId: options.kmsKeyId,
|
|
198
|
+
Tags: tags
|
|
199
|
+
})
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
arn: result.ARN,
|
|
204
|
+
name: result.Name,
|
|
205
|
+
versionId: result.VersionId
|
|
206
|
+
};
|
|
207
|
+
} catch (error: unknown) {
|
|
208
|
+
return mapAwsError(error, options.name);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export const updateSecret = async (
|
|
213
|
+
options: SecretUpdateOptions
|
|
214
|
+
): Promise<{ arn?: string; name?: string; versionId?: string }> => {
|
|
215
|
+
validateSecretName(options.name);
|
|
216
|
+
debug('updateSecret called', { name: options.name });
|
|
217
|
+
|
|
218
|
+
const client = await createClient(options);
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
const result = await client.send(
|
|
222
|
+
new UpdateSecretCommand({
|
|
223
|
+
SecretId: options.name,
|
|
224
|
+
Description: options.description,
|
|
225
|
+
SecretString: options.value,
|
|
226
|
+
KmsKeyId: options.kmsKeyId
|
|
227
|
+
})
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
arn: result.ARN,
|
|
232
|
+
name: result.Name,
|
|
233
|
+
versionId: result.VersionId
|
|
234
|
+
};
|
|
235
|
+
} catch (error: unknown) {
|
|
236
|
+
return mapAwsError(error, options.name);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
export const listSecrets = async (
|
|
241
|
+
options: SecretListOptions
|
|
242
|
+
): Promise<SecretSummary[]> => {
|
|
243
|
+
debug('listSecrets called', {
|
|
244
|
+
prefix: options.prefix,
|
|
245
|
+
hasTags: !!options.tags?.length
|
|
246
|
+
});
|
|
247
|
+
const client = await createClient(options);
|
|
248
|
+
const requiredTags = parseTags(options.tags);
|
|
249
|
+
const secrets: SecretSummary[] = [];
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
let nextToken: string | undefined;
|
|
253
|
+
|
|
254
|
+
do {
|
|
255
|
+
const result = await client.send(
|
|
256
|
+
new ListSecretsCommand({ NextToken: nextToken })
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
for (const secret of result.SecretList || []) {
|
|
260
|
+
if (
|
|
261
|
+
options.prefix &&
|
|
262
|
+
secret.Name &&
|
|
263
|
+
!secret.Name.startsWith(options.prefix)
|
|
264
|
+
) {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (requiredTags && requiredTags.length > 0) {
|
|
269
|
+
const available = tagsToRecord(secret.Tags);
|
|
270
|
+
const matchesAll = requiredTags.every(
|
|
271
|
+
(tag) => tag.Key && tag.Value && available?.[tag.Key] === tag.Value
|
|
272
|
+
);
|
|
273
|
+
if (!matchesAll) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
secrets.push({
|
|
279
|
+
name: secret.Name || '',
|
|
280
|
+
arn: secret.ARN,
|
|
281
|
+
description: secret.Description,
|
|
282
|
+
lastChangedDate: formatDate(secret.LastChangedDate)
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
nextToken = result.NextToken;
|
|
287
|
+
} while (nextToken);
|
|
288
|
+
} catch (error: unknown) {
|
|
289
|
+
return mapAwsError(error);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return secrets;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
export const getSecretMetadata = async (
|
|
296
|
+
options: AwsSecretCommandOptions & { name: string }
|
|
297
|
+
): Promise<SecretMetadata> => {
|
|
298
|
+
validateSecretName(options.name);
|
|
299
|
+
debug('getSecretMetadata called', { name: options.name });
|
|
300
|
+
const client = await createClient(options);
|
|
301
|
+
|
|
302
|
+
try {
|
|
303
|
+
const result = await client.send(
|
|
304
|
+
new DescribeSecretCommand({ SecretId: options.name })
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
name: result.Name,
|
|
309
|
+
arn: result.ARN,
|
|
310
|
+
description: result.Description,
|
|
311
|
+
kmsKeyId: result.KmsKeyId,
|
|
312
|
+
deletedDate: formatDate(result.DeletedDate),
|
|
313
|
+
lastChangedDate: formatDate(result.LastChangedDate),
|
|
314
|
+
lastAccessedDate: formatDate(result.LastAccessedDate),
|
|
315
|
+
createdDate: formatDate(result.CreatedDate),
|
|
316
|
+
versionIdsToStages: result.VersionIdsToStages,
|
|
317
|
+
tags: tagsToRecord(result.Tags)
|
|
318
|
+
};
|
|
319
|
+
} catch (error: unknown) {
|
|
320
|
+
return mapAwsError(error, options.name);
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
export const deleteSecret = async (
|
|
325
|
+
options: SecretDeleteOptions
|
|
326
|
+
): Promise<{ arn?: string; name?: string; deletedDate?: string }> => {
|
|
327
|
+
validateSecretName(options.name);
|
|
328
|
+
debug('deleteSecret called', {
|
|
329
|
+
name: options.name,
|
|
330
|
+
recoveryDays: options.recoveryDays,
|
|
331
|
+
forceDeleteWithoutRecovery: options.forceDeleteWithoutRecovery
|
|
332
|
+
});
|
|
333
|
+
const client = await createClient(options);
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
const result = await client.send(
|
|
337
|
+
new DeleteSecretCommand({
|
|
338
|
+
SecretId: options.name,
|
|
339
|
+
RecoveryWindowInDays: options.recoveryDays,
|
|
340
|
+
ForceDeleteWithoutRecovery: options.forceDeleteWithoutRecovery
|
|
341
|
+
})
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
arn: result.ARN,
|
|
346
|
+
name: result.Name,
|
|
347
|
+
deletedDate: formatDate(result.DeletionDate)
|
|
348
|
+
};
|
|
349
|
+
} catch (error: unknown) {
|
|
350
|
+
return mapAwsError(error, options.name);
|
|
351
|
+
}
|
|
352
|
+
};
|
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
GetSecretValueCommand
|
|
4
4
|
} from '@aws-sdk/client-secrets-manager';
|
|
5
5
|
import { STSClient, GetCallerIdentityCommand } from '@aws-sdk/client-sts';
|
|
6
|
-
import { fromIni } from '@aws-sdk/credential-providers';
|
|
7
6
|
import Debug from 'debug';
|
|
7
|
+
import { buildAwsClientConfig } from './aws-config';
|
|
8
8
|
|
|
9
9
|
const debug = Debug('env-secrets:secretsmanager');
|
|
10
10
|
|
|
@@ -14,8 +14,26 @@ interface secretsmanagerType {
|
|
|
14
14
|
region?: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
interface AWSLikeError {
|
|
18
|
+
name?: string;
|
|
19
|
+
message?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const isCredentialsError = (error: unknown): error is AWSLikeError => {
|
|
23
|
+
if (!error || typeof error !== 'object') {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const errorName = 'name' in error ? error.name : undefined;
|
|
28
|
+
return (
|
|
29
|
+
errorName === 'CredentialsError' || errorName === 'CredentialsProviderError'
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const checkConnection = async (
|
|
34
|
+
config: ReturnType<typeof buildAwsClientConfig>
|
|
35
|
+
) => {
|
|
36
|
+
const stsClient = new STSClient(config);
|
|
19
37
|
const command = new GetCallerIdentityCommand({});
|
|
20
38
|
|
|
21
39
|
try {
|
|
@@ -23,6 +41,12 @@ const checkConnection = async (region?: string) => {
|
|
|
23
41
|
debug(data);
|
|
24
42
|
return true;
|
|
25
43
|
} catch (err) {
|
|
44
|
+
if (isCredentialsError(err) && err.message) {
|
|
45
|
+
// eslint-disable-next-line no-console
|
|
46
|
+
console.error(err.message);
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
26
50
|
// eslint-disable-next-line no-console
|
|
27
51
|
console.error(err);
|
|
28
52
|
return false;
|
|
@@ -31,33 +55,21 @@ const checkConnection = async (region?: string) => {
|
|
|
31
55
|
|
|
32
56
|
export const secretsmanager = async (options: secretsmanagerType) => {
|
|
33
57
|
const { secret, profile, region } = options;
|
|
34
|
-
const {
|
|
35
|
-
AWS_ACCESS_KEY_ID: awsAccessKeyId,
|
|
36
|
-
AWS_SECRET_ACCESS_KEY: awsSecretAccessKey
|
|
37
|
-
} = process.env;
|
|
58
|
+
const config = buildAwsClientConfig({ profile, region });
|
|
38
59
|
|
|
39
|
-
let credentials;
|
|
40
60
|
if (profile) {
|
|
41
61
|
debug(`Using profile: ${profile}`);
|
|
42
|
-
|
|
43
|
-
} else if (awsAccessKeyId && awsSecretAccessKey) {
|
|
44
|
-
debug('Using environment variables');
|
|
45
|
-
credentials = undefined; // Will use environment variables automatically
|
|
46
|
-
} else {
|
|
62
|
+
} else if (config.credentials) {
|
|
47
63
|
debug('Using profile: default');
|
|
48
|
-
|
|
64
|
+
} else {
|
|
65
|
+
debug('Using environment variables');
|
|
49
66
|
}
|
|
50
67
|
|
|
51
|
-
const config = {
|
|
52
|
-
region,
|
|
53
|
-
credentials
|
|
54
|
-
};
|
|
55
|
-
|
|
56
68
|
if (!config.region) {
|
|
57
69
|
debug('no region set');
|
|
58
70
|
}
|
|
59
71
|
|
|
60
|
-
const connected = await checkConnection(
|
|
72
|
+
const connected = await checkConnection(config);
|
|
61
73
|
|
|
62
74
|
if (connected) {
|
|
63
75
|
const client = new SecretsManagerClient(config);
|
|
@@ -28,6 +28,48 @@ Currently supported provider: `aws`
|
|
|
28
28
|
- `-p, --profile <profile>` - AWS profile to use (defaults to environment variables or IAM role)
|
|
29
29
|
- `-o, --output <file>` - Output secrets to a file instead of injecting into environment variables. File will be created with 0400 permissions and will not overwrite existing files
|
|
30
30
|
|
|
31
|
+
## AWS Secret Management Commands
|
|
32
|
+
|
|
33
|
+
Use `env-secrets aws secret <command>` to manage AWS Secrets Manager secrets directly.
|
|
34
|
+
|
|
35
|
+
### Commands
|
|
36
|
+
|
|
37
|
+
- `create` - Create a new secret
|
|
38
|
+
- `update` - Update secret value or metadata
|
|
39
|
+
- `list` - List secrets
|
|
40
|
+
- `get` - Get secret metadata/version info
|
|
41
|
+
- `delete` - Delete a secret
|
|
42
|
+
|
|
43
|
+
### Shared Options
|
|
44
|
+
|
|
45
|
+
- `-r, --region <region>` - AWS region
|
|
46
|
+
- `-p, --profile <profile>` - AWS profile
|
|
47
|
+
- `--output <format>` - Output format: `json` or `table` (default: `table`)
|
|
48
|
+
|
|
49
|
+
### Command-Specific Options
|
|
50
|
+
|
|
51
|
+
- `create`
|
|
52
|
+
- `-n, --name <name>` (required)
|
|
53
|
+
- `-v, --value <value>` or `--value-stdin`
|
|
54
|
+
- `-d, --description <description>`
|
|
55
|
+
- `-k, --kms-key-id <kmsKeyId>`
|
|
56
|
+
- `-t, --tag <key=value...>`
|
|
57
|
+
- `update`
|
|
58
|
+
- `-n, --name <name>` (required)
|
|
59
|
+
- `-v, --value <value>` or `--value-stdin`
|
|
60
|
+
- `-d, --description <description>`
|
|
61
|
+
- `-k, --kms-key-id <kmsKeyId>`
|
|
62
|
+
- `list`
|
|
63
|
+
- `--prefix <prefix>`
|
|
64
|
+
- `-t, --tag <key=value...>`
|
|
65
|
+
- `get`
|
|
66
|
+
- `-n, --name <name>` (required)
|
|
67
|
+
- `delete`
|
|
68
|
+
- `-n, --name <name>` (required)
|
|
69
|
+
- `-y, --yes` (required for delete)
|
|
70
|
+
- `--recovery-days <7-30>`
|
|
71
|
+
- `--force-delete-without-recovery`
|
|
72
|
+
|
|
31
73
|
### Global Options
|
|
32
74
|
|
|
33
75
|
- `--help` - Show help information
|
|
@@ -131,6 +173,31 @@ env-secrets aws -s docker-secrets -r us-east-1 -o .env
|
|
|
131
173
|
docker run --env-file .env my-app:latest
|
|
132
174
|
```
|
|
133
175
|
|
|
176
|
+
### Secret Management
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
# Create
|
|
180
|
+
env-secrets aws secret create -n app/dev/api -v '{"API_KEY":"abc123"}' --output json
|
|
181
|
+
|
|
182
|
+
# Create from stdin (recommended for sensitive values)
|
|
183
|
+
echo -n 'super-secret-value' | env-secrets aws secret create -n app/dev/raw --value-stdin
|
|
184
|
+
|
|
185
|
+
# Update value
|
|
186
|
+
env-secrets aws secret update -n app/dev/api -v '{"API_KEY":"xyz789"}'
|
|
187
|
+
|
|
188
|
+
# Update from stdin
|
|
189
|
+
echo -n 'rotated-value' | env-secrets aws secret update -n app/dev/raw --value-stdin
|
|
190
|
+
|
|
191
|
+
# List
|
|
192
|
+
env-secrets aws secret list --prefix app/dev --output table
|
|
193
|
+
|
|
194
|
+
# Get metadata (does not print secret value)
|
|
195
|
+
env-secrets aws secret get -n app/dev/api --output json
|
|
196
|
+
|
|
197
|
+
# Delete with confirmation
|
|
198
|
+
env-secrets aws secret delete -n app/dev/raw --recovery-days 7 --yes
|
|
199
|
+
```
|
|
200
|
+
|
|
134
201
|
## Environment Variable Behavior
|
|
135
202
|
|
|
136
203
|
### JSON Secret Parsing
|
|
@@ -4,6 +4,8 @@ title: AWS Secrets Manager
|
|
|
4
4
|
|
|
5
5
|
`env-secrets` supports pulling a single JSON secret from AWS Secrets Manager, mapping each top-level key to an environment variable.
|
|
6
6
|
|
|
7
|
+
It also supports secret lifecycle operations with `env-secrets aws secret <command>`.
|
|
8
|
+
|
|
7
9
|
### Create a secret (JSON)
|
|
8
10
|
|
|
9
11
|
```bash
|
|
@@ -22,6 +24,36 @@ env-secrets aws -s local/sample -r us-east-1 -- echo $user/$password
|
|
|
22
24
|
- `-r, --region` — AWS region (or `AWS_DEFAULT_REGION`)
|
|
23
25
|
- `-p, --profile` — AWS profile to use
|
|
24
26
|
|
|
27
|
+
### Secret Management Commands
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Create
|
|
31
|
+
env-secrets aws secret create -n app/dev/api -v '{"API_KEY":"abc123"}' --output json
|
|
32
|
+
|
|
33
|
+
# Create from stdin
|
|
34
|
+
echo -n 'super-secret-value' | env-secrets aws secret create -n app/dev/raw --value-stdin
|
|
35
|
+
|
|
36
|
+
# Update
|
|
37
|
+
env-secrets aws secret update -n app/dev/api -v '{"API_KEY":"rotated"}'
|
|
38
|
+
|
|
39
|
+
# List
|
|
40
|
+
env-secrets aws secret list --prefix app/dev --output table
|
|
41
|
+
|
|
42
|
+
# Get metadata (does not print secret value)
|
|
43
|
+
env-secrets aws secret get -n app/dev/api --output json
|
|
44
|
+
|
|
45
|
+
# Delete (requires --yes)
|
|
46
|
+
env-secrets aws secret delete -n app/dev/raw --recovery-days 7 --yes
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Supported commands:
|
|
50
|
+
|
|
51
|
+
- `create` with `--value` or `--value-stdin`
|
|
52
|
+
- `update` with value and/or metadata changes
|
|
53
|
+
- `list` with optional prefix/tag filters
|
|
54
|
+
- `get` for metadata/version details
|
|
55
|
+
- `delete` with recovery window or force-delete flags
|
|
56
|
+
|
|
25
57
|
### Tips
|
|
26
58
|
|
|
27
59
|
- Use `DEBUG=env-secrets,env-secrets:secretsmanager` for verbose logs.
|
package/.eslintignore
DELETED
package/.eslintrc
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"root": true,
|
|
3
|
-
"parser": "@typescript-eslint/parser",
|
|
4
|
-
"plugins": ["@typescript-eslint", "prettier"],
|
|
5
|
-
"extends": [
|
|
6
|
-
"eslint:recommended",
|
|
7
|
-
"plugin:@typescript-eslint/eslint-recommended",
|
|
8
|
-
"plugin:@typescript-eslint/recommended",
|
|
9
|
-
"prettier"
|
|
10
|
-
],
|
|
11
|
-
"env": {
|
|
12
|
-
"jest": true
|
|
13
|
-
},
|
|
14
|
-
"rules": {
|
|
15
|
-
"no-console": "warn",
|
|
16
|
-
"prettier/prettier": "error"
|
|
17
|
-
}
|
|
18
|
-
}
|
package/.lintstagedrc
DELETED