脚本如何访问服务连接?(Azure Devops管道)

50
根据https://learn.microsoft.com/zh-cn/azure/devops/pipelines/library/service-endpoints,可以使用多种服务连接类型。我可以轻松地在项目级别管理一组服务连接,并设置权限来限制哪些用户能够查看/编辑它们--这很好。
但是我不知道如何在我的构建管道中的脚本步骤中访问服务连接。例如,假设我有一个代表Azure Service Principal凭证的服务连接。我想在脚本步骤中访问这些凭证。
我该如何编写一个利用它们的脚本步骤?

你是指在 YAML 文件中吗? - Sajeetharan
1
所有的管道都是以YAML文件定义的,是的。但更具体地说,Azure DevOps定义了许多任务类型,如https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/deploy/azure-cli或https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/deploy/kubernetes--每个不同的任务都有自己的输入参数,可以用来传递服务连接。我想提供一个服务连接作为通用bash任务(https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/utility/bash)的输入。 - Bosh
@Bosh,你找到解决这个问题的方法了吗?似乎编写一个自定义任务,接收服务连接并导出变量可能是最佳选择。也许已经有这样的任务可用。 - DELUXEnized
1
@DELUXEnized,现在已经存在这样的扩展了。此外,我制作了一个允许您将服务连接与脚本作为一个任务使用的扩展:https://marketplace.visualstudio.com/items?itemName=cloudpup.authenticated-scripts - JoshuaTheMiller
7个回答

13

我也一直在思考这个问题。我解决的方法是使用'Azure CLI'任务而不是基本的'Script'(或'Bash')任务。这主要用于运行Az CLI命令,但你也可以运行标准的Bash脚本(或者如果你喜欢PSCore)。

如果你检查运行此任务时存在的环境变量,你将看到一堆关于服务连接的信息,前缀为'ENDPOINT_DATA_'的变量与Josh E所说的相符。它包括Azure订阅ID、名称、服务原则对象ID等。

可选地,你也可以使服务原则详细信息添加到环境中。这将包括SPN密钥、TenantID等作为秘密环境变量。

下面是任务的样子:

- task: AzureCLI@2
  displayName: 'Azure CLI'
  inputs:
    scriptType: bash
    scriptLocation: inlineScript
    azureSubscription: '<Service Connection Name>'
    inlineScript: |
      env | sort

- task: AzureCLI@2
  displayName: 'Azure CLI, with SPN info'
  inputs:
    scriptType: bash
    scriptLocation: inlineScript
    azureSubscription: '<Service Connection Name>'
    addSpnToEnvironment: true
    inlineScript: |
      env | sort

当然,这一切仅适用于Azure云服务连接。您可能会对其他服务连接使用类似的技术,但我还没有进行调查。


这是一个针对 Azure 连接特定用例的有前途的解决方案。 - Chris DaMour

12
由于服务连接涉及特定于连接的数据(通用服务连接是证明规则的例外...),因此您无法在Bash任务中使用强类型属性。相反,您可能希望手动检查环境变量和处理服务连接数据。
根据对Azure DevOps存储库中一些任务进行的调查,似乎服务连接及其数据是作为代理运行构建任务的环境变量填充的。通过运行给定的name字符串通过以下正则表达式获取结果环境键的值来检索服务连接: process.env [name.replace(/\./g, '_').toUpperCase()]; 各种服务端点数据的检索被包装在vsts-task-lib/task module中,使得消费任务可以编写如下代码:
taskLib.getEndpointAuthorization('SYSTEMVSSCONNECTION', false);

taskLib.getEndpointDataParameter('MYSERVICECONNECTION', 'SOME_PARAMETER_NAME', false);

