Skip to content

Commit

Permalink
Check for wildcard to avoid List Secret permision and use Record type…
Browse files Browse the repository at this point in the history
… instead of Map
  • Loading branch information
suriyanto committed Apr 12, 2022
1 parent b5b41a3 commit 8dda57c
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 82 deletions.
52 changes: 18 additions & 34 deletions __tests__/awsUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ jest.mock('aws-sdk')

config({ path: resolve(__dirname, '../.env') })

const NO_PREFIXED_EXPECTED = {'my_secret_1': 'test-value-1', 'my_secret_2_foo': 'bar', 'my/secret/3_foo': 'bar'}
const PREFIXED_EXPECTED = {'my_prefixed_1': 'prefixed-test-value-1', 'my_prefixed_2_foo': 'bar', 'my/prefixed/3_foo': 'bar'}

const secretsManagerClient = new SecretsManager({})

test ('Construct secret name: with secretPrefix', () => {
Expand Down Expand Up @@ -58,69 +61,50 @@ test('Get Secret Names To Fetch: Multiple Wild Card Names with secretPrefix', ()
})

test('Get Secret Value Maps: Single Secret without secretPrefix', () => {
return getSecretValueMaps(secretsManagerClient, ['my_secret_1'], false).then(maps => {
expect(maps).toEqual(new Map([['my_secret_1', 'test-value-1']]))
})
return getSecretValueMaps(secretsManagerClient, ['my_secret_1'], false).then(maps =>
expect(maps).toEqual({'my_secret_1': 'test-value-1'})
)
})

test('Get Secret Value Maps: Multiple Secrets without secretPrefix', () => {
expect.assertions(1)
return getSecretValueMaps(secretsManagerClient, ['my_secret_1', 'my_secret_2', 'my/secret/3'], true)
.then(maps => {
expect(maps).toEqual(
new Map([['my_secret_1', 'test-value-1'], ['my_secret_2_foo', 'bar'], ['my/secret/3_foo', 'bar']]))
})
.then(maps => expect(maps).toEqual(NO_PREFIXED_EXPECTED))
})

test('Get Secret Value Maps: Single Wild Card Name without secretPrefix', () => {
expect.assertions(1)
return getSecretValueMaps(secretsManagerClient, ['*secret*'], true).then(maps => {
expect(maps).toEqual(
new Map([['my_secret_1', 'test-value-1'], ['my_secret_2_foo', 'bar'], ['my/secret/3_foo', 'bar']]))
})
return getSecretValueMaps(secretsManagerClient, ['*secret*'], true).then(maps =>
expect(maps).toEqual(NO_PREFIXED_EXPECTED))
})

test('Get Secret Value Maps: Multiple Wild Card Name without secretPrefix', () => {
expect.assertions(1)
return getSecretValueMaps(secretsManagerClient, ['my*', 'my_prefixed*', 'invalid'], true).then(maps => {
expect(maps).toEqual(new Map([
['my_secret_1', 'test-value-1'],
['my_secret_2_foo', 'bar'],
['my/secret/3_foo', 'bar']]))
})
return getSecretValueMaps(secretsManagerClient, ['my*', 'my_prefixed*', 'invalid'], true).then(maps =>
expect(maps).toEqual(NO_PREFIXED_EXPECTED))
})

test('Get Secret Value Maps: Single Secret with secretPrefix', () => {
expect.assertions(1)
return getSecretValueMaps(secretsManagerClient, ['my_prefixed_1'], false, 'dev').then(maps => {
expect(maps).toEqual(new Map([['my_prefixed_1', 'prefixed-test-value-1']]))
})
return getSecretValueMaps(secretsManagerClient, ['my_prefixed_1'], false, 'dev').then(maps =>
expect(maps).toEqual({'my_prefixed_1': 'prefixed-test-value-1'})
)
})

test('Get Secret Value Maps: Multiple Secrets with secretPrefix', () => {
expect.assertions(1)
return getSecretValueMaps(secretsManagerClient, ['my_prefixed_1', 'my_prefixed_2', 'my/prefixed/3'], true, 'dev')
.then(maps => expect(maps).toEqual(new Map([
['my_prefixed_1', 'prefixed-test-value-1'],
['my_prefixed_2_foo', 'bar'],
['my/prefixed/3_foo', 'bar']]))
)
.then(maps => expect(maps).toEqual(PREFIXED_EXPECTED))
})

test('Get Secret Value Maps: Single Wild Card Name with secretPrefix', () => {
expect.assertions(1)
return getSecretValueMaps(secretsManagerClient, ['*prefixed*'], true, 'dev').then(maps => {
expect(maps).toEqual(
new Map([['my_prefixed_1', 'prefixed-test-value-1'], ['my_prefixed_2_foo', 'bar'], ['my/prefixed/3_foo', 'bar']]))
})
return getSecretValueMaps(secretsManagerClient, ['*prefixed*'], true, 'dev').then(maps =>
expect(maps).toEqual(PREFIXED_EXPECTED))
})

test('Get Secret Value Maps: Multiple Wild Card Name with secretPrefix', () => {
expect.assertions(1)
return getSecretValueMaps(secretsManagerClient, ['my*', 'my_prefixed*', 'invalid'], true, 'dev')
.then(maps => expect(maps).toEqual(new Map([
['my_prefixed_1', 'prefixed-test-value-1'],
['my_prefixed_2_foo', 'bar'],
['my/prefixed/3_foo', 'bar']]))
)
.then(maps => expect(maps).toEqual(PREFIXED_EXPECTED))
})
2 changes: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: 'AWS Secrets Manager Action'
author: 'Abhilash Kishore'
author: 'suriyanto'
description: 'Use secrets from AWS Secrets Manager as environment variables in your GitHub Actions workflow'
inputs:
secrets:
Expand Down
43 changes: 27 additions & 16 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41473,6 +41473,7 @@ const getPOSIXString = (data) => {
return data.replace(/[^a-zA-Z0-9_]/g, '_').toUpperCase();
};
const injectSecretValueMapToEnvironment = (secretValueMap) => {
core.debug(`Preparing to inject ${Object.keys(secretValueMap)} secrets to environment variable`);
for (const secretName in secretValueMap) {
const secretValue = secretValueMap[secretName];
core.setSecret(secretValue);
Expand Down Expand Up @@ -41604,34 +41605,45 @@ const deconstructSecretName = (secretName, secretPrefix = '') => {
return secretName.replace(new RegExp(`^${secretPrefix}/`, 'g'), '');
};
const getSecretNamesToFetch = (secretsManagerClient, inputSecretNames, secretPrefix = '') => {
const hasWildcard = inputSecretNames.some(secretName => secretName.includes('*'));
return new Promise((resolve, reject) => {
// list secrets, filter against wildcards and fetch filtered secrets
// else, fetch specified secrets directly
const secretNames = [];
listSecrets(secretsManagerClient)
.then(secrets => {
inputSecretNames.forEach(inputSecretName => {
secretNames.push(...filterBy(secrets, constructSecretName(inputSecretName, secretPrefix)));
if (hasWildcard) {
listSecrets(secretsManagerClient)
.then(secrets => {
inputSecretNames.forEach(inputSecretName => {
secretNames.push(...filterBy(secrets, constructSecretName(inputSecretName, secretPrefix)));
});
resolve([...new Set(secretNames)]);
})
.catch(err => {
reject(err);
});
resolve([...new Set(secretNames)]);
})
.catch(err => {
reject(err);
});
}
else {
resolve([...new Set(inputSecretNames.map(n => constructSecretName(n, secretPrefix)))]);
}
});
};
const normalizedSecretMaps = (maps, secretPrefix) => {
const flatArray = maps.map(e => Object.keys(e).map(k => [deconstructSecretName(k, secretPrefix), e[k]])).flat();
return new Map(flatArray);
const ret = {};
flatArray.forEach((e) => ret[e[0]] = e[1]);
core.debug(`Fetched secret names: ${Object.keys(ret)}`);
return ret;
};
const getSecretValueMaps = (secretsManagerClient, inputSecretNames, shouldParseJSON, secretPrefix = '') => {
core.debug(`Will fetch ${inputSecretNames.length} secrets: ${inputSecretNames}`);
return new Promise((resolve, reject) => {
getSecretNamesToFetch(secretsManagerClient, inputSecretNames, secretPrefix).then(names => {
Promise.all(names.map(secretName => getSecretValueMap(secretsManagerClient, secretName, shouldParseJSON)))
.then(maps => {
resolve(normalizedSecretMaps(maps, secretPrefix));
})
core.debug(`Secrets to fetch: ${names.toString()}, requested: ${secretPrefix} - ${inputSecretNames.toString()}`);
Promise.all(names.map(secretName => {
core.debug(`Fetched secret value for: ${secretName}`);
return getSecretValueMap(secretsManagerClient, secretName, shouldParseJSON);
}))
.then(maps => resolve(normalizedSecretMaps(maps, secretPrefix)))
.catch(err => reject(err));
});
});
Expand All @@ -41645,12 +41657,11 @@ const getSecretValueMaps = (secretsManagerClient, inputSecretNames, shouldParseJ

// secretNames input string is a new line separated list of secret names. Take distinct secret names.
const inputSecretNames = [...new Set(core.getMultilineInput(Inputs.SECRETS))];
// Check if any secret name contains a wildcard '*'
// const hasWildcard: boolean = inputSecretNames.some(secretName => secretName.includes('*'))
const shouldParseJSON = core.getBooleanInput(Inputs.PARSE_JSON);
const secretPrefix = core.getInput(Inputs.SECRET_PREFIX) || '';
const AWSConfig = {};
const secretsManagerClient = getSecretsManagerClient(AWSConfig);
core.info(`Loading secrets ${inputSecretNames.toString()} with prefix: ${secretPrefix} and parse json: ${shouldParseJSON}`);
getSecretValueMaps(secretsManagerClient, inputSecretNames, shouldParseJSON, secretPrefix)
.then(maps => {
injectSecretValueMapToEnvironment(maps);
Expand Down
2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,6 @@
"@types/jest": "^27.4.1",
"jest": "^27.5.1",
"ts-jest": "^27.1.4",
"typescript": "^4.6.3"
"typescript": "4.6.2"
}
}
47 changes: 30 additions & 17 deletions src/awsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,39 +121,52 @@ const deconstructSecretName = (secretName: string, secretPrefix = ''): string =>

const getSecretNamesToFetch =
(secretsManagerClient: SecretsManager, inputSecretNames: string[], secretPrefix = ''): Promise<Array<string>> => {
const hasWildcard: boolean = inputSecretNames.some(secretName => secretName.includes('*'))

return new Promise<Array<string>>((resolve, reject) => {
// list secrets, filter against wildcards and fetch filtered secrets
// else, fetch specified secrets directly
const secretNames: string[] = []
listSecrets(secretsManagerClient)
.then(secrets => {
inputSecretNames.forEach(inputSecretName => {
secretNames.push(...filterBy(secrets, constructSecretName(inputSecretName, secretPrefix)))
if (hasWildcard) {
listSecrets(secretsManagerClient)
.then(secrets => {
inputSecretNames.forEach(inputSecretName => {
secretNames.push(...filterBy(secrets, constructSecretName(inputSecretName, secretPrefix)))
})
resolve([...new Set(secretNames)])
})
resolve([...new Set(secretNames)])
})
.catch(err => {
reject(err)
})
.catch(err => {
reject(err)
})
} else {
resolve([...new Set(inputSecretNames.map(n => constructSecretName(n, secretPrefix)))])
}
})
}

const normalizedSecretMaps = (maps: Array<any>, secretPrefix: string): Record<string, any> => {
const flatArray: Array<any> = maps.map(e =>
Object.keys(e).map(k => [deconstructSecretName(k, secretPrefix), e[k]])
const normalizedSecretMaps = (maps, secretPrefix: string): Record<string, any> => {
const flatArray = maps.map(e =>
Object.keys(e).map(k => [deconstructSecretName(k, secretPrefix), e[k]] as [string, string])
).flat()
return new Map(flatArray)

const ret: Record<string, any> = {} as Record<string, any>
flatArray.forEach((e: Array<string>) => ret[e[0]] = e[1])
core.debug(`Fetched secret names: ${Object.keys(ret)}`)

return ret
}

const getSecretValueMaps = (secretsManagerClient: SecretsManager,
inputSecretNames: string[], shouldParseJSON: boolean, secretPrefix = ''): Record<string, any> => {
core.debug(`Will fetch ${inputSecretNames.length} secrets: ${inputSecretNames}`)
return new Promise((resolve, reject) => {
getSecretNamesToFetch(secretsManagerClient, inputSecretNames, secretPrefix).then(names => {
Promise.all(names.map(secretName => getSecretValueMap(secretsManagerClient, secretName, shouldParseJSON)))
.then(maps => {
resolve(normalizedSecretMaps(maps, secretPrefix))
})
core.debug(`Secrets to fetch: ${names.toString()}, requested: ${secretPrefix} - ${inputSecretNames.toString()}`)
Promise.all(names.map(secretName => {
core.debug(`Fetched secret value for: ${secretName}`)
return getSecretValueMap(secretsManagerClient, secretName, shouldParseJSON)
}))
.then(maps => resolve(normalizedSecretMaps(maps, secretPrefix)))
.catch(err => reject(err))
})
})
Expand Down
7 changes: 4 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ import { injectSecretValueMapToEnvironment } from './utils'
// secretNames input string is a new line separated list of secret names. Take distinct secret names.
const inputSecretNames: string[] = [...new Set(core.getMultilineInput(Inputs.SECRETS))]

// Check if any secret name contains a wildcard '*'
// const hasWildcard: boolean = inputSecretNames.some(secretName => secretName.includes('*'))

const shouldParseJSON = core.getBooleanInput(Inputs.PARSE_JSON)

const secretPrefix = core.getInput(Inputs.SECRET_PREFIX) || ''
Expand All @@ -18,6 +15,10 @@ const AWSConfig = {}

const secretsManagerClient = getSecretsManagerClient(AWSConfig)

core.info(
`Loading secrets ${inputSecretNames.toString()} with prefix: ${secretPrefix} and parse json: ${shouldParseJSON}`
)

getSecretValueMaps(secretsManagerClient, inputSecretNames, shouldParseJSON, secretPrefix)
.then(maps => {
injectSecretValueMapToEnvironment(maps)
Expand Down
2 changes: 2 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ export const getPOSIXString = (data: string): string => {
}

export const injectSecretValueMapToEnvironment = (secretValueMap: Record<string, any>): void => {
core.debug(`Preparing to inject ${Object.keys(secretValueMap)} secrets to environment variable`)

for (const secretName in secretValueMap) {
const secretValue: string = secretValueMap[secretName]
core.setSecret(secretValue)
Expand Down

0 comments on commit 8dda57c

Please sign in to comment.