Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 2 additions & 10 deletions api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,16 +221,8 @@ This extension declares that it registers a BranchDataProvider for the `Function

## Getting started

On activation, client extensions can fetch an instance of the Azure Resources API using the `getAzureResourcesExtensionApi` utility provided by the [`@microsoft/vscode-azureresources-api`](https://www.npmjs.com/package/@microsoft/vscode-azureresources-api) package.

```ts
import { getAzureResourcesExtensionApi } from '@microsoft/vscode-azureresources-api';

export async function activate(context: vscode.ExtensionContext): Promise<void> {
const azureResourcesApi = await getAzureResourcesExtensionApi(context, '2.0.0');
// ...register providers
}
```
On activation, client extensions can fetch an instance of the Azure Resources API using utilities provided by the [`@microsoft/vscode-azureresources-api`](https://www.npmjs.com/package/@microsoft/vscode-azureresources-api) package.
A guide on fetching the API through the Azure Resources authentication layer can be found [here](https://github.com/microsoft/vscode-azureresourcegroups/blob/main/api/src/auth/README.md).

<!-- ## Contribute to the Azure resources view

Expand Down
Binary file added api/docs/media/api-request-handshake.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
113 changes: 108 additions & 5 deletions api/docs/vscode-azureresources-api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export declare namespace apiUtils {
*/
export function getAzureExtensionApi<T extends AzureExtensionApi>(context: vscode.ExtensionContext, extensionId: string, apiVersionRange: string, options?: GetApiOptions): Promise<T>;
/**
* Get extension exports for the extension with the given id. Activates extension first if needed.
* Activates an extension and returns its exports.
*
* @returns `undefined` if the extension is not installed
*/
Expand Down Expand Up @@ -124,19 +124,28 @@ export declare interface AzureAuthentication {
/**
* Gets a VS Code authentication session for an Azure subscription.
*
* @param scopes - The scopes for which the authentication is needed. Use AuthenticationWwwAuthenticateRequest for supporting challenge requests.
* Note: use of AuthenticationWwwAuthenticateRequest requires VS Code v1.104
* @param scopeListOrRequest - The scopes for which the authentication is needed. Use AuthenticationWwwAuthenticateRequest for supporting challenge requests.
* Note: use of AuthenticationWwwAuthenticateRequest requires VS Code v1.105.0
*
* @returns A VS Code authentication session or undefined, if none could be obtained.
*/
getSessionWithScopes(scopes: string[] | vscode.AuthenticationWwwAuthenticateRequest): vscode.ProviderResult<vscode.AuthenticationSession>;
getSessionWithScopes(scopeListOrRequest: string[] | vscode.AuthenticationWwwAuthenticateRequest): vscode.ProviderResult<vscode.AuthenticationSession>;
}

export declare interface AzureExtensionApi {
/**
* The API version for this extension. It should be versioned separately from the extension and ideally remains backwards compatible.
*/
apiVersion: string;
/**
* Optional endpoint which Azure client extensions should implement in order to receive an Azure Resources API session.
* See: https://github.com/microsoft/vscode-azureresourcegroups/blob/main/api/src/auth/README.md
*
* @param azureResourcesCredential - The credential to use when requesting the Azure Resources API
* @param clientCredential - The client verification credential initially generated by the client and passed to the Azure Resources API when requesting a new session.
* This credential is used to verify that the real Azure Resources extension is the one providing back the session credential.
*/
receiveAzureResourcesApiSession?(azureResourcesCredential: string, clientCredential: string): void | Promise<void>;
}

/**
Expand Down Expand Up @@ -202,13 +211,94 @@ export declare interface AzureResourceModel extends ResourceModelBase {
readonly viewProperties?: ViewPropertiesModel;
}

export declare interface AzureResourcesApiRequestContext {
clientExtensionId: string;
azureResourcesApiVersions: string[];
/**
* Callback invoked when Azure Resource APIs are successfully obtained through the authentication handshake.
*
* @param azureResourcesApis - Array of APIs corresponding to the requested versions. APIs are returned in the same
* order as provided in this request context. If a requested version is not
* available or does not match, `undefined` will be returned at that position.
*/
onDidReceiveAzureResourcesApis: (azureResourcesApis: (AzureResourcesExtensionApi | AzureExtensionApi | undefined)[]) => void | Promise<void>;
/**
* Optional callback invoked when an error occurs during the Azure Resources API handshake process.
*
* @remarks Errors thrown during execution of this callback may be part of a separate process and may not bubble up to users.
* If you wish to surface specific errors to users, please consider logging them or using the VS Code API to display them through UI.
*
* @param error - The error that occurred during the handshake, containing an error code and message.
*/
onApiRequestError?: (error: AzureResourcesApiRequestError) => void | Promise<void>;
}

export declare type AzureResourcesApiRequestError = {
code: AzureResourcesApiRequestErrorCode;
message: string;
};

/**
* Codes for errors that could appear during the API request handshake between client extension and Azure Resources host extension.
*/
export declare enum AzureResourcesApiRequestErrorCode {
/**
* An error occurred while the client extension was creating its verification credential for the Azure Resources host extension.
*/
ClientFailedCreateCredential = "ERR_CLIENT_FAILED_CREATE_CREDENTIAL",
/**
* An error occurred while the Azure Resources host extension was trying to create an API session.
*/
HostCreateSessionFailed = "ERR_HOST_CREATE_SESSION_FAILED",
/**
* An error occurred because the client's receiver method was provided incomplete or missing credentials.
*/
ClientReceivedInsufficientCredentials = "ERR_CLIENT_RECEIVED_INSUFFICIENT_CREDENTIALS",
/**
* The client's receiver method was provided a client credential that failed verification.
*
* This may occur when:
* - An untrusted extension pretends to be the Azure Resources host extension and tries to pass a fake credential
* - There is a faulty behavior in the client's verification process
*/
ClientCredentialFailedVerification = "ERR_CLIENT_CREDENTIAL_FAILED_VERIFICATION",
/**
* An error occurred while asking the Azure Resources host extension to provision the specified APIs.
*
* This may occur when:
* - The Azure Resources extension cannot verify the issued credential that was passed back
* - The requesting extension is not on the Azure Resources allow list
* - The host extension encounters an internal error during API provisioning
*/
HostApiProvisioningFailed = "ERR_HOST_API_PROVISIONING_FAILED"
}

export declare type AzureResourcesApiRequestPrep<T extends AzureExtensionApi> = {
/**
* The modified client extension API. Ensures the required handshake receiver method has been added.
*/
clientApi: T & Required<Pick<T, 'receiveAzureResourcesApiSession'>>;
/**
* Initiates the authentication handshake required to obtain the Azure Resources API.
*/
requestResourcesApis: () => void;
};

/**
* The current (v2) Azure Resources extension API.
*/
export declare interface AzureResourcesExtensionApi extends AzureExtensionApi {
export declare interface AzureResourcesExtensionApi extends Omit<AzureExtensionApi, 'receiveAzureResourcesApiSession'> {
resources: ResourcesApi;
}

/**
* The authentication layer (v4) protecting the core Azure Resources extension API.
*/
export declare interface AzureResourcesExtensionAuthApi extends Omit<AzureExtensionApi, 'receiveAzureResourcesApiSession'> {
getAzureResourcesApis(clientExtensionId: string, azureResourcesCredential: string, azureResourcesApiVersions: string[]): Promise<(AzureExtensionApi | undefined)[]>;
createAzureResourcesApiSession(clientExtensionId: string, clientExtensionVersion: string, clientExtensionCredential: string): Promise<void>;
}

/**
* Represents a type of resource as designated by Azure.
*/
Expand Down Expand Up @@ -300,10 +390,23 @@ export declare function getAzExtResourceType(resource: {
kind?: string;
}): AzExtResourceType | undefined;

/**
* @deprecated The Azure Resources core API should be accessed through the new auth layer.
* See: https://github.com/microsoft/vscode-azureresourcegroups/blob/main/api/src/auth/README.md
* */
export declare function getAzureResourcesExtensionApi(extensionContext: vscode.ExtensionContext, apiVersionRange: '2.0.0', options?: GetApiOptions): Promise<AzureResourcesExtensionApi>;

export declare function isWrapper(maybeWrapper: unknown): maybeWrapper is Wrapper;

/**
* Prepares a client extension for the Azure Resources authentication handshake.
*
* @param context - Prerequisite configuration and handlers to prepare the request
* @param clientExtensionApi - The base extension API to be modified
* @returns The modified client extension API (with the required receiver method added), and a method to initiate the handshake
*/
export declare function prepareAzureResourcesApiRequest<T extends AzureExtensionApi>(context: AzureResourcesApiRequestContext, clientExtensionApi: T): AzureResourcesApiRequestPrep<T>;

/**
* Represents the base type for all Azure and workspace resources.
*/
Expand Down
85 changes: 85 additions & 0 deletions api/src/auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Azure Resources Authentication and API Retrieval

This guide covers the Azure Resources authentication handshake required for API retrieval by client extensions. It also provides information on the tools available to help client extensions quickly onboard to the new flow.

## The Authentication Handshake

### Overview

Azure Resources APIs are protected behind the new v4 authentication layer. This layer exposes two methods that client extensions must use to gain access: `createAzureResourcesApiSession` and `getAzureResourcesApis`. During activation, client extensions are expected to export an API including a receiver method called `receiveAzureResourcesApiSession` before initiating the API request handshake.

### Steps

1. On activation, the client extension should export its API and initiate the handshake by calling `createAzureResourcesApiSession`. The client extension should provide its own verification credential as part of this request (more on this later).

1. The Azure Resources host extension verifies that the requesting extension is on its approved list. If approved, Azure Resources does not respond directly. Instead, it retrieves the extension's API from VS Code directly using the approved extension ID, then delivers the session credential via the `receiveAzureResourcesApiSession` receiver method. This ensures the credential reaches the approved recipient, even if a malicious actor tried to initiate the request. Azure Resources also returns the original client credential so the client extension can verify that it is communicating with the genuine Azure Resources extension.

1. The client extension should then use the Azure Resources credential to retrieve the Azure Resources APIs by calling `getAzureResourcesApis`.

### Diagram
![Azure Resources API Request Handshake](https://github.com/microsoft/vscode-azureresourcegroups/blob/main/api/docs/media/api-request-handshake.png)

## Automating the Handshake

To simplify the handshake process, the following tools are made available and outlined below.

### The API Request

Create your extension's API (`AzureExtensionApi`) and pass it along with the requisite request context (`AzureResourcesApiRequestContext`). We'll explore how to populate this context in the section that follows.

The `prepareAzureResourcesApiRequest` tool that we provide performs two key operations:

1. **Prepares client extension API** - Returns your modified client extension API with the required `receiveAzureResourcesApiSession` receiver method added.
2. **Provides handshake initializer** - Returns a function that initiates the Resources API request handshake when called. Call this before exporting your API during extension activation.

```ts
const containerAppsApi: AzureExtensionApi = {
apiVersion: '1.0.0',
};

const { clientApi, requestResourcesApis } = prepareAzureResourcesApiRequest(context, containerAppsApi);
requestResourcesApis();
return createApiProvider([clientApi]);
```

### The API Request Context

The following example shows how to configure the context when preparing for an Azure Resources API handshake request.

```ts
const v2: string = '^2.0.0';

const context: AzureResourcesApiRequestContext = {
azureResourcesApiVersions: [v2],
clientExtensionId: 'ms-azuretools.vscode-azurecontainerapps',

// Successful retrieval of Azure Resources APIs will be returned here
onDidReceiveAzureResourcesApis: (azureResourcesApis: (AzureResourcesExtensionApi | undefined)[]) => {
const [rgApiV2] = azureResourcesApis;
if (!rgApiV2) {
throw new Error(l10n.t('Failed to find a matching Azure Resources API for version "{0}".', v2));
}
ext.rgApiV2 = rgApiV2;
ext.rgApiV2.resources.registerAzureResourceBranchDataProvider(AzExtResourceType.ContainerAppsEnvironment, ext.branchDataProvider);
},

// OPTIONAL: Can use for special error handling & telemetry
// NOTE: Errors thrown during execution of this callback may be part of a separate process and may not bubble up to users.
// If you wish to surface specific errors to users, please consider logging them or using the VS Code API to display them through UI.
onApiRequestError: async (error: AzureResourcesApiRequestError) => {
switch (true) {
case error.code === AzureResourcesApiRequestErrorCode.ClientFailedCreateCredential:
case error.code === AzureResourcesApiRequestErrorCode.HostCreateSessionFailed:
case error.code === AzureResourcesApiRequestErrorCode.ClientReceivedInsufficientCredentials:
case error.code === AzureResourcesApiRequestErrorCode.ClientCredentialFailedVerification:
case error.code === AzureResourcesApiRequestErrorCode.HostApiProvisioningFailed:
default:
}
},

};
```

---

[Back to README](https://github.com/microsoft/vscode-azureresourcegroups/blob/main/api/README.md)
32 changes: 32 additions & 0 deletions api/src/auth/apiRequest/AzureResourcesApiRequestContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { AzureResourcesExtensionApi } from "../../extensionApi";
import { AzureExtensionApi } from "../../utils/apiUtils";
import { AzureResourcesApiRequestError } from "./apiRequestErrors";

export interface AzureResourcesApiRequestContext {
clientExtensionId: string;
azureResourcesApiVersions: string[];

/**
* Callback invoked when Azure Resource APIs are successfully obtained through the authentication handshake.
*
* @param azureResourcesApis - Array of APIs corresponding to the requested versions. APIs are returned in the same
* order as provided in this request context. If a requested version is not
* available or does not match, `undefined` will be returned at that position.
*/
onDidReceiveAzureResourcesApis: (azureResourcesApis: (AzureResourcesExtensionApi | AzureExtensionApi | undefined)[]) => void | Promise<void>;

/**
* Optional callback invoked when an error occurs during the Azure Resources API handshake process.
*
* @remarks Errors thrown during execution of this callback may be part of a separate process and may not bubble up to users.
* If you wish to surface specific errors to users, please consider logging them or using the VS Code API to display them through UI.
*
* @param error - The error that occurred during the handshake, containing an error code and message.
*/
onApiRequestError?: (error: AzureResourcesApiRequestError) => void | Promise<void>;
}
Loading