使用模板实现安全性

Azure DevOps Services | Azure DevOps Server 2022 | Azure DevOps Server 2020

本文介绍了模板如何简化 Azure Pipelines 的安全性。 模板可以定义管道的外部结构,并帮助防止恶意代码渗透。 模板还可以自动包括执行凭据扫描等任务的步骤。 如果团队或组织内的多个管道共享相同的结构,请考虑使用模板。

检查受保护的资源 构成了 Azure Pipelines 的基本安全框架。 无论管道结构、阶段和作业如何,这些检查都适用。 可以使用模板来帮助强制实施这些检查。

本文是一系列教程的一部分,可帮助实现 Azure Pipelines 的安全措施。 有关更多信息,请参阅 Secure Azure Pipelines 指南

先决条件

类别 要求
Azure DevOps - 在使 Azure DevOps 安全和保护 Azure Pipelines 中实施建议。
- 对 YAML 和 Azure Pipelines 的基本知识。 有关详细信息,请参阅 创建第一个管道
权限 - 修改管道权限: 项目管理员组的成员
- 要修改组织权限,必须是 项目集合管理员组 的成员。

包括和扩展模板

Azure Pipelines 提供包含扩展模板。

  • 包括模板直接在引用模板的外部文件中包括模板的代码,类似于 #include C++。 以下示例管道会将 include-npm-steps.yml 模板插入到 steps 部分。

      steps:
      - template: templates/include-npm-steps.yml 
    
  • 扩展模板定义管道的外部结构,并为目标自定义项提供特定点。 在C++上下文中, extends 模板类似于继承。

使用 extends 模板时,还可以在 includes 模板和最终管道中使用以执行常见配置部分。 有关完整参考,请参阅 模板使用情况参考

扩展模板

对于最安全的管道,首先使用扩展模板。 这些模板定义管道的外部结构,并防止恶意代码渗透管道。

例如,以下模板文件命名 为template.yml

parameters:
- name: usersteps
  type: stepList
  default: []
steps:
- ${{ each step in parameters.usersteps }}:
  - ${{ step }}

以下管道扩展 template.yml 模板。

# azure-pipelines.yml
resources:
  repositories:
  - repository: templates
    type: git
    name: MyProject/MyTemplates
    ref: refs/tags/v1

extends:
  template: template.yml@templates
  parameters:
    usersteps:
    - script: echo This is my first step
    - script: echo This is my second step

提示

设置 extends 模板时,请考虑将它们定位到特定的 Git 分支或标记,因此,如果有中断性变更,现有管道不会受到影响。 前面的示例使用此功能。

YAML 管道安全功能

YAML 管道语法包括多个内置保护。 扩展模板可以强制使用。 若要增强管道安全性,可以实施以下任何限制。

步骤目标

可以限制在容器中运行的某些步骤,而不是在主机上运行。 容器中的步骤无法访问代理的主机,阻止这些步骤修改代理配置或留下恶意代码供以后执行。

例如,请考虑限制网络访问。 如果没有开放网络访问权限,用户步骤无法从未经授权的源检索包,也无法将代码和机密上传到外部网络位置。

以下示例管道在容器中运行步骤之前,在代理主机上运行步骤。

resources:
  containers:
  - container: builder
    image: mysecurebuildcontainer:latest
steps:
- script: echo This step runs on the agent host, and it could use Docker commands to tear down or limit the container's network
- script: echo This step runs inside the builder container
  target: builder

代理日志记录命令限制

可以限制 Azure Pipelines 代理提供给用户步骤的服务。 用户通过使用 日志记录命令来请求服务,这些命令是打印到标准输出的特制字符串。 在受限模式下,大多数代理的服务(如上传项目和附加测试结果)都不可用。

下面的示例任务失败,因为它 target 的属性指示代理不允许发布项目。

- task: PublishBuildArtifacts@1
  inputs:
    artifactName: myartifacts
  target:
    commands: restricted

restricted 模式下, setvariable 命令仍然允许,因此需要谨慎,因为管道变量作为环境变量导出到后续任务。 如果任务输出用户提供的数据(例如通过 REST API 检索的开放问题),则它们可能容易受到注入攻击。 恶意用户内容可以设置可能被利用的环境变量来入侵代理主机。

为了缓解此风险,管道作者可以使用日志记录命令显式声明哪些变量是可设置的 setvariable 。 指定空列表时,不允许所有变量设置。

下面的示例任务失败,因为仅允许该任务设置变量 expectedVar 或前缀为 ok变量的变量。

- task: PowerShell@2
  target:
    commands: restricted
    settableVariables:
    - expectedVar
    - ok*
  inputs:
    targetType: 'inline'
    script: |
      Write-Host "##vso[task.setvariable variable=BadVar]myValue"

条件阶段或作业执行

可以限制阶段和作业仅在特定条件下运行。 在下面的示例中,该条件可确保限制的代码仅针对主分支生成。

jobs:
- job: buildNormal
  steps:
  - script: echo Building the normal, unsensitive part
