env-secrets 0.2.0 → 0.3.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.
Files changed (60) hide show
  1. package/.devcontainer/devcontainer.json +10 -6
  2. package/.dockerignore +9 -0
  3. package/.eslintignore +4 -2
  4. package/.github/dependabot.yml +4 -0
  5. package/.github/workflows/build-main.yml +6 -2
  6. package/.github/workflows/deploy-docs.yml +50 -0
  7. package/.github/workflows/e2e-tests.yaml +54 -0
  8. package/.github/workflows/lint.yaml +6 -2
  9. package/.github/workflows/release.yml +2 -2
  10. package/.github/workflows/snyk.yaml +5 -1
  11. package/.github/workflows/unittests.yaml +9 -66
  12. package/.lintstagedrc +2 -7
  13. package/.prettierignore +6 -0
  14. package/AGENTS.md +149 -0
  15. package/Dockerfile +14 -0
  16. package/README.md +331 -13
  17. package/__e2e__/README.md +160 -0
  18. package/__e2e__/index.test.ts +334 -32
  19. package/__e2e__/setup.ts +58 -0
  20. package/__e2e__/utils/debug-logger.ts +45 -0
  21. package/__e2e__/utils/test-utils.ts +645 -0
  22. package/__tests__/index.test.ts +266 -9
  23. package/__tests__/vaults/secretsmanager.test.ts +460 -0
  24. package/__tests__/vaults/utils.test.ts +9 -9
  25. package/dist/index.js +36 -10
  26. package/dist/vaults/secretsmanager.js +17 -5
  27. package/dist/vaults/utils.js +2 -2
  28. package/docker-compose.yaml +29 -0
  29. package/docs/AWS.md +257 -0
  30. package/jest.config.js +3 -1
  31. package/jest.e2e.config.js +8 -0
  32. package/package.json +10 -7
  33. package/src/index.ts +44 -10
  34. package/src/vaults/secretsmanager.ts +16 -5
  35. package/src/vaults/utils.ts +6 -4
  36. package/website/docs/advanced-usage.mdx +399 -0
  37. package/website/docs/best-practices.mdx +416 -0
  38. package/website/docs/cli-reference.mdx +204 -0
  39. package/website/docs/examples.mdx +960 -0
  40. package/website/docs/faq.mdx +302 -0
  41. package/website/docs/index.mdx +56 -0
  42. package/website/docs/installation.mdx +30 -0
  43. package/website/docs/overview.mdx +17 -0
  44. package/website/docs/production-deployment.mdx +622 -0
  45. package/website/docs/providers/aws-secrets-manager.mdx +28 -0
  46. package/website/docs/security.mdx +122 -0
  47. package/website/docs/troubleshooting.mdx +236 -0
  48. package/website/docs/tutorials/local-dev/devcontainer-localstack.mdx +31 -0
  49. package/website/docs/tutorials/local-dev/docker-compose.mdx +22 -0
  50. package/website/docs/tutorials/local-dev/nextjs.mdx +18 -0
  51. package/website/docs/tutorials/local-dev/node-python-go.mdx +39 -0
  52. package/website/docs/tutorials/local-dev/quickstart.mdx +23 -0
  53. package/website/docusaurus.config.ts +89 -0
  54. package/website/package.json +21 -0
  55. package/website/sidebars.ts +33 -0
  56. package/website/src/css/custom.css +1 -0
  57. package/website/static/img/env-secrets.png +0 -0
  58. package/website/static/img/favicon.ico +0 -0
  59. package/website/static/img/logo.svg +4 -0
  60. package/website/yarn.lock +8764 -0
package/docs/AWS.md ADDED
@@ -0,0 +1,257 @@
1
+ # AWS Integration Guide
2
+
3
+ This guide explains how to configure and use AWS Secrets Manager with the `env-secrets` tool.
4
+
5
+ ## Overview
6
+
7
+ The `env-secrets` tool supports AWS Secrets Manager as a secret vault. It can retrieve secrets stored in AWS Secrets Manager and inject them as environment variables into your running applications.
8
+
9
+ ## Prerequisites
10
+
11
+ - [AWS CLI](https://docs.aws.amazon.com/cli/index.html) installed and configured
12
+ - AWS credentials with appropriate permissions to access Secrets Manager
13
+ - Node.js 18.0.0 or higher
14
+
15
+ ## Authentication Methods
16
+
17
+ There are several ways to authenticate with AWS when using `env-secrets`:
18
+
19
+ ### 1. IAM Identity Center (Recommended)
20
+
21
+ IAM Identity Center (formerly AWS SSO) is the recommended authentication method for most use cases.
22
+
23
+ **Setup:**
24
+
25
+ 1. Configure AWS CLI with IAM Identity Center:
26
+ ```bash
27
+ aws configure sso
28
+ ```
29
+ 2. Follow the prompts to set up your SSO configuration
30
+ 3. Login to your SSO session:
31
+ ```bash
32
+ aws sso login
33
+ ```
34
+
35
+ **Usage:**
36
+
37
+ ```bash
38
+ env-secrets aws -s my-secret-name -r us-east-1 -- echo "Hello, ${USER_NAME}!"
39
+ ```
40
+
41
+ **Benefits:**
42
+
43
+ - Works from any machine
44
+ - No long-term credentials to manage
45
+ - Automatic credential rotation
46
+ - Centralized access management
47
+
48
+ **Documentation:** [AWS CLI SSO Configuration](https://docs.aws.amazon.com/cli/latest/userguide/sso-configure-profile-token.html)
49
+
50
+ ### 2. IAM Roles
51
+
52
+ IAM roles are primarily used for accessing AWS from EC2 instances, ECS tasks, or other AWS services.
53
+
54
+ **Setup:**
55
+
56
+ 1. Create an IAM role with appropriate Secrets Manager permissions
57
+ 2. Attach the role to your EC2 instance or ECS task
58
+ 3. The AWS SDK will automatically use the instance metadata service
59
+
60
+ **Usage:**
61
+
62
+ ```bash
63
+ env-secrets aws -s my-secret-name -r us-east-1 -- node app.js
64
+ ```
65
+
66
+ **Benefits:**
67
+
68
+ - No credentials to manage
69
+ - Automatic credential rotation
70
+ - Secure for production workloads
71
+
72
+ **Documentation:** [AWS CLI Role Configuration](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-role.html)
73
+
74
+ ### 3. IAM Users (Not Recommended)
75
+
76
+ IAM users with access keys should only be used for development or testing purposes.
77
+
78
+ **Setup:**
79
+
80
+ 1. Create an IAM user with appropriate permissions
81
+ 2. Generate access keys for the user
82
+ 3. Configure credentials using one of the methods below
83
+
84
+ #### Option A: AWS Profile
85
+
86
+ ```bash
87
+ aws configure --profile my-profile
88
+ ```
89
+
90
+ Enter the following when prompted:
91
+
92
+ - Access Key ID
93
+ - Secret Access Key
94
+ - Default region (e.g., `us-east-1`)
95
+ - Output format: `json`
96
+
97
+ **Usage:**
98
+
99
+ ```bash
100
+ env-secrets aws -s my-secret-name -r us-east-1 -p my-profile -- node app.js
101
+ ```
102
+
103
+ #### Option B: Environment Variables
104
+
105
+ Set the following environment variables:
106
+
107
+ ```bash
108
+ export AWS_ACCESS_KEY_ID=A...E
109
+ export AWS_SECRET_ACCESS_KEY=w...Y
110
+ export AWS_DEFAULT_REGION=us-east-1
111
+ ```
112
+
113
+ **Usage:**
114
+
115
+ ```bash
116
+ env-secrets aws -s my-secret-name -r us-east-1 -- node app.js
117
+ ```
118
+
119
+ **Security Note:** Avoid hardcoding credentials in scripts or committing them to version control.
120
+
121
+ ## Required Permissions
122
+
123
+ Your AWS credentials must have the following permissions to use Secrets Manager:
124
+
125
+ ```json
126
+ {
127
+ "Version": "2012-10-17",
128
+ "Statement": [
129
+ {
130
+ "Effect": "Allow",
131
+ "Action": ["secretsmanager:GetSecretValue"],
132
+ "Resource": "arn:aws:secretsmanager:*:*:secret:*"
133
+ },
134
+ {
135
+ "Effect": "Allow",
136
+ "Action": ["sts:GetCallerIdentity"],
137
+ "Resource": "*"
138
+ }
139
+ ]
140
+ }
141
+ ```
142
+
143
+ ## Examples
144
+
145
+ ### Basic Usage
146
+
147
+ 1. **Create a secret using AWS CLI:**
148
+
149
+ ```bash
150
+ aws secretsmanager create-secret \
151
+ --region us-east-1 \
152
+ --profile my-profile \
153
+ --name my-app-secrets \
154
+ --description "Application secrets" \
155
+ --secret-string '{"DATABASE_URL":"postgresql://user:pass@dbhost:5432/db","API_KEY":"abc123"}'
156
+ ```
157
+
158
+ 2. **Use the secret with env-secrets:**
159
+ ```bash
160
+ env-secrets aws -s my-app-secrets -r us-east-1 -p my-profile -- node app.js
161
+ ```
162
+
163
+ ### Advanced Examples
164
+
165
+ 1. **Run a Node.js application with secrets:**
166
+
167
+ ```bash
168
+ env-secrets aws -s production-secrets -r us-west-2 -- node server.js
169
+ ```
170
+
171
+ 2. **Check environment variables:**
172
+
173
+ ```bash
174
+ env-secrets aws -s my-secret -r us-east-1 -p my-profile -- env | grep -E "(DATABASE_URL|API_KEY)"
175
+ ```
176
+
177
+ 3. **Use with Docker containers:**
178
+
179
+ ```bash
180
+ env-secrets aws -s docker-secrets -r us-east-1 -- docker run -e DATABASE_URL -e API_KEY my-app
181
+ ```
182
+
183
+ 4. **Debug mode for troubleshooting:**
184
+ ```bash
185
+ DEBUG=env-secrets,env-secrets:secretsmanager env-secrets aws -s my-secret -r us-east-1 -- env
186
+ ```
187
+
188
+ ## Troubleshooting
189
+
190
+ ### Common Issues
191
+
192
+ 1. **"Unable to connect to AWS"**
193
+
194
+ - Verify AWS credentials are configured correctly
195
+ - Check if the specified region is valid
196
+ - Ensure network connectivity to AWS services
197
+ - Verify IAM permissions include `sts:GetCallerIdentity`
198
+
199
+ 2. **"Secret not found"**
200
+
201
+ - Verify the secret name exists in the specified region
202
+ - Check if you have permissions to access the secret
203
+ - Ensure the secret name is correct (case-sensitive)
204
+ - Verify the secret is in the correct AWS account
205
+
206
+ 3. **"ConfigError"**
207
+
208
+ - Verify AWS profile configuration in `~/.aws/credentials`
209
+ - Check if environment variables are set correctly
210
+ - Ensure IAM role permissions if using EC2/ECS
211
+ - Verify AWS CLI is properly configured
212
+
213
+ 4. **Environment variables not injected**
214
+ - Verify the secret contains valid JSON
215
+ - Check if the secret is accessible
216
+ - Use debug mode to troubleshoot: `DEBUG=env-secrets env-secrets aws ...`
217
+
218
+ ### Debug Mode
219
+
220
+ Enable debug logging to troubleshoot issues:
221
+
222
+ ```bash
223
+ # Debug main application
224
+ DEBUG=env-secrets env-secrets aws -s my-secret -r us-east-1 -- env
225
+
226
+ # Debug vault-specific operations
227
+ DEBUG=env-secrets,env-secrets:secretsmanager env-secrets aws -s my-secret -r us-east-1 -- env
228
+ ```
229
+
230
+ ### Testing Connectivity
231
+
232
+ Test your AWS connectivity before using secrets:
233
+
234
+ ```bash
235
+ # Test with AWS CLI
236
+ aws sts get-caller-identity --region us-east-1
237
+
238
+ # Test with env-secrets (will show connection status)
239
+ DEBUG=env-secrets env-secrets aws -s test-secret -r us-east-1 -- echo "Connection test"
240
+ ```
241
+
242
+ ## Security Best Practices
243
+
244
+ 1. **Use IAM Identity Center** for most use cases
245
+ 2. **Use IAM roles** for production workloads on AWS
246
+ 3. **Rotate credentials regularly** if using IAM users
247
+ 4. **Use least privilege** - only grant necessary permissions
248
+ 5. **Enable CloudTrail** for audit logging
249
+ 6. **Use VPC endpoints** for enhanced security in production
250
+ 7. **Never commit credentials** to version control
251
+
252
+ ## Related Documentation
253
+
254
+ - [AWS Secrets Manager User Guide](https://docs.aws.amazon.com/secretsmanager/latest/userguide/)
255
+ - [AWS CLI Configuration](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html)
256
+ - [IAM Best Practices](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html)
257
+ - [AWS SDK for JavaScript v3](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/)
package/jest.config.js CHANGED
@@ -3,5 +3,7 @@
3
3
  module.exports = {
4
4
  preset: 'ts-jest',
5
5
  testEnvironment: 'node',
6
- collectCoverageFrom: ['src/**/*.ts']
6
+ collectCoverageFrom: ['src/**/*.ts'],
7
+ coverageDirectory: 'coverage',
8
+ coverageReporters: ['text', 'lcov', 'html']
7
9
  };
@@ -0,0 +1,8 @@
1
+ /** @type {import('ts-jest').JestConfigWithTsJest} */
2
+ // eslint-disable-next-line no-undef
3
+ module.exports = {
4
+ preset: 'ts-jest',
5
+ testEnvironment: 'node',
6
+ setupFilesAfterEnv: ['<rootDir>/__e2e__/setup.ts'],
7
+ testTimeout: 30000 // 30 seconds timeout for e2e tests
8
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "env-secrets",
3
- "version": "0.2.0",
3
+ "version": "0.3.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)",
@@ -11,6 +11,7 @@
11
11
  "scripts": {
12
12
  "prepare": "husky install",
13
13
  "build": "rimraf ./dist && tsc -b src",
14
+ "start": "node dist/index.js",
14
15
  "postbuild": "chmod 755 ./dist/index.js",
15
16
  "lint": "eslint . --ext .ts,.js",
16
17
  "release": "release-it",
@@ -19,14 +20,16 @@
19
20
  "test": "npm run test:unit && npm run test:e2e",
20
21
  "test:unit": "jest __tests__",
21
22
  "test:unit:coverage": "jest __tests__ --coverage",
22
- "test:e2e": "npm run build && jest __e2e__"
23
+ "test:e2e": "npm run build && jest --config jest.e2e.config.js",
24
+ "test:e2e:debug": "npm run build && DEBUG=true jest --config jest.e2e.config.js"
23
25
  },
24
26
  "devDependencies": {
25
27
  "@types/debug": "^4.1.12",
26
28
  "@types/jest": "^29.5.14",
27
- "@types/node": "^18.19.121",
29
+ "@types/node": "^18.19.124",
28
30
  "@typescript-eslint/eslint-plugin": "^5.62.0",
29
31
  "@typescript-eslint/parser": "^5.62.0",
32
+ "aws-sdk-client-mock": "^3.0.0",
30
33
  "eslint": "^8.57.1",
31
34
  "eslint-config-prettier": "^8.10.2",
32
35
  "eslint-plugin-prettier": "^4.2.5",
@@ -41,9 +44,9 @@
41
44
  "typescript": "^4.9.5"
42
45
  },
43
46
  "dependencies": {
44
- "@aws-sdk/client-secrets-manager": "^3.525.0",
45
- "@aws-sdk/client-sts": "^3.525.0",
46
- "@aws-sdk/credential-providers": "^3.525.0",
47
+ "@aws-sdk/client-secrets-manager": "^3.883.0",
48
+ "@aws-sdk/client-sts": "^3.883.0",
49
+ "@aws-sdk/credential-providers": "^3.883.0",
47
50
  "commander": "^9.5.0",
48
51
  "debug": "^4.4.1"
49
52
  },
@@ -52,7 +55,7 @@
52
55
  "prettier --write .",
53
56
  "eslint --fix ."
54
57
  ],
55
- "*.{json,md,yaml}": [
58
+ "*.{json,md,mdx,yaml,yml}": [
56
59
  "prettier --write ."
57
60
  ]
58
61
  },
package/src/index.ts CHANGED
@@ -2,10 +2,12 @@
2
2
 
3
3
  import { Command, Argument } from 'commander';
4
4
  import { spawn } from 'node:child_process';
5
+ import { writeFileSync, existsSync } from 'node:fs';
5
6
  import Debug from 'debug';
6
7
 
7
8
  import { LIB_VERSION } from './version';
8
9
  import { secretsmanager } from './vaults/secretsmanager';
10
+ import { objectToExport } from './vaults/utils';
9
11
 
10
12
  const debug = Debug('env-secrets');
11
13
 
@@ -27,17 +29,49 @@ program
27
29
  .requiredOption('-s, --secret <secret>', 'secret to get')
28
30
  .option('-p, --profile <profile>', 'profile to use')
29
31
  .option('-r, --region <region>', 'region to use')
32
+ .option(
33
+ '-o, --output <file>',
34
+ 'output secrets to file instead of environment variables'
35
+ )
30
36
  .action(async (program, options) => {
31
- let env = await secretsmanager(options);
32
- env = Object.assign({}, process.env, env);
33
- debug(env);
34
- if (program && program.length > 0) {
35
- debug(`${program[0]} ${program.slice(1)}`);
36
- spawn(program[0], program.slice(1), {
37
- stdio: 'inherit',
38
- shell: true,
39
- env
40
- });
37
+ const secrets = await secretsmanager(options);
38
+ debug(secrets);
39
+
40
+ if (options.output) {
41
+ // Check if file already exists
42
+ if (existsSync(options.output)) {
43
+ // eslint-disable-next-line no-console
44
+ console.error(
45
+ `Error: File ${options.output} already exists and will not be overwritten`
46
+ );
47
+ process.exit(1);
48
+ }
49
+
50
+ // Write secrets to file with 0400 permissions
51
+ const envContent = objectToExport(secrets);
52
+ writeFileSync(options.output, envContent, { mode: 0o400 });
53
+ // eslint-disable-next-line no-console
54
+ console.log(`Secrets written to ${options.output}`);
55
+ } else {
56
+ // Original behavior: merge secrets into environment and run program
57
+ const env = Object.assign({}, process.env, secrets);
58
+ debug(env);
59
+ if (program && program.length > 0) {
60
+ debug(`${program[0]} ${program.slice(1)}`);
61
+
62
+ // In test mode, just output the environment variables for testing
63
+ if (process.env.NODE_ENV === 'test') {
64
+ // eslint-disable-next-line no-console
65
+ console.log(JSON.stringify(env));
66
+ return;
67
+ }
68
+
69
+ spawn(program[0], program.slice(1), {
70
+ stdio: 'inherit',
71
+ shell: true,
72
+ env
73
+ });
74
+ }
41
75
  }
42
76
  });
43
77
 
@@ -23,6 +23,7 @@ const checkConnection = async (region?: string) => {
23
23
  debug(data);
24
24
  return true;
25
25
  } catch (err) {
26
+ // eslint-disable-next-line no-console
26
27
  console.error(err);
27
28
  return false;
28
29
  }
@@ -73,20 +74,30 @@ export const secretsmanager = async (options: secretsmanagerType) => {
73
74
  return JSON.parse(secretvalue);
74
75
  }
75
76
  } catch (err) {
77
+ // eslint-disable-next-line no-console
76
78
  console.error(err);
77
79
  }
78
- } catch (err: any) {
79
- if (err && err.name === 'ResourceNotFoundException') {
80
- console.error(`${secret} not found`);
81
- } else if (err && err.name === 'ConfigError') {
82
- console.error(err.message);
80
+ } catch (err: unknown) {
81
+ if (err && typeof err === 'object' && 'name' in err) {
82
+ if (err.name === 'ResourceNotFoundException') {
83
+ // eslint-disable-next-line no-console
84
+ console.error(`${secret} not found`);
85
+ } else if (err.name === 'ConfigError' && 'message' in err) {
86
+ // eslint-disable-next-line no-console
87
+ console.error(err.message);
88
+ } else {
89
+ // eslint-disable-next-line no-console
90
+ console.error(err);
91
+ }
83
92
  } else {
93
+ // eslint-disable-next-line no-console
84
94
  console.error(err);
85
95
  }
86
96
  }
87
97
 
88
98
  return {};
89
99
  } else {
100
+ // eslint-disable-next-line no-console
90
101
  console.error('Unable to connect to AWS');
91
102
  return {};
92
103
  }
@@ -14,16 +14,18 @@ export const replaceWithAstrisk = (str: string | undefined) => {
14
14
  }
15
15
  };
16
16
 
17
- export const objectToExport = (obj: Record<string, any>) => {
17
+ export const objectToExport = (
18
+ obj: Record<string, string | number | boolean>
19
+ ) => {
18
20
  return Object.entries(obj).reduce(
19
21
  (env, [OutputKey, OutputValue]) =>
20
- `${env}export ${OutputKey}=${OutputValue}${os.EOL}`,
22
+ `${env}${OutputKey}=${OutputValue}${os.EOL}`,
21
23
  ''
22
24
  );
23
25
  };
24
26
 
25
- export const objectToEnv = (obj: Record<string, any>) => {
27
+ export const objectToEnv = (obj: Record<string, string | number | boolean>) => {
26
28
  return Object.entries(obj).map(
27
- ([OutputKey, OutputValue]) => (process.env[OutputKey] = OutputValue)
29
+ ([OutputKey, OutputValue]) => (process.env[OutputKey] = String(OutputValue))
28
30
  );
29
31
  };