env-secrets 0.3.3 → 0.5.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/AGENTS.md +12 -3
- package/README.md +52 -8
- package/__e2e__/README.md +2 -5
- package/__e2e__/index.test.ts +341 -2
- package/__e2e__/utils/test-utils.ts +61 -1
- package/__tests__/cli/helpers.test.ts +217 -0
- package/__tests__/vaults/aws-config.test.ts +85 -0
- package/__tests__/vaults/secretsmanager-admin.test.ts +355 -0
- package/dist/cli/helpers.js +172 -0
- package/dist/index.js +456 -2
- package/dist/vaults/aws-config.js +29 -0
- package/dist/vaults/secretsmanager-admin.js +273 -0
- package/dist/vaults/secretsmanager.js +8 -16
- package/docs/AWS.md +129 -2
- package/jest.e2e.config.js +1 -0
- package/package.json +5 -5
- package/src/cli/helpers.ts +239 -0
- package/src/index.ts +595 -2
- package/src/vaults/aws-config.ts +51 -0
- package/src/vaults/secretsmanager-admin.ts +397 -0
- package/src/vaults/secretsmanager.ts +8 -21
- package/website/docs/cli-reference.mdx +101 -0
- package/website/docs/providers/aws-secrets-manager.mdx +59 -0
|
@@ -0,0 +1,273 @@
|
|
|
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.getSecretString = exports.secretExists = 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 secretExists = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
217
|
+
(0, exports.validateSecretName)(options.name);
|
|
218
|
+
debug('secretExists called', { name: options.name });
|
|
219
|
+
const client = yield createClient(options);
|
|
220
|
+
try {
|
|
221
|
+
yield client.send(new client_secrets_manager_1.DescribeSecretCommand({ SecretId: options.name }));
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
const awsError = error;
|
|
226
|
+
if ((awsError === null || awsError === void 0 ? void 0 : awsError.name) === 'ResourceNotFoundException') {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
return mapAwsError(error, options.name);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
exports.secretExists = secretExists;
|
|
233
|
+
const getSecretString = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
234
|
+
(0, exports.validateSecretName)(options.name);
|
|
235
|
+
debug('getSecretString called', { name: options.name });
|
|
236
|
+
const client = yield createClient(options);
|
|
237
|
+
try {
|
|
238
|
+
const result = yield client.send(new client_secrets_manager_1.GetSecretValueCommand({ SecretId: options.name }));
|
|
239
|
+
if (typeof result.SecretString !== 'string') {
|
|
240
|
+
throw new Error(`Secret "${options.name}" is not stored as a string value and cannot be edited with append/remove.`);
|
|
241
|
+
}
|
|
242
|
+
return result.SecretString;
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
return mapAwsError(error, options.name);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
exports.getSecretString = getSecretString;
|
|
249
|
+
const deleteSecret = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
250
|
+
(0, exports.validateSecretName)(options.name);
|
|
251
|
+
debug('deleteSecret called', {
|
|
252
|
+
name: options.name,
|
|
253
|
+
recoveryDays: options.recoveryDays,
|
|
254
|
+
forceDeleteWithoutRecovery: options.forceDeleteWithoutRecovery
|
|
255
|
+
});
|
|
256
|
+
const client = yield createClient(options);
|
|
257
|
+
try {
|
|
258
|
+
const result = yield client.send(new client_secrets_manager_1.DeleteSecretCommand({
|
|
259
|
+
SecretId: options.name,
|
|
260
|
+
RecoveryWindowInDays: options.recoveryDays,
|
|
261
|
+
ForceDeleteWithoutRecovery: options.forceDeleteWithoutRecovery
|
|
262
|
+
}));
|
|
263
|
+
return {
|
|
264
|
+
arn: result.ARN,
|
|
265
|
+
name: result.Name,
|
|
266
|
+
deletedDate: formatDate(result.DeletionDate)
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
return mapAwsError(error, options.name);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
exports.deleteSecret = deleteSecret;
|
|
@@ -15,8 +15,8 @@ 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
21
|
const isCredentialsError = (error) => {
|
|
22
22
|
if (!error || typeof error !== 'object') {
|
|
@@ -25,8 +25,8 @@ const isCredentialsError = (error) => {
|
|
|
25
25
|
const errorName = 'name' in error ? error.name : undefined;
|
|
26
26
|
return (errorName === 'CredentialsError' || errorName === 'CredentialsProviderError');
|
|
27
27
|
};
|
|
28
|
-
const checkConnection = (
|
|
29
|
-
const stsClient = new client_sts_1.STSClient(
|
|
28
|
+
const checkConnection = (config) => __awaiter(void 0, void 0, void 0, function* () {
|
|
29
|
+
const stsClient = new client_sts_1.STSClient(config);
|
|
30
30
|
const command = new client_sts_1.GetCallerIdentityCommand({});
|
|
31
31
|
try {
|
|
32
32
|
const data = yield stsClient.send(command);
|
|
@@ -46,28 +46,20 @@ const checkConnection = (region, credentials) => __awaiter(void 0, void 0, void
|
|
|
46
46
|
});
|
|
47
47
|
const secretsmanager = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
48
48
|
const { secret, profile, region } = options;
|
|
49
|
-
const
|
|
50
|
-
let credentials;
|
|
49
|
+
const config = (0, aws_config_1.buildAwsClientConfig)({ profile, region });
|
|
51
50
|
if (profile) {
|
|
52
51
|
debug(`Using profile: ${profile}`);
|
|
53
|
-
credentials = (0, credential_providers_1.fromIni)({ profile });
|
|
54
52
|
}
|
|
55
|
-
else if (
|
|
56
|
-
debug('Using
|
|
57
|
-
credentials = undefined; // Will use environment variables automatically
|
|
53
|
+
else if (config.credentials) {
|
|
54
|
+
debug('Using profile: default');
|
|
58
55
|
}
|
|
59
56
|
else {
|
|
60
|
-
debug('Using
|
|
61
|
-
credentials = (0, credential_providers_1.fromIni)({ profile: 'default' });
|
|
57
|
+
debug('Using environment variables');
|
|
62
58
|
}
|
|
63
|
-
const config = {
|
|
64
|
-
region,
|
|
65
|
-
credentials
|
|
66
|
-
};
|
|
67
59
|
if (!config.region) {
|
|
68
60
|
debug('no region set');
|
|
69
61
|
}
|
|
70
|
-
const connected = yield checkConnection(
|
|
62
|
+
const connected = yield checkConnection(config);
|
|
71
63
|
if (connected) {
|
|
72
64
|
const client = new client_secrets_manager_1.SecretsManagerClient(config);
|
|
73
65
|
try {
|
package/docs/AWS.md
CHANGED
|
@@ -120,7 +120,7 @@ env-secrets aws -s my-secret-name -r us-east-1 -- node app.js
|
|
|
120
120
|
|
|
121
121
|
## Required Permissions
|
|
122
122
|
|
|
123
|
-
Your AWS credentials must have the following permissions to use
|
|
123
|
+
Your AWS credentials must have the following permissions to use secret injection and secret management commands:
|
|
124
124
|
|
|
125
125
|
```json
|
|
126
126
|
{
|
|
@@ -128,7 +128,14 @@ Your AWS credentials must have the following permissions to use Secrets Manager:
|
|
|
128
128
|
"Statement": [
|
|
129
129
|
{
|
|
130
130
|
"Effect": "Allow",
|
|
131
|
-
"Action": [
|
|
131
|
+
"Action": [
|
|
132
|
+
"secretsmanager:GetSecretValue",
|
|
133
|
+
"secretsmanager:CreateSecret",
|
|
134
|
+
"secretsmanager:UpdateSecret",
|
|
135
|
+
"secretsmanager:ListSecrets",
|
|
136
|
+
"secretsmanager:DescribeSecret",
|
|
137
|
+
"secretsmanager:DeleteSecret"
|
|
138
|
+
],
|
|
132
139
|
"Resource": "arn:aws:secretsmanager:*:*:secret:*"
|
|
133
140
|
},
|
|
134
141
|
{
|
|
@@ -140,6 +147,126 @@ Your AWS credentials must have the following permissions to use Secrets Manager:
|
|
|
140
147
|
}
|
|
141
148
|
```
|
|
142
149
|
|
|
150
|
+
## Secret Management Commands
|
|
151
|
+
|
|
152
|
+
In addition to injecting variables into a process, `env-secrets` can manage AWS secrets directly:
|
|
153
|
+
|
|
154
|
+
- `env-secrets aws secret create`
|
|
155
|
+
- `env-secrets aws secret update`
|
|
156
|
+
- `env-secrets aws secret append`
|
|
157
|
+
- `env-secrets aws secret remove`
|
|
158
|
+
- `env-secrets aws secret upsert` (alias: `import`)
|
|
159
|
+
- `env-secrets aws secret list`
|
|
160
|
+
- `env-secrets aws secret get`
|
|
161
|
+
- `env-secrets aws secret delete`
|
|
162
|
+
|
|
163
|
+
`aws secret` subcommands consistently honor `--region`, `--profile`, and `--output`.
|
|
164
|
+
Use these options directly with each subcommand.
|
|
165
|
+
|
|
166
|
+
### `aws -s` vs `aws secret ...`
|
|
167
|
+
|
|
168
|
+
- `env-secrets aws -s <secret-name> -- <command>`: retrieves a secret value and injects it into the environment for the spawned process (or use `-o <file>` to write exports to a file).
|
|
169
|
+
- `env-secrets aws secret ...`: management commands only (`create`, `update`, `append`, `remove`, `upsert/import`, `list`, `get`, `delete`).
|
|
170
|
+
|
|
171
|
+
Example:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# inject secret values
|
|
175
|
+
env-secrets aws -s my-app/dev/api -r us-east-1 -- node app.js
|
|
176
|
+
|
|
177
|
+
# manage secrets
|
|
178
|
+
env-secrets aws secret get -n my-app/dev/api -r us-east-1 --output json
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Load secrets into your current shell
|
|
182
|
+
|
|
183
|
+
`env-secrets aws -s ... -- <command>` injects variables into the spawned child process only.
|
|
184
|
+
If you want variables in your current shell session, write exports to a file and source it:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
env-secrets aws -s my-app/dev/api -r us-east-1 -o secrets.env
|
|
188
|
+
source secrets.env
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Secret Management Examples
|
|
192
|
+
|
|
193
|
+
1. **Create a secret with inline value:**
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
env-secrets aws secret create \
|
|
197
|
+
-n my-app/dev/api \
|
|
198
|
+
-v '{"API_KEY":"abc123"}' \
|
|
199
|
+
-r us-east-1 \
|
|
200
|
+
--output json
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
2. **Create from stdin (recommended for sensitive values):**
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
echo -n 'super-secret-value' | env-secrets aws secret create -n my-app/dev/raw --value-stdin -r us-east-1
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
3. **Update an existing secret value:**
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
env-secrets aws secret update -n my-app/dev/api -v '{"API_KEY":"rotated"}' -r us-east-1
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
4. **Upsert from an env file into one JSON secret (`export KEY=value` or `KEY=value`):**
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
env-secrets aws secret upsert --file .env --name my-app/dev -r us-east-1 --output json
|
|
219
|
+
# alias:
|
|
220
|
+
env-secrets aws secret import --file .env --name my-app/dev -r us-east-1 --output json
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
This creates/updates one secret named `my-app/dev` with a JSON payload like:
|
|
224
|
+
|
|
225
|
+
```json
|
|
226
|
+
{ "API_KEY": "abc123", "DATABASE_URL": "postgres://..." }
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
5. **Append/remove keys in an existing JSON secret:**
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
env-secrets aws secret append -n my-app/dev --key JIRA_EMAIL_TOKEN -v blah -r us-east-1
|
|
233
|
+
env-secrets aws secret remove -n my-app/dev --key OLD_TOKEN -r us-east-1
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
6. **List secrets by prefix:**
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
env-secrets aws secret list --prefix my-app/dev -r us-east-1 --output table
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Multi-region validation example:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
env-secrets aws secret list --prefix my-app/dev -r us-west-2 --output json
|
|
246
|
+
env-secrets aws secret list --prefix my-app/dev -r us-east-1 --output json
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
7. **Get metadata and version info (without printing secret value):**
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
env-secrets aws secret get -n my-app/dev/api -r us-east-1 --output json
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
8. **Delete with explicit confirmation:**
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
env-secrets aws secret delete -n my-app/dev/raw --recovery-days 7 --yes -r us-east-1
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Secret Management Safety Notes
|
|
262
|
+
|
|
263
|
+
- `delete` requires `--yes`.
|
|
264
|
+
- `create`/`update` accept `--value`, `--value-stdin`, or `--file` (use only one).
|
|
265
|
+
- `append` and `remove` require the secret value to be a JSON object.
|
|
266
|
+
- `upsert/import --file --name` parses `export KEY=value` and `KEY=value`, stores them as one JSON secret object, ignores blank lines/comments, and reports `created`, `updated`, `skipped`, and `failed`.
|
|
267
|
+
- Use `--value-stdin` to avoid shell history leakage for sensitive values.
|
|
268
|
+
- Use either `--recovery-days` or `--force-delete-without-recovery` for delete operations.
|
|
269
|
+
|
|
143
270
|
## Examples
|
|
144
271
|
|
|
145
272
|
### Basic Usage
|
package/jest.e2e.config.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "env-secrets",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "get secrets from a secrets vault and inject them into the running environment",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": "Mark C Allen (@markcallen)",
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
"typescript": "^4.9.5"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@aws-sdk/client-secrets-manager": "^3.
|
|
56
|
-
"@aws-sdk/client-sts": "^3.
|
|
57
|
-
"@aws-sdk/credential-providers": "^3.
|
|
55
|
+
"@aws-sdk/client-secrets-manager": "^3.996.0",
|
|
56
|
+
"@aws-sdk/client-sts": "^3.996.0",
|
|
57
|
+
"@aws-sdk/credential-providers": "^3.996.0",
|
|
58
58
|
"commander": "^9.5.0",
|
|
59
59
|
"debug": "^4.4.3"
|
|
60
60
|
},
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"eslint --fix"
|
|
65
65
|
],
|
|
66
66
|
"**/*.ts": [
|
|
67
|
-
"
|
|
67
|
+
"bash -lc 'tsc --noEmit --project src/tsconfig.json'",
|
|
68
68
|
"prettier --write",
|
|
69
69
|
"eslint --fix"
|
|
70
70
|
],
|