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.
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:
- SYSTEM_TEAMFOUNDATIONCOLLECTIONURI
- SYSTEM_TEAMPROJECTID
- SYSTEM_PLANID
- SYSTEM_JOBID
- serviceConnectionId (This is extracted from the variable name of the shape
ENDPOINT_AUTH_d267d7b2-a67e-4f43-8a8a-bdff194d7233
just show like here - https://github.com/geekzter/azure-identity-scripts/blob/e6a4bbc67ffd97433db46f822c96d47b11d02d18/scripts/azure-devops/set_terraform_azurerm_vars.ps1#L37
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
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: 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.
- Just like we have the federatedTokenFilePath parameter, we can introduce a new parameter
serviceconnectionId
ORazureServiceConnectionId
(corresponding to the Azure Service Connections they want to authenticate). - 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.
- 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
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.