VSTS生成管道:测试无法连接到Azure Key Vault

时间:2018-10-16 02:12:12

标签: c# asp.net azure azure-devops

我正在尝试使用VSTS(现在称为Azure DevOps)来执行CI / CD管道。对于我的构建管道,我有一个非常基本的设置,涉及执行还原,构建,测试和发布步骤。

对于我的测试步骤,我将其设置为运行两个测试项目-一个单元测试项目和一个集成测试项目。我具有“密钥保管库”访问策略设置,以提供对本人和Azure Devops的访问。当我使用Visual Studio在本地运行测试时,由于我登录到有权访问Azure密钥保管库的同一帐户,因此我可以运行测试而不会出现任何错误。

我的应用程序配置为使用以下设置访问密钥库:

 public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((ctx, builder) =>
            {
                var keyVaultEndpoint = GetKeyVaultEndpoint();

                if (!string.IsNullOrEmpty(keyVaultEndpoint))
                {
                    var azureServiceTokenProvider = new AzureServiceTokenProvider();
                    var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
                    builder.AddAzureKeyVault(keyVaultEndpoint, keyVaultClient, new DefaultKeyVaultSecretManager());
                }
            }
        )
            .UseStartup<Startup>();

运行构建管道时,我正在使用Hosted VS2017实例构建项目。除了试图访问密钥库的集成测试失败之外,其他所有程序都在运行。我正在使用以下软件包:

  • Microsoft.Azure.Services.AppAuthentication -易于获取 访问服务到Azure服务身份验证方案的令牌。
  • Microsoft.Azure.KeyVault -包含与Key Vault进行交互的方法。
  • Microsoft.Extensions.Configuration.AzureKeyVault -包含
    Azure Key Vault的IConfiguration扩展

我按照本教程https://docs.microsoft.com/en-us/azure/key-vault/tutorial-web-application-keyvault的要求设置了密钥保险库并将其集成到我的应用程序中。

我只是想通过确保单元测试和集成测试都通过来使我的构建工作。我尚未将其部署到应用程序服务。单元测试运行时没有任何问题,因为我正在模拟各种服务。我的集成测试失败,并显示以下错误消息。如何获得对密钥库的测试访问权限?我是否需要为托管的VS2017构建向密钥库添加任何特殊的访问策略?我不确定该怎么做,因为我看不到任何突出的地方。

Build

