env-secrets 0.5.0 → 0.5.2
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/README.md +7 -1
- 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 +316 -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__/index.test.ts +5 -3
- package/__tests__/vaults/secretsmanager.test.ts +47 -0
- package/dist/index.js +39 -3
- package/dist/vaults/secretsmanager.js +47 -8
- package/docs/AWS.md +7 -0
- package/package.json +5 -4
- package/src/index.ts +50 -3
- package/src/vaults/secretsmanager.ts +65 -7
- package/__e2e__/index.test.ts +0 -678
package/src/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from './vaults/secretsmanager-admin';
|
|
19
19
|
import {
|
|
20
20
|
asOutputFormat,
|
|
21
|
+
parseEnvSecrets,
|
|
21
22
|
parseEnvSecretsFile,
|
|
22
23
|
printData,
|
|
23
24
|
parseRecoveryDays,
|
|
@@ -58,6 +59,48 @@ const parseSecretJsonObject = (
|
|
|
58
59
|
return parsed as Record<string, unknown>;
|
|
59
60
|
};
|
|
60
61
|
|
|
62
|
+
const parseEnvToObject = (
|
|
63
|
+
value: string
|
|
64
|
+
): Record<string, unknown> | undefined => {
|
|
65
|
+
try {
|
|
66
|
+
const parsed = parseEnvSecrets(value);
|
|
67
|
+
if (parsed.entries.length === 0) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return Object.fromEntries(
|
|
72
|
+
parsed.entries.map((entry) => [entry.key, entry.value])
|
|
73
|
+
);
|
|
74
|
+
} catch {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const toSecretJsonObject = (value: string): Record<string, unknown> => {
|
|
80
|
+
try {
|
|
81
|
+
const parsed = JSON.parse(value) as unknown;
|
|
82
|
+
if (parsed && !Array.isArray(parsed) && typeof parsed === 'object') {
|
|
83
|
+
return parsed as Record<string, unknown>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (typeof parsed === 'string') {
|
|
87
|
+
const envPayload = parseEnvToObject(parsed);
|
|
88
|
+
if (envPayload) {
|
|
89
|
+
return envPayload;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { value: parsed };
|
|
94
|
+
} catch {
|
|
95
|
+
const envPayload = parseEnvToObject(value);
|
|
96
|
+
if (envPayload) {
|
|
97
|
+
return envPayload;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { value };
|
|
102
|
+
};
|
|
103
|
+
|
|
61
104
|
// main program
|
|
62
105
|
program
|
|
63
106
|
.name('env-secrets')
|
|
@@ -87,6 +130,9 @@ const awsCommand = program
|
|
|
87
130
|
|
|
88
131
|
const secrets = await secretsmanager(options);
|
|
89
132
|
debug(secrets);
|
|
133
|
+
const envSecrets = Object.fromEntries(
|
|
134
|
+
Object.entries(secrets).map(([key, value]) => [key, String(value)])
|
|
135
|
+
);
|
|
90
136
|
|
|
91
137
|
if (options.output) {
|
|
92
138
|
// Check if file already exists
|
|
@@ -99,13 +145,13 @@ const awsCommand = program
|
|
|
99
145
|
}
|
|
100
146
|
|
|
101
147
|
// Write secrets to file with 0400 permissions
|
|
102
|
-
const envContent = objectToExport(
|
|
148
|
+
const envContent = objectToExport(envSecrets);
|
|
103
149
|
writeFileSync(options.output, envContent, { mode: 0o400 });
|
|
104
150
|
// eslint-disable-next-line no-console
|
|
105
151
|
console.log(`Secrets written to ${options.output}`);
|
|
106
152
|
} else {
|
|
107
153
|
// Original behavior: merge secrets into environment and run program
|
|
108
|
-
const env = Object.assign({}, process.env,
|
|
154
|
+
const env = Object.assign({}, process.env, envSecrets);
|
|
109
155
|
debug(env);
|
|
110
156
|
if (program && program.length > 0) {
|
|
111
157
|
debug(`${program[0]} ${program.slice(1)}`);
|
|
@@ -163,9 +209,10 @@ secretCommand
|
|
|
163
209
|
);
|
|
164
210
|
}
|
|
165
211
|
|
|
212
|
+
const payload = toSecretJsonObject(value);
|
|
166
213
|
const result = await createSecret({
|
|
167
214
|
name: options.name,
|
|
168
|
-
value,
|
|
215
|
+
value: JSON.stringify(payload),
|
|
169
216
|
description: options.description,
|
|
170
217
|
kmsKeyId: options.kmsKeyId,
|
|
171
218
|
tags: options.tag,
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
import { STSClient, GetCallerIdentityCommand } from '@aws-sdk/client-sts';
|
|
6
6
|
import Debug from 'debug';
|
|
7
7
|
import { buildAwsClientConfig } from './aws-config';
|
|
8
|
+
import { parseEnvSecrets } from '../cli/helpers';
|
|
8
9
|
|
|
9
10
|
const debug = Debug('env-secrets:secretsmanager');
|
|
10
11
|
|
|
@@ -19,6 +20,68 @@ interface AWSLikeError {
|
|
|
19
20
|
message?: string;
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
type SecretValue = string | number | boolean;
|
|
24
|
+
|
|
25
|
+
const isSecretValue = (value: unknown): value is SecretValue => {
|
|
26
|
+
return (
|
|
27
|
+
typeof value === 'string' ||
|
|
28
|
+
typeof value === 'number' ||
|
|
29
|
+
typeof value === 'boolean'
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const asSecretRecord = (value: unknown): Record<string, SecretValue> => {
|
|
34
|
+
if (!value || Array.isArray(value) || typeof value !== 'object') {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return Object.entries(value).reduce<Record<string, SecretValue>>(
|
|
39
|
+
(result, [key, entryValue]) => {
|
|
40
|
+
if (isSecretValue(entryValue)) {
|
|
41
|
+
result[key] = entryValue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return result;
|
|
45
|
+
},
|
|
46
|
+
{}
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const parseSecretString = (
|
|
51
|
+
secretvalue: string
|
|
52
|
+
): Record<string, SecretValue> => {
|
|
53
|
+
const parseAsEnvRecord = (envSource: string): Record<string, SecretValue> => {
|
|
54
|
+
try {
|
|
55
|
+
const parsedEnv = parseEnvSecrets(envSource);
|
|
56
|
+
if (parsedEnv.entries.length === 0) {
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return Object.fromEntries(
|
|
61
|
+
parsedEnv.entries.map((entry) => [entry.key, entry.value])
|
|
62
|
+
);
|
|
63
|
+
} catch {
|
|
64
|
+
return {};
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const parsedJson = JSON.parse(secretvalue);
|
|
70
|
+
const parsedRecord = asSecretRecord(parsedJson);
|
|
71
|
+
if (Object.keys(parsedRecord).length > 0) {
|
|
72
|
+
return parsedRecord;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (typeof parsedJson === 'string') {
|
|
76
|
+
return parseAsEnvRecord(parsedJson);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {};
|
|
80
|
+
} catch {
|
|
81
|
+
return parseAsEnvRecord(secretvalue);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
22
85
|
const isCredentialsError = (error: unknown): error is AWSLikeError => {
|
|
23
86
|
if (!error || typeof error !== 'object') {
|
|
24
87
|
return false;
|
|
@@ -81,13 +144,8 @@ export const secretsmanager = async (options: secretsmanagerType) => {
|
|
|
81
144
|
const response = await client.send(command);
|
|
82
145
|
const secretvalue = response.SecretString;
|
|
83
146
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return JSON.parse(secretvalue);
|
|
87
|
-
}
|
|
88
|
-
} catch (err) {
|
|
89
|
-
// eslint-disable-next-line no-console
|
|
90
|
-
console.error(err);
|
|
147
|
+
if (secretvalue) {
|
|
148
|
+
return parseSecretString(secretvalue);
|
|
91
149
|
}
|
|
92
150
|
} catch (err: unknown) {
|
|
93
151
|
if (err && typeof err === 'object' && 'name' in err) {
|