在 Azure DevOps 中使用服务主体和托管标识

Azure DevOps Services

注意

Azure Active Directory 现已更名为 Microsoft Entra ID。 有关详细信息,请参阅 Azure AD 的新名称

将 Microsoft Entra 服务主体和托管标识添加到 Azure DevOps Services 组织,以授予对组织资源的访问权限。 对于许多团队,当你对公司中支持自动化工作流的应用程序进行身份验证时,此功能可以是个人访问令牌(PAT)的可行首选替代方法

关于服务主体和托管标识

服务主体 是Microsoft Entra 应用程序中的安全对象,用于定义应用程序在给定租户中可以执行的操作。 它们是在应用程序注册过程中在 Azure 门户中设置的,并配置为访问 Azure 资源,例如 Azure DevOps。 通过将服务主体添加到组织并在其上设置权限,我们可以确定服务主体是否有权访问组织资源以及哪些资源。

托管标识 是另一个Microsoft Entra 功能,其作用类似于应用程序的服务主体。 这些对象为 Azure 资源提供标识,并允许支持 Microsoft Entra 身份验证的服务轻松共享凭据。 它们是一个有吸引力的选项,因为Microsoft Entra ID 负责凭据管理和轮换。 虽然对托管标识的设置在Azure 门户上看起来可能有所不同,但 Azure DevOps 会将这两个安全对象视为组织中具有定义权限的新标识。 在本文的其余部分,除非另有说明,否则我们会将托管标识和服务主体互换为服务主体。

使用以下步骤向 Azure DevOps 对这些标识进行身份验证,以允许他们代表自己执行操作。

配置托管标识和服务主体

实现可能有所不同,但在高级别上,以下步骤可帮助你开始在工作流中使用服务主体。 请考虑查看我们的一个 示例应用 ,然后自行学习示例。

1.创建新的托管标识或应用程序服务主体

在Azure 门户中创建应用程序服务主体托管标识

创建应用程序服务主体

创建新的应用程序注册时,会在 Microsoft Entra ID 中创建应用程序对象。 应用程序服务主体是给定租户的此应用程序对象的表示形式。 将应用程序注册为多租户应用程序时,有一个唯一的服务主体对象表示应用程序添加到的每个租户的应用程序对象。

其他信息:

创建托管标识

在 Azure 门户中创建托管标识与使用服务主体设置应用程序有很大不同。 在开始创建过程之前,必须先考虑要创建的托管标识类型:

  • 系统分配的托管标识: 某些 Azure 服务允许直接在服务实例上启用托管标识。 启用系统分配的托管标识时,会在 Microsoft Entra ID 中创建标识。 此标识与该服务实例的生命周期相关联。 当资源被删除时,Azure 会自动为你删除标识。 按照设计,只有该 Azure 资源可使用此标识从 Microsoft Entra ID 请求令牌。
  • 用户分配的托管标识 还可以通过创建用户分配的托管标识并将其分配给 Azure 服务的一个或多个实例,以独立 Azure 资源的形式创建托管标识。 对于用户分配的托管标识,标识与使用它的资源分开管理。

有关详细信息,请参阅以下文章和视频:

2.将服务主体添加到 Azure DevOps 组织

在 Microsoft Entra 管理中心配置服务主体后,必须在 Azure DevOps 中通过向组织添加服务主体来执行相同的操作。 可以通过 “用户”页 或使用 ServicePrincipalEntitlements API 添加它们。 由于用户无法以交互方式登录,因此可将用户添加到组织、项目或团队的用户帐户必须添加他们。 启用“允许团队和项目管理员邀请新用户”策略时,此类用户包括项目集合管理员 (PCA) 或项目管理员和团队管理员

提示

若要将服务主体添加到组织,请输入应用程序或托管标识的显示名称。 如果选择通过 ServicePrincipalEntitlements API 以编程方式添加服务主体,请确保传入 服务主体的对象 ID ,而不是应用程序的对象 ID。

