-
Notifications
You must be signed in to change notification settings - Fork 69
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
azure: Changes to managed identity steps so they are compatible with functions settings conversion #1889
base: main
Are you sure you want to change the base?
azure: Changes to managed identity steps so they are compatible with functions settings conversion #1889
Changes from 5 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -383,14 +383,20 @@ export declare class RoleAssignmentExecuteStep<T extends IResourceGroupWizardCon | |
* Example: `/subscriptions/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/rgName/providers/Microsoft.Storage/storageAccounts/resourceName` | ||
* This typically won't exist until _after_ the wizard executes and the resource is created, so we need to pass in a function that returns the ID. | ||
* If the scope ID is undefined, the step will throw an error. | ||
* @param roleDefinition The ARM role definition to assign. Use CommonRoleDefinition constant for role defintions that don't require user input. | ||
* @param roles An array of roles. Each role is an object and include the ARM role definition id and name of the role definition. | ||
* */ | ||
public constructor(getScopeId: () => string | undefined, roleDefinition: RoleDefinition); | ||
public constructor(roles: () => Role[] | undefined); | ||
|
||
public execute(wizardContext: T, progress: Progress<{ message?: string; increment?: number }>): Promise<void>; | ||
public shouldExecute(wizardContext: T): boolean; | ||
} | ||
|
||
export interface Role { | ||
scopeId: string | undefined; | ||
roleDefinitionId: string; | ||
roleDefinitionName: string; | ||
} | ||
|
||
export interface IAzureUtilsExtensionVariables extends UIExtensionVariables { | ||
prefix: string; | ||
} | ||
|
@@ -503,6 +509,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"; | ||
|
@@ -512,7 +522,70 @@ 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: { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. where'd you find all this info from? I would put a link in a comment so we know where to get it in the future There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll add a link |
||
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, RoleDefinition: RoleDefinition): string; | ||
|
||
/** | ||
* creates all RoleDefinitionsItem for an entire managed identity object | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,42 +3,82 @@ | |
* 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 { 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'; | ||
import { createAuthorizationManagementClient } from '../clients'; | ||
import { ext } from '../extensionVariables'; | ||
|
||
export class RoleAssignmentExecuteStep<T extends types.IResourceGroupWizardContext> extends AzureWizardExecuteStep<T> { | ||
export interface Role { | ||
scopeId: string | undefined; | ||
roleDefinitionId: string; | ||
roleDefinitionName: string; | ||
} | ||
|
||
export class RoleAssignmentExecuteStep<T extends types.IResourceGroupWizardContext & Partial<ExecuteActivityContext>> extends AzureWizardExecuteStep<T> { | ||
public priority: number = 900; | ||
private getScopeId: () => string | undefined; | ||
private _roleDefinition: RoleDefinition; | ||
public constructor(getScopeId: () => string | undefined, roleDefinition: RoleDefinition) { | ||
private roles: () => Role[] | undefined; | ||
public constructor(roles: () => Role[] | undefined) { | ||
super(); | ||
this.getScopeId = getScopeId; | ||
this._roleDefinition = roleDefinition; | ||
this.roles = roles; | ||
} | ||
|
||
public async execute(wizardContext: T, progress: Progress<{ message?: string; increment?: number }>): Promise<void> { | ||
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 roles = this.roles(); | ||
if (roles) { | ||
for (const role of roles) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we would want to make the whole step fail if one role fails. Maybe do a try/catch so it can continue? |
||
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] ?? ''; | ||
try { | ||
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}".', role.roleDefinitionName ?? '', resourceName, resourceType); | ||
progress.report({ message: roleAssignmentCreated }); | ||
ext.outputChannel.appendLog(roleAssignmentCreated); | ||
if (wizardContext.activityChildren) { | ||
wizardContext.activityChildren.push( | ||
new GenericTreeItem(undefined, { | ||
contextValue: createUniversallyUniqueContextValue(['successfullRoleAssignment', activitySuccessContext]), | ||
label: l10n.t(`Role Assignment ${role.roleDefinitionName} created for ${resourceName}`), | ||
iconPath: activitySuccessIcon | ||
}) | ||
); | ||
} | ||
} catch (error) { | ||
const roleAssignmentFailed = l10n.t('Failed to create role assignment "{0}" for the {2} resource "{1}".', role.roleDefinitionName ?? '', resourceName, resourceType); | ||
progress.report({ message: roleAssignmentFailed }); | ||
ext.outputChannel.appendLog(roleAssignmentFailed); | ||
const parsedError = parseError(error); | ||
ext.outputChannel.appendLog(parsedError.message); | ||
if (wizardContext.activityChildren) { | ||
wizardContext.activityChildren.push(new GenericTreeItem(undefined, { | ||
contextValue: createUniversallyUniqueContextValue(['failedRoleAssignment', activitySuccessContext]), | ||
label: l10n.t(`Role Assignment ${role.roleDefinitionName} failed for ${resourceName}`), | ||
iconPath: activityFailIcon | ||
})); | ||
} | ||
} | ||
|
||
} | ||
|
||
if (wizardContext.activityChildren) { | ||
for (const child of wizardContext.activityChildren) { | ||
if (child.contextValue.includes('failedRoleAssignment')) { | ||
throw new Error(l10n.t('Failed to create role assignment(s).')); | ||
} | ||
} | ||
} | ||
} | ||
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'); | ||
|
||
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); | ||
} | ||
|
||
public shouldExecute(wizardContext: T): boolean { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add doc strings to this interface?