Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add auxiliary authentication header policy in core pipeline (#25270)
### Background Add a policy for external tokens to `x-ms-authorization-auxiliary` header in core lib. This header will be used when creating a cross-tenant application we may need to handle authentication requests for resources that are in different tenants. You can learn [more here](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/authenticate-multi-tenant). Here I collect two use cases: - Create a virtual network peering between virtual networks across tenants ([see here](https://learn.microsoft.com/en-us/azure/virtual-network/create-peering-different-subscriptions?tabs=create-peering-portal#cli)) - Share images across tenants ([see here](https://learn.microsoft.com/en-us/azure/virtual-machine-scale-sets/share-images-across-tenants)) ### Usecase - create a virtual network peering across tenants We have two subscriptions cross two tenants: ``` subscriptionA = "75d6dc7b-9a8d-4f94-81ce-8a9437f3ce2c" in tenantA subscriptionB = "92f95d8f-3c67-4124-91c7-8cf07cdbf241" in tenantB ``` Prepare the app register and grant permission in both subscriptions, please note we'll have one app register with two service principals in two tenants. ``` # Create app registration named `appRegisterB` which allows to be used in any orgnaizational directory located in `tenantB` # Create a service principal for `appRegisterB` in `tenantA` by login url: [https://login.microsoftonline.com/{tenant-id}/adminconsent?client_id={client-id}](https://login.microsoftonline.com/%7Btenant-id%7D/adminconsent?client_id=%7Bclient-id%7D) # Add roles for `appRegisterB` in both `subscriptionA` and `subscriptionB` ``` Prepare the virtual network in both subscriptions: ``` # Switch to subscription A az account set -s $subscriptionA # Create resource group A az group create --name myResourceGroupA --location eastus # Create virtual network A az network vnet create --name myVnetA --resource-group myResourceGroupA --location eastus --address-prefix 10.0.0.0/16 # Switch to subscription B az account set -s $subscriptionB # Create resource group B az group create --name myResourceGroupB --location eastus # Create virtual network B az network vnet create --name myVnetB --resource-group myResourceGroupB --location eastus --address-prefix 10.1.0.0/16 ``` Create a virtual network peering between these two virtual networks. We could build this peer relationship from myVnetA to myVnetB, or from myVnetB to myVnetA. If we build a client under subscriptionB then we could create this peer from myVnetB to myVnetA with below headers: | Header name | Description | Example value | | ----------- | ----------- | ------------ | | Authorization | Primary token, token got from credentialB | Bearer <primary-token> | | x-ms-authorization-auxiliary | Auxiliary tokens, token got from credentialA | Bearer <auxiliary-token1> | ```typescript const tenantA = "c029c2bd-5f77-48fd-b9b8-6dbc7c475125"; const tenantB = "72f988bf-86f1-41af-91ab-2d7cd011db47"; const subscriptionB = "92f95d8f-3c67-4124-91c7-8cf07cdbf241"; const myResourceGroupB = "myResourceGroupB"; const myVnetB = "myVnetB"; const virtualNetworkPeeringName = "myVnetA"; const virtualNetworkPeeringParameters: VirtualNetworkPeering = { allowForwardedTraffic: false, allowGatewayTransit: false, allowVirtualNetworkAccess: true, remoteVirtualNetwork: { id: "/subscriptions/75d6dc7b-9a8d-4f94-81ce-8a9437f3ce2c/resourceGroups/myResourceGroupA/providers/Microsoft.Network/virtualNetworks/myVnetA" }, useRemoteGateways: false }; ``` ### [Preferred] Option 1: Provide an extra policy `auxiliaryAuthenticationHeaderPolicy` Provide a new policy `auxiliaryAuthenticationHeaderPolicy` in core, then customer code could leverage that policy to add auxilary header. ```typescript async function createPeeringWithPolicy() { const credentialA = new DefaultAzureCredential({tenantId: tenantA}); const credentialB = new DefaultAzureCredential({tenantId: tenantB}); const client = new NetworkManagementClient(credentialB, subscriptionB, { // Add the extra policy when building client additionalPolicies: [{ policy: auxiliaryAuthenticationHeaderPolicy({ credentials: [credentialA], scopes: "https://management.core.windows.net//.default" }), position: "perRetry", }] }); const result = await client.virtualNetworkPeerings.beginCreateOrUpdateAndWait( myResourceGroupB, myVnetB, virtualNetworkPeeringName, virtualNetworkPeeringParameters, ); console.log(result); } ``` ### Option 2: Add `auxiliaryTenants` as a client option Similar the implementation in [Go](Azure/azure-sdk-for-go#19309), we could provide an option in `CommonClientOptions`. ```typescript /** * Auxiliary tenant ids which will be used to get token from */ auxiliaryTenants?: string[]; ``` And then enhance the current bearerTokenAuthenticationPolicy logic to detect if we have the `auxiliaryTenants` provided, if yes we could automatically get tokens and add `x-ms-authorization-auxiliary` header in request. And the customer code would be like: ```typescript async function createPeeringWithParam() { const credential = new ClientSecretCredential(tenantB, env.clientB, env.secretB, { // We would also add allowed tenant list into current credential so that we could get relevant tenant tokens additionallyAllowedTenants: [tenantA] }); const client = new NetworkManagementClient(credential, subscriptionB, { // If the parameter is provided the bearer policy would append the extra header auxiliaryTenants: [tenantA] }); const result = await client.virtualNetworkPeerings.beginCreateOrUpdateAndWait( myResourceGroupB, myVnetB, virtualNetworkPeeringName, virtualNetworkPeeringParameters ); console.log(result); } ``` ### Option 3: Add `auxiliaryCredentials` option in BearerTokenAuthenticationPolicyOptions Instead of providing new policy we could add a new option in `BearerTokenAuthenticationPolicyOptions` in original bearerTokenAuthenticationPolicy. Then in that policy we could detect if the parameter `auxiliaryCredentials` is provided, if yes append the header accordingly. ```typescript /** * Provide the auxiliary credentials to get tokens in header x-ms-authorization-auxiliary */ auxiliaryCredentials: TokenCredential[]; ``` But it would be more complex from customer side, because we add bearer policy by default so we have to remove that one first and then re-add a new one. ```typescript async function createPeeringWithNewBearerPolicy() { const credentialA = new ClientSecretCredential(tenantA, clientB, secretB); const credentialB = new ClientSecretCredential(tenantB, clientB, secretB); const client = new NetworkManagementClient(credentialB, subscriptionB); // Build a new policy with auxiliaryCredentials provide const customizedBearerPolicy = bearerTokenAuthenticationPolicy({ credential: credentialB, scopes: "https://management.core.windows.net//.default", auxiliaryCredentials: [credentialA] }); // Remove the original one client.pipeline.removePolicy({ name: bearerTokenAuthenticationPolicyName }); // Add our new policy client.pipeline.addPolicy(customizedBearerPolicy); const result = await client.virtualNetworkPeerings.beginCreateOrUpdateAndWait( myResourceGroupB, myVnetB, virtualNetworkPeeringName, virtualNetworkPeeringParameters ); console.log(result); } ``` Simply speaking I prefer the option 1, you could know more [here](#25270 (comment)). ### Reference Java: Azure/azure-sdk-for-java#14336 Python: Azure/azure-sdk-for-python#24585 Go: Azure/azure-sdk-for-go#19309 .Net: Azure/azure-sdk-for-net#35097 // Only add sample, didn't implement in core --------- Co-authored-by: Jeff Fisher <[email protected]>
- Loading branch information