Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Azure DevOps Services | Azure DevOps Server | Azure DevOps Server 2022 | Azure DevOps Server 2020
Azure Pipelines templates let you define reusable content, logic, and parameters in YAML pipelines. This article describes how templates can help enhance pipeline security by:
- Defining the outer structure of a pipeline to help prevent malicious code infiltration.
- Automatically including steps to do tasks such as credential scanning.
- Helping enforce checks on protected resources, which form the fundamental security framework for Azure Pipelines and apply to all pipeline structures and components.
This article is part of a series that helps you implement security measures for Azure Pipelines. For more information, see Secure Azure Pipelines.
Prerequisites
| Category | Requirements |
|---|---|
| Azure DevOps | - Implement recommendations in Make your Azure DevOps secure and Secure Azure Pipelines. - Basic knowledge of YAML and Azure Pipelines. For more information, see Create your first pipeline. |
| Permissions | - To modify pipelines permissions: Member of the Project Administrators group. - To modify organization permissions: Member of the Project Collection Administrators group. |
Includes and extends templates
Azure Pipelines provides includes and extends templates.
An
includestemplate includes the template's code directly in the outer file that references the template, similar to#includein C++. The following example pipeline inserts the include-npm-steps.yml template into thestepssection.steps: - template: templates/include-npm-steps.ymlAn
extendstemplate defines the outer structure of the pipeline and offers specific points for targeted customizations. In the context of C++,extendstemplates resemble inheritance.
When you use extends templates, you can also use includes to do common configuration pieces in both the template and the final pipeline. For more information, see Use YAML templates in pipelines for reusable and secure processes.
Extends templates
For the most secure pipelines, start by using extends templates. These templates define the outer structure of the pipeline and help prevent malicious code infiltration.
The following example shows a template file named template.yml.
parameters:
- name: usersteps
type: stepList
default: []
steps:
- ${{ each step in parameters.usersteps }}:
- ${{ step }}
The following example pipeline extends the template.yml template.
# 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
Tip
When you set up extends templates, consider anchoring them to a particular Git branch or tag so any breaking changes don't affect existing pipelines. The preceding example uses this feature.
Pipeline security features
YAML pipeline syntax includes several built-in protections. Extends templates can enforce their use to enhance pipeline security. You can implement any of the following restrictions.
Step targets
You can restrict specified steps to run in a container rather than on the host. Steps in containers can't access the agent host, so they can't modify agent configuration or leave malicious code for later execution.
For example, you can run user steps in a container to prevent them from accessing the network, so they can't retrieve packages from unauthorized sources or upload code and secrets to external locations.
The following example pipeline runs a step on the agent host that could potentially alter the host network, followed by a step inside a container that limits network access.
resources:
containers:
- container: builder
image: mysecurebuildcontainer:latest
steps:
- script: echo This step runs on the agent host
- script: echo This step runs inside the builder container
target: builder
Type-safe parameters
Before a pipeline runs, templates and their parameters transform into constants. Template parameters can enhance type safety for input parameters.
In the following example template, the parameters restrict the available pipeline pool options by enumerating specific choices instead of allowing any string.
# template.yml
parameters:
- name: userpool
type: string
default: Azure Pipelines
values:
- Azure Pipelines
- private-pool-1
- private-pool-2
pool: ${{ parameters.userpool }}
steps:
- script: echo Hello world
To extend the template, the pipeline must specify one of the available pool choices.
# azure-pipelines.yml
extends:
template: template.yml
parameters:
userpool: private-pool-1
Agent logging command restrictions
User steps request services by using logging commands, which are specially formatted strings printed to standard output. You can restrict the services that logging commands provide for user steps. In restricted mode, most agent services such as uploading artifacts and attaching test results are unavailable for logging commands.
In the following example, the target property instructs the agent to restrict publishing artifacts, so the artifact publishing task fails.
- task: PublishBuildArtifacts@1
inputs:
artifactName: myartifacts
target:
commands: restricted
Variables in logging commands
The setvariable command remains permissible in restricted mode, so tasks that output user-provided data, such as open issues retrieved via a REST API, might be vulnerable to injection attacks. Malicious user content could set variables that export to subsequent tasks as environment variables and could compromise the agent host.
To mitigate this risk, you can explicitly declare the variables that are settable by using the setvariable logging command. If you specify an empty list in settableVariables, all variable setting is disallowed.
The following example restricts the settableVariables to expectedVar and any variable prefixed with ok. The task fails because it attempts to set a different variable called BadVar.
- task: PowerShell@2
target:
commands: restricted
settableVariables:
- expectedVar
- ok*
inputs:
targetType: 'inline'
script: |
Write-Host "##vso[task.setvariable variable=BadVar]myValue"
Conditional stage or job execution
You can restrict stages and jobs to run only under specific conditions. The following example ensures that restricted code builds only for the main branch.
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
Syntax modification
Azure Pipelines templates have the flexibility to iterate over and modify YAML syntax. By using iteration, you can enforce specific YAML security features.
A template can also rewrite user steps, allowing only approved tasks to run. For example, the template can prevent inline script execution.
The following example template prevents the script step types bash, powershell, pwsh, and script from running. For complete lockdown of scripts, you could also block BatchScript and 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 }}'
In the following example pipeline that extends the preceding template, the script steps are stripped out and not run.
# azure-pipelines.yml
extends:
template: template.yml
parameters:
usersteps:
- task: MyTask@1
- script: echo This step is stripped out and not run
- bash: echo This step is stripped out and not run
- powershell: echo "This step is stripped out and not run"
- pwsh: echo "This step is stripped out and not run"
- script: echo This step is stripped out and not run
- task: CmdLine@2
displayName: Test - stripped out
inputs:
script: echo This step is stripped out and not run
- task: MyOtherTask@2
Template steps
A template can automatically include steps in a pipeline, for example to do credential scanning or static code checks. The following template inserts steps before and after the user steps in every job.
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()
Template enforcement
The effectiveness of templates as a security mechanism relies on enforcement. The key control points for enforcing template usage are protected resources.
You can configure approvals and checks for your agent pool or other protected resources such as repositories. For an example, see Add a repository resource check.
Required templates
To enforce the use of a specific template, configure the required template check on the service connection for a resource. This check applies only when the pipeline extends from a template.
When you view the pipeline job, you can monitor the check's status. If the pipeline doesn't extend from the required template, the check fails.
When you use the required template, the check passes.
For example, the following params.yml template must be referenced in any pipeline that extends it.
# 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 }}
The following example pipeline extends the params.yml template and requires it for approval. To demonstrate a pipeline failure, comment out the extends reference to 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'