env-secrets 0.3.2 → 0.3.3
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 +1 -1
- package/README.md +1 -1
- package/__tests__/vaults/secretsmanager.test.ts +57 -20
- package/dist/vaults/secretsmanager.js +15 -3
- package/docs/AWS.md +1 -1
- package/eslint.config.js +67 -0
- package/package.json +23 -13
- package/src/vaults/secretsmanager.ts +28 -3
- package/website/docs/examples.mdx +1 -1
- package/website/docs/installation.mdx +1 -1
- package/.eslintignore +0 -4
- package/.eslintrc +0 -18
- package/.lintstagedrc +0 -4
|
@@ -1,13 +1,34 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
GetSecretValueCommand
|
|
4
|
-
} from '@aws-sdk/client-secrets-manager';
|
|
5
|
-
import { STSClient, GetCallerIdentityCommand } from '@aws-sdk/client-sts';
|
|
1
|
+
import { SecretsManagerClient } from '@aws-sdk/client-secrets-manager';
|
|
2
|
+
import { STSClient } from '@aws-sdk/client-sts';
|
|
6
3
|
import { fromIni } from '@aws-sdk/credential-providers';
|
|
7
4
|
|
|
5
|
+
// Mock send functions - must be declared before jest.mock calls
|
|
6
|
+
const mockSecretsManagerSend = jest.fn();
|
|
7
|
+
const mockSTSSend = jest.fn();
|
|
8
|
+
|
|
8
9
|
// Mock the AWS SDK and dependencies
|
|
9
|
-
jest.mock('@aws-sdk/client-secrets-manager')
|
|
10
|
-
|
|
10
|
+
jest.mock('@aws-sdk/client-secrets-manager', () => {
|
|
11
|
+
return {
|
|
12
|
+
SecretsManagerClient: jest.fn().mockImplementation(() => ({
|
|
13
|
+
send: mockSecretsManagerSend
|
|
14
|
+
})),
|
|
15
|
+
GetSecretValueCommand: jest.fn().mockImplementation((input) => ({
|
|
16
|
+
input
|
|
17
|
+
}))
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
jest.mock('@aws-sdk/client-sts', () => {
|
|
22
|
+
return {
|
|
23
|
+
STSClient: jest.fn().mockImplementation(() => ({
|
|
24
|
+
send: mockSTSSend
|
|
25
|
+
})),
|
|
26
|
+
GetCallerIdentityCommand: jest.fn().mockImplementation((input) => ({
|
|
27
|
+
input
|
|
28
|
+
}))
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
|
|
11
32
|
jest.mock('@aws-sdk/credential-providers');
|
|
12
33
|
jest.mock('debug', () => {
|
|
13
34
|
const mockDebug = jest.fn();
|
|
@@ -27,8 +48,6 @@ const mockFromIni = fromIni as jest.MockedFunction<typeof fromIni>;
|
|
|
27
48
|
|
|
28
49
|
describe('secretsmanager', () => {
|
|
29
50
|
let originalEnv: NodeJS.ProcessEnv;
|
|
30
|
-
let mockSecretsManagerSend: jest.MockedFunction<any>;
|
|
31
|
-
let mockSTSSend: jest.MockedFunction<any>;
|
|
32
51
|
|
|
33
52
|
beforeEach(() => {
|
|
34
53
|
// Clear all mocks
|
|
@@ -37,13 +56,6 @@ describe('secretsmanager', () => {
|
|
|
37
56
|
// Reset process.env
|
|
38
57
|
originalEnv = { ...process.env };
|
|
39
58
|
process.env = { ...originalEnv };
|
|
40
|
-
|
|
41
|
-
// Setup AWS client mocks
|
|
42
|
-
mockSecretsManagerSend = jest.fn();
|
|
43
|
-
mockSTSSend = jest.fn();
|
|
44
|
-
|
|
45
|
-
mockSecretsManagerClient.prototype.send = mockSecretsManagerSend;
|
|
46
|
-
mockSTSClient.prototype.send = mockSTSSend;
|
|
47
59
|
});
|
|
48
60
|
|
|
49
61
|
afterEach(() => {
|
|
@@ -72,10 +84,10 @@ describe('secretsmanager', () => {
|
|
|
72
84
|
});
|
|
73
85
|
|
|
74
86
|
expect(mockSTSSend).toHaveBeenCalledWith(
|
|
75
|
-
expect.
|
|
87
|
+
expect.objectContaining({ input: {} })
|
|
76
88
|
);
|
|
77
89
|
expect(mockSecretsManagerSend).toHaveBeenCalledWith(
|
|
78
|
-
expect.
|
|
90
|
+
expect.objectContaining({ input: expect.anything() })
|
|
79
91
|
);
|
|
80
92
|
expect(result).toEqual({
|
|
81
93
|
API_KEY: 'test-key',
|
|
@@ -93,11 +105,33 @@ describe('secretsmanager', () => {
|
|
|
93
105
|
});
|
|
94
106
|
|
|
95
107
|
expect(mockSTSSend).toHaveBeenCalledWith(
|
|
96
|
-
expect.
|
|
108
|
+
expect.objectContaining({ input: {} })
|
|
97
109
|
);
|
|
98
110
|
expect(mockSecretsManagerSend).not.toHaveBeenCalled();
|
|
99
111
|
expect(result).toEqual({});
|
|
100
112
|
});
|
|
113
|
+
|
|
114
|
+
it('should surface a clean error when profile credentials cannot be loaded', async () => {
|
|
115
|
+
const consoleErrorSpy = jest
|
|
116
|
+
.spyOn(console, 'error')
|
|
117
|
+
.mockImplementation(() => undefined);
|
|
118
|
+
mockSTSSend.mockRejectedValueOnce({
|
|
119
|
+
name: 'CredentialsProviderError',
|
|
120
|
+
message: 'Could not load credentials from any providers'
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const result = await secretsmanager({
|
|
124
|
+
secret: 'my-secret',
|
|
125
|
+
profile: 'missing-profile',
|
|
126
|
+
region: 'us-east-1'
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
130
|
+
'Could not load credentials from any providers'
|
|
131
|
+
);
|
|
132
|
+
expect(result).toEqual({});
|
|
133
|
+
expect(mockSecretsManagerSend).not.toHaveBeenCalled();
|
|
134
|
+
});
|
|
101
135
|
});
|
|
102
136
|
|
|
103
137
|
describe('credential handling', () => {
|
|
@@ -426,7 +460,10 @@ describe('secretsmanager', () => {
|
|
|
426
460
|
});
|
|
427
461
|
|
|
428
462
|
expect(mockFromIni).toHaveBeenCalledWith({ profile: 'production' });
|
|
429
|
-
expect(mockSTSClient).toHaveBeenCalledWith({
|
|
463
|
+
expect(mockSTSClient).toHaveBeenCalledWith({
|
|
464
|
+
region: 'us-west-2',
|
|
465
|
+
credentials: mockCredentials
|
|
466
|
+
});
|
|
430
467
|
expect(mockSecretsManagerClient).toHaveBeenCalledWith({
|
|
431
468
|
region: 'us-west-2',
|
|
432
469
|
credentials: mockCredentials
|
|
@@ -18,8 +18,15 @@ const client_sts_1 = require("@aws-sdk/client-sts");
|
|
|
18
18
|
const credential_providers_1 = require("@aws-sdk/credential-providers");
|
|
19
19
|
const debug_1 = __importDefault(require("debug"));
|
|
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 = (region, credentials) => __awaiter(void 0, void 0, void 0, function* () {
|
|
29
|
+
const stsClient = new client_sts_1.STSClient({ region, credentials });
|
|
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;
|
|
@@ -55,7 +67,7 @@ const secretsmanager = (options) => __awaiter(void 0, void 0, void 0, function*
|
|
|
55
67
|
if (!config.region) {
|
|
56
68
|
debug('no region set');
|
|
57
69
|
}
|
|
58
|
-
const connected = yield checkConnection(region);
|
|
70
|
+
const connected = yield checkConnection(region, credentials);
|
|
59
71
|
if (connected) {
|
|
60
72
|
const client = new client_secrets_manager_1.SecretsManagerClient(config);
|
|
61
73
|
try {
|
package/docs/AWS.md
CHANGED
|
@@ -10,7 +10,7 @@ The `env-secrets` tool supports AWS Secrets Manager as a secret vault. It can re
|
|
|
10
10
|
|
|
11
11
|
- [AWS CLI](https://docs.aws.amazon.com/cli/index.html) installed and configured
|
|
12
12
|
- AWS credentials with appropriate permissions to access Secrets Manager
|
|
13
|
-
- Node.js
|
|
13
|
+
- Node.js 20.0.0 or higher
|
|
14
14
|
|
|
15
15
|
## Authentication Methods
|
|
16
16
|
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const globals = require('globals');
|
|
2
|
+
const js = require('@eslint/js');
|
|
3
|
+
const tsParser = require('@typescript-eslint/parser');
|
|
4
|
+
const tsPlugin = require('@typescript-eslint/eslint-plugin');
|
|
5
|
+
const prettierPlugin = require('eslint-plugin-prettier');
|
|
6
|
+
const prettierConfig = require('eslint-config-prettier');
|
|
7
|
+
|
|
8
|
+
const tsEslintRecommendedRules =
|
|
9
|
+
tsPlugin.configs['eslint-recommended']?.overrides?.[0]?.rules ?? {};
|
|
10
|
+
|
|
11
|
+
module.exports = [
|
|
12
|
+
{
|
|
13
|
+
ignores: [
|
|
14
|
+
'node_modules/**',
|
|
15
|
+
'dist/**',
|
|
16
|
+
'website/build/**',
|
|
17
|
+
'website/node_modules/**',
|
|
18
|
+
'website/.docusaurus/**',
|
|
19
|
+
'coverage/**',
|
|
20
|
+
'coverage-e2e/**'
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
...js.configs.recommended,
|
|
25
|
+
files: ['**/*.{js,mjs,cjs}'],
|
|
26
|
+
languageOptions: {
|
|
27
|
+
...(js.configs.recommended.languageOptions ?? {}),
|
|
28
|
+
globals: globals.node
|
|
29
|
+
},
|
|
30
|
+
rules: {
|
|
31
|
+
...(js.configs.recommended.rules ?? {}),
|
|
32
|
+
'no-console': 'warn'
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
files: ['**/*.ts'],
|
|
37
|
+
languageOptions: {
|
|
38
|
+
parser: tsParser,
|
|
39
|
+
parserOptions: {
|
|
40
|
+
ecmaVersion: 'latest',
|
|
41
|
+
sourceType: 'module'
|
|
42
|
+
},
|
|
43
|
+
globals: {
|
|
44
|
+
...globals.node,
|
|
45
|
+
...globals.jest
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
plugins: {
|
|
49
|
+
'@typescript-eslint': tsPlugin
|
|
50
|
+
},
|
|
51
|
+
rules: {
|
|
52
|
+
...(js.configs.recommended.rules ?? {}),
|
|
53
|
+
...tsEslintRecommendedRules,
|
|
54
|
+
...(tsPlugin.configs.recommended.rules ?? {}),
|
|
55
|
+
'no-console': 'warn'
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
plugins: {
|
|
60
|
+
prettier: prettierPlugin
|
|
61
|
+
},
|
|
62
|
+
rules: {
|
|
63
|
+
'prettier/prettier': 'error'
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
prettierConfig
|
|
67
|
+
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "env-secrets",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
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)",
|
|
@@ -16,10 +16,12 @@
|
|
|
16
16
|
"build": "rimraf ./dist && tsc -b src",
|
|
17
17
|
"start": "node dist/index.js",
|
|
18
18
|
"postbuild": "chmod 755 ./dist/index.js",
|
|
19
|
-
"lint": "eslint .
|
|
19
|
+
"lint": "eslint .",
|
|
20
|
+
"lint:fix": "eslint . --fix",
|
|
20
21
|
"release": "release-it",
|
|
21
|
-
"prettier
|
|
22
|
-
"prettier:
|
|
22
|
+
"prettier": "prettier . --check",
|
|
23
|
+
"prettier:fix": "prettier . --write",
|
|
24
|
+
"prettier:check": "prettier . --check",
|
|
23
25
|
"test": "npm run test:unit && npm run test:e2e",
|
|
24
26
|
"test:unit": "jest __tests__",
|
|
25
27
|
"test:unit:coverage": "jest __tests__ --coverage",
|
|
@@ -27,6 +29,7 @@
|
|
|
27
29
|
"test:e2e:debug": "npm run build && DEBUG=true jest --config jest.e2e.config.js"
|
|
28
30
|
},
|
|
29
31
|
"devDependencies": {
|
|
32
|
+
"@everydaydevopsio/ballast": "^3.2.1",
|
|
30
33
|
"@types/debug": "^4.1.12",
|
|
31
34
|
"@types/jest": "^29.5.14",
|
|
32
35
|
"@types/node": "^18.19.130",
|
|
@@ -36,30 +39,37 @@
|
|
|
36
39
|
"eslint": "^8.57.1",
|
|
37
40
|
"eslint-config-prettier": "^8.10.2",
|
|
38
41
|
"eslint-plugin-prettier": "^4.2.5",
|
|
42
|
+
"globals": "^16.0.0",
|
|
39
43
|
"husky": "^8.0.3",
|
|
40
44
|
"jest": "^29.7.0",
|
|
41
45
|
"lint-staged": "13.3.0",
|
|
42
46
|
"prettier": "^2.8.8",
|
|
43
47
|
"release-it": "^15.11.0",
|
|
44
48
|
"rimraf": "^3.0.2",
|
|
49
|
+
"tsc-files": "^1.1.4",
|
|
45
50
|
"ts-jest": "^29.4.6",
|
|
46
51
|
"ts-node": "^10.9.2",
|
|
47
52
|
"typescript": "^4.9.5"
|
|
48
53
|
},
|
|
49
54
|
"dependencies": {
|
|
50
|
-
"@aws-sdk/client-secrets-manager": "^3.
|
|
51
|
-
"@aws-sdk/client-sts": "^3.
|
|
52
|
-
"@aws-sdk/credential-providers": "^3.
|
|
55
|
+
"@aws-sdk/client-secrets-manager": "^3.990.0",
|
|
56
|
+
"@aws-sdk/client-sts": "^3.990.0",
|
|
57
|
+
"@aws-sdk/credential-providers": "^3.990.0",
|
|
53
58
|
"commander": "^9.5.0",
|
|
54
59
|
"debug": "^4.4.3"
|
|
55
60
|
},
|
|
56
61
|
"lint-staged": {
|
|
57
|
-
"
|
|
58
|
-
"prettier --write
|
|
59
|
-
"eslint --fix
|
|
62
|
+
"**/*.js": [
|
|
63
|
+
"prettier --write",
|
|
64
|
+
"eslint --fix"
|
|
60
65
|
],
|
|
61
|
-
"
|
|
62
|
-
"
|
|
66
|
+
"**/*.ts": [
|
|
67
|
+
"tsc-files --noEmit --project src/tsconfig.json",
|
|
68
|
+
"prettier --write",
|
|
69
|
+
"eslint --fix"
|
|
70
|
+
],
|
|
71
|
+
"**/*.{json,md,mdx,yaml,yml}": [
|
|
72
|
+
"prettier --write"
|
|
63
73
|
]
|
|
64
74
|
},
|
|
65
75
|
"publishConfig": {
|
|
@@ -70,6 +80,6 @@
|
|
|
70
80
|
"env-secrets": "dist/index.js"
|
|
71
81
|
},
|
|
72
82
|
"engines": {
|
|
73
|
-
"node": ">=
|
|
83
|
+
"node": ">=20.0.0"
|
|
74
84
|
}
|
|
75
85
|
}
|
|
@@ -14,8 +14,27 @@ 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
|
+
region?: string,
|
|
35
|
+
credentials?: ReturnType<typeof fromIni>
|
|
36
|
+
) => {
|
|
37
|
+
const stsClient = new STSClient({ region, credentials });
|
|
19
38
|
const command = new GetCallerIdentityCommand({});
|
|
20
39
|
|
|
21
40
|
try {
|
|
@@ -23,6 +42,12 @@ const checkConnection = async (region?: string) => {
|
|
|
23
42
|
debug(data);
|
|
24
43
|
return true;
|
|
25
44
|
} catch (err) {
|
|
45
|
+
if (isCredentialsError(err) && err.message) {
|
|
46
|
+
// eslint-disable-next-line no-console
|
|
47
|
+
console.error(err.message);
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
26
51
|
// eslint-disable-next-line no-console
|
|
27
52
|
console.error(err);
|
|
28
53
|
return false;
|
|
@@ -57,7 +82,7 @@ export const secretsmanager = async (options: secretsmanagerType) => {
|
|
|
57
82
|
debug('no region set');
|
|
58
83
|
}
|
|
59
84
|
|
|
60
|
-
const connected = await checkConnection(region);
|
|
85
|
+
const connected = await checkConnection(region, credentials);
|
|
61
86
|
|
|
62
87
|
if (connected) {
|
|
63
88
|
const client = new SecretsManagerClient(config);
|
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