以下是错误的堆栈跟踪:

    2018-10-16T00:37:04.6202055Z Test run for D:\a\1\s\SGIntegrationTests\bin\Release\netcoreapp2.1\SGIntegrationTests.dll(.NETCoreApp,Version=v2.1)
    2018-10-16T00:37:05.3640674Z Microsoft (R) Test Execution Command Line Tool Version 15.8.0
    2018-10-16T00:37:05.3641588Z Copyright (c) Microsoft Corporation.  All rights reserved.
    2018-10-16T00:37:05.3641723Z 
    2018-10-16T00:37:06.8873531Z Starting test execution, please wait...
    2018-10-16T00:37:51.9955035Z [xUnit.net 00:00:40.80]     SGIntegrationTests.HomeControllerShould.IndexContentTypeIsTextHtml [FAIL]
    2018-10-16T00:37:52.0883568Z Failed   SGIntegrationTests.HomeControllerShould.IndexContentTypeIsTextHtml
    2018-10-16T00:37:52.0884088Z Error Message:
    2018-10-16T00:37:52.0884378Z  Microsoft.Azure.Services.AppAuthentication.AzureServiceTokenProviderException : Parameters: Connection String: [No connection string specified], Resource: https://vault.azure.net, Authority: https://login.windows.net/63cd8468-5bc3-4c0a-a6f8-1e314d696937. Exception Message: Tried the following 3 methods to get an access token, but none of them worked.
    2018-10-16T00:37:52.0884737Z Parameters: Connection String: [No connection string specified], Resource: https://vault.azure.net, Authority: https://login.windows.net/63cd8468-5bc3-4c0a-a6f8-1e314d696937. Exception Message: Tried to get token using Managed Service Identity. Access token could not be acquired. MSI ResponseCode: BadRequest, Response: {"error":"invalid_request","error_description":"Identity not found"}
    2018-10-16T00:37:52.0884899Z Parameters: Connection String: [No connection string specified], Resource: https://vault.azure.net, Authority: https://login.windows.net/63cd8468-5bc3-4c0a-a6f8-1e314d696937. Exception Message: Tried to get token using Visual Studio. Access token could not be acquired. Visual Studio Token provider file not found at "C:\Users\VssAdministrator\AppData\Local\.IdentityService\AzureServiceAuth\tokenprovider.json"
    2018-10-16T00:37:52.0885142Z Parameters: Connection String: [No connection string specified], Resource: https://vault.azure.net, Authority: https://login.windows.net/63cd8468-5bc3-4c0a-a6f8-1e314d696937. Exception Message: Tried to get token using Azure CLI. Access token could not be acquired. Process took too long to return the token.
    2018-10-16T00:37:52.0885221Z 
    2018-10-16T00:37:52.0885284Z Stack Trace:
    2018-10-16T00:37:52.0885349Z    at Microsoft.Azure.Services.AppAuthentication.AzureServiceTokenProvider.GetAccessTokenAsyncImpl(String authority, String resource, String scope)
    2018-10-16T00:37:52.0885428Z    at Microsoft.Azure.KeyVault.KeyVaultCredential.PostAuthenticate(HttpResponseMessage response)
    2018-10-16T00:37:52.0885502Z    at Microsoft.Azure.KeyVault.KeyVaultCredential.ProcessHttpRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    2018-10-16T00:37:52.0886831Z    at Microsoft.Azure.KeyVault.KeyVaultClient.GetSecretsWithHttpMessagesAsync(String vaultBaseUrl, Nullable`1 maxresults, Dictionary`2 customHeaders, CancellationToken cancellationToken)
    2018-10-16T00:37:52.0886887Z    at Microsoft.Azure.KeyVault.KeyVaultClientExtensions.GetSecretsAsync(IKeyVaultClient operations, String vaultBaseUrl, Nullable`1 maxresults, CancellationToken cancellationToken)
    2018-10-16T00:37:52.0886935Z    at Microsoft.Extensions.Configuration.AzureKeyVault.AzureKeyVaultConfigurationProvider.LoadAsync()
    2018-10-16T00:37:52.0887000Z    at Microsoft.Extensions.Configuration.AzureKeyVault.AzureKeyVaultConfigurationProvider.Load()
    2018-10-16T00:37:52.0887045Z    at Microsoft.Extensions.Configuration.ConfigurationRoot..ctor(IList`1 providers)
    2018-10-16T00:37:52.0887090Z    at Microsoft.Extensions.Configuration.ConfigurationBuilder.Build()
    2018-10-16T00:37:52.0887269Z    at Microsoft.AspNetCore.Hosting.WebHostBuilder.BuildCommonServices(AggregateException& hostingStartupErrors)
    2018-10-16T00:37:52.0887324Z    at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
    2018-10-16T00:37:52.0887371Z    at Microsoft.AspNetCore.TestHost.TestServer..ctor(IWebHostBuilder builder, IFeatureCollection featureCollection)
    2018-10-16T00:37:52.0887433Z    at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateServer(IWebHostBuilder builder)
    2018-10-16T00:37:52.0887477Z    at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.EnsureServer()
    2018-10-16T00:37:52.0887525Z    at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateDefaultClient(DelegatingHandler[] handlers)

更新

我仅发现1个与此问题相关的帖子:https://social.msdn.microsoft.com/Forums/en-US/0bac778a-283a-4be1-bc75-605e776adac0/managed-service-identity-issue?forum=windowsazurewebsitespreview。但是这篇文章与将应用程序部署到一个天蓝色的插槽有关。我只是试图在构建管道中构建我的应用程序。

我仍在尝试解决此问题,并且不确定提供所需访问权限的最佳方法是什么。


更新2

我仍然没有找到解决方案。我迷失了如何使我的管道顺利运行测试的问题。我看到发布管道也可以选择运行测试。但是这些似乎使用.dll文件,而我的构建管道放置文件仅具有Web应用程序(我看不到任何测试项目发布的放置文件)。不知道这是否有可能。


更新3

我设法通过使用此处提供的最后一个选项使其工作:https://docs.microsoft.com/en-us/azure/key-vault/service-to-service-authentication#connection-string-support

我尝试了使用证书的其他方式,但是只要在连接字符串中提供{CurrentUser},构建管道就会失败。它可以在我的本地计算机上工作,但不能在构建管道中工作。

要使其正常工作,我必须做三件事:

  • 登录到Azure。在Azure AD中设置新的应用注册
  • 在新的AD应用程序注册中,创建一个新的客户机密 enter image description here
  • 为您的密钥保险库提供新的AD App访问权限。进入关键保管库访问策略,并添加您在AD中创建的具有对机密的读取权限的应用程序。 enter image description here

  • 将我对 Program.cs 文件中的 AzureServiceTokenProvier() 的调用修改为:

     var azureServiceTokenProvider = new AzureServiceTokenProvider("connectionString={your key vault endpoint};RunAs=App;AppId={your app id that you setup in Azure AD};TenantId={your azure subscription};AppKey={your client secret key}")
    

请注意,您的客户机密必须正确设置格式。应用程序注册(预览)会生成一个随机密钥。有时,此键在连接字符串中不起作用(抛出格式错误的错误)。可以尝试在非预览版的应用程序注册中生成您自己的密钥,也可以生成一个新密钥,然后重试。

之后,我能够在构建管道中成功运行集成测试,并在Azure中创建到Web应用程序的发行版。我对这种方法不满意,因为尽管它可以工作,但它在代码本身中公开了一个秘密值。由于上述方法,不需要打开管理服务身份。我觉得这在这方面非常糟糕。

必须有一个比这更好的方法。一种选择是不在构建管道中运行集成测试。不知道这是否是正确的方法。我仍然希望有人能够提供更好的方法或解释我的方法是否可以使用。

4 个答案:

答案 0 :(得分:2)

由于您使用的是Azure DevOps默认托管代理,因此您不应在Azure DevOps Pipelines构建中对Azure KeyVault进行身份验证的集成测试。

默认情况下,Azure DevOps管道使用的是基本的默认托管代理,并且这些托管代理无法通过Azure订阅进行访问。这些不足为奇,因为这些托管代理是满足所有常见构建需求的通用代理,包括构建/编译,运行单元测试,获取测试覆盖率,并且所有这些任务都没有其他附加功能,例如具有ActiveDirectory,数据库和其他功能。对其他方的实际身份验证/请求,例如对任何Azure Keyvault的身份验证。因此,默认情况下,这些代理未在您的Azure订阅中注册。

如果要针对这些特殊需求进行成功的集成测试,则必须为Azure DevOps Pipelines构建和发布创建自己的代理。因此,除了创建自己的代理并将Azure DevOps配置为使用自己的代理之外,没有其他方法可以强制Azure DevOps默认代理运行您的KeyVault身份验证测试。

要创建自己的代理,请查阅Microsoft的以下文档:

https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/agents?view=vsts#install

更新2018年10月29日

为更清楚起见,我也答复您的“ Update 3”解决方法。当Microsoft更新Azure DevOps的默认托管代理时,无法保证您的解决方法将正常工作。 因此,我还需要补充一点:进行集成测试而不依赖Azure DevOps Pipeline构建领域之外的另一方(例如连接数据库服务器或在其中使用外部身份验证(即使在Azure KeyVault上))也不是一个好习惯CI,尤其是在使用Microsoft的默认托管代理的情况下。

不仅由于无效的身份验证配置而容易出错,而且不能保证默认托管代理上的进一步更新将保证您的第三方逻辑测试能够正常工作。

答案 1 :(得分:1)

使用 Azure CLI pipeline task 来运行成功需要KeyVault机密的集成测试,而不会在源代码控制中暴露任何机密:

  1. 在Azure中创建Service Principal service connection DevOps项目。

  2. 授予Azure Vault的主体获取列表权限。

  3. 在Azure CLI任务中运行集成测试

    - task: AzureCLI@1
      inputs:
        azureSubscription: 'Your Service Connection Name'
        scriptLocation: 'inlineScript'
        inlineScript: 'dotnet test --configuration $(buildConfiguration) --logger trx'
    

    之所以行之有效,是因为测试将在azure cli的上下文中运行,该环境位于AzureServiceTokenProvider tries fetching a token from before it fails。 Azure CLI处理身份验证并在任务完成时清除。

答案 2 :(得分:0)

我自己遇到了完全相同的问题。通过将连接字符串添加到AzureServiceTokenProvider(通过的默认参数为null)来修改代码,我确实走得更远。但是,由于Azure DevOps用户可能或可能没有对KeyVault的必需访问权,我仍然无法使其完全正常工作,但是我没有机会进一步研究。 希望这里发布更好的解决方案。

更新 我们将Build用户添加到Azure AD中,然后将其添加到该用户的KeyVault中的访问策略中。仅授予它访问权限(我们的测试仅测试它是否可以收集秘密)。现在测试成功通过。

答案 3 :(得分:0)

一个更简单的解决方案是使用Azure DevOps Variable Groups

在DevOps项目上具有读取权限和贡献者的人可以创建变量组,将其链接到密钥库并选择所需的机密。

变量组现在可以链接到您的任何管道。

无论如何使它可用于管道中运行的任何代码,必须首先使用此方法导出机密。

您需要通过任务(Azure Powershell或Bash)来执行此操作,但是必须通过内联脚本来完成。您不能在问题的文件中的脚本中导出keyvault变量。因此,在第一个任务中导出所有变量,所有后续任务和引用的脚本都可以使用它们。

PowerShell:

Write-Host "##vso[task.setvariable variable=mysecretexported]$(mysecret1)"
Bash
@echo ##vso[task.setvariable variable=mysecretexported]$(mysecret1)"

You can then refer to the secret using the exported variable

Powershell

Write-Host No problem reading "$env:MYSECRETEXPORTED"

批次:

@echo No problem reading %mysecretexported%

bash的工作原理类似:

#!/bin/bash

echo "No problem reading $MYSECRETEXPORTED"

YAML也支持此功能

令人高兴的是,这些变量将在日志中被屏蔽,因此您的秘密保持秘密。