-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Read-only Implementation of Managed Identities (#933)
- Loading branch information
Showing
19 changed files
with
14,274 additions
and
2,111 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
/*--------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
|
||
import { RoleAssignment } from '@azure/arm-authorization'; | ||
import { uiUtils } from '@microsoft/vscode-azext-azureutils'; | ||
import { callWithTelemetryAndErrorHandling, createSubscriptionContext, type IActionContext } from '@microsoft/vscode-azext-utils'; | ||
import { AzureResource, AzureResourceModel, BranchDataProvider } from '@microsoft/vscode-azureresources-api'; | ||
import * as vscode from 'vscode'; | ||
import { localize } from 'vscode-nls'; | ||
import { ext } from '../extensionVariables'; | ||
import { ResourceGroupsItem } from '../tree/ResourceGroupsItem'; | ||
import { createAuthorizationManagementClient } from '../utils/azureClients'; | ||
import { ManagedIdentityItem } from './ManagedIdentityItem'; | ||
export class ManagedIdentityBranchDataProvider extends vscode.Disposable implements BranchDataProvider<AzureResource, AzureResourceModel> { | ||
private readonly onDidChangeTreeDataEmitter = new vscode.EventEmitter<ResourceGroupsItem | undefined>(); | ||
public roleAssignmentsTask: Promise<{ [id: string]: RoleAssignment[] }>; | ||
|
||
constructor() { | ||
super( | ||
() => { | ||
this.onDidChangeTreeDataEmitter.dispose(); | ||
}); | ||
this.roleAssignmentsTask = this.initialize(); | ||
} | ||
|
||
get onDidChangeTreeData(): vscode.Event<ResourceGroupsItem | undefined> { | ||
return this.onDidChangeTreeDataEmitter.event; | ||
} | ||
|
||
public async initialize(): Promise<{ [id: string]: RoleAssignment[] }> { | ||
return await callWithTelemetryAndErrorHandling('initializeManagedIdentityBranchDataProvider', async (context: IActionContext) => { | ||
const provider = await ext.subscriptionProviderFactory(); | ||
const allSubscriptions = await provider.getSubscriptions(false /*filter*/); | ||
const roleAssignments: { [id: string]: RoleAssignment[] } = {}; | ||
await Promise.allSettled(allSubscriptions.map(async (subscription) => { | ||
const subContext = createSubscriptionContext(subscription); | ||
const authClient = await createAuthorizationManagementClient([context, subContext]); | ||
roleAssignments[subscription.subscriptionId] = await uiUtils.listAllIterator(authClient.roleAssignments.listForSubscription()); | ||
})); | ||
|
||
return roleAssignments; | ||
}) ?? {}; | ||
} | ||
|
||
async getChildren(element: ResourceGroupsItem): Promise<ResourceGroupsItem[] | null | undefined> { | ||
return (await element.getChildren?.())?.map((child) => { | ||
return ext.azureTreeState.wrapItemInStateHandling(child, () => this.refresh(child)) | ||
}); | ||
} | ||
|
||
async getResourceItem(element: AzureResource): Promise<ResourceGroupsItem> { | ||
const resourceItem = await callWithTelemetryAndErrorHandling( | ||
'getResourceItem', | ||
async (context: IActionContext) => { | ||
context.errorHandling.rethrow = true; | ||
return new ManagedIdentityItem(element.subscription, element); | ||
}); | ||
|
||
if (!resourceItem) { | ||
throw new Error(localize('failedToGetResourceItem', 'Failed to get resource item for "{0}"', element.id)); | ||
} | ||
|
||
return ext.azureTreeState.wrapItemInStateHandling(resourceItem, () => this.refresh(resourceItem)); | ||
} | ||
|
||
async getTreeItem(element: ResourceGroupsItem): Promise<vscode.TreeItem> { | ||
return await element.getTreeItem(); | ||
} | ||
|
||
refresh(element?: ResourceGroupsItem): void { | ||
this.onDidChangeTreeDataEmitter.fire(element); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
/*--------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
|
||
import { Identity } from "@azure/arm-msi"; | ||
import { uiUtils } from "@microsoft/vscode-azext-azureutils"; | ||
import { callWithTelemetryAndErrorHandling, createContextValue, createSubscriptionContext, nonNullProp, type IActionContext } from "@microsoft/vscode-azext-utils"; | ||
import { type AzureResource, type AzureSubscription, type ViewPropertiesModel } from "@microsoft/vscode-azureresources-api"; | ||
import { ThemeIcon, TreeItem, TreeItemCollapsibleState } from "vscode"; | ||
import { getAzExtResourceType } from "../../api/src/index"; | ||
import { getAzureResourcesService } from "../services/AzureResourcesService"; | ||
import { GenericItem } from "../tree/GenericItem"; | ||
import { ResourceGroupsItem } from "../tree/ResourceGroupsItem"; | ||
import { createAuthorizationManagementClient, createManagedServiceIdentityClient } from "../utils/azureClients"; | ||
import { getIconPath } from "../utils/azureUtils"; | ||
import { localize } from "../utils/localize"; | ||
import { RoleAssignmentsItem } from "./RoleAssignmentsItem"; | ||
import { RoleDefinitionsItem } from "./RoleDefinitionsItem"; | ||
|
||
export class ManagedIdentityItem implements ResourceGroupsItem { | ||
static readonly contextValue: string = 'managedIdentityItem'; | ||
static readonly contextValueRegExp: RegExp = new RegExp(ManagedIdentityItem.contextValue); | ||
name: string; | ||
id: string; | ||
|
||
constructor(public readonly subscription: AzureSubscription, | ||
public readonly resource: AzureResource) { | ||
this.id = resource.id; | ||
this.name = resource.name; | ||
} | ||
|
||
viewProperties: ViewPropertiesModel = { | ||
data: this.resource, | ||
label: this.resource.name, | ||
} | ||
|
||
private get contextValue(): string { | ||
const values: string[] = []; | ||
values.push(ManagedIdentityItem.contextValue); | ||
return createContextValue(values); | ||
} | ||
|
||
async getChildren(): Promise<(GenericItem | RoleDefinitionsItem | RoleAssignmentsItem)[]> { | ||
const result = await callWithTelemetryAndErrorHandling('managedIdentityItem.getChildren', async (context: IActionContext) => { | ||
const subContext = createSubscriptionContext(this.subscription); | ||
const msiClient = await createManagedServiceIdentityClient([context, subContext]); | ||
const msi: Identity = await msiClient.userAssignedIdentities.get(nonNullProp(this.resource, 'resourceGroup'), this.resource.name); | ||
|
||
const resources = await getAzureResourcesService().listResources(context, this.subscription); | ||
const assignedRoleAssignment = new RoleAssignmentsItem(localize('sourceResources', 'Source resources'), this.subscription, msi); | ||
const accessRoleAssignment = new RoleAssignmentsItem(localize('targetServices', 'Target services'), this.subscription, msi); | ||
|
||
const assignedResources = resources.filter((r) => { | ||
// verify the msi is assigned to the resource by checking if the msi id is in the userAssignedIdentities | ||
const userAssignedIdentities = r.identity?.userAssignedIdentities; | ||
if (!userAssignedIdentities) { | ||
return false; | ||
} | ||
|
||
if (!msi.id) { | ||
return false; | ||
} | ||
|
||
return userAssignedIdentities[msi.id] !== undefined | ||
}).map((r) => { | ||
return new GenericItem(nonNullProp(r, 'name'), { id: `${msi.id}/${r.name}`, iconPath: getIconPath(r.type ? getAzExtResourceType({ type: r.type }) : undefined) }); | ||
}); | ||
|
||
const authClient = await createAuthorizationManagementClient([context, subContext]); | ||
const roleAssignment = await uiUtils.listAllIterator(authClient.roleAssignments.listForSubscription()); | ||
// filter the role assignments to only show the ones that are assigned to the msi | ||
const filteredBySub = roleAssignment.filter((ra) => ra.principalId === msi.principalId); | ||
|
||
const targetResources = await accessRoleAssignment.getRoleDefinitionsItems(context, filteredBySub); | ||
const children = []; | ||
|
||
if (assignedResources.length > 0) { | ||
// if there weren't any assigned resources, don't show that section | ||
assignedRoleAssignment.addChildren(assignedResources); | ||
children.push(assignedRoleAssignment); | ||
} | ||
|
||
accessRoleAssignment.addChildren(targetResources); | ||
children.push(accessRoleAssignment); | ||
accessRoleAssignment.addChild(new GenericItem(localize('showResources', 'Show resources from other subscriptions...'), | ||
{ | ||
id: accessRoleAssignment.id + '/showResourcesFromOtherSubscriptions', | ||
iconPath: new ThemeIcon('sync'), | ||
commandId: 'azureResources.loadAllSubscriptionRoleAssignments', | ||
commandArgs: [accessRoleAssignment] | ||
})) | ||
return children; | ||
}); | ||
|
||
return result ?? []; | ||
} | ||
|
||
getTreeItem(): TreeItem { | ||
return { | ||
label: this.resource.name, | ||
id: this.id, | ||
iconPath: getIconPath(this.resource.resourceType), | ||
contextValue: this.contextValue, | ||
collapsibleState: TreeItemCollapsibleState.Collapsed, | ||
} | ||
} | ||
} | ||
|
||
|
Oops, something went wrong.