Skip to content

Commit

Permalink
Adding support for multiple AWS secrets to be connected (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxGoo authored Oct 17, 2020
1 parent 00e1052 commit edded83
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 11 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 1.5.0 (October 17, 2020)

- Add support for multiple AWS Secrets Manager secrets in the same region

## 1.4.2 (October 17, 2020)

- Warn if a config is undefined, null, 'undefined' or an empty string
Expand Down
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ console.log(config.API_ENDPOINT);
### Using AWS Secrets Manager

In order to use AWS Secrets Manager you have to add a `AWS_SECRETS_MANAGER_NAME` or `awsSecretsManagerName` setting to your config that specifies the name of the secret to look up:
In order to use AWS Secrets Manager you have to add a `AWS_SECRETS_MANAGER_NAME` or `awsSecretsManagerName` setting to your config that specifies the names of the secrets to look up:

```ts
// config.default.ts
Expand All @@ -81,9 +81,20 @@ export default {
};
```

`AWS_SECRETS_MANAGER_NAME` can also be a comma separated list to allow connection to multiple secrets in AWS Secrets Manager. Each secret from the list is evaluated in order mean that if a specific key appears in two secrets the value will be overwritten by the last secret in the list.

```ts
// config.default.ts
export default {
AWS_SECRETS_MANAGER_NAME: 'production/myapp/config, production/myapp/another-config',
API_ENDPOINT: 'https://api.kanye.rest/'
};
```

In addition to specifying the secret name you can also provide a region using the `AWS_SECRETS_MANAGER_REGION` or `awsSecretsManagerRegion` setting. The connection timeout in milliseconds can also be specified using the `AWS_SECRETS_MANAGER_TIMEOUT` or `awsSecretsManagerTimeout` setting:

```ts

// config.default.ts
export default {
AWS_SECRETS_MANAGER_NAME: 'production/myapp/config',
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "config-dug",
"version": "1.4.2",
"version": "1.5.0",
"description": "Config loader with support for AWS Secrets Manager",
"author": "Neo Financial Engineering <engineering@neofinancial.com>",
"main": "build/index.js",
Expand Down
15 changes: 13 additions & 2 deletions src/__mocks__/get-secret.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,19 @@

import awsSecretsManagerResponse from '../../test/fixtures/secrets/aws-secrets-manager-response.json';

const getSecret = (_: string, __: string): object => {
return JSON.parse(awsSecretsManagerResponse.Value);
import multipleAwsSecretsManagerResponse1 from '../../test/fixtures/multiple-secrets/aws-secrets-manager-1-response.json';
import multipleAwsSecretsManagerResponse2 from '../../test/fixtures/multiple-secrets/aws-secrets-manager-2-response.json';

const getSecret = (secretName: string, __: string): object => {
if (secretName === 'development/config-dug') {
return JSON.parse(awsSecretsManagerResponse.Value);
} else if (secretName === 'development/config-dug-1') {
return JSON.parse(multipleAwsSecretsManagerResponse1.Value);
} else if (secretName === 'development/config-dug-2') {
return JSON.parse(multipleAwsSecretsManagerResponse2.Value);
}

return {};
};

export default getSecret;
4 changes: 3 additions & 1 deletion src/get-secret.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import awsParamStore from 'aws-param-store';

const getSecret = (secretName: string, region: string, timeout: number): object => {
import { SecretObject } from '.';

const getSecret = (secretName: string, region: string, timeout: number): SecretObject => {
try {
const secret = awsParamStore.getParameterSync(`/aws/reference/secretsmanager/${secretName}`, {
region,
Expand Down
34 changes: 28 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ interface LoadSecretsArgs {
awsSecretsManagerTimeout?: number;
}

export interface SecretObject {
[key: string]: string;
}

const resolveFile = (appDirectory: string, configPath: string, fileName: string): string => {
if (fs.existsSync(path.resolve(appDirectory, configPath, `${fileName}.ts`))) {
debug(
Expand Down Expand Up @@ -72,21 +76,39 @@ const convertString = (value: string): string | number | boolean => {
return value;
};

const convertToArray = (value: string): string[] => {
return value
.split(',')
.map(entry => entry.trim())
.filter(entry => !!entry);
};

const loadSecrets = (config: LoadSecretsArgs): object => {
const secretName = config.AWS_SECRETS_MANAGER_NAME || config.awsSecretsManagerName;
const region = config.AWS_SECRETS_MANAGER_REGION || config.awsSecretsManagerRegion || 'us-east-1';
const timeout = config.AWS_SECRETS_MANAGER_TIMEOUT || config.awsSecretsManagerTimeout || 5000;

if (secretName) {
debug('loading config from AWS Secrets Manager', secretName, region);
const secrets = convertToArray(secretName).map(name => {
debug('loading config from AWS Secrets Manager', name, region);

const secret = getSecret(secretName, region, timeout);
return getSecret(name, region, timeout);
});

return Object.entries(secret).reduce((result: ConfigObject, [key, value]): ConfigObject => {
result[key] = convertString(value);
const mergedSecrets: SecretObject = {};

return result;
}, {});
secrets.forEach(secret => {
Object.assign(mergedSecrets, secret);
});

return Object.entries(mergedSecrets).reduce(
(result: ConfigObject, [key, value]): ConfigObject => {
result[key] = convertString(value);

return result;
},
{}
);
} else {
return {};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Name": "/aws/reference/secretsmanager/development/config-dug-1",
"Type": "SecureString",
"Value": "{\"DB_USERNAME\":\"config-dug\",\"DB_PASSWORD\":\"secret\",\"TEST_BOOLEAN\":\"true\",\"TEST_INTEGER\":\"42\",\"TEST_FLOAT\":\"4.2\",\"TEST_NUMBER_LIST\":\"123456,123456\"}",
"Version": 0,
"SourceResult": "{\"ARN\":\"arn:aws:secretsmanager:us-east-1:999999999999:secret:development/config-dug-qH33bS\",\"name\":\"development/config-dug\",\"versionId\":\"8439a2e1-9a24-49ff-b9e7-5e8ba5d6d5a6\",\"secretString\":\"{\\\"DB_USERNAME\\\":\\\"config-dug\\\",\\\"DB_PASSWORD\\\":\\\"secret\\\"}\",\"versionStages\":[\"AWSCURRENT\"],\"createdDate\":\"Apr 10, 2019 10:49:20 PM\"}",
"LastModifiedDate": "2019-04-10T22:49:20.589Z",
"ARN": "arn:aws:secretsmanager:us-east-1:999999999999:secret:development/config-dug-qH33bS"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Name": "/aws/reference/secretsmanager/development/config-dug-2",
"Type": "SecureString",
"Value": "{\"TEST_INTEGER\":\"22\",\"TEST_ANOTHER_INTEGER\":\"23\"}",
"Version": 0,
"SourceResult": "{\"ARN\":\"arn:aws:secretsmanager:us-east-1:999999999999:secret:development/config-dug-qH33bS\",\"name\":\"development/config-dug\",\"versionId\":\"8439a2e1-9a24-49ff-b9e7-5e8ba5d6d5a6\",\"secretString\":\"{\\\"DB_USERNAME\\\":\\\"config-dug\\\",\\\"DB_PASSWORD\\\":\\\"secret\\\"}\",\"versionStages\":[\"AWSCURRENT\"],\"createdDate\":\"Apr 10, 2019 10:49:20 PM\"}",
"LastModifiedDate": "2019-04-10T22:49:20.589Z",
"ARN": "arn:aws:secretsmanager:us-east-1:999999999999:secret:development/config-dug-qH33bS"
}
3 changes: 3 additions & 0 deletions test/fixtures/multiple-secrets/config.default.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
AWS_SECRETS_MANAGER_NAME: 'development/config-dug-1, development/config-dug-2'
};
13 changes: 13 additions & 0 deletions test/secrets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,16 @@ test('loading secrets from AWS Secrets Manager works', (): void => {
TEST_NUMBER_LIST: '123456,123456'
});
});

test('loading multiple AWS Secrets Manager secrets works', (): void => {
const testConfig = loadConfig('test/fixtures/multiple-secrets');

expect(testConfig).toMatchObject({
AWS_SECRETS_MANAGER_NAME: 'development/config-dug-1, development/config-dug-2',
DB_USERNAME: 'config-dug',
DB_PASSWORD: 'secret',
TEST_BOOLEAN: true,
TEST_INTEGER: 22,
TEST_ANOTHER_INTEGER: 23
});
});

0 comments on commit edded83

Please sign in to comment.