Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return secrets as outputs instead of values #36

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 48 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
# 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*.
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 Action outputs, accessable from the [`steps` context](https://docs.github.com/en/actions/learn-github-actions/contexts#steps-context) 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.
Output variables have stricter naming requirements than secrets, so this action transforms secret names to meet those requirements. For example, the action transforms special characters like `@/:` to underscores. Because of the transformed names, two output variables might end up with the same name. For example, a secret named "app/secret" and a secret named "app@secret" would both become output variables named "app_secret". In this case, the action will fail, to avoid accidentally overwriting a secret you intended to use. Instead, you must specify the name you want to use for the output 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 output variable value, or if the string is JSON, you can parse the JSON to set individual output 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 view the output 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
Expand All @@ -30,43 +28,54 @@ For more information, see [Authentication and access control for AWS Secrets Man
To use the action, add a step to your workflow that uses the following syntax.
```
```yaml
- name: Step name
uses: aws-actions/aws-secretsmanager-get-secrets@v1
id: secret-step-id
uses: aws-actions/aws-secretsmanager-get-secrets@v2
with:
secret-ids: |
secretId1
ENV_VAR, secretId2
OUT_VAR, secretId2
parse-json-secrets: (Optional) true|false
```

The secrets you retrieve will be available as outputs from the step in which you used this action. To access them, you will need to use the `steps` context, setting them as environment variables or passing them to other actions as needed. You can choose your own id to replace `secret-step-id` in the above snippet.

For example:
```yaml
- name: Step that uses secrets
env:
MY_SECRET: ${{ steps.secret-step-id.outputs.secretId1 }}
run: auth.sh --secret $MY_SECRET
```

### 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 output variable name from the secret name, transformed to include only 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 output variable name, enter it before the secret ID, followed by a comma. For example `OUT_VAR_1, secretId` creates an output variable named **OUT_VAR_1** from the secret `secretId` which will be accessible at `${{ steps.secret-step-id.outputs.OUT_VAR_1 }}`.

The environment variable name can consist of uppercase letters, numbers, and underscores.
The output variable name can consist of 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.
(Optional - default false) By default, the action sets the output 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.
Set `parse-json-secrets` to `true` to return variables for each key/value pair in the JSON.
### Examples
**Example 1: Get secrets by name and by ARN**
The following example creates environment variables for secrets identified by name and by ARN.
The following example returns output variables for secrets identified by name and by ARN.
```
```yaml
- name: Get secrets by name and by ARN
uses: aws-actions/aws-secretsmanager-get-secrets@v1
id: secrets-step
uses: aws-actions/aws-secretsmanager-get-secrets@v2
with:
secret-ids: |
exampleSecretName
Expand All @@ -78,39 +87,39 @@ The following example creates environment variables for secrets identified by na
```


Environment variables created:
Outputs returned:
```
EXAMPLESECRETNAME: secretValue1
TEST1: secretValue2
_0_TEST_SECRET: secretValue3
_PROD_EXAMPLE_SECRET: secretValue4
SECRET_ALIAS_1: secretValue5
SECRET_ALIAS_2: secretValue6
${{ steps.secrets-step.outputs.exampleSecretName }}: secretValue1
${{ steps.secrets-step.outputs.test1 }}: secretValue2
${{ steps.secrets-step.outputs._0_test_secret }}: secretValue3
${{ steps.secrets-step.outputs._prod_example_secret }}: secretValue4
${{ steps.secrets-step.outputs.SECRET_ALIAS_1 }}: secretValue5
${{ steps.secrets-step.outputs.SECRET_ALIAS_2 }}: secretValue6
```
**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*.
The following example creates output variables for all secrets with names that begin with *beta*.
```
```yaml
- name: Get Secret Names by Prefix
uses: aws-actions/aws-secretsmanager-get-secrets@v1
with:
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:
Assuming the search for `beta` produces 3 results (`betaSecretName`, `betaTest` and `beta/NewSecret`, the following output variables are created:
```
BETASECRETNAME: secretValue1
BETATEST: secretValue2
BETA_NEWSECRET: secretValue3
${{ steps.secrets-step.outputs.betaSecretName }}: secretValue1
${{ steps.secrets-step.outputs.betaTest }}: secretValue2
${{ steps.secrets-step.outputs.beta_NewSecret }}: secretValue3
```
**Example 3: Parse JSON in secret**
The following example creates environment variables by parsing the JSON in the secret.
The following example creates output variables by parsing the JSON in the secret.
```
```yaml
- name: Get Secrets by Name and by ARN
uses: aws-actions/aws-secretsmanager-get-secrets@v1
with:
Expand All @@ -120,7 +129,7 @@ The following example creates environment variables by parsing the JSON in the s
```
The secret `test/secret` has the following secret value.
```
```json
{
"api_user": "user",
"api_key": "key",
Expand All @@ -129,12 +138,12 @@ The secret `test/secret` has the following secret value.
}
}
```
Environment variables created:
Output variables created:
```
TEST_SECRET_API_USER: "user"
TEST_SECRET_API_KEY: "key"
TEST_SECRET_CONFIG_ACTIVE: "true"
${{ steps.secrets-step.outputs.test_secret_api_user }}: "user"
${{ steps.secrets-step.outputs.test_secret_api_key }}: "key"
${{ steps.secrets-step.outputs.test_secret_config_active }}: "true"
```

## Security
Expand Down
59 changes: 0 additions & 59 deletions __tests__/cleanup.test.ts

This file was deleted.

36 changes: 14 additions & 22 deletions __tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
SecretsManagerClient,
} from '@aws-sdk/client-secrets-manager';
import { run } from "../src";
import { CLEANUP_NAME } from "../src/constants";

const DEFAULT_TEST_ENV = {
AWS_DEFAULT_REGION: 'us-east-1'
Expand All @@ -22,15 +21,15 @@ const TEST_NAME_2 = "test/two";
const SECRET_2 = '{"user": "integ", "password": "integpw"}';

const TEST_NAME_3 = "app/secret";
const ENV_NAME_3 = "SECRET_ALIAS";
const VAR_NAME_3 = "SECRET_ALIAS";
const SECRET_3 = "secretString1";
const TEST_INPUT_3 = ENV_NAME_3 + "," + TEST_NAME_3;
const TEST_INPUT_3 = VAR_NAME_3 + "," + TEST_NAME_3;

const TEST_ARN_1 = 'arn:aws:secretsmanager:ap-south-1:123456789000:secret:test2-aBcdef';
const TEST_NAME_4 = 'arn/secret-name';
const ENV_NAME_4 = 'ARN_ALIAS';
const VAR_NAME_4 = 'ARN_ALIAS';
const SECRET_4 = "secretString2";
const TEST_ARN_INPUT = ENV_NAME_4 + "," + TEST_ARN_1;
const TEST_ARN_INPUT = VAR_NAME_4 + "," + TEST_ARN_1;

// Mock the inputs for Github action
jest.mock('@actions/core', () => {
Expand All @@ -40,25 +39,20 @@ jest.mock('@actions/core', () => {
setFailed: jest.fn(),
info: jest.fn(),
debug: jest.fn(),
exportVariable: jest.fn((name: string, val: string) => process.env[name] = val),
setOutput: jest.fn(),
setSecret: jest.fn(),
};
});

describe('Test main action', () => {
const OLD_ENV = process.env;

beforeEach(() => {
jest.clearAllMocks();
smMockClient.reset();
process.env = {...OLD_ENV, ...DEFAULT_TEST_ENV};
process.env = DEFAULT_TEST_ENV;
});

afterEach(() => {
process.env = OLD_ENV;
});

test('Retrieves and sets the requested secrets as environment variables, parsing JSON', async () => {
test('Retrieves and sets the requested secrets as output variables, parsing JSON', async () => {
// Mock all Secrets Manager calls
smMockClient
.on(GetSecretValueCommand, { SecretId: TEST_NAME_1})
Expand Down Expand Up @@ -87,19 +81,17 @@ describe('Test main action', () => {
});

await run();
expect(core.exportVariable).toHaveBeenCalledTimes(7);
expect(core.setOutput).toHaveBeenCalledTimes(6);
expect(core.setFailed).not.toHaveBeenCalled();

// JSON secrets should be parsed
expect(core.exportVariable).toHaveBeenCalledWith('TEST_ONE_USER', 'admin');
expect(core.exportVariable).toHaveBeenCalledWith('TEST_ONE_PASSWORD', 'adminpw');
expect(core.exportVariable).toHaveBeenCalledWith('TEST_TWO_USER', 'integ');
expect(core.exportVariable).toHaveBeenCalledWith('TEST_TWO_PASSWORD', 'integpw');

expect(core.exportVariable).toHaveBeenCalledWith(ENV_NAME_3, SECRET_3);
expect(core.exportVariable).toHaveBeenCalledWith(ENV_NAME_4, SECRET_4);
expect(core.setOutput).toHaveBeenCalledWith('test_one_user', 'admin');
expect(core.setOutput).toHaveBeenCalledWith('test_one_password', 'adminpw');
expect(core.setOutput).toHaveBeenCalledWith("test_two_user", "integ");
expect(core.setOutput).toHaveBeenCalledWith('test_two_password', 'integpw');

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]));
expect(core.setOutput).toHaveBeenCalledWith(VAR_NAME_3, SECRET_3);
expect(core.setOutput).toHaveBeenCalledWith(VAR_NAME_4, SECRET_4);
});

test('Fails the action when an error occurs in Secrets Manager', async () => {
Expand Down
Loading