通过模板实现安全性

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

对受保护资源的检查 是 Azure Pipelines 安全性的基本构建基块。 无论管道的结构(阶段和作业)如何,都检查工作。 如果团队或组织中的多个管道具有相同的结构,则可以使用 模板进一步简化安全性。

Azure Pipelines 提供两种类型的模板: includeextends。 包含的模板的行为类似于 #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 }}
# 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 代理将向用户提供哪些服务步骤。 使用“日志记录命令” (输出到 stdout) 的特殊格式字符串的请求服务的步骤。 在受限模式下,大多数代理服务(例如上传项目和附加测试结果)都不可用。

# this task will fail because its `target` property instructs the agent not to allow publishing artifacts
- task: PublishBuildArtifacts@1
  inputs:
    artifactName: myartifacts
  target:
    commands: restricted

在受限模式下仍允许的命令之一是 setvariable 命令。 由于管道变量作为环境变量导出到后续任务,例如输出用户提供的数据的任务 (,因此从 REST API) 检索到的未结问题的内容可能容易受到注入攻击。 此类用户内容可以设置环境变量,进而可用于利用代理主机。 若要禁止此操作,管道作者可以通过日志记录命令显式声明可设置的 setvariable 变量。 指定空列表不允许设置所有变量。

# this task will fail because the task is only allowed to set the 'expectedVar' variable, or a variable prefixed with "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

需要具有扩展模板的某些语法

模板可以循环访问和更改/禁止任何 YAML 语法。 迭代可以强制使用特定的 YAML 语法,包括上述功能。

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

警告

在下面的示例中,将阻止执行类型“bash”、“powershell”、“pwsh”和“script”的步骤。 若要完全锁定临时脚本,还需要阻止“BatchScript”和“ShellScript”。

# 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 lines below will 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

设置所需的模板

若要要求使用特定模板,可以为资源或环境设置所需的模板检查。 从模板扩展时,可以使用所需的模板检查。

查看管道作业时,可以检查检查的状态。 当管道未从所需模板扩展时,检查将失败,运行将停止。 你将看到检查失败。

审批检查失败

使用所需模板时,你将看到检查通过。

审批检查通过

此处需要模板 params.yml ,但需要对资源进行审批。 若要触发管道失败,请注释掉对 的 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 }}
# azure-pipeline.yml

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

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

其他步骤

模板可以添加步骤,而无需管道作者包含这些步骤。 这些步骤可用于运行凭据扫描或静态代码检查。

# template to insert a step before and after user steps in every job
parameters:
  jobs: []

jobs:
- ${{ each job in parameters.jobs }}: # Each job
  - ${{ each pair in job }}:  # Insert all properties other than "steps"
      ${{ if ne(pair.key, 'steps') }}:
        ${{ pair.key }}: ${{ pair.value }}
    steps:                            # Wrap the steps
    - task: CredScan@1                # Pre steps
    - ${{ job.steps }}                # Users steps
    - task: PublishMyTelemetry@1      # Post steps
      condition: always()

模板强制实施

如果可以强制实施模板,则模板只是一种安全机制。 强制使用模板的控制点是 受保护的资源。 可以在代理池或其他受保护的资源(如存储库)上配置审批和检查。 有关示例,请参阅添加存储库资源检查

后续步骤

接下来,了解如何通过 变量和参数安全地获取输入。