使用模板实现安全性
Azure DevOps Services | Azure DevOps Server 2022 | Azure DevOps Server 2020
本文介绍了模板如何简化 Azure Pipelines 的安全性。 模板可以定义管道的外部结构,并帮助防止恶意代码渗透。 模板还可以自动包括执行凭据扫描等任务的步骤。 如果团队或组织内的多个管道共享相同的结构,请考虑使用模板。
检查受保护的资源 构成了 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 安全功能。
模板还可以重写用户步骤,仅允许已批准的任务运行。 例如,可以阻止内联脚本执行。
以下示例模板阻止步骤类型、powershell
pwsh
步骤script
类型和bash
运行。 若要完全锁定即席脚本,还可以阻止 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 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'