Secure access to Azure Repos from pipelines

Your repositories are a critical resource to your business success, because they contain the code that powers your business. Access to repositories shouldn't be granted easily.

This article shows you how to improve the security of your pipelines accessing Azure Repos, to limit the risk of your source code getting into the wrong hands.

The setup for pipelines to securely access Azure repositories is one in which the toggles Limit job authorization scope to current project for non-release pipelines, Limit job authorization scope to current project for release pipelines, and Protect access to repositories in YAML pipelines, are enabled.

We'll cover both build pipelines and classic release pipelines:

Basic process

The steps are similar across all pipelines:

  1. Determine the list of Azure Repos repositories your pipeline needs access to that are part of the same organization, but are in different projects.

    You can compile the list of repositories by inspecting your pipeline. Or, you can turn on the Limit job authorization scope to current project for (non-)release pipelines toggle and note which repositories your pipeline fails to check out. Submodule repositories may not show up in the first failed run.

  2. For each Azure DevOps project that contains a repository your pipeline needs to access, follow the steps to grant the pipeline's build identity access to that project.

  3. For each Azure Repos repository your pipeline checks out, follow the steps to grant the pipeline's build identity Read access to that repository.

  4. For each repository that is used as a submodule by a repository your pipeline checks out and is in the same project, follow the steps to grant the pipeline's build identity Read access to that repository.

  5. Turn on the Limit job authorization scope to current project for non-release pipelines, Limit job authorization scope to current project for release pipelines, and Protect access to repositories in YAML pipelines toggles.

Build pipelines

To illustrate the steps to take to improve the security of your pipelines when they access Azure Repos, we'll use a running example.

Assume you're working on the SpaceGameWeb pipeline hosted in the fabrikam-tailspin/SpaceGameWeb project, in the SpaceGameWeb Azure Repos repository. Furthermore, let's say your SpaceGameWeb pipeline checks out the SpaceGameWebReact repository in the same project, and the FabrikamFiber and FabrikamChat repositories in the fabrikam-tailspin/FabrikamFiber project.

Finally, assume the FabrikamFiber repository uses the FabrikamFiberLib repository as a submodule, hosted in the same project. Read more about how to check out submodules.

The SpaceGameWeb project's repository structures look like in the following screenshot.

Screenshot of the SpaceGameWeb repository structure.

The FabrikamFiber project's repository structures look like in the following screenshot.

Screenshot of the FabrikamFiber repository structure.

Imagine your project isn't set up to use a project-based build identity or to protect access to repositories in YAML pipelines. Also, assume you've already successfully run your pipeline.

Use a Project-based build identity for build pipelines

When a pipeline executes, it uses an identity to access various resources, such as repositories, service connections, variable groups. There are two types of identities a pipeline can use: a project-level one and a collection-level one. The former provides better security, the latter provides ease of use. Read more about scoped build identities and job authorization scope.

We recommend you use project-level identities for running your pipelines. By default, project-level identities can only access resources in the project of which they're a member. Using this identity improves security, because it reduces the access gained by a malicious person when hijacking your pipeline.

To make your pipeline use a project-level identity, turn on the Limit job authorization scope to current project for non-release pipelines setting.

In our running example, when this toggle is off, the SpaceGameWeb pipeline can access all repositories in all projects. When the toggle is on, SpaceGameWeb can only access resources in the fabrikam-tailspin/SpaceGameWeb project, so only the SpaceGameWeb and SpaceGameWebReact repositories.

If you run our example pipeline, when you turn on the toggle, the pipeline will fail, and the error logs will tell you remote: TF401019: The Git repository with name or identifier FabrikamChat does not exist or you do not have permissions for the operation you are attempting. and remote: TF401019: The Git repository with name or identifier FabrikamFiber does not exist or you do not have permissions for the operation you are attempting.

To fix the checkout issues, follow the steps described in Basic process.

Additionally, you need to explicitly check out the submodule repositories, before the repositories that use them. In our example, it means the FabrikamFiberLib repository.

If you now run our example pipeline, it will succeed.

Further configuration

To further improve security when accessing Azure Repos, consider turning on the Protect access to repositories in YAML pipelines setting.

Assume the SpaceGameWeb pipeline is a YAML pipeline, and its YAML source code looks similar to the following code.

trigger:
- main

pool:
  vmImage: ubuntu-latest

resources:
  repositories:
    - repository: SpaceGameWebReact
      name: SpaceGameWeb/SpaceGameWebReact
      type: git
    - repository: FabrikamFiber
      name: FabrikamFiber/FabrikamFiber
      type: git
    - repository: FabrikamChat
      name: FabrikamFiber/FabrikamChat
      type: git

steps:
  - script: echo "Building SpaceGameWeb"
  - checkout: SpaceGameWebReact
  - checkout: FabrikamChat
    condition: always()  
  - checkout: FabrikamFiber
    submodules: true
    condition: always()
  - script: |
      cd FabrikamFiber
      git -c http.extraheader="AUTHORIZATION: bearer $(System.AccessToken)" submodule update --recursive --remote
  - script: cat $(Build.Repository.LocalPath)/FabrikamFiber/FabrikamFiberLib/README.md
  - ...

Protect access to repositories in YAML pipelines