如果你是 PCA,还可以向服务主体授予对特定项目的访问权限并分配许可证。 如果你不是 PCA,则必须联系 PCA 以更新任何项目成员身份或许可证访问级别。

用户中心中的服务主体和托管标识的屏幕截图。

注意

只能为组织连接到的租户添加托管标识或服务主体。 若要访问其他租户中的托管标识,请参阅 常见问题解答中的解决方法。

3.设置服务主体的权限

将服务主体添加到组织后,可以像对待标准用户帐户一样对待它们。 可以直接对服务主体分配权限,将其添加到安全组和团队,将其分配到任何访问级别,并将其从组织中删除。 还可以使用 Service Principal Graph APIs 对服务主体执行 CRUD 操作。

这可能不同于用于为其他 Azure 资源设置Microsoft Entra 应用程序中的应用程序权限的方式。 Azure DevOps 不依赖于通过Azure 门户的应用程序注册可用的“应用程序权限”设置。 这些应用程序权限将权限应用于绑定到租户的所有组织中的服务主体,并且不知道 Azure DevOps 中可用的其他组织、项目或对象权限。 为了提供更精细的权限,我们依赖于自己的权限模型,而不是通过 Entra ID 提供。

4. 管理服务主体

管理服务主体与用户帐户在以下主要方面有所不同:

  • 服务主体没有电子邮件,因此无法通过电子邮件邀请他们加入组织。
  • 许可的组规则当前不适用于服务主体。 如果要将访问级别分配给服务主体,最好直接执行此操作。
  • 虽然可将服务主体添加到Microsoft Entra 组(在Azure 门户中),但我们有一个当前的技术限制,阻止我们在Microsoft Entra 组成员列表中显示它们。 此限制不适用于 Azure DevOps 组。 也就是说,服务主体仍继承在他们所属的Microsoft Entra 组之上设置的任何组权限。
  • 并不是Microsoft Entra 组中的所有用户都立即属于 Azure DevOps 组织,只是因为管理员创建组并将Microsoft Entra 组添加到其中。 我们有一个名为“具体化”的过程,一旦用户从Microsoft Entra 组首次登录到组织,就会发生这种情况。 登录组织的用户允许我们确定应向哪些用户授予许可证。 由于服务主体无法登录,因此管理员必须将其显式添加到组织,如前所述。
  • 无法在 Azure DevOps 上修改服务主体的显示名称或头像。
  • 即使选择了多组织计费服务主体也算作它添加到的每个组织的许可证。

5.使用 Microsoft Entra ID 令牌访问 Azure DevOps 资源

获取Microsoft Entra ID 令牌

可以通过遵循 Microsoft Entra ID 文档来完成为托管标识获取访问令牌。 有关详细信息,请参阅 服务主体托管标识的示例。

返回的访问令牌是具有已定义角色的 JWT,可用于使用令牌作为 持有者访问组织资源。

使用 Microsoft Entra ID 令牌向 Azure DevOps 资源进行身份验证

在以下视频示例中,我们从使用 PAT 进行身份验证转向使用服务主体中的令牌。 我们首先使用客户端密码进行身份验证,然后转到使用客户端证书。

  • 虽然可将服务主体添加到Microsoft Entra ID 组(在 Azure 门户 中),但我们有一个当前的技术限制,阻止我们在 Microsoft Entra ID 组成员列表中显示它们。 此限制不适用于 Azure DevOps 组。 也就是说,服务主体仍继承在他们所属的Microsoft Entra ID 组之上设置的任何组权限。
  • 并不是Microsoft Entra ID 组中的所有用户都立即成为 Azure DevOps 组织的一部分,只是因为管理员创建一个组并将Microsoft Entra ID 组添加到其中。 我们有一个称为“具体化”的过程,一旦用户从Microsoft Entra ID 组首次登录到组织,就会发生这种情况。 登录组织的用户允许我们确定应向哪些用户授予许可证。 由于服务主体无法登录,因此管理员必须将其显式添加到组织,如前所述。
  • 无法在 Azure DevOps 上修改服务主体的显示名称或头像。
  • 即使选择了多组织计费服务主体也算作它添加到的每个组织的许可证。

