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

feat: provide input to optionally mask output docker password #491

Merged
merged 1 commit into from
Aug 8, 2023
Merged
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
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,28 @@ Logs in the local Docker client to one or more Amazon ECR Private registries or
docker push $REGISTRY/$REPOSITORY:$IMAGE_TAG
```

#### Login to Amazon ECR Private, then build and push a Docker image masking the password:

> [!WARNING]
> Setting mask-password to true will prevent the password GitHub output from being shared between separate jobs.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
> Setting mask-password to true will prevent the password GitHub output from being shared between separate jobs.
> Setting mask-password to true will prevent the password used to login to ECR from being shared between jobs.


```yaml
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
with:
mask-password: 'true'

- name: Build, tag, and push docker image to Amazon ECR
env:
REGISTRY: ${{ steps.login-ecr.outputs.registry }}
REPOSITORY: my-ecr-repo
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $REGISTRY/$REPOSITORY:$IMAGE_TAG .
docker push $REGISTRY/$REPOSITORY:$IMAGE_TAG
```

#### Login to Amazon ECR Public, then build and push a Docker image:
```yaml
- name: Login to Amazon ECR Public
Expand Down
27 changes: 17 additions & 10 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,37 @@ branding:
icon: 'cloud'
color: 'orange'
inputs:
registries:
http-proxy:
description: >-
A comma-delimited list of AWS account IDs that are associated with the ECR Private registries.
If you do not specify a registry, the default ECR Private registry is assumed.
If 'public' is given as input to 'registry-type', this input is ignored.
Proxy to use for the AWS SDK agent.
required: false
skip-logout:
mask-password:
description: >-
Whether to skip explicit logout of the registries during post-job cleanup.
Exists for backward compatibility on self-hosted runners.
Not recommended.
Mask the docker password to prevent it being printed to logs to std-out. This will prevent the
password GitHub output from being shared between separate jobs.
Comment on lines +13 to +14

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Mask the docker password to prevent it being printed to logs to std-out. This will prevent the
password GitHub output from being shared between separate jobs.
Mask the docker password to prevent it from appearing in debug logs. This will prevent the
password used to login to Amazon ECR from being shared between jobs.

Options: ['true', 'false']
required: false
default: 'false'
registries:
description: >-
A comma-delimited list of AWS account IDs that are associated with the ECR Private registries.
If you do not specify a registry, the default ECR Private registry is assumed.
If 'public' is given as input to 'registry-type', this input is ignored.
required: false
registry-type:
description: >-
Which ECR registry type to log into.
Options: [private, public]
required: false
default: private
http-proxy:
skip-logout:
description: >-
Proxy to use for the AWS SDK agent.
Whether to skip explicit logout of the registries during post-job cleanup.
Exists for backward compatibility on self-hosted runners.
Not recommended.
Options: ['true', 'false']
required: false
default: 'false'
outputs:
registry:
description: >-
Expand Down
11 changes: 7 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ const ECR_LOGIN_GITHUB_ACTION_USER_AGENT = 'amazon-ecr-login-for-github-actions'
const ECR_PUBLIC_REGISTRY_URI = 'public.ecr.aws';

const INPUTS = {
skipLogout: 'skip-logout',
httpProxy: 'http-proxy',
maskPassword: 'mask-password',
registries: 'registries',
registryType: 'registry-type',
httpProxy: 'http-proxy'
skipLogout: 'skip-logout'
};

