Skip to content
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
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- [Security Updates](#security-updates)
- [Input Parameters](#input-parameters)
- [`client-id`](#client-id)
- [`mask-client-id`](#mask-client-id)
- [`subscription-id`](#subscription-id)
- [`tenant-id`](#tenant-id)
- [`creds`](#creds)
Expand Down Expand Up @@ -144,6 +145,7 @@ Customers using v1 should migrate to v3. End-of-life releases no longer receive
|allow-no-subscriptions|false|boolean|false|if login without subscription is allowed|
|audience|false|string|api://AzureADTokenExchange|the audience to get the JWT ID token from GitHub OIDC provider|
|auth-type|false|string|SERVICE_PRINCIPAL|the auth type|
|mask-client-id|false|boolean|true|if the `client-id` value is masked in workflow logs|

### `client-id`

Expand All @@ -156,7 +158,28 @@ It's better to create a GitHub Action secret for this parameter when using it. R
Refer to [Login With OpenID Connect (OIDC)](#login-with-openid-connect-oidc-recommended) and [Login With User-assigned Managed Identity](#login-with-user-assigned-managed-identity) for its usage.

> [!NOTE]
> The action registers the `client-id` value as a secret (via `core.setSecret`) so it is masked in workflow logs. Some enterprises treat the client ID as sensitive, and masking also prevents it from being printed accidentally, which matters in public repositories. `tenant-id` and `subscription-id` are not masked.
> By default the action registers the `client-id` value as a secret (via `core.setSecret`) so it is masked in workflow logs. Some enterprises treat the client ID as sensitive, and masking also prevents it from being printed accidentally, which matters in public repositories. `tenant-id` and `subscription-id` are not masked. Set [`mask-client-id`](#mask-client-id) to `false` to opt out of the masking.

### `mask-client-id`

The input parameter `mask-client-id` controls whether the login client id is registered as a secret and masked in the workflow logs. It defaults to `true`.

Set it to `false` when the client id is not treated as sensitive and masking gets in the way, for example when the same value appears in log output or command results that you need to read.

The client-id is effectively a username: it is low sensitivity on its own, and only useful to an attacker who already holds the client secret or certificate. Disabling masking is therefore reasonable when the value is treated as configuration rather than as a secret.

```yaml
- name: Azure login
uses: azure/login@v3
with:
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
client-id: ${{ vars.AZURE_CLIENT_ID }}
mask-client-id: false
```

> [!NOTE]
> The value is only unmasked when it is not a GitHub Action secret. A value passed from `${{ secrets.* }}` is still masked by GitHub itself, regardless of this input.

### `subscription-id`

Expand Down
47 changes: 47 additions & 0 deletions __tests__/LoginConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,53 @@ describe("LoginConfig Test", () => {
expect(loginConfig.subscriptionId).toBe("subscription-id-aa");
});

test('initialize with mask-client-id=true', async () => {
setEnv('environment', 'azureusgovernment');
setEnv('enable-AzPSSession', 'false');
setEnv('allow-no-subscriptions', 'true');
setEnv('auth-type', 'SERVICE_PRINCIPAL');
setEnv('tenant-id', 'tenant-id');
setEnv('subscription-id', 'subscription-id');
setEnv('client-id', 'client-id');
setEnv('mask-client-id', 'true');

let loginConfig = new LoginConfig();
await loginConfig.initialize();
expect(loginConfig.maskClientId).toBeTruthy();
expect(loginConfig.servicePrincipalId).toBe("client-id");
});

Comment on lines +178 to +193
test('initialize with mask-client-id=false', async () => {
setEnv('environment', 'azureusgovernment');
setEnv('enable-AzPSSession', 'false');
setEnv('allow-no-subscriptions', 'true');
setEnv('auth-type', 'SERVICE_PRINCIPAL');
setEnv('tenant-id', 'tenant-id');
setEnv('subscription-id', 'subscription-id');
setEnv('client-id', 'client-id');
setEnv('mask-client-id', 'false');

let loginConfig = new LoginConfig();
await loginConfig.initialize();
expect(loginConfig.maskClientId).toBeFalsy();
expect(loginConfig.servicePrincipalId).toBe("client-id");
});

test('initialize without mask-client-id', async () => {
setEnv('environment', 'azureusgovernment');
setEnv('enable-AzPSSession', 'false');
setEnv('allow-no-subscriptions', 'true');
setEnv('auth-type', 'SERVICE_PRINCIPAL');
setEnv('tenant-id', 'tenant-id');
setEnv('subscription-id', 'subscription-id');
setEnv('client-id', 'client-id');

let loginConfig = new LoginConfig();
await loginConfig.initialize();
expect(loginConfig.maskClientId).toBeTruthy();
expect(loginConfig.servicePrincipalId).toBe("client-id");
});

test('validate with wrong environment', async () => {
setEnv('environment', 'aWrongCloud');
setEnv('enable-AzPSSession', 'false');
Expand Down
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ inputs:
description: 'The type of authentication. Supported values are SERVICE_PRINCIPAL, IDENTITY. Default value is SERVICE_PRINCIPAL'
required: false
default: 'SERVICE_PRINCIPAL'
mask-client-id:
description: 'Set this value to false to stop registering the client-id as a secret, so it is not masked in workflow logs'
required: false
default: true
branding:
icon: 'login.svg'
color: 'blue'
Expand Down
6 changes: 5 additions & 1 deletion src/common/LoginConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export class LoginConfig {
enableAzPSSession: boolean;
audience: string;
federatedToken: string;
maskClientId: boolean;

async initialize() {
this.environment = core.getInput("environment").toLowerCase();
Expand All @@ -42,7 +43,10 @@ export class LoginConfig {
this.audience = core.getInput('audience', { required: false });
this.federatedToken = null;

this.mask(this.servicePrincipalId);
this.maskClientId = core.getInput('mask-client-id').toLowerCase() !== "false";
if (this.maskClientId) {
this.mask(this.servicePrincipalId);
}
this.mask(this.servicePrincipalSecret);
}

Expand Down