Use az cli auth credentials for local app debugging (#2541)
#### Details Use az cli auth credentials for local app debugging instead of service principal. #### Pull request checklist <!-- If a checklist item is not applicable to this change, write "n/a" in the checkbox --> - [ ] Addresses an existing issue: Fixes #0000 - [x] Added relevant unit test for your changes. (`yarn test`) - [ ] Verified code coverage for the changes made. Check coverage report at: `<rootDir>/test-results/unit/coverage` - [ ] Ran precheckin (`yarn precheckin`) - [ ] Validated in an Azure resource group
This commit is contained in:
Родитель
8f47a5b6cf
Коммит
0ea3e1289f
|
@ -44,6 +44,7 @@
|
|||
"@azure/cosmos": "^4.0.0",
|
||||
"@azure/identity": "^3.1.3",
|
||||
"@azure/keyvault-secrets": "^4.6.0",
|
||||
"@azure/ms-rest-js": "^2.7.0",
|
||||
"@azure/ms-rest-nodeauth": "^3.1.1",
|
||||
"@azure/storage-blob": "^12.12.0",
|
||||
"@azure/storage-queue": "^12.11.0",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import 'reflect-metadata';
|
||||
|
||||
import { IMock, Mock, Times } from 'typemoq';
|
||||
import { EnvironmentCredential } from '@azure/identity';
|
||||
import { AzureCliCredential } from '@azure/identity';
|
||||
import { CredentialsProvider } from './credentials-provider';
|
||||
import { MSICredentialsProvider, AuthenticationMethod } from './msi-credential-provider';
|
||||
import { ManagedIdentityCredentialCache } from './managed-identity-credential-cache';
|
||||
|
@ -47,13 +47,13 @@ describe(CredentialsProvider, () => {
|
|||
expect(credential).toBe(managedIdentityCredentialCacheMock.object);
|
||||
});
|
||||
|
||||
it('getAzureCredential creates EnvironmentCredential instance', () => {
|
||||
it('getAzureCredential creates AzureCliCredential instance', () => {
|
||||
testSubject = new CredentialsProvider(
|
||||
msiCredProviderMock.object,
|
||||
managedIdentityCredentialCacheMock.object,
|
||||
AuthenticationMethod.servicePrincipal,
|
||||
AuthenticationMethod.azureCliCredentials,
|
||||
);
|
||||
const credential = testSubject.getAzureCredential();
|
||||
expect(credential).toBeInstanceOf(EnvironmentCredential);
|
||||
expect(credential).toBeInstanceOf(AzureCliCredential);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import { TokenCredential, EnvironmentCredential } from '@azure/identity';
|
||||
import { TokenCredential, AzureCliCredential } from '@azure/identity';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { iocTypeNames } from '../ioc-types';
|
||||
import { Credentials, MSICredentialsProvider, AuthenticationMethod } from './msi-credential-provider';
|
||||
|
@ -16,14 +16,12 @@ export class CredentialsProvider {
|
|||
) {}
|
||||
|
||||
public async getCredentialsForBatch(): Promise<Credentials> {
|
||||
// eslint-disable-next-line max-len
|
||||
// ref https://docs.microsoft.com/en-us/rest/api/batchservice/authenticate-requests-to-the-azure-batch-service#authentication-via-azure-ad
|
||||
return this.getCredentialsForResource('https://batch.core.windows.net/');
|
||||
}
|
||||
|
||||
public getAzureCredential(): TokenCredential {
|
||||
if (this.authenticationMethod === AuthenticationMethod.servicePrincipal) {
|
||||
return new EnvironmentCredential();
|
||||
if (this.authenticationMethod === AuthenticationMethod.azureCliCredentials) {
|
||||
return new AzureCliCredential();
|
||||
} else {
|
||||
// must be object instance to reuse an internal cache
|
||||
return this.managedIdentityCredentialCache;
|
||||
|
|
|
@ -14,6 +14,7 @@ describe(MSICredentialsProvider, () => {
|
|||
let testSubject: MSICredentialsProvider;
|
||||
let mockMsRestNodeAuth: IMock<typeof msRestNodeAuth>;
|
||||
let retryHelperMock: IMock<RetryHelper<Credentials>>;
|
||||
|
||||
const maxAttempts = 3;
|
||||
const msBetweenRetries = 0;
|
||||
const expectedCredentials: any = 'test credentials';
|
||||
|
@ -82,33 +83,20 @@ describe(MSICredentialsProvider, () => {
|
|||
expect(creds).toBe(expectedCredentials);
|
||||
});
|
||||
|
||||
it('creates credentials with service principal', async () => {
|
||||
process.env.AZURE_TENANT_ID = 'tenant';
|
||||
process.env.AZURE_CLIENT_ID = 'appId';
|
||||
process.env.AZURE_CLIENT_SECRET = 'password';
|
||||
|
||||
it('creates credentials using Azure CLI credentials', async () => {
|
||||
testSubject = new MSICredentialsProvider(
|
||||
mockMsRestNodeAuth.object,
|
||||
AuthenticationMethod.servicePrincipal,
|
||||
AuthenticationMethod.azureCliCredentials,
|
||||
CredentialType.AppService,
|
||||
retryHelperMock.object,
|
||||
maxAttempts,
|
||||
msBetweenRetries,
|
||||
);
|
||||
|
||||
mockMsRestNodeAuth
|
||||
.setup(async (m) =>
|
||||
m.loginWithServicePrincipalSecret(
|
||||
process.env.AZURE_CLIENT_ID,
|
||||
process.env.AZURE_CLIENT_SECRET,
|
||||
process.env.AZURE_TENANT_ID,
|
||||
{
|
||||
tokenAudience: 'r1',
|
||||
},
|
||||
),
|
||||
)
|
||||
.returns(async () => Promise.resolve(expectedCredentials))
|
||||
.verifiable(Times.once());
|
||||
const azureCliCredentials = {
|
||||
create: async () => Promise.resolve(expectedCredentials),
|
||||
} as typeof msRestNodeAuth.AzureCliCredentials;
|
||||
mockMsRestNodeAuth.object.AzureCliCredentials = azureCliCredentials;
|
||||
|
||||
const creds = await testSubject.getCredentials('r1');
|
||||
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
import * as msRestNodeAuth from '@azure/ms-rest-nodeauth';
|
||||
import * as msRest from '@azure/ms-rest-js';
|
||||
import { RetryHelper, System } from 'common';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { iocTypeNames } from '../ioc-types';
|
||||
|
||||
export type Credentials = msRestNodeAuth.MSITokenCredentials | msRestNodeAuth.ApplicationTokenCredentials;
|
||||
export type Credentials = msRestNodeAuth.MSITokenCredentials | msRestNodeAuth.ApplicationTokenCredentials | msRest.ServiceClientCredentials;
|
||||
|
||||
export enum CredentialType {
|
||||
VM,
|
||||
|
@ -15,7 +16,7 @@ export enum CredentialType {
|
|||
|
||||
export enum AuthenticationMethod {
|
||||
managedIdentity = 'managedIdentity',
|
||||
servicePrincipal = 'servicePrincipal',
|
||||
azureCliCredentials = 'azureCliCredentials',
|
||||
}
|
||||
|
||||
@injectable()
|
||||
|
@ -32,13 +33,8 @@ export class MSICredentialsProvider {
|
|||
public async getCredentials(resource: string): Promise<Credentials> {
|
||||
let getCredentialsFunction: () => Promise<Credentials>;
|
||||
|
||||
if (this.authenticationMethod === AuthenticationMethod.servicePrincipal) {
|
||||
const tenant = process.env.AZURE_TENANT_ID;
|
||||
const clientId = process.env.AZURE_CLIENT_ID;
|
||||
const secret = process.env.AZURE_CLIENT_SECRET;
|
||||
|
||||
getCredentialsFunction = async () =>
|
||||
this.msrestAzureObj.loginWithServicePrincipalSecret(clientId, secret, tenant, { tokenAudience: resource });
|
||||
if (this.authenticationMethod === AuthenticationMethod.azureCliCredentials) {
|
||||
getCredentialsFunction = async () => this.msrestAzureObj.AzureCliCredentials.create({ resource });
|
||||
} else if (this.credentialType === CredentialType.VM) {
|
||||
getCredentialsFunction = async () => this.msrestAzureObj.loginWithVmMSI({ resource });
|
||||
} else {
|
||||
|
|
|
@ -106,7 +106,7 @@ function createCosmosContainerClient(container: interfaces.Container, dbName: st
|
|||
}
|
||||
|
||||
function setupAzureKeyVaultClientProvider(container: Container): void {
|
||||
IoC.setupSingletonProvider<SecretClient>(iocTypeNames.AzureKeyVaultClientProvider, container, async (context) => {
|
||||
IoC.setupSingletonProvider<SecretClient>(iocTypeNames.AzureKeyVaultClientProvider, container, async () => {
|
||||
const credentialProvider = container.get(CredentialsProvider);
|
||||
const credentials = credentialProvider.getAzureCredential();
|
||||
|
||||
|
@ -223,7 +223,7 @@ function setupAuthenticationMethod(container: Container): void {
|
|||
.bind(iocTypeNames.AuthenticationMethod)
|
||||
.toConstantValue(
|
||||
System.isDebugEnabled() === true || process.env.LOCAL_AUTH === 'true'
|
||||
? AuthenticationMethod.servicePrincipal
|
||||
? AuthenticationMethod.azureCliCredentials
|
||||
: AuthenticationMethod.managedIdentity,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -108,10 +108,6 @@ AZURE_STORAGE_NAME=$storageAccountName
|
|||
COSMOS_DB_URL=$cosmosDbUrl
|
||||
COSMOS_DB_KEY=$cosmosDbAccessKey
|
||||
|
||||
AZURE_TENANT_ID=$tenant
|
||||
AZURE_CLIENT_ID=$clientId
|
||||
AZURE_CLIENT_SECRET=$password
|
||||
|
||||
AZ_BATCH_POOL_ID=
|
||||
AZ_BATCH_JOB_ID=1-dev-test-job
|
||||
AZ_BATCH_TASK_ID=dev-test-task
|
||||
|
|
|
@ -12,10 +12,6 @@ export subscription
|
|||
export resourceGroupName
|
||||
export keyVault
|
||||
|
||||
export clientId
|
||||
export tenant
|
||||
export password
|
||||
|
||||
# Disable POSIX to Windows path conversion
|
||||
export MSYS_NO_PATHCONV=1
|
||||
|
||||
|
@ -26,6 +22,31 @@ Usage: ${BASH_SOURCE} -r <resource group> [-s <subscription name or id>] [-k <ke
|
|||
exit 1
|
||||
}
|
||||
|
||||
grantAccess() {
|
||||
local assignee=$1
|
||||
|
||||
# Set key vault access policy
|
||||
echo "Granting access to the $keyVault Key Vault"
|
||||
az role assignment create \
|
||||
--role "Key Vault Secrets User" \
|
||||
--assignee "$assignee" \
|
||||
--scope "/subscriptions/$subscription/resourcegroups/$resourceGroupName/providers/Microsoft.KeyVault/vaults/$keyVault" 1>/dev/null
|
||||
|
||||
# Granting access to storage blob
|
||||
echo "Granting access to the $storageAccountName Blob storage"
|
||||
az role assignment create \
|
||||
--role "Storage Blob Data Contributor" \
|
||||
--assignee "$assignee" \
|
||||
--scope "/subscriptions/$subscription/resourceGroups/$resourceGroupName/providers/Microsoft.Storage/storageAccounts/$storageAccountName" 1>/dev/null
|
||||
|
||||
# Granting access to storage queue
|
||||
echo "Granting access to the $storageAccountName Queue storage"
|
||||
az role assignment create \
|
||||
--role "Storage Queue Data Contributor" \
|
||||
--assignee "$assignee" \
|
||||
--scope "/subscriptions/$subscription/resourceGroups/$resourceGroupName/providers/Microsoft.Storage/storageAccounts/$storageAccountName" 1>/dev/null
|
||||
}
|
||||
|
||||
# Read script arguments
|
||||
while getopts ":s:r:k:" option; do
|
||||
case $option in
|
||||
|
@ -49,36 +70,9 @@ if [[ -z $subscription ]]; then
|
|||
. "${0%/*}/get-resource-names.sh"
|
||||
fi
|
||||
|
||||
# Generate service principal name
|
||||
user=$(az ad signed-in-user show --query "userPrincipalName" -o tsv)
|
||||
displayName="$user-$resourceGroupName"
|
||||
echo "Granting permissions to $user user..."
|
||||
|
||||
# Create or update service principal object
|
||||
# Use display name instead of service principal name to prevent az cli assiging a random display name
|
||||
echo "Creating $displayName service principal..."
|
||||
password=$(az ad sp create-for-rbac --role contributor --scopes "/subscriptions/$subscription/resourceGroups/$resourceGroupName" --name "$displayName" --query "password" -o tsv)
|
||||
clientId=$(az ad signed-in-user show --query "id" -o tsv)
|
||||
|
||||
# Retrieve service principal object properties
|
||||
tenant=$(az ad sp list --display-name "$displayName" --query "[].appOwnerOrganizationId" -o tsv)
|
||||
clientId=$(az ad sp list --display-name "$displayName" --query "[].appId" -o tsv)
|
||||
|
||||
# Set key vault access policy
|
||||
echo "Granting service principal permissions to the $keyVault Key Vault"
|
||||
az role assignment create \
|
||||
--role "Key Vault Secrets User" \
|
||||
--assignee "$clientId" \
|
||||
--scope "/subscriptions/$subscription/resourcegroups/$resourceGroupName/providers/Microsoft.KeyVault/vaults/$keyVault" 1>/dev/null
|
||||
|
||||
# Granting access to storage blob
|
||||
echo "Granting service principal permissions to the $storageAccountName Blob storage"
|
||||
az role assignment create \
|
||||
--role "Storage Blob Data Contributor" \
|
||||
--assignee "$clientId" \
|
||||
--scope "/subscriptions/$subscription/resourceGroups/$resourceGroupName/providers/Microsoft.Storage/storageAccounts/$storageAccountName" 1>/dev/null
|
||||
|
||||
# Granting access to storage queue
|
||||
echo "Granting service principal permissions to the $storageAccountName Queue storage"
|
||||
az role assignment create \
|
||||
--role "Storage Queue Data Contributor" \
|
||||
--assignee "$clientId" \
|
||||
--scope "/subscriptions/$subscription/resourceGroups/$resourceGroupName/providers/Microsoft.Storage/storageAccounts/$storageAccountName" 1>/dev/null
|
||||
grantAccess $clientId
|
||||
|
|
19
yarn.lock
19
yarn.lock
|
@ -472,6 +472,22 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@azure/ms-rest-js@npm:^2.7.0":
|
||||
version: 2.7.0
|
||||
resolution: "@azure/ms-rest-js@npm:2.7.0"
|
||||
dependencies:
|
||||
"@azure/core-auth": ^1.1.4
|
||||
abort-controller: ^3.0.0
|
||||
form-data: ^2.5.0
|
||||
node-fetch: ^2.6.7
|
||||
tslib: ^1.10.0
|
||||
tunnel: 0.0.6
|
||||
uuid: ^8.3.2
|
||||
xml2js: ^0.5.0
|
||||
checksum: 38434010f3fc54a625f637a7758358d7ce0ad3e55ce9a6c7490bf05bbec8ea75ae95fe80041d2376beb3ef78ee6e55858bd0541477d7a88703246e368cfd59c1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@azure/ms-rest-nodeauth@npm:^3.1.1":
|
||||
version: 3.1.1
|
||||
resolution: "@azure/ms-rest-nodeauth@npm:3.1.1"
|
||||
|
@ -5596,6 +5612,7 @@ __metadata:
|
|||
"@azure/cosmos": ^4.0.0
|
||||
"@azure/identity": ^3.1.3
|
||||
"@azure/keyvault-secrets": ^4.6.0
|
||||
"@azure/ms-rest-js": ^2.7.0
|
||||
"@azure/ms-rest-nodeauth": ^3.1.1
|
||||
"@azure/storage-blob": ^12.12.0
|
||||
"@azure/storage-queue": ^12.11.0
|
||||
|
@ -16669,7 +16686,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"xml2js@npm:^0.4.19":
|
||||
"xml2js@npm:^0.4.19, xml2js@npm:^0.5.0":
|
||||
version: 0.5.0
|
||||
resolution: "xml2js@npm:0.5.0"
|
||||
dependencies:
|
||||
|
|
Загрузка…
Ссылка в новой задаче