另一个示例演示了如何在 Azure 函数中使用用户分配的托管标识连接到 Azure DevOps。

按照这些示例,在 示例应用集合中查找应用代码。

服务主体可用于调用 Azure DevOps REST API 并执行大多数操作,但受以下操作的限制:

  • 服务主体不能组织所有者或创建组织。
  • 服务主体无法创建令牌,例如 个人访问令牌 (PAC) SSH 密钥。 他们可以生成自己的Microsoft Entra ID 令牌,这些令牌可用于调用 Azure DevOps REST API。
  • 不支持服务主体的 Azure DevOps OAuth

注意

只能使用应用程序 ID,而不能使用与 Azure DevOps 关联的资源 URI 来生成令牌。

常见问题

常规

问:为什么应使用服务主体或托管标识而不是 PAT?

答:我们的许多客户寻求服务主体或托管标识来替换现有的 PAT (个人访问令牌) 。 此类 PAC 通常属于服务帐户 (共享团队帐户) ,该帐户使用它们通过 Azure DevOps 资源对应用程序进行身份验证。 PAC 必须经常 (至少 180 天) 轮换一次。 由于 PAC 只是持有者令牌,即表示用户用户名和密码的令牌字符串,因此使用它们的风险非常大,因为它们很容易落入错误人之手。 Microsoft Entra 令牌每小时过期一次,必须使用刷新令牌重新生成才能获取新的访问令牌,这会在泄露时限制总体风险因素。

无法使用服务主体创建个人访问令牌。

问:服务主体和托管标识的速率限制是什么?

答:目前,服务主体和托管标识与用户具有相同的 速率限制

问:使用此功能的成本会更高吗?

答:根据访问级别,服务主体和托管标识的定价与用户类似。 一个值得注意的变化涉及我们如何对待服务主体的“多组织计费”。 无论用户加入多少个组织,用户都只能算作一个许可证。 服务主体按用户所属的每个组织计为一个许可证。 此方案类似于标准“基于用户分配的计费”。

问:是否可以将服务主体或托管标识与 Azure CLI 配合使用?

答:是的! 在 Azure CLI 中请求 PAT 的任何位置也可以接受 Microsoft Entra ID 访问令牌。 请参阅这些示例,了解如何将 Microsoft Entra 令牌传入以使用 CLI 进行身份验证。

# To authenticate with a command: After typing this command, the az devops login will prompt you to enter a token. You can add an Entra ID token too! Not just a PAT.
az devops login

# To authenticate a service principal with a password or cert:
az login --service-principal -u <app-id> -p <password-or-cert> --tenant <tenant>

# To authenticate a managed identity:
az login --identity

现在,让我们获取一个Microsoft Entra 令牌(Azure DevOps 资源的 UUID 是 499b84ac-1321-427f-aa17-267ca6975798),并尝试通过在标头中将其作为 Bearer 令牌传入来调用 Azure DevOps API:

Write-Host "Obtain access token for Service Connection identity..."
$accessToken = az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query "accessToken" --output tsv