taskLib.getEndpointUrl('MYSERVICECONNECTION', false) // <-- last param indicates required or not
因此,如果您想要在bash脚本中访问服务连接而不需要进行任何其他自定义,则建议您:
a) 通过迭代并编写环境变量,在构建脚本任务中验证服务连接信息的可用性,并设置system.debug环境变量。有一些迹象表明,构建任务没有“种子”与它们没有明确请求的连接,因此您可能需要创建一个自定义构建任务,其中之一是其输入的服务连接名称您想要使用的
b) 如上所述从变量中读取所需值在您的bash脚本中。可以类似计算服务连接变量名称,如此处
   var dataParam = getVariable('ENDPOINT_DATA_' + id + '_' + key.toUpperCase());  

您可能需要反复迭代来确定数据模式/结构。


2
这是我遇到的更好的答案之一...条件环境变量设置对我来说很惊讶。另外,似乎使用sdk的任务会将sdk从环境中删除 https://github.com/microsoft/azure-pipelines-task-lib/blob/9cedd04cc64c6b899b8b7826c54ec67c4682aa28/powershell/VstsTaskSdk/InputFunctions.ps1#L452 还没有找到如何处理它... - Chris DaMour
2
@ChrisDaMour 我已经创建了一个任务,允许您将服务连接与脚本作为一个任务使用:https://marketplace.visualstudio.com/items?itemName=cloudpup.authenticated-scripts。不过,我只允许该服务连接与一个脚本一起使用。 - JoshuaTheMiller
1
@JoshuaTheMiller 谢谢你证明了互联网上的“如果你能想象到它,就一定有人做过”的规则。 - Chris DaMour
请问您能否更新一下您的代码,看看我需要把您的代码放在管道的哪个位置。我不太清楚应该在哪里使用。如果能提供一个例子就更好了。 - undefined

11
我发现如果我在运行bash任务之前使用Kubectl任务并使用登录命令,我不需要认证或使用主机名。
KUBERNETESNODE和SERVICEPROTOCOL是我预先设定的管道变量。
      - task: Kubernetes@1
        displayName: 'Kubernetes Login'
        # This is needed to run kubectl command from bash.
        inputs:
          connectionType: 'Kubernetes Service Connection'
          kubernetesServiceEndpoint: '<Service Connection Name>'
          command: 'login'

      - task: Bash@3
        displayName: 'Run Component Test'        
        inputs:
          targetType: 'inline'
          script: |
            #Get the Node Port
            nodePort=`kubectl get --namespace $(Build.BuildId) svc <service name> -o=jsonpath='{.spec.ports[0].nodePort}'`
            #Run Newman test
            newman run postman/Service.postman_collection.json --global-var host=$KUBERNETESNODE --global-var protocol=$SERVICEPROTOCOL --global-var port=$nodePort -r junit

1
这是否意味着Kubernetes任务建立环境,使其在第二个任务中仍然可用?即导出环境变量? - DELUXEnized
它可以工作,但它不记得命名空间,所以你必须在bash任务中再次显式设置它。 - Tobag
@Tobag 登录不会设置命名空间。是的,您需要在后续命令中传递它。或者,在Bash@3任务开始时使用kubectl config set-context命令设置您的命名空间。 - Joe Kampf
@DELUXEnized 我认为正在发生的是 Kubernetes 登录任务正在创建一个 .kube 目录并设置 KUBECONFIG 环境变量。kubectl 命令使用此来访问集群。 - Joe Kampf

8
我在我的脚本/工具中使用与ARM部署相同的服务连接。
为了导出变量,我创建了以下模板。
parameters:
- name: azureSubscription
  type: string
- name: exportAsOutput
  type: boolean
  default: false
  
steps:  
- task: AzureCLI@2
  name: exported_azure_credentials
  displayName: 'Export Azure Credentials'
  inputs:
    azureSubscription: '${{ parameters.azureSubscription }}'
    scriptType: pscore
    scriptLocation: inlineScript
    addSpnToEnvironment: true
    ${{ if eq(parameters.exportAsOutput, true) }}:
      inlineScript: |
        Write-Host "##vso[task.setvariable variable=AZURE_TENANT_ID]$env:tenantId"
        Write-Host "##vso[task.setvariable variable=AZURE_TENANT_ID;isOutput=true]$env:tenantId"
        Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID]$env:servicePrincipalId"
        Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID;isOutput=true]$env:servicePrincipalId"
        Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_SECRET]$env:servicePrincipalKey"
        Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_SECRET;isOutput=true]$env:servicePrincipalKey"
    ${{ if eq(parameters.exportAsOutput, false) }}:
      inlineScript: |
        Write-Host "##vso[task.setvariable variable=AZURE_TENANT_ID]$env:tenantId"
        Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID]$env:servicePrincipalId"
        Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_SECRET]$env:servicePrincipalKey"

DevOps 在处理秘密信息时非常聪明,因此这些信息不会出现在管道日志中。


3年过去了,这个解决方案对我来说在.NET 6应用程序中通过DotNetCoreCLI任务构建和运行的管道中进行身份验证成功了!现在,在可执行文件中通过new DefaultAzureCredential()进行身份验证可以正常工作了!真是疯狂,'azureSubscription'输入在那里不被认可。 - Aaron Burke

6

正如其他人所述,没有一个很好的内置方式来使用脚本访问Service Connections。因为我不喜欢通过长期存在的环境变量暴露凭据(出于安全和懒惰的原因),所以我编写了一个扩展,允许您使用自定义脚本和Generic Service Connection:https://marketplace.visualstudio.com/items?itemName=cloudpup.authenticated-scripts

此扩展通过将服务连接公开为环境变量实现,这些变量仅在单个脚本任务的生命周期内存在:

服务连接变量 环境变量
url AS_SC_URL
username AS_SC_USERNAME
password AS_SC_PASSWORD

显示服务连接作为环境变量的截图

示例

此扩展包含的任务使您可以编写简洁的流水线,例如以下内容:

steps:
- task: AuthenticatedPowerShell@1  
  inputs:
    serviceConnection: 'Testing Authenticated Shell'
    targetType: inline
    script: 'Write-Host "url: $env:AS_SC_URL | username: $env:AS_SC_USERNAME | password: $env:AS_SC_PASSWORD"'

5

是的,这是可以实现的,我经常使用这种方法。您首先需要一个任务,该任务将凭据输出到环境变量中,然后您可以从任务输出的环境变量创建自己的变量。我通常使用 AzureCLI:

# Set Variables.
- task: AzureCLI@2
  name: setVariables
  displayName: Set Output Variables
  continueOnError: false
  inputs:
    azureSubscription: nameOfAzureServiceConnection
    scriptType: ps
    scriptLocation: inlineScript
    addSpnToEnvironment: true # this must be set to true
    inlineScript: |
      Write-Host "##vso[task.setvariable variable=azureClientId;isOutput=true]$($env:servicePrincipalId)"
      Write-Host "##vso[task.setvariable variable=azureClientSecret;isOutput=true]$($env:servicePrincipalKey)"
      Write-Host "##vso[task.setvariable variable=azureTenantId;isOutput=true]$($env:tenantId)"

接下来,您可以在下一步中使用您设置的这些新变量,请确保您使用任务名称调用变量,如$(taskName.variableName)。以下示例使用新变量在后续的PowerShell任务中设置环境变量,以供Terraform进行身份验证:

- PowerShell: |
     terraform plan -input=false -out=tfplan
 displayName: 'Terraform Plan'
 env:
   ARM_CLIENT_ID: $(setVariables.azureClientId)
   ARM_CLIENT_SECRET: $(setVariables.azureClientSecret)
   ARM_TENANT_ID: $(setVariables.azureTenantId)

参考:用脚本访问Azure服务连接


1
如果您需要使用服务连接来获取对不同服务/资源的授权,您也可以使用服务连接获取所需的令牌,并将它们传递给不能直接使用服务连接的脚本,例如:
- task: AzurePowerShell@5
  inputs:
    azureSubscription: 'AzureServiceConnection'
    ScriptType: 'InlineScript'
    Inline: |
      $token = Get-AzAccessToken 
      echo "##vso[task.setvariable variable=accesstoken;]$($token.Token)"
    azurePowerShellVersion: 'LatestVersion'
  
- script: 'echo This is the token: $(accesstoken)'

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接