Events
Mar 17, 9 PM - Mar 21, 10 AM
Join the meetup series to build scalable AI solutions based on real-world use cases with fellow developers and experts.
Register nowThis browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
Azure DevOps Services | Azure DevOps Server 2022 | Azure DevOps Server 2020
This article describes how templates can streamline security for Azure Pipelines. Templates can define the outer structure of your pipeline and help prevent malicious code infiltration. Templates can also automatically include steps to do tasks such as credential scanning. If multiple pipelines within your team or organization share the same structure, consider using templates.
Checks on protected resources form the fundamental security framework for Azure Pipelines. These checks apply regardless of pipeline structure, stages, and jobs. You can use templates to help enforce these checks.
Azure Pipelines provides includes and extends templates.
Includes templates include the template's code directly in the outer file that references it, similar to #include
in C++. The following example pipeline inserts the include-npm-steps.yml template into the steps
section.
steps:
- template: templates/include-npm-steps.yml
Extends templates define the outer structure of the pipeline and offer specific points for targeted customizations. In the context of C++, extends
templates resemble inheritance.
When you use extends
templates, you can also use includes
in both the template and the final pipeline to do common configuration pieces. For a complete reference, see the Template usage reference.
For the most secure pipelines, start by using extends templates. These templates define the outer structure of the pipeline and prevent malicious code from infiltrating the pipeline.
For example, the following template file is named template.yml.
parameters:
- name: usersteps
type: stepList
default: []
steps:
- ${{ each step in parameters.usersteps }}:
- ${{ step }}
The following 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 if there are breaking changes, existing pipelines aren't affected. The preceding example uses this feature.
The YAML pipeline syntax includes several built-in protections. Extends template can enforce their use. To enhance pipeline security, you can implement any of the following restrictions.
You can restrict certain steps to run in a container rather than on the host. Steps in containers don't have access to the agent's host, preventing these steps from modifying agent configuration or leaving malicious code for later execution.
For example, consider limiting network access. Without open network access, user steps can't retrieve packages from unauthorized sources or upload code and secrets to external network locations.
The following example pipeline runs steps on the agent host before running steps inside a container.
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
You can restrict the services the Azure Pipelines agent provides to user steps. User steps request services by using logging commands, which are specially formatted strings printed to standard output. In restricted mode, most of the agent's services, such as uploading artifacts and attaching test results, are unavailable.
The following example task fails because its target
property instructs the agent not to allow publishing artifacts.
- task: PublishBuildArtifacts@1
inputs:
artifactName: myartifacts
target:
commands: restricted
In restricted
mode, the setvariable
command remains permissible, so caution is necessary because pipeline variables are exported as environment variables to subsequent tasks. If tasks output user-provided data, such as open issues retrieved via a REST API, they might be vulnerable to injection attacks. Malicious user content could set environment variables that might be exploited to compromise the agent host.
To mitigate this risk, pipeline authors can explicitly declare which variables are settable by using the setvariable
logging command. When you specify an empty list, all variable setting is disallowed.
The following example task fails 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"
You can restrict stages and jobs to run only under specific conditions. In the following example, the condition 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
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, you can prevent inline script execution.
The following example template prevents the step types bash
, powershell
, pwsh
, and script
from running. For complete lockdown of ad-hoc 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 pipeline that extends this 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 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
Before a pipeline runs, templates and their parameters are transformed 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 providing an enumeration of specific choices instead of allowing freeform strings.
# 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
When the pipeline extends the template, it has to specify one of the available pool choices.
# azure-pipelines.yml
extends:
template: template.yml
parameters:
userpool: private-pool-1
A template can automatically include steps in a pipeline. These steps can do tasks such as 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()
Templates are a valuable security mechanism, but their effectiveness 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.
To enforce the use of a specific template, configure the required template check 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. The run stops and notifies you of the failed check.
When you use the required template, the check passes.
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 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'
Events
Mar 17, 9 PM - Mar 21, 10 AM
Join the meetup series to build scalable AI solutions based on real-world use cases with fellow developers and experts.
Register nowTraining
Module
Extend a pipeline to use multiple templates - Training
Fundamental concepts and best practices for creating nested templates.
Certification
Microsoft Certified: Azure Security Engineer Associate - Certifications
Demonstrate the skills needed to implement security controls, maintain an organization’s security posture, and identify and remediate security vulnerabilities.
Documentation
Azure Pipelines task reference
Reference for the built-in tasks for Azure Pipelines & TFS.
Securely use variables and parameters in a pipeline - Azure Pipelines
Find out how to safely accept input from pipeline users.
Secure pipeline resources - Azure Pipelines
Learn about permissions, checks, and approvals for Azure Pipeline resources.