From f9526e7d465d83e472c755ff69392050d3d74fac Mon Sep 17 00:00:00 2001 From: Megan Mott Date: Mon, 24 Feb 2025 12:48:26 -0800 Subject: [PATCH 1/5] changes for settings conversion --- azure/index.d.ts | 76 ++++++++++++++++++- azure/src/constants.ts | 61 ++++++++++++++- azure/src/index.ts | 7 +- azure/src/wizard/RoleAssignmentExecuteStep.ts | 48 +++++++----- .../wizard/UserAssignedIdentityListStep.ts | 2 + 5 files changed, 166 insertions(+), 28 deletions(-) diff --git a/azure/index.d.ts b/azure/index.d.ts index f62f77c250..bdb433df5b 100644 --- a/azure/index.d.ts +++ b/azure/index.d.ts @@ -5,7 +5,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { type RoleDefinition } from '@azure/arm-authorization'; import { Identity } from '@azure/arm-msi'; import type { ExtendedLocation, ResourceGroup } from '@azure/arm-resources'; import type { Location } from '@azure/arm-resources-subscriptions'; @@ -383,14 +382,20 @@ export declare class RoleAssignmentExecuteStep string | undefined, roleDefinition: RoleDefinition); + public constructor(roles: () => Roles[] | undefined); public execute(wizardContext: T, progress: Progress<{ message?: string; increment?: number }>): Promise; public shouldExecute(wizardContext: T): boolean; } +export interface Roles { + scopeId: string | undefined; + roleDefinitionId: string; + roleDefinitionName: string; +} + export interface IAzureUtilsExtensionVariables extends UIExtensionVariables { prefix: string; } @@ -506,4 +511,69 @@ export declare const CommonRoleDefinitions: { readonly description: "Allows for read, write and delete access to Azure Storage blob containers and data"; readonly roleType: "BuiltInRole"; }; + readonly storageBlobDataOwner: { + readonly name: "b7e6dc6d-f1e8-4753-8033-0f276bb0955b", + readonly type: "Microsoft.Authorization/roleDefinitions", + readonly roleName: "Storage Blob Data Owner", + readonly description: "Allows for full access to Azure Storage blob containers and data, including assigning POSIX access control.", + readonly roleType: "BuiltInRole" + }; + readonly storageQueueDataContributor: { + readonly name: "974c5e8b-45b9-4653-ba55-5f855dd0fb88", + readonly type: "Microsoft.Authorization/roleDefinitions", + readonly roleName: "Storage Queue Data Contributor", + readonly description: "Read, write, and delete Azure Storage queues and queue messages.", + readonly roleType: "BuiltInRole" + }; + readonly azureServiceBusDataReceiver: { + readonly name: "4f6d3b9b-027b-4f4c-9142-0e5a2a2247e0", + readonly type: "Microsoft.Authorization/roleDefinitions", + readonly roleName: "Azure Service Bus Data Receiver", + readonly description: "Allows for receive access to Azure Service Bus resources.", + readonly sroleType: "BuiltInRole" + }; + readonly azureServiceBusDataOwner: { + readonly name: "090c5cfd-751d-490a-894a-3ce6f1109419", + readonly type: "Microsoft.Authorization/roleDefinitions", + readonly roleName: "Azure Service Bus Data Owner", + readonly description: "Allows for full access to Azure Service Bus resources.", + readonly roleType: "BuiltInRole" + }; + readonly azureEventHubsDataReceiver: { + readonly name: "a638d3c7-ab3a-418d-83e6-5f17a39d4fde", + readonly type: "Microsoft.Authorization/roleDefinitions", + readonly roleName: "Azure Event Hubs Data Receiver", + readonly description: "Allows receive access to Azure Event Hubs resources.", + readonly roleType: "BuiltInRole" + }; + readonly azureEventHubsDataOwner: { + readonly name: "f526a384-b230-433a-b45c-95f59c4a2dec", + readonly type: "Microsoft.Authorization/roleDefinitions", + readonly roleName: "Azure Event Hubs Data Owner", + readonly description: "Allows for full access to Azure Event Hubs resources.", + readonly roleType: "BuiltInRole" + }; + readonly cosmosDBAccountReader: { + readonly name: "fbdf93bf-df7d-467e-a4d2-9458aa1360c8", + readonly type: "Microsoft.Authorization/roleDefinitions", + readonly roleName: "Cosmos DB Account Reader", + readonly description: "Can read Azure Cosmos DB account data.", + readonly roleType: "BuiltInRole" + }; + readonly documentDBAccountContributor: { + readonly name: "5bd9cd88-fe45-4216-938b-f97437e15450", + readonly type: "Microsoft.Authorization/roleDefinitions", + readonly roleName: "DocumentDB Account Contributor", + readonly description: "Can manage Azure Cosmos DB accounts.", + readonly roleType: "BuiltInRole" + } }; +/** + * Constructs the role id for a given subscription and role name id + * + * @param subscriptionId - Id for the subscription + * @param roleId - Name id for the role to be assigned (i.e CommonRoleDefinitions.storageBlobDataContributor.name) + */ +export function createRoleId(subscriptionId: string, roleId: string): string; + + diff --git a/azure/src/constants.ts b/azure/src/constants.ts index fb9128a1b1..591aa1f2a7 100644 --- a/azure/src/constants.ts +++ b/azure/src/constants.ts @@ -11,11 +11,70 @@ export const storageProviderType = "Microsoft.Storage/storageAccounts"; export const CommonRoleDefinitions = { storageBlobDataContributor: { - id: "/subscriptions/9b5c7ccb-9857-4307-843b-8875e83f65e9/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe", name: "ba92f5b4-2d11-453d-a403-e96b0029c9fe", type: "Microsoft.Authorization/roleDefinitions", roleName: "Storage Blob Data Contributor", description: "Allows for read, write and delete access to Azure Storage blob containers and data", roleType: "BuiltInRole" + } as RoleDefinition, + storageBlobDataOwner: { + name: "b7e6dc6d-f1e8-4753-8033-0f276bb0955b", + type: "Microsoft.Authorization/roleDefinitions", + roleName: "Storage Blob Data Owner", + description: "Allows for full access to Azure Storage blob containers and data, including assigning POSIX access control.", + roleType: "BuiltInRole" + } as RoleDefinition, + storageQueueDataContributor: { + name: "974c5e8b-45b9-4653-ba55-5f855dd0fb88", + type: "Microsoft.Authorization/roleDefinitions", + roleName: "Storage Queue Data Contributor", + description: "Read, write, and delete Azure Storage queues and queue messages.", + roleType: "BuiltInRole" + } as RoleDefinition, + azureServiceBusDataReceiver: { + name: "4f6d3b9b-027b-4f4c-9142-0e5a2a2247e0", + type: "Microsoft.Authorization/roleDefinitions", + roleName: "Azure Service Bus Data Receiver", + description: "Allows for receive access to Azure Service Bus resources.", + roleType: "BuiltInRole" + } as RoleDefinition, + azureServiceBusDataOwner: { + name: "090c5cfd-751d-490a-894a-3ce6f1109419", + type: "Microsoft.Authorization/roleDefinitions", + roleName: "Azure Service Bus Data Owner", + description: "Allows for full access to Azure Service Bus resources.", + roleType: "BuiltInRole" + } as RoleDefinition, + azureEventHubsDataReceiver: { + name: "a638d3c7-ab3a-418d-83e6-5f17a39d4fde", + type: "Microsoft.Authorization/roleDefinitions", + roleName: "Azure Event Hubs Data Receiver", + description: "Allows receive access to Azure Event Hubs resources.", + roleType: "BuiltInRole" + } as RoleDefinition, + azureEventHubsDataOwner: { + name: "f526a384-b230-433a-b45c-95f59c4a2dec", + type: "Microsoft.Authorization/roleDefinitions", + roleName: "Azure Event Hubs Data Owner", + description: "Allows for full access to Azure Event Hubs resources.", + roleType: "BuiltInRole" + } as RoleDefinition, + cosmosDBAccountReader: { + name: "fbdf93bf-df7d-467e-a4d2-9458aa1360c8", + type: "Microsoft.Authorization/roleDefinitions", + roleName: "Cosmos DB Account Reader", + description: "Can read Azure Cosmos DB account data.", + roleType: "BuiltInRole" + } as RoleDefinition, + documentDBAccountContributor: { + name: "5bd9cd88-fe45-4216-938b-f97437e15450", + type: "Microsoft.Authorization/roleDefinitions", + roleName: "DocumentDB Account Contributor", + description: "Can manage Azure Cosmos DB accounts.", + roleType: "BuiltInRole" } as RoleDefinition } as const; + +export function createRoleId(subscriptionId: string, roleId: string): string { + return `/subscriptions/${subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/${roleId}` +} diff --git a/azure/src/index.ts b/azure/src/index.ts index 1a31fedc0c..e1f797a68f 100644 --- a/azure/src/index.ts +++ b/azure/src/index.ts @@ -3,13 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export { CommonRoleDefinitions as const } from './constants'; +export { CommonRoleDefinitions, createRoleId } from './constants'; export * from './createAzureClient'; +export { registerAzureUtilsExtensionVariables } from './extensionVariables'; export * from './openInPortal'; export * from './tree/AzureAccountTreeItemBase'; export * from './tree/SubscriptionTreeItemBase'; export * from './utils/createPortalUri'; export * from './utils/parseAzureResourceId'; +export * from './utils/setupAzureLogger'; export * from './utils/uiUtils'; export * from './wizard/LocationListStep'; export * from './wizard/ResourceGroupCreateStep'; @@ -22,7 +24,4 @@ export * from './wizard/StorageAccountNameStep'; export * from './wizard/UserAssignedIdentityCreateStep'; export * from './wizard/UserAssignedIdentityListStep'; export * from './wizard/VerifyProvidersStep'; -export * from './utils/setupAzureLogger'; -export { registerAzureUtilsExtensionVariables } from './extensionVariables'; - // NOTE: The auto-fix action "source.organizeImports" does weird things with this file, but there doesn't seem to be a way to disable it on a per-file basis so we'll just let it happen diff --git a/azure/src/wizard/RoleAssignmentExecuteStep.ts b/azure/src/wizard/RoleAssignmentExecuteStep.ts index 25fc20d646..50dc52cb20 100644 --- a/azure/src/wizard/RoleAssignmentExecuteStep.ts +++ b/azure/src/wizard/RoleAssignmentExecuteStep.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { type RoleDefinition } from '@azure/arm-authorization'; import { AzureWizardExecuteStep, nonNullValueAndProp } from '@microsoft/vscode-azext-utils'; import { randomUUID } from 'crypto'; import { l10n, Progress } from 'vscode'; @@ -11,34 +10,43 @@ import * as types from '../../index'; import { createAuthorizationManagementClient } from '../clients'; import { ext } from '../extensionVariables'; +export interface Roles { + scopeId: string | undefined; + roleDefinitionId: string; + roleDefinitionName: string; +} + export class RoleAssignmentExecuteStep extends AzureWizardExecuteStep { public priority: number = 900; - private getScopeId: () => string | undefined; - private _roleDefinition: RoleDefinition; - public constructor(getScopeId: () => string | undefined, roleDefinition: RoleDefinition) { + private roles: () => Roles[] | undefined; + public constructor(roles: () => Roles[] | undefined) { super(); - this.getScopeId = getScopeId; - this._roleDefinition = roleDefinition; + this.roles = roles; } public async execute(wizardContext: T, progress: Progress<{ message?: string; increment?: number }>): Promise { const amClient = await createAuthorizationManagementClient(wizardContext) - const scope = this.getScopeId(); - if (!scope) { - throw new Error(l10n.t('No scope was provided for the role assignment.')); - } - const scopeSplitArr = scope.split('/'); - const resourceName = scopeSplitArr[scopeSplitArr.length - 1] ?? ''; - const resourceType = scopeSplitArr[scopeSplitArr.length - 2] ?? ''; + const roles = this.roles(); + if (roles) { + for (const role of roles) { + const scope = role.scopeId; + if (!scope) { + throw new Error(l10n.t('No scope was provided for the role assignment.')); + } + const scopeSplitArr = scope.split('/'); + const resourceName = scopeSplitArr[scopeSplitArr.length - 1] ?? ''; + const resourceType = scopeSplitArr[scopeSplitArr.length - 2] ?? ''; - const guid = randomUUID(); - const roleDefinitionId = this._roleDefinition.id as string; - const principalId = nonNullValueAndProp(wizardContext.managedIdentity, 'principalId'); + const guid = randomUUID(); + const roleDefinitionId = role.roleDefinitionId; + const principalId = nonNullValueAndProp(wizardContext.managedIdentity, 'principalId'); - await amClient.roleAssignments.create(scope, guid, { roleDefinitionId, principalId }); - const roleAssignmentCreated = l10n.t('Role assignment "{0}" created for the {2} resource "{1}".', this._roleDefinition.roleName ?? '', resourceName, resourceType); - progress.report({ message: roleAssignmentCreated }); - ext.outputChannel.appendLog(roleAssignmentCreated); + await amClient.roleAssignments.create(scope, guid, { roleDefinitionId, principalId }); + const roleAssignmentCreated = l10n.t('Role assignment "{0}" created for the {2} resource "{1}".', role.roleDefinitionName ?? '', resourceName, resourceType); + progress.report({ message: roleAssignmentCreated }); + ext.outputChannel.appendLog(roleAssignmentCreated); + } + } } public shouldExecute(wizardContext: T): boolean { diff --git a/azure/src/wizard/UserAssignedIdentityListStep.ts b/azure/src/wizard/UserAssignedIdentityListStep.ts index cd6407e05d..b18f88e39c 100644 --- a/azure/src/wizard/UserAssignedIdentityListStep.ts +++ b/azure/src/wizard/UserAssignedIdentityListStep.ts @@ -9,6 +9,7 @@ import * as vscode from 'vscode'; import * as types from '../../index'; import { createManagedServiceIdentityClient } from '../clients'; import { uiUtils } from '../utils/uiUtils'; +import { ResourceGroupListStep } from './ResourceGroupListStep'; import { UserAssignedIdentityCreateStep } from './UserAssignedIdentityCreateStep'; export class UserAssignedIdentityListStep extends AzureWizardPromptStep { @@ -31,6 +32,7 @@ export class UserAssignedIdentityListStep | undefined> { if (!wizardContext.managedIdentity) { return { + promptSteps: [new ResourceGroupListStep()], executeSteps: [new UserAssignedIdentityCreateStep()] } } From 15b10accb83396277dcd40ea4b3d6598d0d140d1 Mon Sep 17 00:00:00 2001 From: Megan Mott Date: Wed, 26 Feb 2025 09:59:18 -0800 Subject: [PATCH 2/5] small changes --- azure/index.d.ts | 8 ++++++-- azure/src/wizard/RoleAssignmentExecuteStep.ts | 6 +++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/azure/index.d.ts b/azure/index.d.ts index bdb433df5b..f7cd20380d 100644 --- a/azure/index.d.ts +++ b/azure/index.d.ts @@ -384,13 +384,13 @@ export declare class RoleAssignmentExecuteStep Roles[] | undefined); + public constructor(roles: () => Role[] | undefined); public execute(wizardContext: T, progress: Progress<{ message?: string; increment?: number }>): Promise; public shouldExecute(wizardContext: T): boolean; } -export interface Roles { +export interface Role { scopeId: string | undefined; roleDefinitionId: string; roleDefinitionName: string; @@ -502,6 +502,10 @@ export function setupAzureLogger(logOutputChannel: LogOutputChannel): Disposable */ export function addBasicAuthenticationCredentialsToClient(client: ServiceClient, userName: string, password: string): void; +/** + * Common Roles that should be used to assign permissions to resources + * The role definitions can be found here: https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles + */ export declare const CommonRoleDefinitions: { readonly storageBlobDataContributor: { readonly id: "/subscriptions/9b5c7ccb-9857-4307-843b-8875e83f65e9/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe"; diff --git a/azure/src/wizard/RoleAssignmentExecuteStep.ts b/azure/src/wizard/RoleAssignmentExecuteStep.ts index 50dc52cb20..1cec3bfa89 100644 --- a/azure/src/wizard/RoleAssignmentExecuteStep.ts +++ b/azure/src/wizard/RoleAssignmentExecuteStep.ts @@ -10,7 +10,7 @@ import * as types from '../../index'; import { createAuthorizationManagementClient } from '../clients'; import { ext } from '../extensionVariables'; -export interface Roles { +export interface Role { scopeId: string | undefined; roleDefinitionId: string; roleDefinitionName: string; @@ -18,8 +18,8 @@ export interface Roles { export class RoleAssignmentExecuteStep extends AzureWizardExecuteStep { public priority: number = 900; - private roles: () => Roles[] | undefined; - public constructor(roles: () => Roles[] | undefined) { + private roles: () => Role[] | undefined; + public constructor(roles: () => Role[] | undefined) { super(); this.roles = roles; } From 14ca3d168f60c1412dca5b09eccc2276d2674aae Mon Sep 17 00:00:00 2001 From: Megan Mott Date: Thu, 27 Feb 2025 14:38:40 -0800 Subject: [PATCH 3/5] changes plus add activity children --- azure/index.d.ts | 3 +- azure/src/constants.ts | 4 +- azure/src/wizard/RoleAssignmentExecuteStep.ts | 42 +++++++++++++++---- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/azure/index.d.ts b/azure/index.d.ts index f7cd20380d..68ed2f8392 100644 --- a/azure/index.d.ts +++ b/azure/index.d.ts @@ -5,6 +5,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import { type RoleDefinition } from "@azure/arm-authorization"; import { Identity } from '@azure/arm-msi'; import type { ExtendedLocation, ResourceGroup } from '@azure/arm-resources'; import type { Location } from '@azure/arm-resources-subscriptions'; @@ -578,6 +579,6 @@ export declare const CommonRoleDefinitions: { * @param subscriptionId - Id for the subscription * @param roleId - Name id for the role to be assigned (i.e CommonRoleDefinitions.storageBlobDataContributor.name) */ -export function createRoleId(subscriptionId: string, roleId: string): string; +export function createRoleId(subscriptionId: string, RoleDefinition: RoleDefinition): string; diff --git a/azure/src/constants.ts b/azure/src/constants.ts index 591aa1f2a7..b9ec9d12f2 100644 --- a/azure/src/constants.ts +++ b/azure/src/constants.ts @@ -75,6 +75,6 @@ export const CommonRoleDefinitions = { } as RoleDefinition } as const; -export function createRoleId(subscriptionId: string, roleId: string): string { - return `/subscriptions/${subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/${roleId}` +export function createRoleId(subscriptionId: string, RoleDefinition: RoleDefinition): string { + return `/subscriptions/${subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/${RoleDefinition.name}` } diff --git a/azure/src/wizard/RoleAssignmentExecuteStep.ts b/azure/src/wizard/RoleAssignmentExecuteStep.ts index 1cec3bfa89..0ba1e54937 100644 --- a/azure/src/wizard/RoleAssignmentExecuteStep.ts +++ b/azure/src/wizard/RoleAssignmentExecuteStep.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AzureWizardExecuteStep, nonNullValueAndProp } from '@microsoft/vscode-azext-utils'; +import { activityFailIcon, activitySuccessContext, activitySuccessIcon, AzureWizardExecuteStep, createUniversallyUniqueContextValue, ExecuteActivityContext, GenericTreeItem, nonNullValueAndProp, parseError } from '@microsoft/vscode-azext-utils'; import { randomUUID } from 'crypto'; import { l10n, Progress } from 'vscode'; import * as types from '../../index'; @@ -16,7 +16,7 @@ export interface Role { roleDefinitionName: string; } -export class RoleAssignmentExecuteStep extends AzureWizardExecuteStep { +export class RoleAssignmentExecuteStep> extends AzureWizardExecuteStep { public priority: number = 900; private roles: () => Role[] | undefined; public constructor(roles: () => Role[] | undefined) { @@ -36,15 +36,39 @@ export class RoleAssignmentExecuteStep Date: Thu, 27 Feb 2025 15:14:42 -0800 Subject: [PATCH 4/5] throw error --- azure/src/wizard/RoleAssignmentExecuteStep.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/azure/src/wizard/RoleAssignmentExecuteStep.ts b/azure/src/wizard/RoleAssignmentExecuteStep.ts index 0ba1e54937..b9b33302ff 100644 --- a/azure/src/wizard/RoleAssignmentExecuteStep.ts +++ b/azure/src/wizard/RoleAssignmentExecuteStep.ts @@ -48,7 +48,7 @@ export class RoleAssignmentExecuteStep Date: Wed, 5 Mar 2025 10:07:34 -0800 Subject: [PATCH 5/5] add doc strings --- azure/index.d.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/azure/index.d.ts b/azure/index.d.ts index bc6f06b948..d420e19663 100644 --- a/azure/index.d.ts +++ b/azure/index.d.ts @@ -392,8 +392,20 @@ export declare class RoleAssignmentExecuteStep