const OUTPUTS = {
Expand Down Expand Up @@ -104,10 +105,11 @@ function replaceSpecialCharacters(registryUri) {

async function run() {
// Get inputs
const skipLogout = core.getInput(INPUTS.skipLogout, { required: false }).toLowerCase() === 'true';
const httpProxy = core.getInput(INPUTS.httpProxy, { required: false });
const maskPassword = core.getInput(INPUTS.maskPassword, { required: false }).toLowerCase() === 'true';
const registries = core.getInput(INPUTS.registries, { required: false });
const registryType = core.getInput(INPUTS.registryType, { required: false }).toLowerCase() || REGISTRY_TYPES.private;
const httpProxy = core.getInput(INPUTS.httpProxy, { required: false });
const skipLogout = core.getInput(INPUTS.skipLogout, { required: false }).toLowerCase() === 'true';

const registryUriState = [];

Expand Down Expand Up @@ -169,6 +171,7 @@ async function run() {

// Output docker username and password
const secretSuffix = replaceSpecialCharacters(registryUri);
if (maskPassword) core.setSecret(creds[1]);
core.setOutput(`${OUTPUTS.dockerUsername}_${secretSuffix}`, creds[0]);
core.setOutput(`${OUTPUTS.dockerPassword}_${secretSuffix}`, creds[1]);

Expand Down
76 changes: 58 additions & 18 deletions index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,19 @@ function mockGetInput(requestResponse) {
}

const ECR_DEFAULT_INPUTS = {
'http-proxy': '',
'mask-password': '',
'registries': '',
'skip-logout': '',
'registry-type': '',
'http-proxy': ''
'skip-logout': ''
};

const ECR_PUBLIC_DEFAULT_INPUTS = {
'http-proxy': '',
'mask-password': '',
'registries': '',
'skip-logout': '',
'registry-type': 'public',
'http-proxy': ''
'skip-logout': ''
};

const defaultAuthToken = {
Expand Down Expand Up @@ -76,9 +78,10 @@ describe('Login to ECR', () => {

test('gets auth token from ECR and logins the Docker client for each provided registry', async () => {
const mockInputs = {
'mask-password': '',
'registries': '123456789012,111111111111',
'skip-logout': '',
'registry-type': ''
'registry-type': '',
'skip-logout': ''
};
core.getInput = jest.fn().mockImplementation(mockGetInput(mockInputs));
ecrMock.on(GetAuthorizationTokenCommand).resolves({
Expand Down Expand Up @@ -116,9 +119,10 @@ describe('Login to ECR', () => {

test('outputs the registry ID if a single registry is provided in the input', async () => {
const mockInputs = {
'mask-password': '',
'registries': '111111111111',
'skip-logout': '',
'registry-type': ''
'registry-type': '',
'skip-logout': ''
};
core.getInput = jest.fn().mockImplementation(mockGetInput(mockInputs));
ecrMock.on(GetAuthorizationTokenCommand).resolves({
Expand Down Expand Up @@ -160,9 +164,10 @@ describe('Login to ECR', () => {

test('logged-in registries are saved as state even if the action fails', async () => {
const mockInputs = {
'mask-password': '',
'registries': '123456789012,111111111111',
'skip-logout': '',
'registry-type': ''
'registry-type': '',
'skip-logout': ''
};
core.getInput = jest.fn().mockImplementation(mockGetInput(mockInputs));
ecrMock.on(GetAuthorizationTokenCommand).resolves({
Expand Down Expand Up @@ -261,9 +266,10 @@ describe('Login to ECR', () => {
test('skips logout when specified and logging into default registry', async () => {
ecrMock.on(GetAuthorizationTokenCommand).resolves(defaultOutputToken);
const mockInputs = {
'mask-password': '',
'registries': '',
'skip-logout': 'true',
'registry-type': ''
'registry-type': '',
'skip-logout': 'true'
};
core.getInput = jest.fn().mockImplementation(mockGetInput(mockInputs));

Expand All @@ -280,9 +286,10 @@ describe('Login to ECR', () => {

test('skips logout when specified and logging into multiple registries', async () => {
const mockInputs = {
'mask-password': '',
'registries': '123456789012,111111111111',
'skip-logout': 'true',
'registry-type': ''
'registry-type': '',
'skip-logout': 'true'
};
core.getInput = jest.fn().mockImplementation(mockGetInput(mockInputs));
ecrMock.on(GetAuthorizationTokenCommand).resolves({
Expand Down Expand Up @@ -314,9 +321,10 @@ describe('Login to ECR', () => {

test('sets the Actions outputs to the docker credentials', async () => {
const mockInputs = {
'mask-password': '',
'registries': '123456789012,111111111111',
'skip-logout': 'true',
'registry-type': ''
'registry-type': '',
'skip-logout': 'true'
};
core.getInput = jest.fn().mockImplementation(mockGetInput(mockInputs));
ecrMock.on(GetAuthorizationTokenCommand).resolves({
Expand All @@ -341,6 +349,37 @@ describe('Login to ECR', () => {
expect(core.setOutput).toHaveBeenNthCalledWith(4, 'docker_password_111111111111_dkr_ecr_aws_region_1_amazonaws_com', 'bar');
});

test('sets the Actions outputs to the docker credentials with masked password', async () => {
const mockInputs = {
'mask-password': 'true',
'registries': '123456789012,111111111111',
'registry-type': '',
'skip-logout': 'true'
};
core.getInput = jest.fn().mockImplementation(mockGetInput(mockInputs));
ecrMock.on(GetAuthorizationTokenCommand).resolves({
authorizationData: [
{
authorizationToken: Buffer.from('hello:world').toString('base64'),
proxyEndpoint: 'https://123456789012.dkr.ecr.aws-region-1.amazonaws.com'
},
{
authorizationToken: Buffer.from('foo:bar').toString('base64'),
proxyEndpoint: 'https://111111111111.dkr.ecr.aws-region-1.amazonaws.com'
}
]
});

await run();

expect(core.setOutput).toHaveBeenCalledTimes(4);
expect(core.setSecret).toHaveBeenCalledTimes(2);
expect(core.setOutput).toHaveBeenNthCalledWith(1, 'docker_username_123456789012_dkr_ecr_aws_region_1_amazonaws_com', 'hello');
expect(core.setOutput).toHaveBeenNthCalledWith(2, 'docker_password_123456789012_dkr_ecr_aws_region_1_amazonaws_com', 'world');
expect(core.setOutput).toHaveBeenNthCalledWith(3, 'docker_username_111111111111_dkr_ecr_aws_region_1_amazonaws_com', 'foo');
expect(core.setOutput).toHaveBeenNthCalledWith(4, 'docker_password_111111111111_dkr_ecr_aws_region_1_amazonaws_com', 'bar');
});

describe('proxy settings', () => {
afterEach(() => {
process.env = {};
Expand Down Expand Up @@ -392,9 +431,10 @@ describe('Login to ECR Public', () => {
describe('inputs and outputs', () => {
test('error is caught by core.setFailed for invalid registry-type input', async () => {
const mockInputs = {
'mask-password': '',
'registries': '',
'skip-logout': '',
'registry-type': 'invalid'
'registry-type': 'invalid',
'skip-logout': ''
};
core.getInput = jest.fn().mockImplementation(mockGetInput(mockInputs));
ecrPublicMock.on(GetAuthorizationTokenCommandPublic).resolves(defaultAuthToken);
Expand Down
Loading