Конвейер сборки VSTS: при тестировании не удается подключиться к хранилищу ключей Azure

Я пытаюсь использовать VSTS (теперь Azure DevOps) для создания конвейера CI / CD. Для моего конвейера сборки у меня есть очень простая настройка, включающая выполнение шагов восстановления, сборки, тестирования и публикации.

На моем этапе тестирования я настроил его для запуска двух тестовых проектов - одного проекта модульного тестирования и одного проекта тестирования интеграции. Моя политика доступа к Key Vault настроена для предоставления доступа как мне, так и 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>();

Когда я запускаю конвейер сборки, я использую размещенный экземпляр VS2017 для сборки моего проекта. Все работает, за исключением интеграционных тестов, которые не могут получить доступ к хранилищу ключей. Я использую следующие пакеты:

  • Microsoft.Azure.Services.AppAuthentication - упрощает получение токенов доступа для сценариев аутентификации Service-to-Azure-Service.
  • Microsoft.Azure.KeyVault - содержит методы для взаимодействия с Key Vault.
  • Microsoft.Extensions.Configuration.AzureKeyVault - содержит
    расширения IConfiguration для Azure Key Vault.

Я следил за этим руководством https://docs.microsoft.com/en-us/azure/key-vault/tutorial-web-application-keyvault, чтобы настроить хранилище ключей и интегрировать его в мое приложение.

Я просто пытаюсь заставить свою сборку работать, проверяя, проходят ли модульные и интеграционные тесты. Я еще не развертываю его в службе приложений. Модульные тесты проходят без каких-либо проблем, поскольку я высмеиваю различные службы. Мой интеграционный тест не проходит с сообщениями об ошибках ниже. Как получить тестовый доступ к хранилищу ключей? Нужно ли мне добавлять какие-либо специальные политики доступа к моему хранилищу ключей для размещенной сборки VS2017? Не знаю, что делать, потому что не вижу ничего особенного.

Сборка

Ниже приведена трассировка стека для ошибки:

    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)

Обновлять

Я нашел только одно сообщение, связанное с этой проблемой: https://social.msdn.microsoft.com/Forums/en-US/0bac778a-283a-4be1-bc75-605e776adac0/managed-service-identity-проблема?forum=windowsazurewebsitespreview. Но сообщение связано с развертыванием приложения в лазурном слоте. Я просто пытаюсь построить свое приложение в конвейере сборки.

Я все еще пытаюсь решить эту проблему и не знаю, как лучше всего предоставить требуемый доступ.


Обновление 2

Я до сих пор не нашел решения для этого. Я не знаю, как заставить мой конвейер без проблем запускать мой тест. Я видел, что в конвейере выпуска у вас тоже есть возможность запускать тесты. Но они, похоже, принимают файлы .dll, а в моем файле сброса конвейера сборки есть только веб-приложение (я не вижу ни одного из опубликованных тестовых проектов). Не уверен, что это вообще возможно.


Обновление 3

Мне удалось заставить его работать, используя последний вариант, представленный здесь: https://docs.microsoft.com/en-us/azure/key-vault/service-to-service-authentication#connection-string-support

Я пробовал другие способы использования сертификата, но каждый раз, когда {CurrentUser} указывается в строке подключения, конвейер сборки не работает. Он работает на моем локальном компьютере, но не в конвейере сборки.

Чтобы заставить его работать, мне пришлось сделать три вещи:

  • Войдите в Azure. Настройка регистрации нового приложения в Azure AD
  • При регистрации нового приложения AD создайте новый секрет клиента  введите описание изображения здесь
  • Предоставьте новому приложению AD доступ к хранилищу ключей. Зайдите в политики доступа к хранилищу ключей и добавьте созданное вами в AD приложение с доступом для чтения к вашим секретам. введите описание изображения здесь

  • Изменил мой вызов AzureServiceTokenProvier () в моем файле Program.cs следующим образом:

     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. Я не удовлетворен этим подходом, потому что, хотя он работает, он раскрывает секретное значение в самом коде. Управляет удостоверением службы не нужно включать из-за вышеупомянутого подхода. Я считаю, что в этом отношении это очень плохо.

