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:
Maxim Laikine 2024-04-15 12:23:39 -07:00 коммит произвёл GitHub
Родитель 8f47a5b6cf
Коммит 0ea3e1289f
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
9 изменённых файлов: 68 добавлений и 78 удалений

Просмотреть файл

@ -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

Просмотреть файл

@ -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: