diff --git a/README.md b/README.md index 3d6fc6840..ad0279b56 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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). diff --git a/action.yml b/action.yml index c08d94d71..ba1438e9b 100644 --- a/action.yml +++ b/action.yml @@ -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' diff --git a/lib/PowerShell/Constants.js b/lib/PowerShell/Constants.js index 51d0a3a07..567a7002d 100644 --- a/lib/PowerShell/Constants.js +++ b/lib/PowerShell/Constants.js @@ -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"; diff --git a/lib/PowerShell/ServicePrincipalLogin.js b/lib/PowerShell/ServicePrincipalLogin.js index 716f07f52..b70074bfb 100644 --- a/lib/PowerShell/ServicePrincipalLogin.js +++ b/lib/PowerShell/ServicePrincipalLogin.js @@ -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]; @@ -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; }; @@ -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; @@ -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* () { @@ -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 }; diff --git a/lib/PowerShell/Utilities/PowerShellToolRunner.js b/lib/PowerShell/Utilities/PowerShellToolRunner.js index 0ac63086d..367f18fa6 100644 --- a/lib/PowerShell/Utilities/PowerShellToolRunner.js +++ b/lib/PowerShell/Utilities/PowerShellToolRunner.js @@ -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]; @@ -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; }; diff --git a/lib/PowerShell/Utilities/ScriptBuilder.js b/lib/PowerShell/Utilities/ScriptBuilder.js index 74aa6ae64..2b36fcf7c 100644 --- a/lib/PowerShell/Utilities/ScriptBuilder.js +++ b/lib/PowerShell/Utilities/ScriptBuilder.js @@ -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]; @@ -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; }; @@ -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;`; } } diff --git a/lib/PowerShell/Utilities/Utils.js b/lib/PowerShell/Utilities/Utils.js index d890766e5..2b0bf2757 100644 --- a/lib/PowerShell/Utilities/Utils.js +++ b/lib/PowerShell/Utilities/Utils.js @@ -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]; @@ -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; }; diff --git a/lib/main.js b/lib/main.js index 90d9206a4..b3e13b52c 100644 --- a/lib/main.js +++ b/lib/main.js @@ -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]; @@ -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; }; @@ -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; @@ -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 { @@ -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) { @@ -206,7 +211,7 @@ 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(); } @@ -214,7 +219,7 @@ function main() { } 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"`); diff --git a/src/PowerShell/Constants.ts b/src/PowerShell/Constants.ts index 543226df4..72aed16e8 100644 --- a/src/PowerShell/Constants.ts +++ b/src/PowerShell/Constants.ts @@ -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"; diff --git a/src/PowerShell/ServicePrincipalLogin.ts b/src/PowerShell/ServicePrincipalLogin.ts index c089d994f..53048524b 100644 --- a/src/PowerShell/ServicePrincipalLogin.ts +++ b/src/PowerShell/ServicePrincipalLogin.ts @@ -16,6 +16,7 @@ export class ServicePrincipalLogin implements IAzurePowerShellSession { resourceManagerEndpointUrl: string; allowNoSubscriptionsLogin: boolean; federatedToken: string; + scopeLevel: string; constructor(servicePrincipalId: string, servicePrincipalKey: string, @@ -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; @@ -34,6 +36,7 @@ export class ServicePrincipalLogin implements IAzurePowerShellSession { this.environment = environment; this.resourceManagerEndpointUrl = resourceManagerEndpointUrl; this.allowNoSubscriptionsLogin = allowNoSubscriptionsLogin; + this.scopeLevel = scopeLevel; } async initialize() { @@ -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 } @@ -81,4 +84,4 @@ export class ServicePrincipalLogin implements IAzurePowerShellSession { console.log(`Azure PowerShell session successfully initialized`); } -} \ No newline at end of file +} diff --git a/src/PowerShell/Utilities/ScriptBuilder.ts b/src/PowerShell/Utilities/ScriptBuilder.ts index 9b1a0e7df..b55b1ca57 100644 --- a/src/PowerShell/Utilities/ScriptBuilder.ts +++ b/src/PowerShell/Utilities/ScriptBuilder.ts @@ -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;`; } } @@ -64,4 +64,4 @@ export default class ScriptBuilder { return this.script; } -} \ No newline at end of file +} diff --git a/src/main.ts b/src/main.ts index 13ebea0cb..c2e737cb3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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 }); @@ -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 { @@ -200,7 +201,8 @@ async function main() { subscriptionId, allowNoSubscriptionsLogin, environment, - resourceManagerEndpointUrl); + resourceManagerEndpointUrl, + scopeLevel); await spnlogin.initialize(); await spnlogin.login(); }