-
Notifications
You must be signed in to change notification settings - Fork 70
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add RBAC permission request flow for NoSQL accounts
If connecting to a database account using AAD fails because of missing RBAC permissions, we now notify the user with instructions and an option to assign a contributor role for them. If this fails, show an error notification with a link to RBAC instructions.
- Loading branch information
Showing
4 changed files
with
130 additions
and
7 deletions.
There are no files selected for viewing
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,20 @@ | ||
/*--------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
|
||
// eslint-disable-next-line import/no-internal-modules | ||
import { getSessionFromVSCode } from '@microsoft/vscode-azext-azureauth/out/src/getSessionFromVSCode'; | ||
import * as vscode from "vscode"; | ||
|
||
export async function getSignedInPrincipalIdForAccountEndpoint(accountEndpoint: string): Promise<string | undefined> { | ||
const session = await getSessionForDatabaseAccount(accountEndpoint); | ||
const principalId = session?.account.id.split('/')[1] ?? session?.account.id; | ||
return principalId; | ||
} | ||
|
||
async function getSessionForDatabaseAccount(endpoint: string): Promise<vscode.AuthenticationSession | undefined> { | ||
const endpointUrl = new URL(endpoint); | ||
const scrope = `${endpointUrl.origin}${endpointUrl.pathname}.default`; | ||
return await getSessionFromVSCode(scrope, undefined, { createIfNone: false }); | ||
} |
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,88 @@ | ||
/*--------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
|
||
import { SqlRoleAssignmentCreateUpdateParameters } from '@azure/arm-cosmosdb'; | ||
import { getResourceGroupFromId } from '@microsoft/vscode-azext-azureutils'; | ||
import { IActionContext, ISubscriptionContext } from '@microsoft/vscode-azext-utils'; | ||
import { randomUUID } from 'crypto'; | ||
import * as vscode from 'vscode'; | ||
import { Uri } from 'vscode'; | ||
import { createCosmosDBClient } from '../../utils/azureClients'; | ||
import { getDatabaseAccountNameFromId } from '../../utils/azureUtils'; | ||
import { DocDBAccountTreeItemBase } from '../tree/DocDBAccountTreeItemBase'; | ||
|
||
export async function ensureRbacPermission(docDbItem: DocDBAccountTreeItemBase, principalId: string, context: IActionContext): Promise<boolean> { | ||
const accountName: string = getDatabaseAccountNameFromId(docDbItem.fullId); | ||
if (await askForRbacPermissions(accountName, docDbItem.subscription.subscriptionDisplayName)) { | ||
const resourceGroup: string = getResourceGroupFromId(docDbItem.fullId); | ||
try { | ||
await addRBACContributorPermission(accountName, principalId, resourceGroup, context, docDbItem.subscription); | ||
return true; | ||
} catch (error) { | ||
// swallow the error, we want the user to reach out to the account owner if this failed | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
export function isRbacException(error: Error): boolean { | ||
return (error instanceof Error && error.message.includes("does not have required RBAC permissions to perform action")); | ||
} | ||
|
||
export async function showRBACPermissionError(accountName: string, principalId: string): Promise<void> { | ||
const message = `You do not have the required permissions to access '${accountName}' with your principal Id '${principalId}'.\nPlease contact the account owner to get the required permissions.`; | ||
const readMoreItem = "Read More"; | ||
await vscode.window.showErrorMessage(message, { modal: false }, ...[readMoreItem]).then((item) => { | ||
if (item === readMoreItem) { | ||
void vscode.env.openExternal(Uri.parse("https://aka.ms/cosmos-native-rbac")); | ||
} | ||
}); | ||
} | ||
|
||
async function askForRbacPermissions(databaseAccount: string, subscription: string): Promise<boolean> { | ||
const message = | ||
["You need the 'Data Contributor' RBAC role to enable all Azure Databases Extension features for the selected account.\n\n", | ||
"Account Name: ", databaseAccount, "\n", | ||
"Subscription: ", subscription, "\n" | ||
].join(""); | ||
const options: vscode.MessageOptions = { modal: true, detail: message }; | ||
const readMoreItem = "Read More"; | ||
const setPermissionItem = "Extend RBAC permissions"; | ||
|
||
const result = await vscode.window.showWarningMessage('No required RBAC permissions', options, ...[setPermissionItem, readMoreItem]); | ||
if (result === setPermissionItem) { | ||
return true; | ||
} else if (result === readMoreItem) { | ||
void vscode.env.openExternal(Uri.parse("https://aka.ms/cosmos-native-rbac")); | ||
} | ||
return false; | ||
} | ||
|
||
async function addRBACContributorPermission(databaseAccount: string, principalId: string, resourceGroup: string, context: IActionContext, subscription: ISubscriptionContext): Promise<string | undefined> { | ||
const defaultRoleId = "00000000-0000-0000-0000-000000000002"; // this is a predefined role with read and write access to data plane resources | ||
const fullAccountId = `/subscriptions/${subscription.subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${databaseAccount}`; | ||
|
||
const createUpdateSqlRoleAssignmentParameters: SqlRoleAssignmentCreateUpdateParameters = | ||
{ | ||
principalId: principalId, | ||
roleDefinitionId: fullAccountId + "/sqlRoleDefinitions/" + defaultRoleId, | ||
scope: fullAccountId, | ||
}; | ||
|
||
/* | ||
// TODO: find a better way to check if a role assignment for the current user already exists, | ||
// iterating over all role assignments and definitions is not efficient. | ||
const rbac = client.sqlResources.listSqlRoleAssignments(resourceGroup, databaseAccount) | ||
for await (const role of rbac) { | ||
console.log(role); | ||
}*/ | ||
|
||
const roleAssignmentId = randomUUID(); | ||
const client = await createCosmosDBClient([context, subscription]); | ||
const create = await client.sqlResources.beginCreateUpdateSqlRoleAssignmentAndWait(roleAssignmentId, resourceGroup, databaseAccount, createUpdateSqlRoleAssignmentParameters); | ||
|
||
return create.id; | ||
} | ||
|