Должен быть способ получше. Один из вариантов - не запускать интеграционный тест в конвейере сборки. Не уверен, что это правильный подход. Я все еще надеюсь, что кто-то сможет предложить лучший подход к этому или объяснить, подходит ли мой подход для использования.


person Help123    schedule 16.10.2018    source источник
comment
Такая же проблема. Переместили секреты в Key Vault. В Visual Studio я могу предоставить секреты конфигурации aspnetcore через usersecrets или позволить Visual Studio аутентифицировать меня в Key Vault. В службе приложений я могу предоставить учетной записи MSI доступ к Key Vault. На фазе тестирования конвейера сборки меня закидывают. Я установил группу переменных конвейера для Key Vault, но поскольку это секреты, они не вставляются в среду, поэтому не используются системой конфигурации aspnetcore для интеграционных тестов.   -  person robaker    schedule 25.10.2018
comment
@robaker Я опубликовал обходной путь. Не доволен, но, надеюсь, кто-то может предложить лучшее решение, чем то, что я предлагал.   -  person Help123    schedule 25.10.2018
comment
@ Help123 ваш обходной путь с обновлением 3 не рекомендуется, потому что вы взламываете агент Azure DevOps Pipelines по умолчанию для запуска пользовательской проверки подлинности в рамках вашего интеграционного теста. Нет гарантии, что ваша проверка подлинности KeyVault будет работать, если размещенный агент по умолчанию обновлен Microsoft. Смотрите мой полный ответ ниже.   -  person Eriawan Kusumawardhono    schedule 29.10.2018
comment
Это сводит меня с ума уже несколько месяцев ...   -  person Sam    schedule 03.02.2019
comment
Больше не нужно @Sam, посмотрите мой ответ ниже :)   -  person Saeb Amini    schedule 03.07.2019


Ответы (4)


Не следует выполнять интеграционный тест проверки подлинности в Azure KeyVault в сборке Azure DevOps Pipelines, поскольку вы используете размещенные по умолчанию агенты Azure DevOps.

По умолчанию конвейеры Azure DevOps используют базовые размещенные агенты по умолчанию, и эти размещенные агенты недоступны из вашей подписки Azure. Это неудивительно, потому что эти размещенные агенты являются общими агентами для всех общих потребностей сборки, включая сборку / компиляцию, запуск модульных тестов, получение тестовых покрытий, и все эти задачи не имеют других дополнительных функций, таких как ActiveDirectory, база данных и другие фактическая проверка подлинности / запросы к другой стороне, например проверка подлинности для любого хранилища ключей Azure. Поэтому эти агенты по умолчанию не зарегистрированы в вашей подписке Azure.

Если вы хотите иметь успешные интеграционные тесты для этих особых потребностей, вам необходимо создать свои собственные агенты для сборки и выпуска Azure DevOps Pipelines. Следовательно, нет другого способа заставить агент Azure DevOps по умолчанию запускать тесты проверки подлинности KeyVault, кроме создания собственных агентов и настройки Azure DevOps для использования ваших собственных агентов.

Чтобы создать собственных агентов, обратитесь к этой документации от Microsoft:

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

ОБНОВЛЕНИЕ 29 октября 2018 г.:

Для большей ясности я также отвечаю за ваш обходной путь «Обновление 3». Нет гарантии, что ваш обходной путь будет работать нормально, когда Microsoft обновит размещенный агент Azure DevOps по умолчанию. Поэтому мне также нужно добавить еще один момент: не рекомендуется иметь интеграционный тест, который полагается на другую сторону за пределами области вашей сборки Azure DevOps Pipelines, например, подключение к серверу базы данных или использование внешней аутентификации (даже в Azure KeyVault) внутри ваш CI, особенно если вы используете размещенные по умолчанию агенты Microsoft.

