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
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
/* istanbul ignore file */
|
|
3
4
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
4
5
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
5
6
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -19,24 +20,34 @@ const node_fs_1 = require("node:fs");
|
|
|
19
20
|
const debug_1 = __importDefault(require("debug"));
|
|
20
21
|
const version_1 = require("./version");
|
|
21
22
|
const secretsmanager_1 = require("./vaults/secretsmanager");
|
|
23
|
+
const secretsmanager_admin_1 = require("./vaults/secretsmanager-admin");
|
|
24
|
+
const helpers_1 = require("./cli/helpers");
|
|
22
25
|
const utils_1 = require("./vaults/utils");
|
|
23
26
|
const debug = (0, debug_1.default)('env-secrets');
|
|
24
27
|
const program = new commander_1.Command();
|
|
28
|
+
const exitWithError = (error) => {
|
|
29
|
+
// eslint-disable-next-line no-console
|
|
30
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
};
|
|
25
33
|
// main program
|
|
26
34
|
program
|
|
27
35
|
.name('env-secrets')
|
|
28
36
|
.description('pull secrets from vaults and inject them into the running environment')
|
|
29
37
|
.version(version_1.LIB_VERSION);
|
|
30
38
|
// aws secretsmanager
|
|
31
|
-
program
|
|
39
|
+
const awsCommand = program
|
|
32
40
|
.command('aws')
|
|
33
41
|
.description('get secrets from AWS secrets manager')
|
|
34
42
|
.addArgument(new commander_1.Argument('[program...]', 'program to run'))
|
|
35
|
-
.
|
|
43
|
+
.option('-s, --secret <secret>', 'secret to get')
|
|
36
44
|
.option('-p, --profile <profile>', 'profile to use')
|
|
37
45
|
.option('-r, --region <region>', 'region to use')
|
|
38
46
|
.option('-o, --output <file>', 'output secrets to file instead of environment variables')
|
|
39
47
|
.action((program, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
48
|
+
if (!options.secret) {
|
|
49
|
+
exitWithError(new Error('Missing required option --secret for this command.'));
|
|
50
|
+
}
|
|
40
51
|
const secrets = yield (0, secretsmanager_1.secretsmanager)(options);
|
|
41
52
|
debug(secrets);
|
|
42
53
|
if (options.output) {
|
|
@@ -72,4 +83,212 @@ program
|
|
|
72
83
|
}
|
|
73
84
|
}
|
|
74
85
|
}));
|
|
86
|
+
const secretCommand = awsCommand
|
|
87
|
+
.command('secret')
|
|
88
|
+
.description('manage AWS secrets');
|
|
89
|
+
secretCommand
|
|
90
|
+
.command('create')
|
|
91
|
+
.description('create a secret in AWS Secrets Manager')
|
|
92
|
+
.requiredOption('-n, --name <name>', 'secret name')
|
|
93
|
+
.option('-v, --value <value>', 'secret value')
|
|
94
|
+
.option('--value-stdin', 'read secret value from stdin')
|
|
95
|
+
.option('-d, --description <description>', 'secret description')
|
|
96
|
+
.option('-k, --kms-key-id <kmsKeyId>', 'kms key id')
|
|
97
|
+
.option('-t, --tag <tag...>', 'tag in key=value format')
|
|
98
|
+
.option('-p, --profile <profile>', 'profile to use')
|
|
99
|
+
.option('-r, --region <region>', 'region to use')
|
|
100
|
+
.option('--output <format>', 'output format: json|table')
|
|
101
|
+
.action((options, command) => __awaiter(void 0, void 0, void 0, function* () {
|
|
102
|
+
var _a;
|
|
103
|
+
try {
|
|
104
|
+
const { profile, region } = (0, helpers_1.resolveAwsScope)(options, command);
|
|
105
|
+
const globalOptions = command.optsWithGlobals();
|
|
106
|
+
const output = (_a = options.output) !== null && _a !== void 0 ? _a : (typeof globalOptions.output === 'string'
|
|
107
|
+
? globalOptions.output
|
|
108
|
+
: 'table');
|
|
109
|
+
const value = yield (0, helpers_1.resolveSecretValue)(options.value, options.valueStdin);
|
|
110
|
+
if (!value) {
|
|
111
|
+
throw new Error('Secret value is required. Provide --value or --value-stdin.');
|
|
112
|
+
}
|
|
113
|
+
const result = yield (0, secretsmanager_admin_1.createSecret)({
|
|
114
|
+
name: options.name,
|
|
115
|
+
value,
|
|
116
|
+
description: options.description,
|
|
117
|
+
kmsKeyId: options.kmsKeyId,
|
|
118
|
+
tags: options.tag,
|
|
119
|
+
profile,
|
|
120
|
+
region
|
|
121
|
+
});
|
|
122
|
+
(0, helpers_1.printData)((0, helpers_1.asOutputFormat)(output), [
|
|
123
|
+
{ key: 'name', label: 'Name' },
|
|
124
|
+
{ key: 'arn', label: 'ARN' },
|
|
125
|
+
{ key: 'versionId', label: 'VersionId' }
|
|
126
|
+
], [result]);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
exitWithError(error);
|
|
130
|
+
}
|
|
131
|
+
}));
|
|
132
|
+
secretCommand
|
|
133
|
+
.command('update')
|
|
134
|
+
.description('update secret value or metadata')
|
|
135
|
+
.requiredOption('-n, --name <name>', 'secret name')
|
|
136
|
+
.option('-v, --value <value>', 'new secret value')
|
|
137
|
+
.option('--value-stdin', 'read secret value from stdin')
|
|
138
|
+
.option('-d, --description <description>', 'secret description')
|
|
139
|
+
.option('-k, --kms-key-id <kmsKeyId>', 'kms key id')
|
|
140
|
+
.option('-p, --profile <profile>', 'profile to use')
|
|
141
|
+
.option('-r, --region <region>', 'region to use')
|
|
142
|
+
.option('--output <format>', 'output format: json|table')
|
|
143
|
+
.action((options, command) => __awaiter(void 0, void 0, void 0, function* () {
|
|
144
|
+
var _b;
|
|
145
|
+
try {
|
|
146
|
+
const { profile, region } = (0, helpers_1.resolveAwsScope)(options, command);
|
|
147
|
+
const globalOptions = command.optsWithGlobals();
|
|
148
|
+
const output = (_b = options.output) !== null && _b !== void 0 ? _b : (typeof globalOptions.output === 'string'
|
|
149
|
+
? globalOptions.output
|
|
150
|
+
: 'table');
|
|
151
|
+
const value = yield (0, helpers_1.resolveSecretValue)(options.value, options.valueStdin);
|
|
152
|
+
if (!value && !options.description && !options.kmsKeyId) {
|
|
153
|
+
throw new Error('Nothing to update. Provide --value/--value-stdin, --description, or --kms-key-id.');
|
|
154
|
+
}
|
|
155
|
+
const result = yield (0, secretsmanager_admin_1.updateSecret)({
|
|
156
|
+
name: options.name,
|
|
157
|
+
value,
|
|
158
|
+
description: options.description,
|
|
159
|
+
kmsKeyId: options.kmsKeyId,
|
|
160
|
+
profile,
|
|
161
|
+
region
|
|
162
|
+
});
|
|
163
|
+
(0, helpers_1.printData)((0, helpers_1.asOutputFormat)(output), [
|
|
164
|
+
{ key: 'name', label: 'Name' },
|
|
165
|
+
{ key: 'arn', label: 'ARN' },
|
|
166
|
+
{ key: 'versionId', label: 'VersionId' }
|
|
167
|
+
], [result]);
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
exitWithError(error);
|
|
171
|
+
}
|
|
172
|
+
}));
|
|
173
|
+
secretCommand
|
|
174
|
+
.command('list')
|
|
175
|
+
.description('list secrets in AWS Secrets Manager')
|
|
176
|
+
.option('--prefix <prefix>', 'filter secrets by name prefix')
|
|
177
|
+
.option('-t, --tag <tag...>', 'filter tags in key=value format')
|
|
178
|
+
.option('-p, --profile <profile>', 'profile to use')
|
|
179
|
+
.option('-r, --region <region>', 'region to use')
|
|
180
|
+
.option('--output <format>', 'output format: json|table')
|
|
181
|
+
.action((options, command) => __awaiter(void 0, void 0, void 0, function* () {
|
|
182
|
+
var _c;
|
|
183
|
+
try {
|
|
184
|
+
const { profile, region } = (0, helpers_1.resolveAwsScope)(options, command);
|
|
185
|
+
const globalOptions = command.optsWithGlobals();
|
|
186
|
+
const output = (_c = options.output) !== null && _c !== void 0 ? _c : (typeof globalOptions.output === 'string'
|
|
187
|
+
? globalOptions.output
|
|
188
|
+
: 'table');
|
|
189
|
+
const result = yield (0, secretsmanager_admin_1.listSecrets)({
|
|
190
|
+
prefix: options.prefix,
|
|
191
|
+
tags: options.tag,
|
|
192
|
+
profile,
|
|
193
|
+
region
|
|
194
|
+
});
|
|
195
|
+
const rows = result.map((secret) => ({
|
|
196
|
+
name: secret.name,
|
|
197
|
+
description: secret.description,
|
|
198
|
+
lastChangedDate: secret.lastChangedDate
|
|
199
|
+
}));
|
|
200
|
+
(0, helpers_1.printData)((0, helpers_1.asOutputFormat)(output), [
|
|
201
|
+
{ key: 'name', label: 'Name' },
|
|
202
|
+
{ key: 'description', label: 'Description' },
|
|
203
|
+
{ key: 'lastChangedDate', label: 'LastChanged' }
|
|
204
|
+
], rows);
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
exitWithError(error);
|
|
208
|
+
}
|
|
209
|
+
}));
|
|
210
|
+
secretCommand
|
|
211
|
+
.command('get')
|
|
212
|
+
.description('get secret metadata and version information')
|
|
213
|
+
.requiredOption('-n, --name <name>', 'secret name')
|
|
214
|
+
.option('-p, --profile <profile>', 'profile to use')
|
|
215
|
+
.option('-r, --region <region>', 'region to use')
|
|
216
|
+
.option('--output <format>', 'output format: json|table')
|
|
217
|
+
.action((options, command) => __awaiter(void 0, void 0, void 0, function* () {
|
|
218
|
+
var _d;
|
|
219
|
+
try {
|
|
220
|
+
const { profile, region } = (0, helpers_1.resolveAwsScope)(options, command);
|
|
221
|
+
const globalOptions = command.optsWithGlobals();
|
|
222
|
+
const output = (_d = options.output) !== null && _d !== void 0 ? _d : (typeof globalOptions.output === 'string'
|
|
223
|
+
? globalOptions.output
|
|
224
|
+
: 'table');
|
|
225
|
+
const result = yield (0, secretsmanager_admin_1.getSecretMetadata)({
|
|
226
|
+
name: options.name,
|
|
227
|
+
profile,
|
|
228
|
+
region
|
|
229
|
+
});
|
|
230
|
+
const row = {
|
|
231
|
+
name: result.name,
|
|
232
|
+
arn: result.arn,
|
|
233
|
+
description: result.description,
|
|
234
|
+
kmsKeyId: result.kmsKeyId,
|
|
235
|
+
createdDate: result.createdDate,
|
|
236
|
+
lastChangedDate: result.lastChangedDate,
|
|
237
|
+
deletedDate: result.deletedDate
|
|
238
|
+
};
|
|
239
|
+
(0, helpers_1.printData)((0, helpers_1.asOutputFormat)(output), [
|
|
240
|
+
{ key: 'name', label: 'Name' },
|
|
241
|
+
{ key: 'arn', label: 'ARN' },
|
|
242
|
+
{ key: 'description', label: 'Description' },
|
|
243
|
+
{ key: 'kmsKeyId', label: 'KmsKeyId' },
|
|
244
|
+
{ key: 'createdDate', label: 'Created' },
|
|
245
|
+
{ key: 'lastChangedDate', label: 'LastChanged' },
|
|
246
|
+
{ key: 'deletedDate', label: 'Deleted' }
|
|
247
|
+
], [row]);
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
exitWithError(error);
|
|
251
|
+
}
|
|
252
|
+
}));
|
|
253
|
+
secretCommand
|
|
254
|
+
.command('delete')
|
|
255
|
+
.description('delete a secret in AWS Secrets Manager')
|
|
256
|
+
.requiredOption('-n, --name <name>', 'secret name')
|
|
257
|
+
.option('--recovery-days <days>', 'recovery window in days (7-30)', helpers_1.parseRecoveryDays)
|
|
258
|
+
.option('--force-delete-without-recovery', 'permanently delete secret without recovery window', false)
|
|
259
|
+
.option('-y, --yes', 'confirm delete action', false)
|
|
260
|
+
.option('-p, --profile <profile>', 'profile to use')
|
|
261
|
+
.option('-r, --region <region>', 'region to use')
|
|
262
|
+
.option('--output <format>', 'output format: json|table')
|
|
263
|
+
.action((options, command) => __awaiter(void 0, void 0, void 0, function* () {
|
|
264
|
+
var _e;
|
|
265
|
+
try {
|
|
266
|
+
const { profile, region } = (0, helpers_1.resolveAwsScope)(options, command);
|
|
267
|
+
const globalOptions = command.optsWithGlobals();
|
|
268
|
+
const output = (_e = options.output) !== null && _e !== void 0 ? _e : (typeof globalOptions.output === 'string'
|
|
269
|
+
? globalOptions.output
|
|
270
|
+
: 'table');
|
|
271
|
+
if (!options.yes) {
|
|
272
|
+
throw new Error('Delete requires --yes confirmation.');
|
|
273
|
+
}
|
|
274
|
+
if (options.recoveryDays && options.forceDeleteWithoutRecovery) {
|
|
275
|
+
throw new Error('Use either --recovery-days or --force-delete-without-recovery, not both.');
|
|
276
|
+
}
|
|
277
|
+
const result = yield (0, secretsmanager_admin_1.deleteSecret)({
|
|
278
|
+
name: options.name,
|
|
279
|
+
recoveryDays: options.recoveryDays,
|
|
280
|
+
forceDeleteWithoutRecovery: options.forceDeleteWithoutRecovery,
|
|
281
|
+
profile,
|
|
282
|
+
region
|
|
283
|
+
});
|
|
284
|
+
(0, helpers_1.printData)((0, helpers_1.asOutputFormat)(output), [
|
|
285
|
+
{ key: 'name', label: 'Name' },
|
|
286
|
+
{ key: 'arn', label: 'ARN' },
|
|
287
|
+
{ key: 'deletedDate', label: 'DeletedDate' }
|
|
288
|
+
], [result]);
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
exitWithError(error);
|
|
292
|
+
}
|
|
293
|
+
}));
|
|
75
294
|
program.parse();
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildAwsClientConfig = void 0;
|
|
4
|
+
const credential_providers_1 = require("@aws-sdk/credential-providers");
|
|
5
|
+
const getCredentialsProvider = (options) => {
|
|
6
|
+
const { AWS_ACCESS_KEY_ID: awsAccessKeyId, AWS_SECRET_ACCESS_KEY: awsSecretAccessKey } = process.env;
|
|
7
|
+
if (options.profile) {
|
|
8
|
+
return (0, credential_providers_1.fromIni)({ profile: options.profile });
|
|
9
|
+
}
|
|
10
|
+
if (awsAccessKeyId && awsSecretAccessKey) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
return (0, credential_providers_1.fromIni)({ profile: 'default' });
|
|
14
|
+
};
|
|
15
|
+
const getEndpoint = () => {
|
|
16
|
+
return (process.env.AWS_ENDPOINT_URL || process.env.AWS_SECRETS_MANAGER_ENDPOINT);
|
|
17
|
+
};
|
|
18
|
+
const buildAwsClientConfig = (options) => {
|
|
19
|
+
const endpoint = getEndpoint();
|
|
20
|
+
const config = {
|
|
21
|
+
region: options.region,
|
|
22
|
+
credentials: getCredentialsProvider(options)
|
|
23
|
+
};
|
|
24
|
+
if (endpoint) {
|
|
25
|
+
config.endpoint = endpoint;
|
|
26
|
+
}
|
|
27
|
+
return config;
|
|
28
|
+
};
|
|
29
|
+
exports.buildAwsClientConfig = buildAwsClientConfig;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.deleteSecret = exports.getSecretMetadata = exports.listSecrets = exports.updateSecret = exports.createSecret = exports.validateSecretName = void 0;
|
|
16
|
+
const client_secrets_manager_1 = require("@aws-sdk/client-secrets-manager");
|
|
17
|
+
const client_sts_1 = require("@aws-sdk/client-sts");
|
|
18
|
+
const debug_1 = __importDefault(require("debug"));
|
|
19
|
+
const aws_config_1 = require("./aws-config");
|
|
20
|
+
const debug = (0, debug_1.default)('env-secrets:secretsmanager-admin');
|
|
21
|
+
// Allowed characters are documented by AWS Secrets Manager naming rules.
|
|
22
|
+
// See: https://docs.aws.amazon.com/secretsmanager/latest/userguide/reference_limits.html
|
|
23
|
+
const SECRET_NAME_PATTERN = /^[A-Za-z0-9/_+=.@-]+$/;
|
|
24
|
+
const formatDate = (value) => {
|
|
25
|
+
if (!value) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
return value.toISOString();
|
|
29
|
+
};
|
|
30
|
+
const parseTags = (tags) => {
|
|
31
|
+
if (!tags || tags.length === 0) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
return tags.map((tag) => {
|
|
35
|
+
const parts = tag.split('=');
|
|
36
|
+
if (parts.length < 2) {
|
|
37
|
+
throw new Error(`Invalid tag format: ${tag}. Use key=value.`);
|
|
38
|
+
}
|
|
39
|
+
const key = parts[0].trim();
|
|
40
|
+
const value = parts.slice(1).join('=').trim();
|
|
41
|
+
if (!key || !value) {
|
|
42
|
+
throw new Error(`Invalid tag format: ${tag}. Use key=value.`);
|
|
43
|
+
}
|
|
44
|
+
return { Key: key, Value: value };
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
const tagsToRecord = (tags) => {
|
|
48
|
+
if (!tags || tags.length === 0) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
const result = {};
|
|
52
|
+
for (const tag of tags) {
|
|
53
|
+
if (tag.Key && tag.Value) {
|
|
54
|
+
result[tag.Key] = tag.Value;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
58
|
+
};
|
|
59
|
+
const mapAwsError = (error, secretName) => {
|
|
60
|
+
const awsError = error;
|
|
61
|
+
const secretLabel = secretName ? ` for "${secretName}"` : '';
|
|
62
|
+
if ((awsError === null || awsError === void 0 ? void 0 : awsError.name) === 'AlreadyExistsException') {
|
|
63
|
+
throw new Error(`Secret${secretLabel} already exists.`);
|
|
64
|
+
}
|
|
65
|
+
if ((awsError === null || awsError === void 0 ? void 0 : awsError.name) === 'ResourceNotFoundException') {
|
|
66
|
+
throw new Error(`Secret${secretLabel} was not found.`);
|
|
67
|
+
}
|
|
68
|
+
if ((awsError === null || awsError === void 0 ? void 0 : awsError.name) === 'InvalidRequestException') {
|
|
69
|
+
throw new Error(awsError.message || 'Invalid request to AWS Secrets Manager.');
|
|
70
|
+
}
|
|
71
|
+
if ((awsError === null || awsError === void 0 ? void 0 : awsError.name) === 'AccessDeniedException') {
|
|
72
|
+
throw new Error(awsError.message ||
|
|
73
|
+
'Access denied while calling AWS Secrets Manager. Verify IAM permissions.');
|
|
74
|
+
}
|
|
75
|
+
if (awsError === null || awsError === void 0 ? void 0 : awsError.message) {
|
|
76
|
+
throw new Error(awsError.message);
|
|
77
|
+
}
|
|
78
|
+
throw new Error(String(error));
|
|
79
|
+
};
|
|
80
|
+
const ensureConnected = (clientConfig) => __awaiter(void 0, void 0, void 0, function* () {
|
|
81
|
+
const stsClient = new client_sts_1.STSClient(clientConfig);
|
|
82
|
+
yield stsClient.send(new client_sts_1.GetCallerIdentityCommand({}));
|
|
83
|
+
});
|
|
84
|
+
const createClient = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
85
|
+
const config = (0, aws_config_1.buildAwsClientConfig)(options);
|
|
86
|
+
debug('Creating AWS clients', {
|
|
87
|
+
hasProfile: Boolean(options.profile),
|
|
88
|
+
region: options.region,
|
|
89
|
+
hasEndpoint: Boolean(config.endpoint)
|
|
90
|
+
});
|
|
91
|
+
yield ensureConnected(config);
|
|
92
|
+
return new client_secrets_manager_1.SecretsManagerClient(config);
|
|
93
|
+
});
|
|
94
|
+
const validateSecretName = (name) => {
|
|
95
|
+
if (!SECRET_NAME_PATTERN.test(name)) {
|
|
96
|
+
throw new Error(`Invalid secret name "${name}". Use only letters, numbers, and /_+=.@- characters.`);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
exports.validateSecretName = validateSecretName;
|
|
100
|
+
const createSecret = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
101
|
+
var _a;
|
|
102
|
+
(0, exports.validateSecretName)(options.name);
|
|
103
|
+
debug('createSecret called', {
|
|
104
|
+
name: options.name,
|
|
105
|
+
hasTags: !!((_a = options.tags) === null || _a === void 0 ? void 0 : _a.length)
|
|
106
|
+
});
|
|
107
|
+
const client = yield createClient(options);
|
|
108
|
+
const tags = parseTags(options.tags);
|
|
109
|
+
try {
|
|
110
|
+
const result = yield client.send(new client_secrets_manager_1.CreateSecretCommand({
|
|
111
|
+
Name: options.name,
|
|
112
|
+
Description: options.description,
|
|
113
|
+
SecretString: options.value,
|
|
114
|
+
KmsKeyId: options.kmsKeyId,
|
|
115
|
+
Tags: tags
|
|
116
|
+
}));
|
|
117
|
+
return {
|
|
118
|
+
arn: result.ARN,
|
|
119
|
+
name: result.Name,
|
|
120
|
+
versionId: result.VersionId
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
return mapAwsError(error, options.name);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
exports.createSecret = createSecret;
|
|
128
|
+
const updateSecret = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
129
|
+
(0, exports.validateSecretName)(options.name);
|
|
130
|
+
debug('updateSecret called', { name: options.name });
|
|
131
|
+
const client = yield createClient(options);
|
|
132
|
+
try {
|
|
133
|
+
const result = yield client.send(new client_secrets_manager_1.UpdateSecretCommand({
|
|
134
|
+
SecretId: options.name,
|
|
135
|
+
Description: options.description,
|
|
136
|
+
SecretString: options.value,
|
|
137
|
+
KmsKeyId: options.kmsKeyId
|
|
138
|
+
}));
|
|
139
|
+
return {
|
|
140
|
+
arn: result.ARN,
|
|
141
|
+
name: result.Name,
|
|
142
|
+
versionId: result.VersionId
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
return mapAwsError(error, options.name);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
exports.updateSecret = updateSecret;
|
|
150
|
+
const listSecrets = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
151
|
+
var _b;
|
|
152
|
+
debug('listSecrets called', {
|
|
153
|
+
prefix: options.prefix,
|
|
154
|
+
hasTags: !!((_b = options.tags) === null || _b === void 0 ? void 0 : _b.length)
|
|
155
|
+
});
|
|
156
|
+
const client = yield createClient(options);
|
|
157
|
+
const requiredTags = parseTags(options.tags);
|
|
158
|
+
const secrets = [];
|
|
159
|
+
try {
|
|
160
|
+
let nextToken;
|
|
161
|
+
do {
|
|
162
|
+
const result = yield client.send(new client_secrets_manager_1.ListSecretsCommand({ NextToken: nextToken }));
|
|
163
|
+
for (const secret of result.SecretList || []) {
|
|
164
|
+
if (options.prefix &&
|
|
165
|
+
secret.Name &&
|
|
166
|
+
!secret.Name.startsWith(options.prefix)) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (requiredTags && requiredTags.length > 0) {
|
|
170
|
+
const available = tagsToRecord(secret.Tags);
|
|
171
|
+
const matchesAll = requiredTags.every((tag) => tag.Key && tag.Value && (available === null || available === void 0 ? void 0 : available[tag.Key]) === tag.Value);
|
|
172
|
+
if (!matchesAll) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
secrets.push({
|
|
177
|
+
name: secret.Name || '',
|
|
178
|
+
arn: secret.ARN,
|
|
179
|
+
description: secret.Description,
|
|
180
|
+
lastChangedDate: formatDate(secret.LastChangedDate)
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
nextToken = result.NextToken;
|
|
184
|
+
} while (nextToken);
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
return mapAwsError(error);
|
|
188
|
+
}
|
|
189
|
+
return secrets;
|
|
190
|
+
});
|
|
191
|
+
exports.listSecrets = listSecrets;
|
|
192
|
+
const getSecretMetadata = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
193
|
+
(0, exports.validateSecretName)(options.name);
|
|
194
|
+
debug('getSecretMetadata called', { name: options.name });
|
|
195
|
+
const client = yield createClient(options);
|
|
196
|
+
try {
|
|
197
|
+
const result = yield client.send(new client_secrets_manager_1.DescribeSecretCommand({ SecretId: options.name }));
|
|
198
|
+
return {
|
|
199
|
+
name: result.Name,
|
|
200
|
+
arn: result.ARN,
|
|
201
|
+
description: result.Description,
|
|
202
|
+
kmsKeyId: result.KmsKeyId,
|
|
203
|
+
deletedDate: formatDate(result.DeletedDate),
|
|
204
|
+
lastChangedDate: formatDate(result.LastChangedDate),
|
|
205
|
+
lastAccessedDate: formatDate(result.LastAccessedDate),
|
|
206
|
+
createdDate: formatDate(result.CreatedDate),
|
|
207
|
+
versionIdsToStages: result.VersionIdsToStages,
|
|
208
|
+
tags: tagsToRecord(result.Tags)
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
return mapAwsError(error, options.name);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
exports.getSecretMetadata = getSecretMetadata;
|
|
216
|
+
const deleteSecret = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
217
|
+
(0, exports.validateSecretName)(options.name);
|
|
218
|
+
debug('deleteSecret called', {
|
|
219
|
+
name: options.name,
|
|
220
|
+
recoveryDays: options.recoveryDays,
|
|
221
|
+
forceDeleteWithoutRecovery: options.forceDeleteWithoutRecovery
|
|
222
|
+
});
|
|
223
|
+
const client = yield createClient(options);
|
|
224
|
+
try {
|
|
225
|
+
const result = yield client.send(new client_secrets_manager_1.DeleteSecretCommand({
|
|
226
|
+
SecretId: options.name,
|
|
227
|
+
RecoveryWindowInDays: options.recoveryDays,
|
|
228
|
+
ForceDeleteWithoutRecovery: options.forceDeleteWithoutRecovery
|
|
229
|
+
}));
|
|
230
|
+
return {
|
|
231
|
+
arn: result.ARN,
|
|
232
|
+
name: result.Name,
|
|
233
|
+
deletedDate: formatDate(result.DeletionDate)
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
return mapAwsError(error, options.name);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
exports.deleteSecret = deleteSecret;
|
|
@@ -15,11 +15,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
exports.secretsmanager = void 0;
|
|
16
16
|
const client_secrets_manager_1 = require("@aws-sdk/client-secrets-manager");
|
|
17
17
|
const client_sts_1 = require("@aws-sdk/client-sts");
|
|
18
|
-
const credential_providers_1 = require("@aws-sdk/credential-providers");
|
|
19
18
|
const debug_1 = __importDefault(require("debug"));
|
|
19
|
+
const aws_config_1 = require("./aws-config");
|
|
20
20
|
const debug = (0, debug_1.default)('env-secrets:secretsmanager');
|
|
21
|
-
const
|
|
22
|
-
|
|
21
|
+
const isCredentialsError = (error) => {
|
|
22
|
+
if (!error || typeof error !== 'object') {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const errorName = 'name' in error ? error.name : undefined;
|
|
26
|
+
return (errorName === 'CredentialsError' || errorName === 'CredentialsProviderError');
|
|
27
|
+
};
|
|
28
|
+
const checkConnection = (config) => __awaiter(void 0, void 0, void 0, function* () {
|
|
29
|
+
const stsClient = new client_sts_1.STSClient(config);
|
|
23
30
|
const command = new client_sts_1.GetCallerIdentityCommand({});
|
|
24
31
|
try {
|
|
25
32
|
const data = yield stsClient.send(command);
|
|
@@ -27,6 +34,11 @@ const checkConnection = (region) => __awaiter(void 0, void 0, void 0, function*
|
|
|
27
34
|
return true;
|
|
28
35
|
}
|
|
29
36
|
catch (err) {
|
|
37
|
+
if (isCredentialsError(err) && err.message) {
|
|
38
|
+
// eslint-disable-next-line no-console
|
|
39
|
+
console.error(err.message);
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
30
42
|
// eslint-disable-next-line no-console
|
|
31
43
|
console.error(err);
|
|
32
44
|
return false;
|
|
@@ -34,28 +46,20 @@ const checkConnection = (region) => __awaiter(void 0, void 0, void 0, function*
|
|
|
34
46
|
});
|
|
35
47
|
const secretsmanager = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
36
48
|
const { secret, profile, region } = options;
|
|
37
|
-
const
|
|
38
|
-
let credentials;
|
|
49
|
+
const config = (0, aws_config_1.buildAwsClientConfig)({ profile, region });
|
|
39
50
|
if (profile) {
|
|
40
51
|
debug(`Using profile: ${profile}`);
|
|
41
|
-
credentials = (0, credential_providers_1.fromIni)({ profile });
|
|
42
52
|
}
|
|
43
|
-
else if (
|
|
44
|
-
debug('Using
|
|
45
|
-
credentials = undefined; // Will use environment variables automatically
|
|
53
|
+
else if (config.credentials) {
|
|
54
|
+
debug('Using profile: default');
|
|
46
55
|
}
|
|
47
56
|
else {
|
|
48
|
-
debug('Using
|
|
49
|
-
credentials = (0, credential_providers_1.fromIni)({ profile: 'default' });
|
|
57
|
+
debug('Using environment variables');
|
|
50
58
|
}
|
|
51
|
-
const config = {
|
|
52
|
-
region,
|
|
53
|
-
credentials
|
|
54
|
-
};
|
|
55
59
|
if (!config.region) {
|
|
56
60
|
debug('no region set');
|
|
57
61
|
}
|
|
58
|
-
const connected = yield checkConnection(
|
|
62
|
+
const connected = yield checkConnection(config);
|
|
59
63
|
if (connected) {
|
|
60
64
|
const client = new client_secrets_manager_1.SecretsManagerClient(config);
|
|
61
65
|
try {
|