Skip to content

Add login through federated identity for Azure Devops #6649

@martinlingstuyl

Description

@martinlingstuyl

Implementing Federated Identity when running in Azure DevOps should be added to the CLI so we can start supporting deployment of apps without adding certificates to CI/CD.

Implementation

I've discovered that an environment variable called SYSTEM_OIDCREQUESTURI is available in Azure DevOps. Based on that I found this documentation.

Some researching and try-outs resulted in following specs.

We can start supporting m365 login --authType federatedIdentity within Azure DevOps. This can be done as follows.

We can implement two roads at the same time:

  1. Not using a service connection
  2. Using a Service Connection

Road 1: Not using a Service Connection

It's possible to not use a service connection. When not using a service connection you need to specify the following yaml in your build pipeline:

- task: PowerShell@2
  env:
    SYSTEM_ACCESSTOKEN: $(System.AccessToken)  
  inputs:
    targetType: 'inline'
    script: |
      m365 login --authType federatedIdentity --appId "<some-appid-or-variable>" --tenant "<some-tenantid-or-variable>"

How it works:

We're not running in a service connection, so we need to input the appId and tenantId.
We also need to add the env: SYSTEM_ACCESSTOKEN: $(System.AccessToken) section, to make sure the SYSTEM_ACCESSTOKEN environment variable is available. That token is necessary to call the local OIDC endpoint.

The m365 login sequence will check if SYSTEM_OIDCREQUESTURI and SYSTEM_ACCESSTOKEN are available. It will start signing in. It will request a federation token from the URL <oidcRequestUrl>?api-version=7.1. This will result in a token that can be passed to Entra ID to be swapped for an access token. The appId and tenant values from the command options will be used for retrieving the token.

The specified app will need a federated identity credential with the right Subject and Issuer. These values are normally determined when you create a Service Connection, but in this case you'll need to configure them manually. The subject will look like this: p://contoso/MyProjectName/MyRepoName. The issuer will be https://vstoken.dev.azure.com/<some-devops-organization-id>. The organization id can be determined by exporting the organizations from DevOps:

Image

Road 2: Using a Service Connection

When using a service connection you need to specify the following yaml in your build pipeline:

- task: AzurePowerShell@5 
  env:
    SYSTEM_ACCESSTOKEN: $(System.AccessToken)
  inputs:
    azureSubscription: 'MyServiceConnection'
    ScriptType: 'InlineScript'
    azurePowerShellVersion: LatestVersion
    Inline: |
      m365 login --authType federatedIdentity

How it works:

When using a service connection we need the AzurePowerShell@5 task as a wrapper task so that we can input the service connection. The task will make the following environment variables available:

  • AZURESUBSCRIPTION_SERVICE_CONNECTION_ID
  • AZURESUBSCRIPTION_CLIENT_ID
  • AZURESUBSCRIPTION_TENANT_ID

We also need to add the env: SYSTEM_ACCESSTOKEN: $(System.AccessToken) section, to make sure the SYSTEM_ACCESSTOKEN environment variable is available. That token is necessary to call the local OIDC endpoint.

The m365 login sequence will check if SYSTEM_OIDCREQUESTURI and SYSTEM_ACCESSTOKEN are available. It will start signing in. It will request a federation token from the URL <oidcRequestUrl>?api-version=7.1&serviceConnectionId=<serviceConnectionId. This will result in a token that can be passed to Entra ID to be swapped for an access token. The App AZURESUBSCRIPTION_CLIENT_ID on the tenant AZURESUBSCRIPTION_TENANT_ID will be used for retrieving the token.

The specified app will need a federated identity credential with the right Subject and Issuer. These values are determined when you create the Service Connection. The subject will look like this: sc://contoso/MyProjectName/MyServiceConnectionName.

Earlier specs idea 2: create a devops extension task and add a federatedToken option to the login command

This idea is superseded by the latest idea because we'd rather avoid having to force devs to use an extension task. We'd like to use just the m365 login command if possible. Also it takes another repo to manage.

The point with Azure DevOps is that we need to use service connections. It seems though that service connections are not just available within any task. You need to build a task extension to be able to leverage the azure-pipelines-task-lib to get at the service connection.

Studying the AzureCLIV2 task and how it interacts with the actual az cli, I believe I know how we can tackle this challenge. We can do it in the exact same way as the az cli:

  1. We'll need to build an Azure DevOps extension (just like we have a GitHub extension) that is able to get at the service connections and can retrieve a federated token. This extension installs the CLI, retrieves the federated token and then calls the login command, giving the federated token as an option.
  2. We'll add a --federatedToken [federatedToken] option to the login command. The login command calls login.microsoftonline.com and swaps the federated token for an access token.

And that should be it! I believe I have all these steps figured out now. But of course, I'm interested to hear your opinions. I've already created a repository and uploaded some code for the extension.

We'll need to publish the extension to the DevOps marketplace when it's finished.

As a side-thing, the extension could be made in such a way that it can also deal with other types of service connections, like one with a secret, or one with a certificate.

The CLI option should be like:

Option

Option Description
-t, --federatedToken [federatedToken] Federated token that can be used for OIDC token exchange. Can be used together with --authType federatedIdentity when running the CLI in Azure DevOps.
Earlier specs idea 1: pass the service connection to the CLI

This option is impossible. You need a DevOps extension task to pass a service connection into and load data from the azure devops task library.

Options

We'll allow logging in through the existing option --authType federatedIdentity (see #6610)

But Azure DevOps needs to know what Service Connection will be used, so we'll need an additional option:

Option Description
-c, --serviceConnection [serviceConnection] The Azure DevOps service connection to use. Can only used in Azure DevOps when logging in with --authType federatedIdentity

Implementation

For inspiration, we'll need to check how the az cli does it: check out the source code here

Just like with GitHub it first retrieves an idToken. That will probably get posted to Entra Id to switch it for an access token. The rest will need to be researched yet.

We may need to install azure-pipelines-task-lib. For example for retrieving variables that we need.
Let's check out if we really need it.

Requirements

  • If the service connection is not of the Workload Identity Federation type, logging in should fail with a clear exception message.

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions