Skip to content

Instantly share code, notes, and snippets.

@KarishmaGhiya
Last active April 12, 2024 22:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save KarishmaGhiya/81ee6265ab6e9109d3bf510678878b34 to your computer and use it in GitHub Desktop.
Save KarishmaGhiya/81ee6265ab6e9109d3bf510678878b34 to your computer and use it in GitHub Desktop.
[Identity] API Design for Workload Identity Credential for Azure Service connections

Introduction

This document is to plan the design for WI Credential to extend support to Azure Service Connections in Azure SDK for Identity. The user scenario for this is anyone trying to authenticate to the WI for a service connection in the Azure Devops Pipeline.

image

Read up on details on how WI federation is enabled for Service COnnections - https://devblogs.microsoft.com/devops/public-preview-of-workload-identity-federation-for-azure-pipelines/

The proposal for Azure SDK for Identity is to extend the existing WorkloadIdentity Credential that currently supports AKS to also support the Service Connections.

What does Identity SDK need to do?

Looking at the workings of the WI in the diagram above, we see that in order for the WI to work for Service Connections, an OIDC token needs to be provided by Azure Devops first and then the call to get the access token for AAD authentication is made with the OIDC token.

Unlike the AKS environment, where the token is provided in the token file and the env var AZURE_FEDERATED_TOKEN_FILE is already set by the AKS env, the Azure Devops DOES NOT automatically provide the OIDC token to the environment. It has to be requested with the help of a rest api and the request url for which is formulated with the help of a few system variables that are always available in Devops.

Rest API Call - OIDC token Look at this REST API call made in Powershell script - https://github.com/geekzter/azure-identity-scripts/blob/e6a4bbc67ffd97433db46f822c96d47b11d02d18/scripts/azure-devops/set_terraform_azurerm_vars.ps1#L43 It makes use of the following system variables to build the OIDC token request url:

To build the authorization header for this rest api call, it uses a secret provided in the devops environment called SYSTEM_ACCESSTOKEN - https://github.com/geekzter/azure-identity-scripts/blob/e6a4bbc67ffd97433db46f822c96d47b11d02d18/scripts/azure-devops/set_terraform_azurerm_vars.ps1#L54

Now let's compare this to what we actually see in our Devops Pipeline. I have enabled system debugging on one of the pipeline runs here - https://dev.azure.com/azure-sdk/internal/_build/results?buildId=3499927&view=logs&j=3dc8fd7e-4368-5a92-293e-d53cefc8c4b3&t=e77055a3-6358-5204-c080-7a2e41553284

image

We see the service connection id variable ENDPOINT_AUTH... and something called as SECRET_SYSTEM_ACCESSTOKEN (instead of SYSTEM_ACCESS_TOKEN) in our pipelines.

The other 4 system variables are available as well: image Notice how the OIDC token is granted and is not available as an env var. So we need to do the same in our SDK.

Now if you look at all the service connections assoicated with this pipeline, and look at the corresponding logs for the task where it downloads the secrets, each service connection (in this case the different keyvaults are our service connections) has a different "ENDPOINT_AUTH_XXX" with it.

Proposal for Rest API call In order to understand which exact service connection the user needs to authenticate with WI, we can just request them to provide the service connection id as a parameter. That way we don't have to do the extraction from the weird ENDPOINT_AUTH_XX variable name and we're certain that we are authenticating to the corret service connections.

Then we can make the rest api call. Snippet from the TS code here -

private async requestOidcToken(oidcRequestUrl: string, systemAccessToken: string): Promise<string> {
    console.log("Requesting OIDC token from Azure DevOps...");
    console.debug(oidcRequestUrl);

    const requestOptions = {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${systemAccessToken}`,
      },
    };

    const response = await fetch(oidcRequestUrl, requestOptions);
    const result = await response.json();
    return result;
  }

Now this Rest API method will provide the OIDC token which can now be used for our step 2.

Requesting Access Token from AAD Once we have the OIDC token, things are quite simple from here. We can just use this OIDC token like an assertion to request the Access token from Microsoft Entra ID. For this we can simply use ClientAssertionCredential and pass in this OIDC token.

Proposing API update

For reference please look at the PR here - Azure/azure-sdk-for-js#28628

I tried to make an analogy for the AKS scenario and the Service Connection scenario here.

  1. Just like we have the federatedTokenFilePath parameter, we can introduce a new parameter serviceconnectionId OR azureServiceConnectionId (corresponding to the Azure Service Connections they want to authenticate).
  2. Then just like we have an async method that reads the token from the give file path, we can have an analogous function that reads in the required 5 system variables and makes a rest API call to Azure Devops to request an OIDC token.
  3. Pass in this OIDC token to Client Assertion Credential

With this, the only API changes are introducing the field serviceConenctionId to the existing WorkloadIdentityCredentialOptions and DefaultAzureCredentialOptions image

What is holding us back?

Apparently the service team has informed us that the WI support for Service Connections has gone GA. But they were thinking of conslidating the system variables into one single variable. That work is not yet scheduled even for Dilitium from their side. The Azure SDK team wants to work on this after the consolidation of the variables. Also they said that instead of using rest api version 7.1.0-preview.1 we can use 7.1.0.

@KarishmaGhiya
Copy link
Author

@KarishmaGhiya
Copy link
Author

Adding more comments on the need for the serviceConnectionId parameter v/s just the env var for it if Devops adds it.

for the env variable in Azure Devops for the service connection id - I have a qs. Today we do have the token file path parameter that is accepted both as an argument and an env variable. If the env var is present the users don't need to pass in as an argument. Let's say in the best case scenario, the devops does decide to introduce the env var for service connection id.

  1. one possibility is that the users will get confused regarding what the token file path parameter is for? We'll have to be explicit in our ref docs that it should only be used for AKS? Today they are confused not aware that we don't support service connections federation yet.
  2. the other possibility is that let's say for some reason the service connection env var is not set correctly and they want to get access token for another credential, the users should have the ability to override that env var right?

I know these questions are only for the scenario that Devops agrees to introduce the env var soon. But even when they do, I think we may still need the serviceConnectionId param maybe?

@KarishmaGhiya
Copy link
Author

KarishmaGhiya commented Apr 12, 2024

Please note - We are only providing this support through Workload Identity Credential at the moment and not through DAC. There are multiple reasons for this -

  • introduction of complicated design issues and logic in DAC.
  • Niche scenario that can be left out of DAC.
  • Can always make additional changes in the future to DAC with minimal design changes once Devops service provides better environment variable support for this scenario.
  • But if we introduce the support now, we'll have to expose the parameter through DAC Options bag and it can be disruptive to remove in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment