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
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ jobs:
pwd
```



Users can also specify `audience` field for access-token in the input parameters of the action. If not specified, it is defaulted to `api://AzureADTokenExchange`. This action supports login az powershell as well for both Windows and Linux runners by setting an input parameter `enable-AzPSSession: true`. Below is the sample workflow for the same using the Windows runner. Please note that powershell login is not supported in macOS runners.

## Sample workflow that uses Azure login action using OIDC to run az PowerShell (Windows)
Expand Down Expand Up @@ -269,6 +271,28 @@ jobs:
allow-no-subscriptions: true
```

## Support for using `scope-level`

Capability has been added to support access to the different scope levels (`subscription`, `managementgroup`, or `tenant`) for both OIDC and non-OIDC. This can be useful if you need to target a particular management group and actually if scope level is `tenant` its the same as having `allow-no-subscription` enabled

```yaml
# File: .github/workflows/workflow.yml

on: [push]

name: AzureLoginWithNoSubscriptions

jobs:

build-and-deploy:
runs-on: ubuntu-latest
steps:

- uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
scope-level: 'managementgroup'
```
## Az logout and security hardening

This action doesn't implement ```az logout``` by default at the end of execution. However there is no way of tampering the credentials or account information because the github hosted runner is on a VM that will get reimaged for every customer run which gets everything deleted. But if the runner is self-hosted which is not github provided it is recommended to manually logout at the end of the workflow as shown below. More details on security of the runners can be found [here](https://docs.github.com/actions/learn-github-actions/security-hardening-for-github-actions#hardening-for-self-hosted-runners).
Expand Down
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ inputs:
description: 'Provide audience field for access-token. Default value is api://AzureADTokenExchange'
required: false
default: 'api://AzureADTokenExchange'
scope-level:
description: 'Scope Level. Supported values are subscription, tenant, or managementgroup. Default is subscription'
required: false
default: 'subscription'
branding:
icon: 'login.svg'
color: 'blue'
Expand Down
2 changes: 2 additions & 0 deletions lib/PowerShell/Constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Constants.prefix = "az_";
Constants.moduleName = "Az.Accounts";
Constants.versionPattern = /[0-9]+\.[0-9]+\.[0-9]+/;
Constants.AzureCloud = "AzureCloud";
Constants.Tenant = "Tenant";
Constants.ManagementGroup = "ManagementGroup";
Constants.Subscription = "Subscription";
Constants.ServicePrincipal = "ServicePrincipal";
Constants.Success = "Success";
Expand Down
13 changes: 9 additions & 4 deletions lib/PowerShell/ServicePrincipalLogin.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
Expand All @@ -14,7 +18,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Expand All @@ -38,7 +42,7 @@ const PowerShellToolRunner_1 = __importDefault(require("./Utilities/PowerShellTo
const ScriptBuilder_1 = __importDefault(require("./Utilities/ScriptBuilder"));
const Constants_1 = __importDefault(require("./Constants"));
class ServicePrincipalLogin {
constructor(servicePrincipalId, servicePrincipalKey, federatedToken, tenantId, subscriptionId, allowNoSubscriptionsLogin, environment, resourceManagerEndpointUrl) {
constructor(servicePrincipalId, servicePrincipalKey, federatedToken, tenantId, subscriptionId, allowNoSubscriptionsLogin, environment, resourceManagerEndpointUrl, scopeLevel) {
this.servicePrincipalId = servicePrincipalId;
this.servicePrincipalKey = servicePrincipalKey;
this.federatedToken = federatedToken;
Expand All @@ -47,6 +51,7 @@ class ServicePrincipalLogin {
this.environment = environment;
this.resourceManagerEndpointUrl = resourceManagerEndpointUrl;
this.allowNoSubscriptionsLogin = allowNoSubscriptionsLogin;
this.scopeLevel = scopeLevel;
}
initialize() {
return __awaiter(this, void 0, void 0, function* () {
Expand Down Expand Up @@ -80,7 +85,7 @@ class ServicePrincipalLogin {
federatedToken: this.federatedToken,
subscriptionId: this.subscriptionId,
environment: this.environment,
scopeLevel: ServicePrincipalLogin.scopeLevel,
scopeLevel: this.scopeLevel,
allowNoSubscriptionsLogin: this.allowNoSubscriptionsLogin,
resourceManagerEndpointUrl: this.resourceManagerEndpointUrl
};
Expand Down
8 changes: 6 additions & 2 deletions lib/PowerShell/Utilities/PowerShellToolRunner.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
Expand All @@ -14,7 +18,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Expand Down
10 changes: 7 additions & 3 deletions lib/PowerShell/Utilities/ScriptBuilder.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
Expand All @@ -14,7 +18,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Expand Down Expand Up @@ -46,7 +50,7 @@ class ScriptBuilder {
-Environment '${args.environment}' | out-null;`;
}
// command to set the subscription
if (args.scopeLevel === Constants_1.default.Subscription && !args.allowNoSubscriptionsLogin) {
if (args.scopeLevel.toLowerCase() === Constants_1.default.Subscription.toLowerCase() && !args.allowNoSubscriptionsLogin) {
command += `Set-AzContext -SubscriptionId '${args.subscriptionId}' -TenantId '${tenantId}' | out-null;`;
}
}
Expand Down
8 changes: 6 additions & 2 deletions lib/PowerShell/Utilities/Utils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
Expand All @@ -14,7 +18,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Expand Down
17 changes: 11 additions & 6 deletions lib/main.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
Expand All @@ -14,7 +18,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Expand Down Expand Up @@ -90,6 +94,7 @@ function main() {
let environment = core.getInput("environment").toLowerCase();
const enableAzPSSession = core.getInput('enable-AzPSSession').toLowerCase() === "true";
const allowNoSubscriptionsLogin = core.getInput('allow-no-subscriptions').toLowerCase() === "true";
const scopeLevel = core.getInput('scope-level').toLowerCase();
//Check for the credentials in individual parameters in the workflow.
var servicePrincipalId = core.getInput('client-id', { required: false });
var servicePrincipalKey = null;
Expand All @@ -101,7 +106,7 @@ function main() {
// If any of the individual credentials (clent_id, tenat_id, subscription_id) is present.
if (servicePrincipalId || tenantId || subscriptionId) {
//If few of the individual credentials (clent_id, tenat_id, subscription_id) are missing in action inputs.
if (!(servicePrincipalId && tenantId && (subscriptionId || allowNoSubscriptionsLogin)))
if (!(servicePrincipalId && tenantId && ((subscriptionId || allowNoSubscriptionsLogin) && scopeLevel == "subscription")))
throw new Error("Few credentials are missing. ClientId, tenantId are mandatory. SubscriptionId is also mandatory if allow-no-subscriptions is not set.");
}
else {
Expand Down Expand Up @@ -191,7 +196,7 @@ function main() {
}
else {
console.log("Note: Azure/login action also supports OIDC login mechanism. Refer https://github.com/azure/login#configure-a-service-principal-with-a-federated-credential-to-use-oidc-based-authentication for more details.");
commonArgs = commonArgs.concat("-p", servicePrincipalKey);
commonArgs = commonArgs.concat(`--password=${servicePrincipalKey}`);
}
yield executeAzCliCommand(`login`, true, loginOptions, commonArgs);
if (!allowNoSubscriptionsLogin) {
Expand All @@ -206,15 +211,15 @@ function main() {
// Attempting Az PS login
console.log(`Running Azure PS Login`);
var spnlogin;
spnlogin = new ServicePrincipalLogin_1.ServicePrincipalLogin(servicePrincipalId, servicePrincipalKey, federatedToken, tenantId, subscriptionId, allowNoSubscriptionsLogin, environment, resourceManagerEndpointUrl);
spnlogin = new ServicePrincipalLogin_1.ServicePrincipalLogin(servicePrincipalId, servicePrincipalKey, federatedToken, tenantId, subscriptionId, allowNoSubscriptionsLogin, environment, resourceManagerEndpointUrl, scopeLevel);
yield spnlogin.initialize();
yield spnlogin.login();
}
console.log("Login successful.");
}
catch (error) {
if (!isAzCLISuccess) {
core.setFailed(`Az CLI Login failed with ${error}. Please check the credentials and make sure az is installed on the runner. For more information refer https://aka.ms/create-secrets-for-GitHub-workflows`);
core.setFailed(`Az CLI Login failed with ${error}. Please check the credentials and make sure az is installed on the runner. For more information refer https://aka.ms/create-secrets-for-GitHub-workflows"`);
}
else {
core.setFailed(`Azure PowerShell Login failed with ${error}. Please check the credentials and make sure az is installed on the runner. For more information refer https://aka.ms/create-secrets-for-GitHub-workflows"`);
Expand Down
2 changes: 2 additions & 0 deletions src/PowerShell/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export default class Constants {
static readonly versionPattern = /[0-9]+\.[0-9]+\.[0-9]+/;

static readonly AzureCloud: string = "AzureCloud";
static readonly Tenant: string = "Tenant";
static readonly ManagementGroup: string = "ManagementGroup";
static readonly Subscription: string = "Subscription";
static readonly ServicePrincipal: string = "ServicePrincipal";

Expand Down
9 changes: 6 additions & 3 deletions src/PowerShell/ServicePrincipalLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export class ServicePrincipalLogin implements IAzurePowerShellSession {
resourceManagerEndpointUrl: string;
allowNoSubscriptionsLogin: boolean;
federatedToken: string;
scopeLevel: string;

constructor(servicePrincipalId: string,
servicePrincipalKey: string,
Expand All @@ -24,7 +25,8 @@ export class ServicePrincipalLogin implements IAzurePowerShellSession {
subscriptionId: string,
allowNoSubscriptionsLogin: boolean,
environment: string,
resourceManagerEndpointUrl: string) {
resourceManagerEndpointUrl: string,
scopeLevel: string) {

this.servicePrincipalId = servicePrincipalId;
this.servicePrincipalKey = servicePrincipalKey;
Expand All @@ -34,6 +36,7 @@ export class ServicePrincipalLogin implements IAzurePowerShellSession {
this.environment = environment;
this.resourceManagerEndpointUrl = resourceManagerEndpointUrl;
this.allowNoSubscriptionsLogin = allowNoSubscriptionsLogin;
this.scopeLevel = scopeLevel;
}

async initialize() {
Expand Down Expand Up @@ -67,7 +70,7 @@ export class ServicePrincipalLogin implements IAzurePowerShellSession {
federatedToken: this.federatedToken,
subscriptionId: this.subscriptionId,
environment: this.environment,
scopeLevel: ServicePrincipalLogin.scopeLevel,
scopeLevel: this.scopeLevel,
allowNoSubscriptionsLogin: this.allowNoSubscriptionsLogin,
resourceManagerEndpointUrl: this.resourceManagerEndpointUrl
}
Expand All @@ -81,4 +84,4 @@ export class ServicePrincipalLogin implements IAzurePowerShellSession {
console.log(`Azure PowerShell session successfully initialized`);
}

}
}
4 changes: 2 additions & 2 deletions src/PowerShell/Utilities/ScriptBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default class ScriptBuilder {
-Environment '${args.environment}' | out-null;`;
}
// command to set the subscription
if (args.scopeLevel === Constants.Subscription && !args.allowNoSubscriptionsLogin) {
if (args.scopeLevel.toLowerCase() === Constants.Subscription.toLowerCase() && !args.allowNoSubscriptionsLogin) {
command += `Set-AzContext -SubscriptionId '${args.subscriptionId}' -TenantId '${tenantId}' | out-null;`;
}
}
Expand Down Expand Up @@ -64,4 +64,4 @@ export default class ScriptBuilder {
return this.script;
}

}
}
6 changes: 4 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ async function main() {
let environment = core.getInput("environment").toLowerCase();
const enableAzPSSession = core.getInput('enable-AzPSSession').toLowerCase() === "true";
const allowNoSubscriptionsLogin = core.getInput('allow-no-subscriptions').toLowerCase() === "true";
const scopeLevel = core.getInput('scope-level').toLowerCase();

//Check for the credentials in individual parameters in the workflow.
var servicePrincipalId = core.getInput('client-id', { required: false });
Expand All @@ -78,7 +79,7 @@ async function main() {
if (servicePrincipalId || tenantId || subscriptionId) {

//If few of the individual credentials (clent_id, tenat_id, subscription_id) are missing in action inputs.
if (!(servicePrincipalId && tenantId && (subscriptionId || allowNoSubscriptionsLogin)))
if (!(servicePrincipalId && tenantId && ((subscriptionId || allowNoSubscriptionsLogin) && scopeLevel == "subscription")))
throw new Error("Few credentials are missing. ClientId, tenantId are mandatory. SubscriptionId is also mandatory if allow-no-subscriptions is not set.");
}
else {
Expand Down Expand Up @@ -200,7 +201,8 @@ async function main() {
subscriptionId,
allowNoSubscriptionsLogin,
environment,
resourceManagerEndpointUrl);
resourceManagerEndpointUrl,
scopeLevel);
await spnlogin.initialize();
await spnlogin.login();
}
Expand Down