Azure DevOps provides a fine-grained permissions mechanism for Azure Repos repositories, in the form of the Protect access to repositories in YAML pipelines setting. This setting makes a YAML pipeline explicitly ask for permission to access all Azure Repos repositories, regardless of which project they belong to. Read more about this setting. Checking out other types of repositories, for example, GitHub-hosted ones, isn't affected by this setting.

In our running example, when this toggle is on, the SpaceGameWeb pipeline will ask permission to access the SpaceGameWebReact repository in the fabrikam-tailspin/SpaceGameWeb project, and the FabrikamFiber and FabrikamChat repositories in the fabrikam-tailspin/FabrikamFiber project.

When you run the example pipeline, you'll see a build similar to the following screenshot. Screenshot of running the SpaceGameWeb pipeline the first time after turning on the Protect access to repositories in YAML pipelines toggle.

You'll be asked to grant permission to the repositories your pipeline checks out or has defined as resources. Screenshot of being asked to grant permission to the SpaceGameWeb pipeline to access three repositories.

Once you do, your pipeline will run, but it will fail because it will not be able to check out the FabrikamFiberLib repository as a submodule of FabrikamFiber. To solve this issue, explicitly check out the FabrikamFiberLib, for example, add a - checkout: git://FabrikamFiber/FabrikamFiberLib step, before the -checkout: FabrikamFiber step.

If you now run the example pipeline, it will succeed.

Our final YAML pipeline source code looks like the following code snippet.

trigger:
- main

pool:
  vmImage: ubuntu-latest

resources:
  repositories:
    - repository: SpaceGameWebReact
      name: SpaceGameWeb/SpaceGameWebReact
      type: git
    - repository: FabrikamFiber
      name: FabrikamFiber/FabrikamFiber
      type: git
    - repository: FabrikamChat
      name: FabrikamFiber/FabrikamChat
      type: git

steps:
  - script: echo "Building SpaceGameWeb"
  - checkout: SpaceGameWebReact
  - checkout: FabrikamChat
    condition: always()  
  - checkout: git://FabrikamFiber/FabrikamFiberLib  
  - checkout: FabrikamFiber
    submodules: true
    condition: always()
  - script: |
      cd FabrikamFiber
      git -c http.extraheader="AUTHORIZATION: bearer $(System.AccessToken)" submodule update --recursive --remote
  - script: cat $(Build.Repository.LocalPath)/FabrikamFiber/FabrikamFiberLib/README.md

Troubleshooting

Here are a couple of problematic situations and how to handle them.

You use git in command line to check out repositories in the same organization

For example, you're using - script: git clone https://$(System.AccessToken)@dev.azure.com/fabrikam-tailspin/FabrikamFiber/_git/OtherRepo/. The command will fail when the Protect access to repositories in YAML pipelines toggle is on.

To solve the issue, check out the OtherRepo repository using the checkout command, for example, - checkout: git://FabrikamFiber/OtherRepo.

A repository is using another repository as submodule

Say one of the repositories your pipeline checks out uses another repository (in the same project) as submodule, as is the case in our example for the FabrikamFiber and FabrikamFiberLib repositories. Read more about how to check out submodules.

Furthermore, assume you gave the SpaceGame build identity Read access to this repo, but the checkout of the FabrikamFiber repository still fails when checking out the FabrikamFiberLib submodule.

To solve this issue, explicitly check out the FabrikamFiberLib, for example, add a - checkout: git://FabrikamFiber/FabrikamFiberLib step before the -checkout: FabrikamFiber one.

Classic release pipelines

The process for securing access to repositories for release pipelines is similar to the one for build pipelines.

To illustrate the steps you need to take, we'll use a running example. In our example, there's a release pipeline named FabrikamFiberDocRelease in the fabrikam-tailspin/FabrikamFiberDocRelease project. Assume the pipeline checks out the FabrikamFiber repository in the fabrikam-tailspin/FabrikamFiber project, runs a command to generate public documentation, and then publishes it to a website. Additionally, imagine the FabrikamFiber repository uses the FabrikamFiberLib repository (in the same project) as a submodule

Use a Project-based build identity for classic release pipelines

When a pipeline executes, it uses an identity to access various resources, such as repositories, service connections, variable groups. There are two types of identities a pipeline can use: a project-level one and a collection-level one. The former provides better security, the latter provides ease of use. Read more about scoped build identities and job authorization scope.

We recommend you use project-level identities for running your pipelines. By default, project-level identities can only access resources in the project of which they're a member. Using this identity improves security, because it reduces the access gained by a malicious person when hijacking your pipeline.

To make your pipeline use a project-level identity, turn on the Limit job authorization scope to current project for release pipelines setting.

In our running example, when this toggle is off, the FabrikamFiberDocRelease release pipeline can access all repositories in all projects, including the FabrikamFiber repository. When the toggle is on, FabrikamFiberDocRelease can only access resources in the fabrikam-tailspin/FabrikamFiberDocRelease project, so the FabrikamFiber repository becomes inaccessible.

If you run our example pipeline, when you turn on the toggle, the pipeline will fail, and the logs will tell you remote: TF401019: The Git repository with name or identifier FabrikamFiber does not exist or you do not have permissions for the operation you are attempting.

To fix these issues, follow the steps in Basic process.

If you now run our example pipeline, it will succeed.

See also