Это не только будет подвержено ошибкам из-за неверной конфигурации аутентификации, но и нет гарантии, что дальнейшие обновления размещенных по умолчанию агентов будут гарантировать, что ваш сторонний логический тест будет работать.

person Eriawan Kusumawardhono    schedule 26.10.2018
comment
Спасибо, что указали на это. Я удивлен, что так много документации относится к использованию хранилища ключей для защиты секретов, но проверить это непросто. Надеюсь, это может измениться в будущем, когда агентам, размещенным в Azure DevOps, может быть предоставлен доступ MSI по мере необходимости. Не лучше ли в таких случаях запустить интеграционные тесты локально? - person Help123; 27.10.2018
comment
Я думаю, дело в том, что то, что вы тестируете, - это доступ к удостоверениям, и без тестирования с использованием фактического удостоверения вы на самом деле не выполняете тот тест, которого ожидаете. При локальном тестировании в VS будет использоваться удостоверение, настроенное в VS через проверку подлинности службы приложений. - person Josh; 29.10.2018
comment
@Eriawan Kusumawardhono Спасибо за ответ. Думаю, я буду продолжать тестировать доступ к хранилищу ключей локально в VS с использованием аутентификации службы приложений. Я знаю, что он работает так, как предполагалось, с MSI в веб-приложении Azure. Я посмотрю, насколько легко в качестве альтернативы настроить собственный агент для конвейера сборки. - person Help123; 29.10.2018
comment
@ Help123 пожалуйста. Если вы сочтете мой ответ полезным для вас, отметьте его как ответ на свой вопрос :) - person Eriawan Kusumawardhono; 30.10.2018

Используйте Задача конвейера Azure CLI для успешного выполнения интеграционных тестов, которым требуются секреты KeyVault, без раскрытия каких-либо секретов в системе управления версиями:

  1. Создайте ссылку Подключение к службе субъекта-службы в проекте Azure DevOps.

  2. Предоставьте основные разрешения Получить и Список для Vault в Azure.

  3. Выполните интеграционные тесты внутри задачи Azure CLI:

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

    Это работает, потому что тесты будут выполняться в контексте azure cli, где AzureServiceTokenProvider пытается получить токен до того, как это произойдет. Azure CLI обрабатывает аутентификацию и очищает, когда задача выполнена.

person Saeb Amini    schedule 03.07.2019
comment
Интересное решение Saeb. Я обязательно попробую! - person Sam; 04.07.2019
comment
Этот ответ работает (поэтому я проголосовал за), но для читателей - если вы делаете это для модульных тестов, вам следует подумать о насмешке этих значений и сохранении подключений к Keyvault для интеграционных тестов после развертывания (для промежуточного тестирования или чего-то еще). - person HockeyJ; 24.01.2020
comment
Протестировано тоже работает. Чтобы добавить, если вы переключаетесь с DotNetCoreCLI на AzureClI, ваш тестовый путь dotnet должен совпадать с путем к папке, содержащей .csproj, а не с полным путем к .csproj. Мне потребовалось время, чтобы понять. Например. $ (testDirectory) вместо $ (testDirectory) / *. csproj - person csamleong; 29.03.2020

Я сталкиваюсь с той же проблемой. Я продвинулся немного дальше, изменив код, добавив строку подключения к AzureServiceTokenProvider (переданный параметр по умолчанию - null). Я все еще не смог заставить его полностью работать, возможно, потому, что пользователь Azure DevOps может иметь или не иметь требуемый доступ к KeyVault, но у меня не было возможности копаться дальше. Надеюсь, что здесь опубликовано лучшее решение.

