From accc0d4b27adbb76dcd1ed3825f8ee2f11f8d83f Mon Sep 17 00:00:00 2001 From: YuvalShAz <159810020+YuvalShAz@users.noreply.github.com> Date: Wed, 21 Feb 2024 16:59:12 -0500 Subject: [PATCH 1/7] Updated environment variable name behavior to allow for blank aliases --- README.md | 43 ++++++++++++++++-- __tests__/index.test.ts | 96 ++++++++++++++++++++++++++++++++++++++--- __tests__/utils.test.ts | 13 ++++-- src/index.ts | 15 +++++-- src/utils.ts | 4 +- 5 files changed, 154 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 6672599..39c3c17 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,9 @@ To use the action, add a step to your workflow that uses the following syntax. ### Parameters - `secret-ids`: Secret ARNS, names, and name prefixes. -By default, the step creates each environment variable name from the secret name, transformed to include only uppercase letters, numbers, and underscores, and so that it doesn't begin with a number. +By default, the step creates each environment variable name from the secret name, transformed to include only uppercase letters, numbers, and underscores, and so that it doesn't begin with a number. -To set the environment variable name, enter it before the secret ID, followed by a comma. For example `ENV_VAR_1, secretId` creates an environment variable named **ENV_VAR_1** from the secret `secretId`. +To set the environment variable name, enter it before the secret ID, followed by a comma. For example `ENV_VAR_1, secretId` creates an environment variable named **ENV_VAR_1** from the secret `secretId`. A line with an empty environment variable name but a leading comma (ex: `, secretId`) behaves as if the entry didn't have that leading comma (ex: `secretId`) if the secret is not valid JSON or the `parse-json-secrets` flag is false. The environment variable name can consist of uppercase letters, numbers, and underscores. @@ -57,7 +57,7 @@ To use a prefix, enter at least three characters followed by an asterisk. For ex Set `parse-json-secrets` to `true` to create environment variables for each key/value pair in the JSON. -Note that if the JSON uses case-sensitive keys such as "name" and "Name", the action will have duplicate name conflicts. In this case, set `parse-json-secrets` to `false` and parse the JSON secret value separately. +Note that if the JSON uses case-sensitive keys such as "name" and "Name", the action will have duplicate name conflicts. In this case, set `parse-json-secrets` to `false` and parse the JSON secret value separately. Additionally, if the secret is JSON and this flag is true: blank aliases are allowed and result in an environment variables with a leading underscore (see Example 4). ​ ### Examples ​ @@ -137,6 +137,43 @@ TEST_SECRET_API_KEY: "key" TEST_SECRET_CONFIG_ACTIVE: "true" ``` +**Example 4: Parse JSON in secret with blank alias** +The following example creates environment variables by parsing the JSON in the secret without assigning an alias. +``` +- name: Get Secrets by Name and by ARN + uses: aws-actions/aws-secretsmanager-get-secrets@v1 + with: + secret-ids: | + , test/blankAliasSecret + , test/blankAliasSecret2 + parse-json-secrets: true +``` +The secret `test/blankAliasSecret` has the following secret value. +​ +``` +{ + "api_user": "user", + "api_key": "key", + "config": { + "active": "true" + } +} +``` +And secret `test/blankAliasSecret2` has the following secret value. +​ +``` +plaintextsecret +``` +Environment variables created: +​ +``` +_API_USER: "user" +_API_KEY: "key" +_CONFIG_ACTIVE: "true" +TEST_BLANKALIASSECRET2: "plaintextsecret" +``` +If the `parse-json-secrets` flag is toggled to false; each secret is treated as a plaintext string (even if it's JSON formatted) and the behavior of `test/blankAliasSecret2` is applied for a blank alias. + ## Security See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. diff --git a/__tests__/index.test.ts b/__tests__/index.test.ts index 7494be9..22c7999 100644 --- a/__tests__/index.test.ts +++ b/__tests__/index.test.ts @@ -32,11 +32,23 @@ const ENV_NAME_4 = 'ARN_ALIAS'; const SECRET_4 = "secretString2"; const TEST_ARN_INPUT = ENV_NAME_4 + "," + TEST_ARN_1; +const BLANK_NAME= "test/blank"; +const SECRET_FOR_BLANK = '{"user": "integ", "password": "integpw", "config": {"id1": "example1"}}'; +const BLANK_ALIAS_INPUT = "," + BLANK_NAME; + +const BLANK_NAME_2= "test/blank2"; +const SECRET_FOR_BLANK_2 = "blankNameSecretString"; +const BLANK_ALIAS_INPUT_2 = "," + BLANK_NAME_2; + +const BLANK_NAME_3= "test/blank3"; +const SECRET_FOR_BLANK_3 = '{"user": "integ", "password": "integpw", "config": {"id2": "example2"}}'; +const BLANK_ALIAS_INPUT_3 = "," + BLANK_NAME_3; + // Mock the inputs for Github action jest.mock('@actions/core', () => { return { - getMultilineInput: jest.fn((name: string, options?: core.InputOptions) => [TEST_NAME, TEST_INPUT_3, TEST_ARN_INPUT] ), - getBooleanInput: jest.fn((name: string, options?: core.InputOptions) => true), + getMultilineInput: jest.fn(), + getBooleanInput: jest.fn(), setFailed: jest.fn(), info: jest.fn(), debug: jest.fn(), @@ -59,6 +71,11 @@ describe('Test main action', () => { }); test('Retrieves and sets the requested secrets as environment variables, parsing JSON', async () => { + const booleanSpy = jest.spyOn(core, "getBooleanInput").mockReturnValue(true); + const multilineInputSpy = jest.spyOn(core, "getMultilineInput").mockReturnValue( + [TEST_NAME, TEST_INPUT_3, TEST_ARN_INPUT, BLANK_ALIAS_INPUT] + ); + // Mock all Secrets Manager calls smMockClient .on(GetSecretValueCommand, { SecretId: TEST_NAME_1}) @@ -84,10 +101,12 @@ describe('Test main action', () => { Name: TEST_NAME_2 } ] - }); + }) + .on(GetSecretValueCommand, { SecretId: BLANK_NAME }) + .resolves({ Name: BLANK_NAME, SecretString: SECRET_FOR_BLANK }); await run(); - expect(core.exportVariable).toHaveBeenCalledTimes(7); + expect(core.exportVariable).toHaveBeenCalledTimes(10); expect(core.setFailed).not.toHaveBeenCalled(); // JSON secrets should be parsed @@ -99,17 +118,81 @@ describe('Test main action', () => { expect(core.exportVariable).toHaveBeenCalledWith(ENV_NAME_3, SECRET_3); expect(core.exportVariable).toHaveBeenCalledWith(ENV_NAME_4, SECRET_4); - expect(core.exportVariable).toHaveBeenCalledWith(CLEANUP_NAME, JSON.stringify(['TEST_ONE_USER', 'TEST_ONE_PASSWORD', 'TEST_TWO_USER', 'TEST_TWO_PASSWORD', ENV_NAME_3, ENV_NAME_4])); + // Case when alias is blank, but still comma delimited in workflow and json is parsed + // ex: ,test5/secret + expect(core.exportVariable).toHaveBeenCalledWith("_USER", "integ"); + expect(core.exportVariable).toHaveBeenCalledWith("_PASSWORD", "integpw"); + expect(core.exportVariable).toHaveBeenCalledWith("_CONFIG_ID1", "example1"); + + expect(core.exportVariable).toHaveBeenCalledWith( + CLEANUP_NAME, + JSON.stringify([ + 'TEST_ONE_USER', 'TEST_ONE_PASSWORD', + 'TEST_TWO_USER', 'TEST_TWO_PASSWORD', + ENV_NAME_3, + ENV_NAME_4, + "_USER", "_PASSWORD", "_CONFIG_ID1" + ]) + ); + + booleanSpy.mockClear(); + multilineInputSpy.mockClear(); + }); + + test('Defaults to correct behavior with empty string alias', async () => { + const booleanSpy = jest.spyOn(core, "getBooleanInput").mockReturnValue(false); + const multilineInputSpy = jest.spyOn(core, "getMultilineInput").mockReturnValue( + [BLANK_ALIAS_INPUT_2, BLANK_ALIAS_INPUT_3] + ); + + smMockClient + .on(GetSecretValueCommand, { SecretId: BLANK_NAME_2 }) + .resolves({ Name: BLANK_NAME_2, SecretString: SECRET_FOR_BLANK_2 }) + .on(GetSecretValueCommand, { SecretId: BLANK_NAME_3 }) + .resolves({ Name: BLANK_NAME_3, SecretString: SECRET_FOR_BLANK_3 }); + + await run(); + expect(core.exportVariable).toHaveBeenCalledTimes(3); + expect(core.setFailed).not.toHaveBeenCalled(); + + // Case when alias is blank, but still comma delimited in workflow and no json is parsed + // ex: ,test/blank2 + expect(core.exportVariable).toHaveBeenCalledWith("TEST_BLANK2", "blankNameSecretString"); + expect(core.exportVariable).toHaveBeenCalledWith("TEST_BLANK3", '{"user": "integ", "password": "integpw", "config": {"id2": "example2"}}'); + + expect(core.exportVariable).toHaveBeenCalledWith( + CLEANUP_NAME, + JSON.stringify([ + "TEST_BLANK2", + "TEST_BLANK3" + ]) + ); + + booleanSpy.mockClear(); + multilineInputSpy.mockClear(); }); test('Fails the action when an error occurs in Secrets Manager', async () => { + const booleanSpy = jest.spyOn(core, "getBooleanInput").mockReturnValue(true); + const multilineInputSpy = jest.spyOn(core, "getMultilineInput").mockReturnValue( + [TEST_NAME, TEST_INPUT_3, TEST_ARN_INPUT] + ); + smMockClient.onAnyCommand().resolves({}); await run(); expect(core.setFailed).toHaveBeenCalledTimes(1); + + booleanSpy.mockClear(); + multilineInputSpy.mockClear(); }); test('Fails the action when multiple secrets exported the same variable name', async () => { + const booleanSpy = jest.spyOn(core, "getBooleanInput").mockReturnValue(true); + const multilineInputSpy =jest.spyOn(core, "getMultilineInput").mockReturnValue( + [TEST_NAME, TEST_INPUT_3, TEST_ARN_INPUT] + ); + smMockClient .on(GetSecretValueCommand, { SecretId: TEST_NAME_1}) .resolves({ Name: TEST_NAME_1, SecretString: SECRET_1 }) @@ -140,5 +223,8 @@ describe('Test main action', () => { await run(); expect(core.setFailed).toHaveBeenCalledTimes(1); + + booleanSpy.mockClear(); + multilineInputSpy.mockClear(); }); }); \ No newline at end of file diff --git a/__tests__/utils.test.ts b/__tests__/utils.test.ts index 1d6d988..7fd3419 100644 --- a/__tests__/utils.test.ts +++ b/__tests__/utils.test.ts @@ -31,6 +31,8 @@ const TEST_NAME_1 = 'test/secret1'; const VALID_ARN_2 = 'arn:aws:secretsmanager:ap-south-1:123456789000:secret:test2-aBcdef'; const TEST_NAME_2 = 'test/secret2'; +const VALID_ARN_3 = 'arn:aws:secretsmanager:ap-south-1:123456789000:secret:test3-aBcdef'; + const INVALID_ARN = 'aws:secretsmanager:us-east-1:123456789000:secret:test3-aBcdef'; jest.mock('@actions/core'); @@ -330,9 +332,14 @@ describe('Test secret parsing and handling', () => { expect(extractAliasAndSecretIdFromInput(`ARN_ALIAS,${VALID_ARN_1}`)).toEqual(['ARN_ALIAS', VALID_ARN_1]); }); - test('Returns blank for alias if none is provided', () => { - expect(extractAliasAndSecretIdFromInput("test/secret")).toEqual(['', 'test/secret']); - expect(extractAliasAndSecretIdFromInput(VALID_ARN_1)).toEqual(['', VALID_ARN_1]); + test('Returns undefined for alias if none is provided', () => { + expect(extractAliasAndSecretIdFromInput("test/secret")).toEqual([undefined, 'test/secret']); + expect(extractAliasAndSecretIdFromInput(VALID_ARN_1)).toEqual([undefined, VALID_ARN_1]); + }); + + test('Returns empty string for alias if none is provided, but comma delimited', () => { + expect(extractAliasAndSecretIdFromInput(" , test/secret")).toEqual(['', 'test/secret']); + expect(extractAliasAndSecretIdFromInput(" , "+VALID_ARN_3)).toEqual(['', VALID_ARN_3]); }); test('Throws an error if the provided alias cannot be used as the environment name', () => { diff --git a/src/index.ts b/src/index.ts index 2285323..9fa6759 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,7 @@ import { getSecretValue, injectSecret, extractAliasAndSecretIdFromInput, - SecretValueResponse + SecretValueResponse, isJSONString } from "./utils"; import { CLEANUP_NAME } from "./constants"; @@ -29,7 +29,7 @@ export async function run(): Promise { // Get and inject secret values for (let secretId of secretIds) { // Optionally let user set an alias, i.e. `ENV_NAME,secret_name` - let secretAlias = ''; + let secretAlias: string | undefined = undefined; [secretAlias, secretId] = extractAliasAndSecretIdFromInput(secretId); // Retrieves the secret name also, if the value is an ARN @@ -37,11 +37,18 @@ export async function run(): Promise { try { const secretValueResponse : SecretValueResponse = await getSecretValue(client, secretId); - if (!secretAlias){ + const secretValue = secretValueResponse.secretValue; + + // Catch if blank prefix is specified but no json is parsed to avoid blank environment variable + if ((secretAlias === '') && !(parseJsonSecrets && isJSONString(secretValue))) { + secretAlias = undefined; + } + + if (secretAlias === undefined) { secretAlias = isArn ? secretValueResponse.name : secretId; } - const injectedSecrets = injectSecret(secretAlias, secretValueResponse.secretValue, parseJsonSecrets); + const injectedSecrets = injectSecret(secretAlias, secretValue, parseJsonSecrets); secretsToCleanup = [...secretsToCleanup, ...injectedSecrets]; } catch (err) { // Fail action for any error diff --git a/src/utils.ts b/src/utils.ts index 47809bf..266f00e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -205,7 +205,7 @@ export function isSecretArn(secretId: string): boolean { /* * Separates a secret alias from the secret name/arn, if one was provided */ -export function extractAliasAndSecretIdFromInput(input: string): [string, string] { +export function extractAliasAndSecretIdFromInput(input: string): [undefined | string, string] { const parsedInput = input.split(','); if (parsedInput.length > 1){ const alias = parsedInput[0].trim(); @@ -222,7 +222,7 @@ export function extractAliasAndSecretIdFromInput(input: string): [string, string } // No alias - return [ '', input.trim() ]; + return [ undefined , input.trim() ]; } /* From d4ae0de4d39db49a5135374e2571f2f5410fa0bb Mon Sep 17 00:00:00 2001 From: YuvalShAz <159810020+YuvalShAz@users.noreply.github.com> Date: Wed, 21 Feb 2024 16:59:12 -0500 Subject: [PATCH 2/7] Updated blank aliases environment variable name behavior to remove leading underscore and removed whitespace. --- README.md | 10 +++++++--- __tests__/index.test.ts | 22 +++++++++++----------- src/utils.ts | 6 +++++- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 39c3c17..9dfee0c 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,11 @@ To use a prefix, enter at least three characters followed by an asterisk. For ex Set `parse-json-secrets` to `true` to create environment variables for each key/value pair in the JSON. +<<<<<<< HEAD Note that if the JSON uses case-sensitive keys such as "name" and "Name", the action will have duplicate name conflicts. In this case, set `parse-json-secrets` to `false` and parse the JSON secret value separately. Additionally, if the secret is JSON and this flag is true: blank aliases are allowed and result in an environment variables with a leading underscore (see Example 4). +======= +Note that if the JSON uses case-sensitive keys such as "name" and "Name", the action will have duplicate name conflicts. In this case, set `parse-json-secrets` to `false` and parse the JSON secret value separately. Additionally, if the secret is JSON and this flag is true: blank aliases are allowed and result in environment variables with no prefix (see Example 4). +>>>>>>> 5c54aff (Updated blank alias prefixing to remove leading underscore) ​ ### Examples ​ @@ -167,9 +171,9 @@ plaintextsecret Environment variables created: ​ ``` -_API_USER: "user" -_API_KEY: "key" -_CONFIG_ACTIVE: "true" +API_USER: "user" +API_KEY: "key" +CONFIG_ACTIVE: "true" TEST_BLANKALIASSECRET2: "plaintextsecret" ``` If the `parse-json-secrets` flag is toggled to false; each secret is treated as a plaintext string (even if it's JSON formatted) and the behavior of `test/blankAliasSecret2` is applied for a blank alias. diff --git a/__tests__/index.test.ts b/__tests__/index.test.ts index 22c7999..1bd9939 100644 --- a/__tests__/index.test.ts +++ b/__tests__/index.test.ts @@ -32,16 +32,16 @@ const ENV_NAME_4 = 'ARN_ALIAS'; const SECRET_4 = "secretString2"; const TEST_ARN_INPUT = ENV_NAME_4 + "," + TEST_ARN_1; -const BLANK_NAME= "test/blank"; -const SECRET_FOR_BLANK = '{"user": "integ", "password": "integpw", "config": {"id1": "example1"}}'; +const BLANK_NAME = "test/blank"; +const SECRET_FOR_BLANK = '{"username": "integ", "password": "integpw", "config": {"id1": "example1"}}'; const BLANK_ALIAS_INPUT = "," + BLANK_NAME; -const BLANK_NAME_2= "test/blank2"; +const BLANK_NAME_2 = "test/blank2"; const SECRET_FOR_BLANK_2 = "blankNameSecretString"; const BLANK_ALIAS_INPUT_2 = "," + BLANK_NAME_2; -const BLANK_NAME_3= "test/blank3"; -const SECRET_FOR_BLANK_3 = '{"user": "integ", "password": "integpw", "config": {"id2": "example2"}}'; +const BLANK_NAME_3 = "test/blank3"; +const SECRET_FOR_BLANK_3 = '{"username": "integ", "password": "integpw", "config": {"id2": "example2"}}'; const BLANK_ALIAS_INPUT_3 = "," + BLANK_NAME_3; // Mock the inputs for Github action @@ -120,9 +120,9 @@ describe('Test main action', () => { // Case when alias is blank, but still comma delimited in workflow and json is parsed // ex: ,test5/secret - expect(core.exportVariable).toHaveBeenCalledWith("_USER", "integ"); - expect(core.exportVariable).toHaveBeenCalledWith("_PASSWORD", "integpw"); - expect(core.exportVariable).toHaveBeenCalledWith("_CONFIG_ID1", "example1"); + expect(core.exportVariable).toHaveBeenCalledWith("USERNAME", "integ"); + expect(core.exportVariable).toHaveBeenCalledWith("PASSWORD", "integpw"); + expect(core.exportVariable).toHaveBeenCalledWith("CONFIG_ID1", "example1"); expect(core.exportVariable).toHaveBeenCalledWith( CLEANUP_NAME, @@ -131,7 +131,7 @@ describe('Test main action', () => { 'TEST_TWO_USER', 'TEST_TWO_PASSWORD', ENV_NAME_3, ENV_NAME_4, - "_USER", "_PASSWORD", "_CONFIG_ID1" + "USERNAME", "PASSWORD", "CONFIG_ID1" ]) ); @@ -158,7 +158,7 @@ describe('Test main action', () => { // Case when alias is blank, but still comma delimited in workflow and no json is parsed // ex: ,test/blank2 expect(core.exportVariable).toHaveBeenCalledWith("TEST_BLANK2", "blankNameSecretString"); - expect(core.exportVariable).toHaveBeenCalledWith("TEST_BLANK3", '{"user": "integ", "password": "integpw", "config": {"id2": "example2"}}'); + expect(core.exportVariable).toHaveBeenCalledWith("TEST_BLANK3", '{"username": "integ", "password": "integpw", "config": {"id2": "example2"}}'); expect(core.exportVariable).toHaveBeenCalledWith( CLEANUP_NAME, @@ -189,7 +189,7 @@ describe('Test main action', () => { test('Fails the action when multiple secrets exported the same variable name', async () => { const booleanSpy = jest.spyOn(core, "getBooleanInput").mockReturnValue(true); - const multilineInputSpy =jest.spyOn(core, "getMultilineInput").mockReturnValue( + const multilineInputSpy = jest.spyOn(core, "getMultilineInput").mockReturnValue( [TEST_NAME, TEST_INPUT_3, TEST_ARN_INPUT] ); diff --git a/src/utils.ts b/src/utils.ts index 266f00e..78f8bb1 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -138,8 +138,12 @@ export function injectSecret(secretName: string, secretValue: string, parseJsonS for (const k in secretMap) { const keyValue = typeof secretMap[k] === 'string' ? secretMap[k] as string : JSON.stringify(secretMap[k]); + // Check to avoid prepending an underscore + const newEnvNamePrefix = tempEnvName || transformToValidEnvName(secretName); + const newEnvNameSpacer: "_"|"" = newEnvNamePrefix ? "_" : ""; + // Append the current key to the name of the env variable - const newEnvName = `${tempEnvName || transformToValidEnvName(secretName)}_${transformToValidEnvName(k)}`; + const newEnvName = `${newEnvNamePrefix}${newEnvNameSpacer}${transformToValidEnvName(k)}`; secretsToCleanup = [...secretsToCleanup, ...injectSecret(secretName, keyValue, parseJsonSecrets, newEnvName)]; } } else { From d7344f74e9c594e63a6b5bad399266fbebe3418d Mon Sep 17 00:00:00 2001 From: YuvalShAz <159810020+YuvalShAz@users.noreply.github.com> Date: Thu, 22 Feb 2024 17:44:25 -0500 Subject: [PATCH 3/7] Updated blank aliases environment variable name behavior to remove leading underscore and removed whitespace, removed merge artifact from README and implemented cleaner implementation for the leading underscore removal --- README.md | 4 ---- src/utils.ts | 12 +++++++----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 9dfee0c..4986dfe 100644 --- a/README.md +++ b/README.md @@ -57,11 +57,7 @@ To use a prefix, enter at least three characters followed by an asterisk. For ex Set `parse-json-secrets` to `true` to create environment variables for each key/value pair in the JSON. -<<<<<<< HEAD -Note that if the JSON uses case-sensitive keys such as "name" and "Name", the action will have duplicate name conflicts. In this case, set `parse-json-secrets` to `false` and parse the JSON secret value separately. Additionally, if the secret is JSON and this flag is true: blank aliases are allowed and result in an environment variables with a leading underscore (see Example 4). -======= Note that if the JSON uses case-sensitive keys such as "name" and "Name", the action will have duplicate name conflicts. In this case, set `parse-json-secrets` to `false` and parse the JSON secret value separately. Additionally, if the secret is JSON and this flag is true: blank aliases are allowed and result in environment variables with no prefix (see Example 4). ->>>>>>> 5c54aff (Updated blank alias prefixing to remove leading underscore) ​ ### Examples ​ diff --git a/src/utils.ts b/src/utils.ts index 78f8bb1..b23453b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -138,12 +138,14 @@ export function injectSecret(secretName: string, secretValue: string, parseJsonS for (const k in secretMap) { const keyValue = typeof secretMap[k] === 'string' ? secretMap[k] as string : JSON.stringify(secretMap[k]); - // Check to avoid prepending an underscore - const newEnvNamePrefix = tempEnvName || transformToValidEnvName(secretName); - const newEnvNameSpacer: "_"|"" = newEnvNamePrefix ? "_" : ""; + // Append the current key to the name of the env variable and check to avoid prepending an underscore + const newEnvName = [ + tempEnvName || transformToValidEnvName(secretName), + transformToValidEnvName(k) + ] + .filter(elem => elem) // Uses truthy-ness of elem to determine if it remains + .join("_"); // Join the remaining elements with an underscore - // Append the current key to the name of the env variable - const newEnvName = `${newEnvNamePrefix}${newEnvNameSpacer}${transformToValidEnvName(k)}`; secretsToCleanup = [...secretsToCleanup, ...injectSecret(secretName, keyValue, parseJsonSecrets, newEnvName)]; } } else { From 28150ab1af1736cb33acc1b98a36f4fd1484874d Mon Sep 17 00:00:00 2001 From: YuvalShAz <159810020+YuvalShAz@users.noreply.github.com> Date: Thu, 29 Feb 2024 11:11:58 -0500 Subject: [PATCH 4/7] Updated README with documentation consistent to AWS Docs page --- README.md | 179 +++++++++++++++++++++++++----------------------------- 1 file changed, 82 insertions(+), 97 deletions(-) diff --git a/README.md b/README.md index 4986dfe..e3aada2 100644 --- a/README.md +++ b/README.md @@ -1,69 +1,89 @@ -# Use AWS Secrets Manager secrets in GitHub jobs -​ +# Use AWS Secrets Manager secrets in GitHub jobs + To use a secret in a GitHub job, you can use a GitHub action to retrieve secrets from AWS Secrets Manager and add them as masked [Environment variables](https://docs.github.com/en/actions/learn-github-actions/environment-variables) in your GitHub workflow. For more information about GitHub Actions, see [Understanding GitHub Actions](https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions) in the *GitHub Docs*. -​ When you add a secret to your GitHub environment, it is available to all other steps in your GitHub job. Follow the guidance in [Security hardening for GitHub Actions](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions) to help prevent secrets in your environment from being misused. -​ - -Environment variables have stricter naming requirements than secrets, so this action transforms secret names to meet those requirements. For example, the action transforms lowercase letters to uppercase letters. Because of the transformed names, two environment variables might end up with the same name. For example, a secret named "MySecret" and a secret named "mysecret" would both become environment variables named "MYSECRET". In this case, the action will fail, because environment variable names must be unique. Instead, you must specify the name you want to use for the environment variable. -​ -You can set the entire string in the secret value as the environment variable value, or if the string is JSON, you can parse the JSON to set individual environment variables for each JSON key-value pair. If the secret value is a binary, the action converts it to a string. -​ +You can set the entire string in the secret value as the environment variable value, or if the string is JSON, you can parse the JSON to set individual environment variables for each JSON key\-value pair. If the secret value is a binary, the action converts it to a string. To view the environment variables created from your secrets, turn on debug logging. For more information, see [Enabling debug logging](https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/enabling-debug-logging) in the *GitHub Docs*. -​ -​ -### Prerequisites -​ -To use this action, you first need to configure AWS credentials and set the AWS Region in your GitHub environment by using the `configure-aws-credentials` step. Follow the instructions in [Configure AWS Credentials Action For GitHub Actions](https://github.com/aws-actions/configure-aws-credentials) to **Assume role directly using GitHub OIDC provider**. This allows you to use short-lived credentials and avoid storing additional access keys outside of Secrets Manager. -​ + +To use the environment variables created from your secrets, see [Environment variables](https://docs.github.com/en/actions/learn-github-actions/environment-variables) in the *GitHub Docs*. + +## Prerequisites + +To use this action, you first need to configure AWS credentials and set the AWS Region in your GitHub environment by using the `configure-aws-credentials` step. Follow the instructions in [Configure AWS Credentials Action For GitHub Actions](https://github.com/aws-actions/configure-aws-credentials) to **Assume role directly using GitHub OIDC provider**. This allows you to use short\-lived credentials and avoid storing additional access keys outside of Secrets Manager. + The IAM role the action assumes must have the following permissions: -+ `GetSecretValue` on the secrets you want to retrieve -+ `ListSecrets` on all secrets -+ (Optional) `Decrypt` on the KMS key if the secrets are encrypted with a customer managed key. -​ -For more information, see [Authentication and access control for AWS Secrets Manager](https://docs.aws.amazon.com/secretsmanager/latest/userguide/auth-and-access.html). -​ -### Usage -​ ++ `GetSecretValue` on the secrets you want to retrieve. ++ `ListSecrets` on all secrets. ++ \(Optional\) `Decrypt` on the KMS key if the secrets are encrypted with a customer managed key. + +For more information, see [Authentication and access control for AWS Secrets Manager](auth-and-access.md). + +## Usage + To use the action, add a step to your workflow that uses the following syntax. -​ + ``` - name: Step name uses: aws-actions/aws-secretsmanager-get-secrets@v1 with: - secret-ids: | + secret-ids: secretId1 - ENV_VAR, secretId2 + ENV_VAR_NAME, secretId2 parse-json-secrets: (Optional) true|false ``` +Parameters -### Parameters -- `secret-ids`: Secret ARNS, names, and name prefixes. +- `secret-ids` Secret ARNS, names, and name prefixes. -By default, the step creates each environment variable name from the secret name, transformed to include only uppercase letters, numbers, and underscores, and so that it doesn't begin with a number. +By default, the step creates each environment variable name from the secret name, transformed to include only uppercase letters, numbers, and underscores, and so that it doesn't begin with a number. -To set the environment variable name, enter it before the secret ID, followed by a comma. For example `ENV_VAR_1, secretId` creates an environment variable named **ENV_VAR_1** from the secret `secretId`. A line with an empty environment variable name but a leading comma (ex: `, secretId`) behaves as if the entry didn't have that leading comma (ex: `secretId`) if the secret is not valid JSON or the `parse-json-secrets` flag is false. +To set the environment variable name, enter it before the secret ID, followed by a comma. For example `ENV_VAR_1, secretId` creates an environment variable named **ENV\_VAR\_1** from the secret `secretId`. -The environment variable name can consist of uppercase letters, numbers, and underscores. +The environment variable name can consist of uppercase letters, numbers, and underscores. To use a prefix, enter at least three characters followed by an asterisk. For example `dev*` matches all secrets with a name beginning in **dev**. The maximum number of matching secrets that can be retrieved is 100. If you set the variable name, and the prefix matches multiple secrets, then the action fails. -​ + - `parse-json-secrets` (Optional - default false) By default, the action sets the environment variable value to the entire JSON string in the secret value. Set `parse-json-secrets` to `true` to create environment variables for each key/value pair in the JSON. -Note that if the JSON uses case-sensitive keys such as "name" and "Name", the action will have duplicate name conflicts. In this case, set `parse-json-secrets` to `false` and parse the JSON secret value separately. Additionally, if the secret is JSON and this flag is true: blank aliases are allowed and result in environment variables with no prefix (see Example 4). -​ -### Examples -​ -**Example 1: Get secrets by name and by ARN** +Note that if the JSON uses case-sensitive keys such as "name" and "Name", the action will have duplicate name conflicts. In this case, set `parse-json-secrets` to `false` and parse the JSON secret value separately. + +## Environment variable naming + +The environment variables created by the action are named the same as the secrets they comes from. If you parse the JSON of the secret, then the environment variable name includes both the secret name and the JSON key name, for example `MYSECRET_KEYNAME`. + +Environment variables have stricter naming requirements than secrets, so this action transforms secret names to meet those requirements. For example, the action transforms lowercase letters to uppercase letters. Because of the transformed names, two environment variables might end up with the same name. For example, a secret named "MySecret" and a secret named "mysecret" would both become environment variables named "MYSECRET". In this case, the action will fail, because environment variable names must be unique. Instead, you must specify the name you want to use for the environment variable. + +You can set the environment variable name by specifying an *alias*, as shown in the following example which creates a variable named `ENV_VAR_NAME`. + +``` +secret-ids: +ENV_VAR_NAME, secretId2 +``` + +**Blank aliases** ++ If you set `parse-json-secrets: true` and enter a blank alias, followed by a comma and then the secret ID, the action names the environment variable the same as the parsed JSON keys. The variable names do not include the secret name. + + If the secret doesn't contain valid JSON, then the action creates one environment variable and names it the same as the secret name. ++ If you set `parse-json-secrets: false` and enter a blank alias, followed by a comma and the secret ID, the action names the environment variables as if you did not specify an alias. + +The following example shows a blank alias. + +``` +,secret2 +``` + +## Examples + +**Example 1 Get secrets by name and by ARN** The following example creates environment variables for secrets identified by name and by ARN. -​ + ``` - name: Get secrets by name and by ARN uses: aws-actions/aws-secretsmanager-get-secrets@v1 @@ -75,11 +95,10 @@ The following example creates environment variables for secrets identified by na /prod/example/secret SECRET_ALIAS_1,test/secret SECRET_ALIAS_2,arn:aws:secretsmanager:us-east-2:123456789012:secret:test2-a1b2c3 + ,secret2 ``` - - Environment variables created: -​ + ``` EXAMPLESECRETNAME: secretValue1 TEST1: secretValue2 @@ -87,11 +106,12 @@ _0_TEST_SECRET: secretValue3 _PROD_EXAMPLE_SECRET: secretValue4 SECRET_ALIAS_1: secretValue5 SECRET_ALIAS_2: secretValue6 +SECRET2: secretValue7 ``` -​ -**Example 2: Get all secrets that begin with a prefix** + +**Example 2 Get all secrets that begin with a prefix** The following example creates environment variables for all secrets with names that begin with *beta*. -​ + ``` - name: Get Secret Names by Prefix uses: aws-actions/aws-secretsmanager-get-secrets@v1 @@ -99,27 +119,28 @@ The following example creates environment variables for all secrets with names t secret-ids: | beta* # Retrieves all secrets that start with 'beta' ``` -Assuming the search for `beta` produces 3 results (`betaSecretName`, `betaTest` and `beta/NewSecret`, environment variables created: -​ +Environment variables created: + ``` BETASECRETNAME: secretValue1 BETATEST: secretValue2 BETA_NEWSECRET: secretValue3 ``` -​ -**Example 3: Parse JSON in secret** + +**Example 3 Parse JSON in secret** The following example creates environment variables by parsing the JSON in the secret. -​ + ``` - name: Get Secrets by Name and by ARN uses: aws-actions/aws-secretsmanager-get-secrets@v1 with: secret-ids: | test/secret - parse-json-secrets: true + ,secret2 + parse-json-secrets: true ``` The secret `test/secret` has the following secret value. -​ + ``` { "api_user": "user", @@ -129,56 +150,20 @@ The secret `test/secret` has the following secret value. } } ``` -Environment variables created: -​ -``` -TEST_SECRET_API_USER: "user" -TEST_SECRET_API_KEY: "key" -TEST_SECRET_CONFIG_ACTIVE: "true" -``` +The secret `secret2` has the following secret value. -**Example 4: Parse JSON in secret with blank alias** -The following example creates environment variables by parsing the JSON in the secret without assigning an alias. -``` -- name: Get Secrets by Name and by ARN - uses: aws-actions/aws-secretsmanager-get-secrets@v1 - with: - secret-ids: | - , test/blankAliasSecret - , test/blankAliasSecret2 - parse-json-secrets: true -``` -The secret `test/blankAliasSecret` has the following secret value. -​ ``` { - "api_user": "user", - "api_key": "key", - "config": { - "active": "true" - } + "myusername": "alejandro_rosalez", + "mypassword": "EXAMPLE_PASSWORD" } ``` -And secret `test/blankAliasSecret2` has the following secret value. -​ -``` -plaintextsecret -``` Environment variables created: -​ -``` -API_USER: "user" -API_KEY: "key" -CONFIG_ACTIVE: "true" -TEST_BLANKALIASSECRET2: "plaintextsecret" -``` -If the `parse-json-secrets` flag is toggled to false; each secret is treated as a plaintext string (even if it's JSON formatted) and the behavior of `test/blankAliasSecret2` is applied for a blank alias. - -## Security - -See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. - -## License - -This library is licensed under the MIT-0 License. See the LICENSE file. +``` +TEST_SECRET_API_USER: "user" +TEST_SECRET_API_KEY: "key" +TEST_SECRET_CONFIG_ACTIVE: "true" +MYUSERNAME: "alejandro_rosalez" +MYPASSWORD: "EXAMPLE_PASSWORD" +``` \ No newline at end of file From 344244a4f16c3c6ed4d9021e61917b214b381bb2 Mon Sep 17 00:00:00 2001 From: YuvalShAz <159810020+YuvalShAz@users.noreply.github.com> Date: Thu, 29 Feb 2024 11:18:52 -0500 Subject: [PATCH 5/7] Added back Security and License sections - and changed some whitespace --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e3aada2..0ae362b 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ You can set the environment variable name by specifying an *alias*, as shown in ``` secret-ids: -ENV_VAR_NAME, secretId2 + ENV_VAR_NAME, secretId2 ``` **Blank aliases** @@ -166,4 +166,12 @@ TEST_SECRET_API_KEY: "key" TEST_SECRET_CONFIG_ACTIVE: "true" MYUSERNAME: "alejandro_rosalez" MYPASSWORD: "EXAMPLE_PASSWORD" -``` \ No newline at end of file +``` + +## Security + +See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. + +## License + +This library is licensed under the MIT-0 License. See the LICENSE file. \ No newline at end of file From 083fc595cf55e8b9703f2adb100f779d57910e29 Mon Sep 17 00:00:00 2001 From: YuvalShAz <159810020+YuvalShAz@users.noreply.github.com> Date: Thu, 29 Feb 2024 17:44:26 -0500 Subject: [PATCH 6/7] removed hyperlinks and leftover escaped backslashes, added back removed hashtags to section-headers and pipes to indicate multiline comments --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 0ae362b..c316546 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,18 @@ -# Use AWS Secrets Manager secrets in GitHub jobs +# Use AWS Secrets Manager secrets in GitHub jobs To use a secret in a GitHub job, you can use a GitHub action to retrieve secrets from AWS Secrets Manager and add them as masked [Environment variables](https://docs.github.com/en/actions/learn-github-actions/environment-variables) in your GitHub workflow. For more information about GitHub Actions, see [Understanding GitHub Actions](https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions) in the *GitHub Docs*. When you add a secret to your GitHub environment, it is available to all other steps in your GitHub job. Follow the guidance in [Security hardening for GitHub Actions](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions) to help prevent secrets in your environment from being misused. -You can set the entire string in the secret value as the environment variable value, or if the string is JSON, you can parse the JSON to set individual environment variables for each JSON key\-value pair. If the secret value is a binary, the action converts it to a string. +You can set the entire string in the secret value as the environment variable value, or if the string is JSON, you can parse the JSON to set individual environment variables for each JSON key-value pair. If the secret value is a binary, the action converts it to a string. To view the environment variables created from your secrets, turn on debug logging. For more information, see [Enabling debug logging](https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/enabling-debug-logging) in the *GitHub Docs*. To use the environment variables created from your secrets, see [Environment variables](https://docs.github.com/en/actions/learn-github-actions/environment-variables) in the *GitHub Docs*. -## Prerequisites +### Prerequisites -To use this action, you first need to configure AWS credentials and set the AWS Region in your GitHub environment by using the `configure-aws-credentials` step. Follow the instructions in [Configure AWS Credentials Action For GitHub Actions](https://github.com/aws-actions/configure-aws-credentials) to **Assume role directly using GitHub OIDC provider**. This allows you to use short\-lived credentials and avoid storing additional access keys outside of Secrets Manager. +To use this action, you first need to configure AWS credentials and set the AWS Region in your GitHub environment by using the `configure-aws-credentials` step. Follow the instructions in [Configure AWS Credentials Action For GitHub Actions](https://github.com/aws-actions/configure-aws-credentials) to **Assume role directly using GitHub OIDC provider**. This allows you to use short-lived credentials and avoid storing additional access keys outside of Secrets Manager. The IAM role the action assumes must have the following permissions: + `GetSecretValue` on the secrets you want to retrieve. @@ -21,7 +21,7 @@ The IAM role the action assumes must have the following permissions: For more information, see [Authentication and access control for AWS Secrets Manager](auth-and-access.md). -## Usage +### Usage To use the action, add a step to your workflow that uses the following syntax. @@ -29,7 +29,7 @@ To use the action, add a step to your workflow that uses the following syntax. - name: Step name uses: aws-actions/aws-secretsmanager-get-secrets@v1 with: - secret-ids: + secret-ids: | secretId1 ENV_VAR_NAME, secretId2 parse-json-secrets: (Optional) true|false @@ -54,7 +54,7 @@ Set `parse-json-secrets` to `true` to create environment variables for each key/ Note that if the JSON uses case-sensitive keys such as "name" and "Name", the action will have duplicate name conflicts. In this case, set `parse-json-secrets` to `false` and parse the JSON secret value separately. -## Environment variable naming +### Environment variable naming The environment variables created by the action are named the same as the secrets they comes from. If you parse the JSON of the secret, then the environment variable name includes both the secret name and the JSON key name, for example `MYSECRET_KEYNAME`. @@ -63,7 +63,7 @@ Environment variables have stricter naming requirements than secrets, so this ac You can set the environment variable name by specifying an *alias*, as shown in the following example which creates a variable named `ENV_VAR_NAME`. ``` -secret-ids: +secret-ids: | ENV_VAR_NAME, secretId2 ``` @@ -79,7 +79,7 @@ The following example shows a blank alias. ,secret2 ``` -## Examples +### Examples **Example 1 Get secrets by name and by ARN** The following example creates environment variables for secrets identified by name and by ARN. From ef7134db189c30443d11d11745e6e522feb963be Mon Sep 17 00:00:00 2001 From: YuvalShAz <159810020+YuvalShAz@users.noreply.github.com> Date: Wed, 6 Mar 2024 11:00:12 -0500 Subject: [PATCH 7/7] added env var naming conflict guide --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c316546..b0c55bb 100644 --- a/README.md +++ b/README.md @@ -56,9 +56,13 @@ Note that if the JSON uses case-sensitive keys such as "name" and "Name", the ac ### Environment variable naming -The environment variables created by the action are named the same as the secrets they comes from. If you parse the JSON of the secret, then the environment variable name includes both the secret name and the JSON key name, for example `MYSECRET_KEYNAME`. +The environment variables created by the action are named the same as the secrets they come from. Environment variables have stricter naming requirements than secrets, so the action transforms secret names to meet those requirements. For example, the action transforms lowercase letters to uppercase letters. If you parse the JSON of the secret, then the environment variable name includes both the secret name and the JSON key name, for example `MYSECRET_KEYNAME`. -Environment variables have stricter naming requirements than secrets, so this action transforms secret names to meet those requirements. For example, the action transforms lowercase letters to uppercase letters. Because of the transformed names, two environment variables might end up with the same name. For example, a secret named "MySecret" and a secret named "mysecret" would both become environment variables named "MYSECRET". In this case, the action will fail, because environment variable names must be unique. Instead, you must specify the name you want to use for the environment variable. +If two environment variables would end up with the same name, the action fails. In this case, you must specify the names you want to use for the environment variables as *aliases*. + +Examples of when the names might conflict: ++ A secret named "MySecret" and a secret named "mysecret" would both become environment variables named "MYSECRET". ++ A secret named "Secret_keyname" and a JSON-parsed secret named "Secret" with a key named "keyname" would both become environment variables named "SECRET_KEYNAME". You can set the environment variable name by specifying an *alias*, as shown in the following example which creates a variable named `ENV_VAR_NAME`.