Secure managed identity federation by only allowing Azure DevOps

Last week I wrote about setting up a connection between Azure DevOps and Azure by using managed identities. While I was checking out how it works, I learned that it’s actually quite simple to set up and doesn’t require special admin powers. This makes it possible for pretty much anyone using Azure to add their own outside services into the mix. In this article, you learn how to secure managed identity federation by only allowing Azure DevOps organizations.

Overview

This blog post is the second in a series where we will be taking our first steps into federation on Azure using managed identities:

  1. Configure workload identity federation in Azure DevOps.
  2. Secure managed identity federation by only allowing Azure DevOps. (You’re here now)
  3. Deep-dive into federation using Enterprise Applications. (Coming soon)

In the sections below, we explore how to obtain the requirements such as the Issuer URL used by Azure DevOps for federation, deploy a custom definition with Azure Policy to restrict configuration, and assign this policy to effectively deny unapproved third-party services.

Workload identity federation is booming

Since the introduction of workload identity federation, organizations explore migrating from service principals to user-assigned managed identities. Implementing this new feature is pretty simple. You just:

  • Create a new service connection, selecting Workload identity federation.
  • Fill in the details in the federation properties of the managed identity.
  • And with a glance, you now have a new service connection.

This is a security risk. So, lets start by limiting what third-party services are allowed to federate with Azure.

Get the unique ID of a DevOps organization

When configuring federation for the managed identity, you need to configure an Issuer URL. This URL is used to communicate with Azure (Microsoft Entra ID), requesting tokens from it. For Azure DevOps, this URL always starts with https://vstoken.dev.azure.com/ followed by the unique identifier (GUID) of the DevOps organization.

Follow the steps below to get your organization’s unique ID:

  1. Navigate to the Azure DevOps portal in your web browser.
  2. In the portal, click on the Organization settings button located on the bottom left-hand side of the screen.
  3. Scroll through the list of settings, and click on Microsoft Entra.
  4. Find and select the option Download to get a list of organizations connected to the directory.
download connected devops organizations from the portal
  1. Open the CSV file and look for the Organization Id column.
excel overview of connected devops organizations in entra id
  1. Copy the ID or save it somewhere to retrieve later. We will need it for the Azure Policy assignment.

Note: If you have any tips or recommendations to get this unique ID using PowerShell, Azure CLI or the REST API, please let me know in the comments below!

Deploy a custom policy definition in Azure Policy

Unfortunately, there is no built-in policy that we can use. Therefore, we deploy our own custom policy definition. Use the example below and upload it to your Azure tenant.

JSON
{
  "mode": "All",
  "policyRule": {
    "if": {
      "allOf": [
        {
          "equals": "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials",
          "field": "type"
        },
        {
          "not": {
            "equals": "[concat('https://vstoken.dev.azure.com/', parameters('organizationId'))]",
            "field": "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/issuer"
          }
        }
      ]
    },
    "then": {
      "effect": "[parameters('effect')]"
    }
  },
  "parameters": {
    "effect": {
      "type": "String",
      "metadata": {
        "displayName": "Effect",
        "description": "Enable or disable the execution of the policy."
      },
      "allowedValues": [
        "Audit",
        "Disabled",
        "Deny"
      ],
      "defaultValue": "Audit"
    },
    "organizationId": {
      "type": "String",
      "metadata": {
        "displayName": "Organization ID",
        "description": "Set the Organization ID of the allowed Azure DevOps organization."
      }
    }
  }
}
Expand

About the custom definition

The purpose of this custom policy is to enforce federation restrictions between Azure managed identities and a specified Azure DevOps organization. We identify the allowed organization by populating its unique ID as a string in the policy assignment. Also, I want to highlight a few properties from the snippet above:

  • parameters: The string organizationId is expected to be populated with GUID of the Azure DevOps organization you would like to allow to federate with.
  • policyRule: It defines the conditions under which the policy is enforced:
    • Since we use the allOf logical operator, all conditions must evaluate to True in order for the policy to take effect.
    • The first condition checks whether the type field equals "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials".
    • The second condition uses the equals operation in combination with the not logical operator to ensure that the issuer property of the federated identity credential matches the issuer URL in combination with the given GUID.

Create a new policy assignment

The final step is to assign the policy such that it constrains federation configurations to an approved GUID of an organization. This ensures that only the specified Azure DevOps organization can be set up for federation with Entra ID, enhancing security and compliance.

  • Create a new assignment: Select the custom definition you just deployed.
  • Basics: Select the scope, assignment name and other relevant information.
  • Parameters:
    • Populate the unique ID of the Azure DevOps organization in the format of a string.
    • Also, be sure to select the Effect of the policy (Disable Only show parameters that need input or review).
policy assignment of the custom definition
  • Remediation: Leave as is. This policies denies operations up front and do not require to be remediated after.
  • Non-compliance messages: Optionally, provide a message for the user when the operation is denied. This gives an indication why the operation is not allowed.

Note: Please pay attention to the effect of the policy when configuring your assignment. By default, the effect of this policy is set to Audit. Use Deny as effect to enforce this policy.

Verify compliance

Give the Azure Policy engine some time to process the new assignment. After a few minutes, verify compliance of your managed identities.

view compliance for the new policy assignment

Now, when federation credentials are configured and the issuer URL is something other than what we set, the deployment is denied, and the following message will be displayed in the portal.

non-compliant message from Azure Policy when the deployment is denied

Conclusion

As we’ve seen throughout this article, securing Azure DevOps federation with managed identities in Azure can be made more robust by implementing a custom Azure Policy.

The process begins with obtaining the unique ID of your Azure DevOps organization, a crucial piece for federation configuration. Subsequently, we explored how to create and deploy a policy definition in Azure Policy that carefully stipulates which DevOps organizations can federate by comparing issuer URLs against approved GUIDs.

Finally, after assigning this policy and selecting the appropriate effect, preferring ‘Deny’ for stricter enforcement, organizations can verify compliance, thus ensuring that only authorized Azure DevOps services engage in federation with managed identities.