- ${{ if eq(variables['Build.SourceBranchName'], 'refs/heads/main') }}:
  - job: buildMainOnly
    steps:
    - script: echo Building the restricted part that only builds for main branch

语法修改

Azure Pipelines 模板可以灵活地循环访问和修改 YAML 语法。 通过使用迭代,可以强制实施特定的 YAML 安全功能。

模板还可以重写用户步骤,仅允许已批准的任务运行。 例如,可以阻止内联脚本执行。

以下示例模板阻止步骤类型、bashpowershell步骤pwsh类型和script运行。 若要完全锁定即席脚本,还可以阻止 BatchScriptShellScript锁定。

# template.yml
parameters:
- name: usersteps
  type: stepList
  default: []
steps:
- ${{ each step in parameters.usersteps }}:
  - ${{ if not(or(startsWith(step.task, 'Bash'),startsWith(step.task, 'CmdLine'),startsWith(step.task, 'PowerShell'))) }}:  
    - ${{ step }}
  # The following lines replace tasks like Bash@3, CmdLine@2, PowerShell@2
  - ${{ else }}:  
    - ${{ each pair in step }}:
        ${{ if eq(pair.key, 'inputs') }}:
          inputs:
            ${{ each attribute in pair.value }}:
              ${{ if eq(attribute.key, 'script') }}:
                script: echo "Script removed by template"
              ${{ else }}:
                ${{ attribute.key }}: ${{ attribute.value }}
        ${{ elseif ne(pair.key, 'displayName') }}:
          ${{ pair.key }}: ${{ pair.value }}

          displayName: 'Disabled by template: ${{ step.displayName }}'

在扩展此模板的以下管道中,脚本步骤将剥离出来,且未运行。

# azure-pipelines.yml
extends:
  template: template.yml
  parameters:
    usersteps:
    - task: MyTask@1
    - script: echo This step will be stripped out and not run!
    - bash: echo This step will be stripped out and not run!
    - powershell: echo "This step will be stripped out and not run!"
    - pwsh: echo "This step will be stripped out and not run!"
    - script: echo This step will be stripped out and not run!
    - task: CmdLine@2
      displayName: Test - Will be stripped out
      inputs:
        script: echo This step will be stripped out and not run!
    - task: MyOtherTask@2

类型安全参数

在管道运行之前,模板及其参数将转换为常量。 模板参数 可以增强输入参数的类型安全性。

在以下示例模板中,参数通过提供特定选项的枚举来限制可用的管道池选项,而不是允许任意多边形字符串。

# template.yml
parameters:
- name: userpool
  type: string
  default: Azure Pipelines
  values:
  - Azure Pipelines
  - private-pool-1
  - private-pool-2

pool: ${{ parameters.userpool }}
steps:
- script: # ... removed for clarity

管道扩展模板时,必须指定可用池选项之一。

# azure-pipelines.yml
extends:
  template: template.yml
  parameters:
    userpool: private-pool-1

模板步骤

模板可以自动在管道中包含步骤。 这些步骤可以执行凭据扫描或静态代码检查等任务。 以下模板在每个作业中的用户步骤之前和之后插入步骤。

parameters:
  jobs: []

jobs:
- ${{ each job in parameters.jobs }}: 
  - ${{ each pair in job }}:  
      ${{ if ne(pair.key, 'steps') }}:
        ${{ pair.key }}: ${{ pair.value }}
    steps:                            
    - task: CredScan@1 
    - ${{ job.steps }} 
    - task: PublishMyTelemetry@1 
      condition: always()

模板强制实施

模板是一种有价值的安全机制,但其有效性依赖于强制实施。 强制实施模板使用的关键控制点是 受保护的资源。 可以配置代理池或其他受保护资源(例如存储库)的审批和检查。 有关示例,请参阅 “添加存储库资源检查”。

所需的模板

若要强制使用特定模板,请为资源配置所需模板检查。 此检查仅适用于管道从模板扩展时。

查看管道作业时,可以监视检查的状态。 如果管道未从所需的模板进行扩展,则检查将失败。 运行会停止并通知失败的检查。

显示审批检查失败的屏幕截图。

使用所需的模板时,检查将通过。

显示已通过审批检查的屏幕截图。

在任何扩展此模板的管道中,必须引用以下 params.yml 模板。

# params.yml
parameters:
- name: yesNo 
  type: boolean
  default: false
- name: image
  displayName: Pool Image
  type: string
  default: ubuntu-latest
  values:
  - windows-latest
  - ubuntu-latest
  - macOS-latest

steps:
- script: echo ${{ parameters.yesNo }}
- script: echo ${{ parameters.image }}

以下示例管道扩展 params.yml 模板,并要求其批准。 若要演示管道故障,请注释掉对 params.yml的引用。

# azure-pipeline.yml

resources:
 containers:
     - container: my-container
       endpoint: my-service-connection
       image: mycontainerimages

extends:
    template: params.yml
    parameters:
        yesNo: true
        image: 'windows-latest'