Write-Host "Use access token with Azure DevOps REST API to list projects in the organization..."
$apiVersion = "7.1-preview.1"
$uri = "https://dev.azure.com/${yourOrgname}/_apis/projects?api-version=${apiVersion}"
$headers = @{
    Accept = "application/json"
    Authorization = "Bearer $accessToken"
}
Invoke-RestMethod -Uri $uri -Headers $headers -Method Get | Select-Object -ExpandProperty value ` | Select-Object id, name

现在,可以按常规使用 az cli 命令。

问:是否可以将不同租户中的托管标识添加到我的组织?

答:只能从组织连接到的同一租户添加托管标识。 但是,我们提供了一种解决方法,可用于在“资源租户”中设置托管标识,其中所有资源都位于其中。 然后,可以在组织连接的“目标租户”中启用服务主体使用它。 执行以下步骤作为解决方法:

  1. 在资源租户的 Azure 门户 中创建用户分配的托管标识
  2. 将其连接到 虚拟机,并向其分配此托管标识
  3. 创建 密钥保管库 并生成 证书 (类型不能为“PEM”) 。 生成此证书时,还会生成同名的机密,我们稍后会用到该机密。
  4. 授予对托管标识的访问权限,以便它可以从密钥保管库读取私钥。 使用“获取/列表”权限在密钥保管库中创建访问策略(在“机密权限”下)并在“选择主体”下搜索托管标识。
  5. 下载“CER”格式的已创建证书,确保它不包含证书的专用部分。
  6. 在目标租户中创建新的应用程序注册。
  7. 在“证书和机密”选项卡中将下载的证书上传到此新应用程序。
  8. 将此应用程序的服务主体添加到 Azure DevOps 组织,我们希望它能够访问并记住使用任何所需权限设置服务主体。
  9. 若要从使用此托管标识证书的此服务主体获取 Microsoft Entra 访问令牌,请参阅以下代码示例:

注意

必须像往常一样定期轮换证书。

public static async Task<string> GetSecret(string keyVaultName, string secretName)
{
	var keyVaultUri = new Uri("https://" + keyVaultName + ".vault.azure.net");
	var client = new SecretClient(keyVaultUri, new ManagedIdentityCredential());
	var keyVaultSecret = await client.GetSecretAsync(secretName);

	var secret = keyVaultSecret.Value;
	return secret.Value;
}

private static async Task<AuthenticationResult> GetAppRegistrationAADAccessToken(string applicationClientID, string appTenantId)
{
	IConfidentialClientApplication app;

	byte[] privateKeyBytes = Convert.FromBase64String(GetSecret(keyVaultName, secretName));
	X509Certificate2 certificateWithPrivateKey = new X509Certificate2(privateKeyBytes, (string)null, X509KeyStorageFlags.MachineKeySet);

	app = ConfidentialClientApplicationBuilder.Create(applicationClientID)
		.WithCertificate(certificateWithPrivateKey)
		.WithAuthority(new Uri(string.Format(CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}", appTenantId)))
		.Build();
	app.AddInMemoryTokenCache();

	string AdoAppClientID = "499b84ac-1321-427f-aa17-267ca6975798/.default";
	string[] scopes = new string[] { AdoAppClientID };

	var result = await app.AcquireTokenForClient(scopes).ExecuteAsync();

	return result;
}

Artifacts

问:是否可以使用服务主体连接到源?

答:可以,可以使用服务主体连接到任何 Azure Artifacts 源。 在以下示例中,我们演示如何使用 NuGet、npm 和 Maven 的 Microsoft Entra ID 令牌进行连接,但其他源类型也应正常工作。

使用 Microsoft Entra 令牌设置 NuGet 项目
  1. 请确保具有 最新的 NuGet

  2. 下载并安装 Azure Artifacts 凭据提供程序:

  3. 将 nuget.config 文件添加到项目,该文件与 .csproj.sln文件位于同一文件夹中

    • 项目作用域的源:

      <?xml version="1.0" encoding="utf-8"?>
      <configuration>
        <packageSources>
          <clear />
          <add key="<FEED_NAME>" value="https://pkgs.dev.azure.com/<ORGANIZATION_NAME>/<PROJECT_NAME>/_packaging/<FEED_NAME>/nuget/v3/index.json" />
        </packageSources>
      </configuration>
      
    • 组织作用域的源:

      <?xml version="1.0" encoding="utf-8"?>
      <configuration>
        <packageSources>
          <clear />
          <add key="<FEED_NAME>" value="https://pkgs.dev.azure.com/<ORGANIZATION_NAME>/_packaging/<FEED_NAME>/nuget/v3/index.json" />
        </packageSources>
      </configuration>
      
  4. 设置ARTIFACTS_CREDENTIALPROVIDER_FEED_ENDPOINTS环境变量,如下所示,指定源 URL、服务主体的应用程序(客户端)ID 以及服务主体证书的路径:

    • PowerShell:

      $env:ARTIFACTS_CREDENTIALPROVIDER_FEED_ENDPOINTS = @'
      {
        "endpointCredentials": [
          {
            "endpoint": "<FEED_URL>",
            "clientId": "<SERVICE_PRINCIPAL_APPLICATION_(CLIENT)_ID>",
            "clientCertificateFilePath": "<SERVICE_PRINCIPAL_CERTIFICATE_PATH>"
          }
        ]
      }
      '@
      
    • Bash

      export ARTIFACTS_CREDENTIALPROVIDER_FEED_ENDPOINTS='{
        "endpointCredentials": [
          {
            "endpoint": "<FEED_URL>",
            "clientId": "<SERVICE_PRINCIPAL_APPLICATION_(CLIENT)_ID>",
            "clientCertificateFilePath": "<SERVICE_PRINCIPAL_CERTIFICATE_PATH>"
          }
        ]
      }'
      

使用 Microsoft Entra 令牌设置 npm 项目

注意

vsts-npm-auth 工具不支持Microsoft Entra 访问令牌。

  1. .npmrc在 与 相同的目录中package.json,将 添加到项目。

    registry=https://pkgs.dev.azure.com/Fabrikam/_packaging/FabrikamFeed/npm/registry/ 
    always-auth=true
    
  2. 获取服务主体或托管标识的访问令牌。

  3. 将此代码添加到 , ${user.home}/.npmrc 并将 占位符 [AAD_SERVICE_PRINCIPAL_ACCESS_TOKEN] 替换为上一步中的访问令牌。

    //pkgs.dev.azure.com/Fabrikam/_packaging/FabrikamFeed/npm/:_authToken=[AAD_SERVICE_PRINCIPAL_ACCESS_TOKEN]
    

使用 Microsoft Entra 令牌设置 Maven 项目
  1. 将存储库添加到 pom.xml<repositories><distributionManagement> 部分。

    <repository>
      <id>Fabrikam</id>
      <url>https://pkgs.dev.azure.com/Fabrikam/_packaging/FabrikamFeed/maven/v1</url>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
    
  2. 获取服务主体或托管标识的访问令牌。

  3. 在 中添加或编辑 settings.xml 文件,并将 占位符[AAD_SERVICE_PRINCIPAL_ACCESS_TOKEN]替换为上一步中的访问${user.home}/.m2令牌。

    <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                                  https://maven.apache.org/xsd/settings-1.0.0.xsd">
      <servers>
        <server>
          <id>Fabrikam</id>
          <username>Fabrikam</username>
          <password>[AAD_SERVICE_PRINCIPAL_ACCESS_TOKEN]</password>
        </server>
      </servers>
    </settings>
    

市场

问:是否可以使用服务主体将扩展发布到 Visual Studio Marketplace?

答:是的。 执行以下步骤。

  1. 将服务主体作为成员添加到发布者帐户。 可以使用 配置文件 - 获取从其配置文件中获取服务主体的 ID。 然后,可以使用上一步中的 ID 将 服务主体作为成员添加到 发布者。

  2. 使用 SP 通过 TFX CLI 发布扩展。 执行以下 TFX CLI 命令以使用 SP 访问令牌:

tfx extension publish --publisher my-publisher --vsix my-publisher.my-extension-1.0.0.vsix --auth-type pat -t <AAD_ACCESS_TOKEN>

管道

问:是否可以在服务连接中使用托管标识? 如何在服务连接中更轻松地轮换服务主体的机密? 是否可以避免将机密完全存储在服务连接中?

Azure 支持使用 OpenID Connect 协议的工作负荷标识联合,这样我们就可以在 Azure Pipelines 中创建无机密服务连接,这些连接由服务主体或托管标识提供支持,并在 Microsoft Entra ID 中使用联合凭据。 作为其执行的一部分,管道可以使用 Microsoft Entra 令牌交换自己的内部令牌,从而获得对 Azure 资源的访问权限。 实现后,建议在产品中使用此机制来使用 当前存在的其他类型的 Azure 服务连接 。 此机制还可用于使用任何其他符合 OIDC 的服务提供商设置无机密部署。 此机制处于预览状态。

Repos

问:是否可以使用服务主体执行 git 操作,例如克隆存储库?

答:请参阅以下示例,了解如何在 PowerShell 脚本中传递 服务主体的 Microsoft Entra ID 令牌 ,而不是 PAT 来克隆存储库。

$ServicePrincipalAadAccessToken = 'Entra ID access token of a service principal'
git -c http.extraheader="AUTHORIZATION: bearer $ServicePrincipalAadAccessToken" clone https://dev.azure.com/{yourOrgName}/{yourProjectName}/_git/{yourRepoName}

提示

若要使令牌更安全,请使用凭据管理器,这样就不必每次都输入凭据。 建议 使用 Git 凭据管理器,如果环境变量发生更改,则可以接受 Microsoft Entra 令牌(即Microsoft标识 OAuth 令牌), 而不是 PAT。

有关如何从 Azure CLI 获取访问令牌以调用 git fetch 的有用提示:

  1. 打开存储库的 Git 配置:
git config -e
  1. 添加以下行,其中 Azure DevOps 资源的 UUID 为 00000000-0000-0000-0000-000000000000
[credential]
    helper = "!f() { test \"$1\" = get && echo \"password=$(az account get-access-token --resource     00000000-0000-0000-0000-000000000000 --query accessToken -o tsv)\"; }; f" 
  1. 使用测试它是否正常工作:
GIT_TRACE=1 GCM_TRACE=1 GIT_CURL_VERBOSE=1 git fetch

潜在错误

未能创建对象 ID 为“{provided objectId}”的服务主体

连接到组织的租户中没有服务主体 provided objectId 。 一个常见原因是传入应用注册的对象 ID,而不是其服务主体的对象 ID。 请记住,服务主体是表示给定租户的应用程序的对象,它不是应用程序本身。 service principal object ID可以在租户的“企业应用程序”页中找到 。 搜索应用程序的名称,然后选择返回的“企业应用程序”结果。 此结果是服务主体/企业应用程序的页面,可以使用此页上的对象 ID 在 Azure DevOps 中创建服务主体。

拒绝访问:{ID of the caller identity} 需要以下权限 (资源) 用户执行此操作:添加用户

此错误可能是由于以下原因之一造成的:

  • 你不是组织的所有者、项目集合管理员或项目或团队管理员。
  • 你是项目或团队管理员,但禁用策略“允许团队和项目管理员邀请新用户”。
  • 你是可以邀请新用户的项目或团队管理员,但在邀请新用户时尝试分配许可证。 不允许项目或团队管理员将许可证分配给新用户。 任何新受邀用户将添加到 新用户的默认访问级别。 请联系 PCA 以更改许可证访问级别。

Azure DevOps Graph 列表 API 返回空列表,即使我们知道组织中存在服务主体

即使仍有更多用户要返回的页面,Azure DevOps Graph 列表 API 也可能返回空列表。 continuationToken使用 循环访问列表,最终可以找到返回服务主体的页面。 continuationToken如果返回 ,则意味着可以通过 API 获取更多结果。 虽然我们计划改进此逻辑,但目前可能第一个 X 结果返回空。

TF401444:在 Web 浏览器中至少以 {tenantId'tenantId\servicePrincipalObjectId'} 身份登录一次,以便访问该服务。

如果未邀请服务主体加入组织,则可能遇到以下错误。 确保将服务主体添加到相应的组织,并具有访问任何所需资源所需的所有权限。