Обновление. Мы добавили пользователя сборки в Azure AD, а затем добавили его в политики доступа в KeyVault для этого пользователя. Предоставление ему только доступа (наш тест был только для проверки того, может ли он собрать секрет). Испытания сейчас проходят успешно.

person Bobin Cherian    schedule 24.10.2018
comment
Ага. Я посмотрел на docs.microsoft.com/en-us/azure/key-vault/ для использования строк подключения. Я не уверен, нужно ли нам идти по пути создания сертификата и его использования. Странно, что вы не можете предоставить доступ к KeyVault для Azure DevOps. Особенно странно, учитывая, что Azure и Azure DevOps взаимодействуют друг с другом. - person Help123; 24.10.2018
comment
Я опубликовал обновление об обходном пути. Однако меня не устраивает такой подход. Я считаю, что это разоблачение того, что хранилище ключей должно сохранять, не идеально. - person Help123; 25.10.2018
comment
@ Help123 Спасибо, что сообщили мне об обновлении. Любопытно узнать, есть ли более чистый подход, но это хороший материал. - person Bobin Cherian; 26.10.2018
comment
Что вы имеете в виду, создавая пользователя? Я уже имею себя в Azure AD, а также в политике доступа к хранилищу ключей. Я не смог запустить свой интеграционный тест без подхода, описанного в обновлении 3 выше. Не могли бы вы подробнее рассказать о своем подходе? Например, как выглядит ваш вызов program.cs в хранилище ключей azure и каков пользователь сборки? - person Help123; 30.10.2018
comment
Конвейер сборки в Azure DevOps настроен на пул агентов, связанный с учетной записью пользователя. Это требуется при регистрации пула агентов в первый раз, он может быть зарегистрирован или не зарегистрирован с вашей собственной учетной записью. В нашем случае это был отдельный сервисный аккаунт. Поэтому, когда keyvault попытался аутентифицировать этого пользователя сборки, он потерпел неудачу. - person Bobin Cherian; 30.10.2018
comment
Это фрагмент кода, с которым мы проводим тест; `общедоступная статическая строка GetSecretFromKeyVault (string secretName) {AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider (); var azureKeyVaultURI = GetStringFromConfig (azureKeyVaultURI); var keyVaultClient = новый KeyVaultClient (новый KeyVaultClient.AuthenticationCallback (azureServiceTokenProvider.KeyVaultTokenCallback)); var secret = keyVaultClient.GetSecretAsync (azureKeyVaultURI, secretName) .GetAwaiter (). GetResult (); return secret.Value; } - person Bobin Cherian; 30.10.2018
comment
Привет, Бобин. Я посмотрел на пулы агентов и использую размещенный пул агентов VS2017. Вы используете частный агент для запуска конвейера сборки? Если вы используете размещенный агент, не могли бы вы пройти этапы регистрации размещенного агента с помощью учетной записи? Если вы используете частного агента, возможно, поэтому он работает. Интересно узнать, как вы настроены и используете ли вы размещенный агент. - person Help123; 30.10.2018
comment
Привет! У нас есть собственный пул агентов. Я не уверен на 100%, почему мы используем это в пуле Hosted VS2017. - person Bobin Cherian; 30.10.2018
comment
Понял, спасибо! Пулы частных агентов позволяют настраивать то, что вы хотите использовать, и обеспечивают более детальный контроль. Я использую размещенный агент, потому что не хотел выделять собственный частный сервер для сборки / выпуска. Кажется, что вы можете использовать частный пул агентов для получения доступа к ресурсам Azure, если вы планируете проводить интеграционные тесты таким образом (как в вашем случае). Спасибо за вашу помощь! - person Help123; 31.10.2018

Более простым решением было бы использовать Azure DevOps Группы переменных.

Кто-то с разрешениями на чтение и участник проекта 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.

Приятно то, что эти переменные будут замаскированы в ваших журналах, поэтому ваши секреты останутся в секрете.

person Marc Wolfson    schedule 05.12.2019