Compare commits

..

7 Commits

Author SHA1 Message Date
Ganeshrockz
0ea4c38d97 Merge branch 'master' into ganeshrockz/no-subscription-login 2020-10-30 16:39:39 +05:30
Ganeshrockz
1ba06e2c3a test changes 2020-10-30 16:38:45 +05:30
Ganeshrockz
01ee4cac28 Merge branch 'ganeshrockz/no-subscription-login' of https://github.com/Azure/login into ganeshrockz/no-subscription-login 2020-10-30 16:32:31 +05:30
Ganeshrockz
2cd1bbfddf added no subcriptions login support 2020-10-30 16:30:10 +05:30
aksm-ms
f25d384c17 Merge branch 'master' into ganeshrockz/no-subscription-login 2020-10-22 09:59:54 +05:30
Ganeshrockz
b1b178e95a Added L0s 2020-10-20 16:49:38 +05:30
Ganesh S
db750ed598 Added no subscription support 2020-10-20 13:26:18 +05:30
10 changed files with 131 additions and 414 deletions

View File

@@ -1,10 +0,0 @@
---
name: Bug Report / Feature Request
about: Create a report to help us improve
title: ''
labels: need-to-triage
assignees: ''
---

107
README.md
View File

@@ -12,11 +12,9 @@ Get started today with a [free Azure account](https://azure.com/free/open-source
# GitHub Action for Azure Login # GitHub Action for Azure Login
With the Azure login Action, you can automate your workflow to do an Azure login using [Azure service principal](https://docs.microsoft.com/azure/active-directory/develop/app-objects-and-service-principals) and run Azure CLI and Azure PowerShell scripts. You can leverage this action for the public or soverign clouds including Azure Government and Azure Stack Hub (using the `environment` parameter). With the Azure login Action, you can automate your workflow to do an Azure login using [Azure service principal](https://docs.microsoft.com/azure/active-directory/develop/app-objects-and-service-principals) and run Azure CLI and Azure PowerShell scripts.
By default, the action only logs in with the Azure CLI (using the `az login` command). To log in with the Az PowerShell module, set `enable-AzPSSession` to true. To login to Azure tenants without any subscriptions, set the optional parameter `allow-no-subscriptions` to true. By default, the action only logs in with the Azure CLI (using the `az login` command). To log in with the Az PowerShell module, set `enable-AzPSSession` to true.
To login into one of the Azure Government clouds, set the optional parameter environment with supported cloud names AzureUSGovernment or AzureChinaCloud. If this parameter is not specified, it takes the default value AzureCloud and connect to the Azure Public Cloud. Additionally the parameter creds takes the Azure service principal created in the particular cloud to connect (Refer to Configure deployment credentials section below for details).
This repository contains GitHub Action for [Azure Login](https://github.com/Azure/login/blob/master/action.yml). This repository contains GitHub Action for [Azure Login](https://github.com/Azure/login/blob/master/action.yml).
@@ -76,51 +74,8 @@ jobs:
Get-AzVM -ResourceGroupName "ActionsDemo" Get-AzVM -ResourceGroupName "ActionsDemo"
``` ```
## Sample to connect to Azure US Government cloud
```
- name: Login to Azure US Gov Cloud with CLI
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_US_GOV_CREDENTIALS }}
environment: 'AzureUSGovernment'
enable-AzPSSession: false
- name: Login to Azure US Gov Cloud with Az Powershell
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_US_GOV_CREDENTIALS }}
environment: 'AzureUSGovernment'
enable-AzPSSession: true
```
Refer to the [Azure PowerShell](https://github.com/azure/powershell) Github action to run your Azure PowerShell scripts. Refer to the [Azure PowerShell](https://github.com/azure/powershell) Github action to run your Azure PowerShell scripts.
## Sample Azure Login workflow that to run az cli on Azure Stack Hub
```yaml
# File: .github/workflows/workflow.yml
on: [push]
name: AzureLoginSample
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
environment: 'AzureStack'
- run: |
az webapp list --query "[?state=='Running']"
```
Refer to the [Azure Stack Hub Login Action Tutorial](https://docs.microsoft.com/en-us/azure-stack/user/ci-cd-github-action-login-cli?view=azs-2008) for more detailed instructions.
## Configure deployment credentials: ## Configure deployment credentials:
The previous sample workflows depend on a [secrets](https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets) named `AZURE_CREDENTIALS` in your repository. The value of this secret is expected to be a JSON object that represents a service principal (an identifer for an application or process) that authenticates the workflow with Azure. The previous sample workflows depend on a [secrets](https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets) named `AZURE_CREDENTIALS` in your repository. The value of this secret is expected to be a JSON object that represents a service principal (an identifer for an application or process) that authenticates the workflow with Azure.
@@ -131,40 +86,31 @@ The following steps describe how to create the service principal, assign the rol
1. Open the Azure Cloud Shell at [https://shell.azure.com](https://shell.azure.com). You can alternately use the [Azure CLI](https://docs.microsoft.com/cli/azure/install-azure-cli?view=azure-cli-latest) if you've installed it locally. (For more information on Cloud Shell, see the [Cloud Shell Overview](https://docs.microsoft.com/azure/cloud-shell/overview).) 1. Open the Azure Cloud Shell at [https://shell.azure.com](https://shell.azure.com). You can alternately use the [Azure CLI](https://docs.microsoft.com/cli/azure/install-azure-cli?view=azure-cli-latest) if you've installed it locally. (For more information on Cloud Shell, see the [Cloud Shell Overview](https://docs.microsoft.com/azure/cloud-shell/overview).)
1.1 **(Required ONLY when environment is Azure Stack Hub)** Run the following command to set the SQL Management endpoint to 'not supported'
```bash
az cloud update -n {environmentName} --endpoint-sql-management https://notsupported
```
2. Use the [az ad sp create-for-rbac](https://docs.microsoft.com/cli/azure/ad/sp?view=azure-cli-latest#az_ad_sp_create_for_rbac) command to create a service principal and assign a Contributor role: 2. Use the [az ad sp create-for-rbac](https://docs.microsoft.com/cli/azure/ad/sp?view=azure-cli-latest#az_ad_sp_create_for_rbac) command to create a service principal and assign a Contributor role:
For web apps (also more secure)
```azurecli ```azurecli
az ad sp create-for-rbac --name "{sp-name}" --sdk-auth --role contributor \ az ad sp create-for-rbac --name "{sp-name}" --sdk-auth --role contributor \
--scopes /subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.Web/sites/{app-name} --scopes /subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.Web/sites/{app-name}
``` ```
For usage with other Azure services (Storage Accounts, Active Directory, etc.) Replace the following:
* `{sp-name}` with a suitable name for your service principal, such as the name of the app itself. The name must be unique within your organization.
* `{subscription-id}` with the subscription you want to use
* `{resource-group}` the resource group containing the web app.
* `{app-name}` with the name of the web app.
This command invokes Azure Active Directory (via the `ad` part of the command) to create a service principal (via `sp`) specifically for [Role-Based Access Control (RBAC)](https://docs.microsoft.com/azure/role-based-access-control/overview) (via `create-for-rbac`).
The `--role` argument specifies the permissions to grant to the service principal at the specified `--scope`. In this case, you grant the built-in [Contributor](https://docs.microsoft.com/azure/role-based-access-control/built-in-roles#contributor) role at the scope of the web app in the specified resource group in the specified subscription.
If desired, you can omit the part of the scope starting with `/providers/...` to grant the service principal the Contributor role for the entire resource group:
```azurecli ```azurecli
az ad sp create-for-rbac --name "{sp-name}" --sdk-auth --role contributor \ az ad sp create-for-rbac --name "{sp-name}" --sdk-auth --role contributor \
--scopes /subscriptions/{subscription-id}/resourceGroups/{resource-group} --scopes /subscriptions/{subscription-id}/resourceGroups/{resource-group}
``` ```
Replace the following: For security purposes, however, it's always preferable to grant permissions at the most restrictive scope possible.
* `{sp-name}` with a suitable name for your service principal, such as the name of the app itself. The name must be unique within your organization.
* `{subscription-id}` with the subscription ID you want to use (found in Subscriptions in portal)
* `{resource-group}` the resource group containing the web app.
* [optional] `{app-name}` if you wish to have a tighter & more secure scope, use the first option and replace this with the name of the web app.
More info can be found [here](https://docs.microsoft.com/en-us/cli/azure/ad/sp?view=azure-cli-latest#az_ad_sp_create_for_rbac).
This command invokes Azure Active Directory (via the `ad` part of the command) to create a service principal (via `sp`) specifically for [Role-Based Access Control (RBAC)](https://docs.microsoft.com/azure/role-based-access-control/overview) (via `create-for-rbac`).
The `--role` argument specifies the permissions to grant to the service principal at the specified `--scope`. In this case, you grant the built-in [Contributor](https://docs.microsoft.com/azure/role-based-access-control/built-in-roles#contributor) role at the scope of the web app in the specified resource group in the specified subscription. If desired, you can omit the part of the scope starting with `/providers/...` to grant the service principal the Contributor role for the entire resource group. For security purposes, however, it's always preferable to grant permissions at the most restrictive scope possible.
3. When complete, the `az ad sp create-for-rbac` command displays JSON output in the following form (which is specified by the `--sdk-auth` argument): 3. When complete, the `az ad sp create-for-rbac` command displays JSON output in the following form (which is specified by the `--sdk-auth` argument):
@@ -184,35 +130,10 @@ The following steps describe how to create the service principal, assign the rol
NOTE: to manage service principals created with `az ad sp create-for-rbac`, visit the [Azure portal](https://portal.azure.com), navigate to your Azure Active Directory, then select **Manage** > **App registrations** on the left-hand menu. Your service principal should appear in the list. Select a principal to navigate to its properties. You can also manage role assignments using the [az role assignment](https://docs.microsoft.com/cli/azure/role/assignment?view=azure-cli-latest) command. NOTE: to manage service principals created with `az ad sp create-for-rbac`, visit the [Azure portal](https://portal.azure.com), navigate to your Azure Active Directory, then select **Manage** > **App registrations** on the left-hand menu. Your service principal should appear in the list. Select a principal to navigate to its properties. You can also manage role assignments using the [az role assignment](https://docs.microsoft.com/cli/azure/role/assignment?view=azure-cli-latest) command.
## Support for using `allow-no-subscriptions` flag with az login
Capability has been added to support access to tenants without subscriptions. This can be useful to run tenant level commands, such as `az ad`. The action accepts an optional parameter `allow-no-subscriptions` which is `false` by default.
```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 }}
allow-no-subscriptions: true
```
# Contributing # Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
For detailed developer guidelines, visit [developer guidelines for azure actions](https://github.com/Azure/actions/blob/main/docs/developer-guildelines.md).
When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA. When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.

View File

@@ -5,7 +5,7 @@ jest.mock('../../src/PowerShell/Utilities/PowerShellToolRunner');
let spnlogin: ServicePrincipalLogin; let spnlogin: ServicePrincipalLogin;
beforeAll(() => { beforeAll(() => {
spnlogin = new ServicePrincipalLogin("servicePrincipalID", "servicePrinicipalkey", "tenantId", "subscriptionId", false, null, null); spnlogin = new ServicePrincipalLogin("servicePrincipalID", "servicePrinicipalkey", "tenantId", "subscriptionId", false);
}); });
afterEach(() => { afterEach(() => {

View File

@@ -9,10 +9,6 @@ inputs:
description: 'SetthisvaluetotruetoenableAzurePowerShellLogininadditiontoAzCLIlogin' description: 'SetthisvaluetotruetoenableAzurePowerShellLogininadditiontoAzCLIlogin'
required: false required: false
default: false default: false
environment:
description: 'Name of the environment. Supported values are azurecloud, azurestack, azureusgovernment, azurechinacloud, azuregermancloud. Default being azurecloud'
required: false
default: AzureCloud
allow-no-subscriptions: allow-no-subscriptions:
description: 'Setthisvaluetotrueto enable support for accessing tenants without subscriptions' description: 'Setthisvaluetotrueto enable support for accessing tenants without subscriptions'
required: false required: false

View File

@@ -25,14 +25,11 @@ const PowerShellToolRunner_1 = __importDefault(require("./Utilities/PowerShellTo
const ScriptBuilder_1 = __importDefault(require("./Utilities/ScriptBuilder")); const ScriptBuilder_1 = __importDefault(require("./Utilities/ScriptBuilder"));
const Constants_1 = __importDefault(require("./Constants")); const Constants_1 = __importDefault(require("./Constants"));
class ServicePrincipalLogin { class ServicePrincipalLogin {
constructor(servicePrincipalId, servicePrincipalKey, tenantId, subscriptionId, allowNoSubscriptionsLogin, environment, resourceManagerEndpointUrl) { constructor(servicePrincipalId, servicePrincipalKey, tenantId, subscriptionId) {
this.servicePrincipalId = servicePrincipalId; this.servicePrincipalId = servicePrincipalId;
this.servicePrincipalKey = servicePrincipalKey; this.servicePrincipalKey = servicePrincipalKey;
this.tenantId = tenantId; this.tenantId = tenantId;
this.subscriptionId = subscriptionId; this.subscriptionId = subscriptionId;
this.environment = environment;
this.resourceManagerEndpointUrl = resourceManagerEndpointUrl;
this.allowNoSubscriptionsLogin = allowNoSubscriptionsLogin;
} }
initialize() { initialize() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
@@ -56,10 +53,8 @@ class ServicePrincipalLogin {
servicePrincipalId: this.servicePrincipalId, servicePrincipalId: this.servicePrincipalId,
servicePrincipalKey: this.servicePrincipalKey, servicePrincipalKey: this.servicePrincipalKey,
subscriptionId: this.subscriptionId, subscriptionId: this.subscriptionId,
environment: this.environment, environment: ServicePrincipalLogin.environment,
scopeLevel: ServicePrincipalLogin.scopeLevel, scopeLevel: ServicePrincipalLogin.scopeLevel
allowNoSubscriptionsLogin: this.allowNoSubscriptionsLogin,
resourceManagerEndpointUrl: this.resourceManagerEndpointUrl
}; };
const script = new ScriptBuilder_1.default().getAzPSLoginScript(ServicePrincipalLogin.scheme, this.tenantId, args); const script = new ScriptBuilder_1.default().getAzPSLoginScript(ServicePrincipalLogin.scheme, this.tenantId, args);
yield PowerShellToolRunner_1.default.init(); yield PowerShellToolRunner_1.default.init();
@@ -73,5 +68,6 @@ class ServicePrincipalLogin {
} }
} }
exports.ServicePrincipalLogin = ServicePrincipalLogin; exports.ServicePrincipalLogin = ServicePrincipalLogin;
ServicePrincipalLogin.environment = Constants_1.default.AzureCloud;
ServicePrincipalLogin.scopeLevel = Constants_1.default.Subscription; ServicePrincipalLogin.scopeLevel = Constants_1.default.Subscription;
ServicePrincipalLogin.scheme = Constants_1.default.ServicePrincipal; ServicePrincipalLogin.scheme = Constants_1.default.ServicePrincipal;

View File

@@ -20,13 +20,10 @@ class ScriptBuilder {
let command = `Clear-AzContext -Scope Process; let command = `Clear-AzContext -Scope Process;
Clear-AzContext -Scope CurrentUser -Force -ErrorAction SilentlyContinue;`; Clear-AzContext -Scope CurrentUser -Force -ErrorAction SilentlyContinue;`;
if (scheme === Constants_1.default.ServicePrincipal) { if (scheme === Constants_1.default.ServicePrincipal) {
if (args.environment.toLowerCase() == "azurestack") {
command += `Add-AzEnvironment -Name ${args.environment} -ARMEndpoint ${args.resourceManagerEndpointUrl} | out-null;`;
}
command += `Connect-AzAccount -ServicePrincipal -Tenant '${tenantId}' -Credential \ command += `Connect-AzAccount -ServicePrincipal -Tenant '${tenantId}' -Credential \
(New-Object System.Management.Automation.PSCredential('${args.servicePrincipalId}',(ConvertTo-SecureString '${args.servicePrincipalKey.replace("'", "''")}' -AsPlainText -Force))) \ (New-Object System.Management.Automation.PSCredential('${args.servicePrincipalId}',(ConvertTo-SecureString '${args.servicePrincipalKey.replace("'", "''")}' -AsPlainText -Force))) \
-Environment '${args.environment}' | out-null;`; -Environment '${args.environment}' | out-null;`;
if (args.scopeLevel === Constants_1.default.Subscription && !args.allowNoSubscriptionsLogin) { if (args.scopeLevel === Constants_1.default.Subscription) {
command += `Set-AzContext -SubscriptionId '${args.subscriptionId}' -TenantId '${tenantId}' | out-null;`; command += `Set-AzContext -SubscriptionId '${args.subscriptionId}' -TenantId '${tenantId}' | out-null;`;
} }
} }

View File

@@ -36,103 +36,25 @@ function main() {
core.exportVariable('AZURE_HTTP_USER_AGENT', userAgentString); core.exportVariable('AZURE_HTTP_USER_AGENT', userAgentString);
core.exportVariable('AZUREPS_HOST_ENVIRONMENT', azurePSHostEnv); core.exportVariable('AZUREPS_HOST_ENVIRONMENT', azurePSHostEnv);
azPath = yield io.which("az", true); azPath = yield io.which("az", true);
let azureSupportedCloudName = new Set([ yield executeAzCliCommand("--version");
"azureusgovernment",
"azurechinacloud",
"azuregermancloud",
"azurecloud",
"azurestack"
]);
let output = "";
const execOptions = {
listeners: {
stdout: (data) => {
output += data.toString();
}
}
};
yield executeAzCliCommand("--version", true, execOptions);
core.debug(`az cli version used:\n${output}`);
let creds = core.getInput('creds', { required: true }); let creds = core.getInput('creds', { required: true });
let secrets = new actions_secret_parser_1.SecretParser(creds, actions_secret_parser_1.FormatType.JSON); let secrets = new actions_secret_parser_1.SecretParser(creds, actions_secret_parser_1.FormatType.JSON);
let servicePrincipalId = secrets.getSecret("$.clientId", false); let servicePrincipalId = secrets.getSecret("$.clientId", false);
let servicePrincipalKey = secrets.getSecret("$.clientSecret", true); let servicePrincipalKey = secrets.getSecret("$.clientSecret", true);
let tenantId = secrets.getSecret("$.tenantId", false); let tenantId = secrets.getSecret("$.tenantId", false);
let subscriptionId = secrets.getSecret("$.subscriptionId", false); let subscriptionId = secrets.getSecret("$.subscriptionId", false);
let resourceManagerEndpointUrl = secrets.getSecret("$.resourceManagerEndpointUrl", false);
let environment = core.getInput("environment").toLowerCase();
const enableAzPSSession = core.getInput('enable-AzPSSession').toLowerCase() === "true"; const enableAzPSSession = core.getInput('enable-AzPSSession').toLowerCase() === "true";
const allowNoSubscriptionsLogin = core.getInput('allow-no-subscriptions').toLowerCase() === "true"; if (!servicePrincipalId || !servicePrincipalKey || !tenantId || !subscriptionId) {
if (!servicePrincipalId || !servicePrincipalKey || !tenantId) { throw new Error("Not all values are present in the creds object. Ensure clientId, clientSecret, tenantId and subscriptionId are supplied.");
throw new Error("Not all values are present in the creds object. Ensure clientId, clientSecret and tenantId are supplied.");
}
if (!subscriptionId && !allowNoSubscriptionsLogin) {
throw new Error("Not all values are present in the creds object. Ensure subscriptionId is supplied.");
}
if (!azureSupportedCloudName.has(environment)) {
throw new Error("Unsupported value for environment is passed.The list of supported values for environment are azureusgovernment', azurechinacloud, azuregermancloud, azurecloud or azurestack");
} }
// Attempting Az cli login // Attempting Az cli login
if (environment == "azurestack") { yield executeAzCliCommand(`login --service-principal -u "${servicePrincipalId}" -p "${servicePrincipalKey}" --tenant "${tenantId}"`, true);
if (!resourceManagerEndpointUrl) { yield executeAzCliCommand(`account set --subscription "${subscriptionId}"`, true);
throw new Error("resourceManagerEndpointUrl is a required parameter when environment is defined.");
}
console.log(`Unregistering cloud: "${environment}" first if it exists`);
try {
yield executeAzCliCommand(`cloud set -n AzureCloud`, true);
yield executeAzCliCommand(`cloud unregister -n "${environment}"`, false);
}
catch (error) {
console.log(`Ignore cloud not registered error: "${error}"`);
}
console.log(`Registering cloud: "${environment}" with ARM endpoint: "${resourceManagerEndpointUrl}"`);
try {
let baseUri = resourceManagerEndpointUrl;
if (baseUri.endsWith('/')) {
baseUri = baseUri.substring(0, baseUri.length - 1); // need to remove trailing / from resourceManagerEndpointUrl to correctly derive suffixes below
}
let suffixKeyvault = ".vault" + baseUri.substring(baseUri.indexOf('.')); // keyvault suffix starts with .
let suffixStorage = baseUri.substring(baseUri.indexOf('.') + 1); // storage suffix starts without .
let profileVersion = "2019-03-01-hybrid";
yield executeAzCliCommand(`cloud register -n "${environment}" --endpoint-resource-manager "${resourceManagerEndpointUrl}" --suffix-keyvault-dns "${suffixKeyvault}" --suffix-storage-endpoint "${suffixStorage}" --profile "${profileVersion}"`, false);
}
catch (error) {
core.error(`Error while trying to register cloud "${environment}": "${error}"`);
}
console.log(`Done registering cloud: "${environment}"`);
}
yield executeAzCliCommand(`cloud set -n "${environment}"`, false);
console.log(`Done setting cloud: "${environment}"`);
// Attempting Az cli login
if (allowNoSubscriptionsLogin) {
let args = [
"--allow-no-subscriptions",
"--service-principal",
"-u", servicePrincipalId,
"-p", servicePrincipalKey,
"--tenant", tenantId
];
yield executeAzCliCommand(`login`, true, {}, args);
}
else {
let args = [
"--service-principal",
"-u", servicePrincipalId,
"-p", servicePrincipalKey,
"--tenant", tenantId
];
yield executeAzCliCommand(`login`, true, {}, args);
args = [
"--subscription",
subscriptionId
];
yield executeAzCliCommand(`account set`, true, {}, args);
}
isAzCLISuccess = true; isAzCLISuccess = true;
if (enableAzPSSession) { if (enableAzPSSession) {
// Attempting Az PS login // Attempting Az PS login
console.log(`Running Azure PS Login`); console.log(`Running Azure PS Login`);
const spnlogin = new ServicePrincipalLogin_1.ServicePrincipalLogin(servicePrincipalId, servicePrincipalKey, tenantId, subscriptionId, allowNoSubscriptionsLogin, environment, resourceManagerEndpointUrl); const spnlogin = new ServicePrincipalLogin_1.ServicePrincipalLogin(servicePrincipalId, servicePrincipalKey, tenantId, subscriptionId);
yield spnlogin.initialize(); yield spnlogin.initialize();
yield spnlogin.login(); yield spnlogin.login();
} }
@@ -154,11 +76,10 @@ function main() {
} }
}); });
} }
function executeAzCliCommand(command, silent, execOptions = {}, args = []) { function executeAzCliCommand(command, silent) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
execOptions.silent = !!silent;
try { try {
yield exec.exec(`"${azPath}" ${command}`, args, execOptions); yield exec.exec(`"${azPath}" ${command}`, [], { silent: !!silent });
} }
catch (error) { catch (error) {
throw new Error(error); throw new Error(error);

View File

@@ -6,30 +6,24 @@ import ScriptBuilder from './Utilities/ScriptBuilder';
import Constants from './Constants'; import Constants from './Constants';
export class ServicePrincipalLogin implements IAzurePowerShellSession { export class ServicePrincipalLogin implements IAzurePowerShellSession {
static readonly environment: string = Constants.AzureCloud;
static readonly scopeLevel: string = Constants.Subscription; static readonly scopeLevel: string = Constants.Subscription;
static readonly scheme: string = Constants.ServicePrincipal; static readonly scheme: string = Constants.ServicePrincipal;
environment: string;
servicePrincipalId: string; servicePrincipalId: string;
servicePrincipalKey: string; servicePrincipalKey: string;
tenantId: string; tenantId: string;
subscriptionId: string; subscriptionId: string;
resourceManagerEndpointUrl: string;
allowNoSubscriptionsLogin: boolean; allowNoSubscriptionsLogin: boolean;
constructor(servicePrincipalId: string, constructor(servicePrincipalId: string,
servicePrincipalKey: string, servicePrincipalKey: string,
tenantId: string, tenantId: string,
subscriptionId: string, subscriptionId: string,
allowNoSubscriptionsLogin: boolean, allowNoSubscriptionsLogin: boolean) {
environment: string,
resourceManagerEndpointUrl: string) {
this.servicePrincipalId = servicePrincipalId; this.servicePrincipalId = servicePrincipalId;
this.servicePrincipalKey = servicePrincipalKey; this.servicePrincipalKey = servicePrincipalKey;
this.tenantId = tenantId; this.tenantId = tenantId;
this.subscriptionId = subscriptionId; this.subscriptionId = subscriptionId;
this.environment = environment;
this.resourceManagerEndpointUrl = resourceManagerEndpointUrl;
this.allowNoSubscriptionsLogin = allowNoSubscriptionsLogin; this.allowNoSubscriptionsLogin = allowNoSubscriptionsLogin;
} }
@@ -53,10 +47,9 @@ export class ServicePrincipalLogin implements IAzurePowerShellSession {
servicePrincipalId: this.servicePrincipalId, servicePrincipalId: this.servicePrincipalId,
servicePrincipalKey: this.servicePrincipalKey, servicePrincipalKey: this.servicePrincipalKey,
subscriptionId: this.subscriptionId, subscriptionId: this.subscriptionId,
environment: this.environment, environment: ServicePrincipalLogin.environment,
scopeLevel: ServicePrincipalLogin.scopeLevel, scopeLevel: ServicePrincipalLogin.scopeLevel,
allowNoSubscriptionsLogin: this.allowNoSubscriptionsLogin, allowNoSubscriptionsLogin: this.allowNoSubscriptionsLogin
resourceManagerEndpointUrl: this.resourceManagerEndpointUrl
} }
const script: string = new ScriptBuilder().getAzPSLoginScript(ServicePrincipalLogin.scheme, this.tenantId, args); const script: string = new ScriptBuilder().getAzPSLoginScript(ServicePrincipalLogin.scheme, this.tenantId, args);
await PowerShellToolRunner.init(); await PowerShellToolRunner.init();

View File

@@ -9,9 +9,6 @@ export default class ScriptBuilder {
let command = `Clear-AzContext -Scope Process; let command = `Clear-AzContext -Scope Process;
Clear-AzContext -Scope CurrentUser -Force -ErrorAction SilentlyContinue;`; Clear-AzContext -Scope CurrentUser -Force -ErrorAction SilentlyContinue;`;
if (scheme === Constants.ServicePrincipal) { if (scheme === Constants.ServicePrincipal) {
if (args.environment.toLowerCase() == "azurestack") {
command += `Add-AzEnvironment -Name ${args.environment} -ARMEndpoint ${args.resourceManagerEndpointUrl} | out-null;`;
}
command += `Connect-AzAccount -ServicePrincipal -Tenant '${tenantId}' -Credential \ command += `Connect-AzAccount -ServicePrincipal -Tenant '${tenantId}' -Credential \
(New-Object System.Management.Automation.PSCredential('${args.servicePrincipalId}',(ConvertTo-SecureString '${args.servicePrincipalKey.replace("'", "''")}' -AsPlainText -Force))) \ (New-Object System.Management.Automation.PSCredential('${args.servicePrincipalId}',(ConvertTo-SecureString '${args.servicePrincipalKey.replace("'", "''")}' -AsPlainText -Force))) \
-Environment '${args.environment}' | out-null;`; -Environment '${args.environment}' | out-null;`;

View File

@@ -1,6 +1,7 @@
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as exec from '@actions/exec'; import * as exec from '@actions/exec';
import * as io from '@actions/io'; import * as io from '@actions/io';
import { FormatType, SecretParser } from 'actions-secret-parser'; import { FormatType, SecretParser } from 'actions-secret-parser';
import { ServicePrincipalLogin } from './PowerShell/ServicePrincipalLogin'; import { ServicePrincipalLogin } from './PowerShell/ServicePrincipalLogin';
@@ -20,24 +21,7 @@ async function main() {
core.exportVariable('AZUREPS_HOST_ENVIRONMENT', azurePSHostEnv); core.exportVariable('AZUREPS_HOST_ENVIRONMENT', azurePSHostEnv);
azPath = await io.which("az", true); azPath = await io.which("az", true);
await executeAzCliCommand("--version");
let azureSupportedCloudName = new Set([
"azureusgovernment",
"azurechinacloud",
"azuregermancloud",
"azurecloud",
"azurestack"]);
let output: string = "";
const execOptions: any = {
listeners: {
stdout: (data: Buffer) => {
output += data.toString();
}
}
};
await executeAzCliCommand("--version", true, execOptions);
core.debug(`az cli version used:\n${output}`);
let creds = core.getInput('creds', { required: true }); let creds = core.getInput('creds', { required: true });
let secrets = new SecretParser(creds, FormatType.JSON); let secrets = new SecretParser(creds, FormatType.JSON);
@@ -45,11 +29,8 @@ async function main() {
let servicePrincipalKey = secrets.getSecret("$.clientSecret", true); let servicePrincipalKey = secrets.getSecret("$.clientSecret", true);
let tenantId = secrets.getSecret("$.tenantId", false); let tenantId = secrets.getSecret("$.tenantId", false);
let subscriptionId = secrets.getSecret("$.subscriptionId", false); let subscriptionId = secrets.getSecret("$.subscriptionId", false);
let resourceManagerEndpointUrl = secrets.getSecret("$.resourceManagerEndpointUrl", false);
let environment = core.getInput("environment").toLowerCase();
const enableAzPSSession = core.getInput('enable-AzPSSession').toLowerCase() === "true"; const enableAzPSSession = core.getInput('enable-AzPSSession').toLowerCase() === "true";
const allowNoSubscriptionsLogin = core.getInput('allow-no-subscriptions').toLowerCase() === "true"; const allowNoSubscriptionsLogin = core.getInput('allow-no-subscriptions').toLowerCase() === "true";
if (!servicePrincipalId || !servicePrincipalKey || !tenantId) { if (!servicePrincipalId || !servicePrincipalKey || !tenantId) {
throw new Error("Not all values are present in the creds object. Ensure clientId, clientSecret and tenantId are supplied."); throw new Error("Not all values are present in the creds object. Ensure clientId, clientSecret and tenantId are supplied.");
} }
@@ -58,115 +39,40 @@ async function main() {
throw new Error("Not all values are present in the creds object. Ensure subscriptionId is supplied."); throw new Error("Not all values are present in the creds object. Ensure subscriptionId is supplied.");
} }
if (!azureSupportedCloudName.has(environment)){
throw new Error("Unsupported value for environment is passed.The list of supported values for environment are azureusgovernment', azurechinacloud, azuregermancloud, azurecloud or azurestack");
}
// Attempting Az cli login
if (environment == "azurestack") {
if (!resourceManagerEndpointUrl) {
throw new Error("resourceManagerEndpointUrl is a required parameter when environment is defined.");
}
console.log(`Unregistering cloud: "${environment}" first if it exists`);
try {
await executeAzCliCommand(`cloud set -n AzureCloud`, true);
await executeAzCliCommand(`cloud unregister -n "${environment}"`, false);
}
catch (error) {
console.log(`Ignore cloud not registered error: "${error}"`);
}
console.log(`Registering cloud: "${environment}" with ARM endpoint: "${resourceManagerEndpointUrl}"`);
try {
let baseUri = resourceManagerEndpointUrl;
if (baseUri.endsWith('/')) {
baseUri = baseUri.substring(0, baseUri.length-1); // need to remove trailing / from resourceManagerEndpointUrl to correctly derive suffixes below
}
let suffixKeyvault = ".vault" + baseUri.substring(baseUri.indexOf('.')); // keyvault suffix starts with .
let suffixStorage = baseUri.substring(baseUri.indexOf('.')+1); // storage suffix starts without .
let profileVersion = "2019-03-01-hybrid";
await executeAzCliCommand(`cloud register -n "${environment}" --endpoint-resource-manager "${resourceManagerEndpointUrl}" --suffix-keyvault-dns "${suffixKeyvault}" --suffix-storage-endpoint "${suffixStorage}" --profile "${profileVersion}"`, false);
}
catch (error) {
core.error(`Error while trying to register cloud "${environment}": "${error}"`);
}
console.log(`Done registering cloud: "${environment}"`)
}
await executeAzCliCommand(`cloud set -n "${environment}"`, false);
console.log(`Done setting cloud: "${environment}"`);
// Attempting Az cli login // Attempting Az cli login
if (allowNoSubscriptionsLogin) { if (allowNoSubscriptionsLogin) {
let args = [ await executeAzCliCommand(`login --allow-no-subscriptions --service-principal -u "${servicePrincipalId}" -p "${servicePrincipalKey}" --tenant "${tenantId}"`, true);
"--allow-no-subscriptions",
"--service-principal",
"-u", servicePrincipalId,
"-p", servicePrincipalKey,
"--tenant", tenantId
];
await executeAzCliCommand(`login`, true, {}, args);
} }
else { else {
let args = [ await executeAzCliCommand(`login --service-principal -u "${servicePrincipalId}" -p "${servicePrincipalKey}" --tenant "${tenantId}"`, true);
"--service-principal", await executeAzCliCommand(`account set --subscription "${subscriptionId}"`, true);
"-u", servicePrincipalId,
"-p", servicePrincipalKey,
"--tenant", tenantId
];
await executeAzCliCommand(`login`, true, {}, args);
args = [
"--subscription",
subscriptionId
];
await executeAzCliCommand(`account set`, true, {}, args);
} }
isAzCLISuccess = true; isAzCLISuccess = true;
if (enableAzPSSession) { if (enableAzPSSession) {
// Attempting Az PS login // Attempting Az PS login
console.log(`Running Azure PS Login`); console.log(`Running Azure PS Login`);
const spnlogin: ServicePrincipalLogin = new ServicePrincipalLogin( const spnlogin: ServicePrincipalLogin = new ServicePrincipalLogin(servicePrincipalId, servicePrincipalKey, tenantId, subscriptionId, allowNoSubscriptionsLogin);
servicePrincipalId,
servicePrincipalKey,
tenantId,
subscriptionId,
allowNoSubscriptionsLogin,
environment,
resourceManagerEndpointUrl);
await spnlogin.initialize(); await spnlogin.initialize();
await spnlogin.login(); await spnlogin.login();
} }
console.log("Login successful."); console.log("Login successful.");
} } catch (error) {
catch (error) {
if (!isAzCLISuccess) { if (!isAzCLISuccess) {
core.error("Az CLI Login failed. Please check the credentials. For more information refer https://aka.ms/create-secrets-for-GitHub-workflows"); core.error("Az CLI Login failed. Please check the credentials. For more information refer https://aka.ms/create-secrets-for-GitHub-workflows");
} } else {
else {
core.error(`Azure PowerShell Login failed. Please check the credentials. For more information refer https://aka.ms/create-secrets-for-GitHub-workflows"`); core.error(`Azure PowerShell Login failed. Please check the credentials. For more information refer https://aka.ms/create-secrets-for-GitHub-workflows"`);
} }
core.setFailed(error); core.setFailed(error);
} } finally {
finally {
// Reset AZURE_HTTP_USER_AGENT // Reset AZURE_HTTP_USER_AGENT
core.exportVariable('AZURE_HTTP_USER_AGENT', prefix); core.exportVariable('AZURE_HTTP_USER_AGENT', prefix);
core.exportVariable('AZUREPS_HOST_ENVIRONMENT', azPSHostEnv); core.exportVariable('AZUREPS_HOST_ENVIRONMENT', azPSHostEnv);
} }
} }
async function executeAzCliCommand( async function executeAzCliCommand(command: string, silent?: boolean) {
command: string,
silent?: boolean,
execOptions: any = {},
args: any = []) {
execOptions.silent = !!silent;
try { try {
await exec.exec(`"${azPath}" ${command}`, args, execOptions); await exec.exec(`"${azPath}" ${command}`, [], {silent: !!silent});
} }
catch(error) { catch(error) {
throw new Error(error); throw new Error(error);