Protect production branches from having secrets through an Azure DevOps branch policy

James Dumont le Douarec
Microsoft Azure
Published in
7 min readApr 18, 2023

Introduction

The objective of this article is to share a strategy that has been implemented for a client that asked how we can protect its production branches from having secrets in code.

After few discussions we have highlighted the following requirements.

  1. The solution should be simple to implement: no more than one script to maintain and we didn’t want to have some sort of regex file to teach the script what is a secret.
  2. A comprehensive monitoring dashboard should be available to reference all our repositories, pointing out the ones that have secrets and ideally what kind of secrets have been founded (ex: SQL connection strings, Certificates with private keys, etc..).
  3. The solution should alert the developer when he is trying to add a secret in the git Organisation.
  4. Developers should have a solution to suppress false positives in order not to block production deliveries.
  5. Any new repository should be automatically onboarded into this scanning and blocking strategy.
  6. The solution should work on Azure DevOps but also on GitHub.
  7. Ideally, the solution should be free.

Solution

At this point, you can certainly have serious doubts that a solution can meet all these requirements.

We did actually find one solution that answers all these requirements, the solution is a combination of the following Azure DevOps features.

  • An Azure DevOps build validation policy to automate code scanning when a pull request is done on any production branch of the Azure DevOps project.
  • The Azure DevOps extension: Microsoft Security DevOps to scan secret through the Credscan tool that is maintained by Microsoft.
  • OUTDATED → The Azure DevOps extension: SARIF SAST Scans Tab to display the analysis results under the “Scans” tab of each Azure DevOps pipelines.

“Effective September 20, 2023, the secret scanning (CredScan) tool within the Microsoft Security DevOps (MSDO) Extension for Azure DevOps has been deprecated. MSDO secret scanning will be replaced with GitHub Advanced Security for Azure DevOps.”

The alternative was to switch with the Gitleaks Azure DevOps extension.

A build validation policy queues a new build when a new PR is created or changes are pushed to an existing PR that targets the branch. The build policy evaluates the build results to determine whether the PR can be completed.

As illustrated in the following diagram our build validation policy will be unique across our repositories and its scan tasks will be located in one yaml file: “secret-scanning.yaml”.

Prerequisite

  • Add the following Azure DevOps extension: Gitleaks.
  • Add the following Azure DevOps extension: SARIF SAST Scans Tab.
  • Create a “commons” git repository.
  • Disable the option “Protect access to repositories in YAML pipelines” to allow the Azure DevOps Build Service to pull remote repositories. You can find this option by navigating to the project settings page under “Pipeline” > “Settings”.

Script

Create a Yaml script file “secret-scanning.yaml” in the git repository “commons” and commit the following code.

This code is the Yaml description of an Azure DevOps pipeline, it is composed of the following actions:

  • trigger: none” disables Continuous Integration triggers. This pipeline will actually be triggered by a build validation policy we will create in the next chapter.
  • Both variables SOURCE_REPOSITORY_URI & SOURCE_BRANCH are retrieving Azure DevOps system variables that belong to the repository that will initiate the pull request. We need them to retrieve the source code that will be scanned.
  • checkout, clean and persistCredentialswill avoid keeping the repository that contains our build validation script (the “commons” repository), our objective is not to scan this repository but the one that will initiate the pull request. We will also persist the Build Service credentials in order to clone the remote repository.
  • The task named “enableGitCommands” will enable the next tasks to run Git commands.
  • The task named “gitClone” will clone the repository that initiated the pull request (the remote repository).
  • The latest task will perform the secrets scanning with the Azure DevOps extension Gitleaks.
# This is a pipeline that can be used as a build validation pipeline to prevent secrets from being in the code.
# Reference: https://techcommunity.microsoft.com/t5/azure-developer-community-blog/protect-production-branches-from-having-secrets-through-an-azure/ba-p/3780659?WT.mc_id=DOP-MVP-5003548
# Prerequisite:
# - Disable the option "Protect access to repositories in YAML pipelines" to allow the Azure DevOps Build Service to pull remote repositories. You can find this option by navigating to the project settings page under "Pipeline" > "Settings".
# - Grant version control permissions to the build service, cf https://learn.microsoft.com/en-us/azure/devops/pipelines/scripts/git-commands?view=azure-devops&tabs=yaml&WT.mc_id=DOP-MVP-5003548#grant-version-control-permissions-to-the-build-service

trigger: none

pool:
vmImage: 'ubuntu-latest'

variables:
GIT_USER_EMAIL: "you@example.com"
GIT_USER_NAME: "Your Name"
SOURCE_REPOSITORY_URI: $[variables['System.PullRequest.SourceRepositoryURI']] #Azure DevOps System variable, cf https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml&WT.mc_id=DOP-MVP-5003548#system-variables-devops-services
SOURCE_BRANCH: $[variables['System.PullRequest.SourceBranch']] #Azure DevOps System variable

steps:
- checkout: self
clean: true
persistCredentials: true

- script: |
git config --global user.email "$(GIT_USER_EMAIL)"
git config --global user.name "$(GIT_USER_NAME)"
displayName: 'Enable scripts to run Git commands, cf https://learn.microsoft.com/en-us/azure/devops/pipelines/scripts/git-commands?view=azure-devops&tabs=yaml&WT.mc_id=DOP-MVP-5003548#enable-scripts-to-run-git-commands'
name: enableGitCommands

- script: |
#Variable
SOURCE_REPOSITORY_URI=$(SOURCE_REPOSITORY_URI)
REPOSITORY_NAME=$(echo ${SOURCE_REPOSITORY_URI//*\/})
BRANCH_NAME=$(echo ${SOURCE_BRANCH//refs\/heads\/})
SOURCE_REPOSITORY_URI_WITHOUT_USER=$(cut -d @ -f 2 <<< "$SOURCE_REPOSITORY_URI")
SOURCE_REPOSITORY_URI_SUFFIX=$(echo ${SOURCE_REPOSITORY_URI_WITHOUT_USER//https:\/\/})
SOURCE_REPOSITORY_URI="https://$SYSTEM_ACCESSTOKEN@$SOURCE_REPOSITORY_URI_SUFFIX"

#Action
echo "Cleaning the local folder"
ls -lart
rm -rf .git ; rm -rf .gitignore ; rm -rf .dockerignore ; rm -rf ./*
echo "git clone $SOURCE_REPOSITORY_URI ."
git clone $SOURCE_REPOSITORY_URI .
git checkout $BRANCH_NAME
displayName: 'Clone the source repository'
name: gitClone
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)

- task: Gitleaks@2
inputs:
scanlocation: '$(Build.SourcesDirectory)'
configtype: 'default'
scanmode: 'nogit'
reportformat: 'sarif'

# Breaking news!"Effective September 20, 2023, the secret scanning (CredScan) tool within the Microsoft Security DevOps (MSDO) Extension for Azure DevOps has been deprecated. MSDO secret scanning will be replaced with GitHub Advanced Security for Azure DevOps."
# - task: MicrosoftSecurityDevOps@1
# displayName: 'Secrets scanning with Microsoft Security DevOps, cf https://learn.microsoft.com/fr-fr/azure/defender-for-cloud/azure-devops-extension?WT.mc_id=DOP-MVP-5003548'
# inputs:
# categories: 'secrets'
# break: true

Pipeline

We need to publish the pipeline in order to use it in our build validation policy.

  1. From Azure DevOps, navigate to “Pipelines” and click on the icon “New pipeline”.
  2. Select “Azure Repos Git”.
  3. Select the repository “commons”.
  4. Select “Existing Azure Pipelines YAML file”.
  5. Select the existing YAML file “secret-scanning.yaml”, click on the icon “Continue” and then select “Save”.
  6. Click on the “More actions”, select “Rename/move” and under “Name” fill in “Secret scanning with Gitleaks”, then click on the “Save” icon.

Build validation policy

We will now create a build validation policy to make sure a secret scan is performed whenever a pull request is performed on the production branch “main”.

  1. Go to the project settings page and navigate under“Settings” > “Repositories” > “Policies”.
  2. Select the + button next to “Build validation”.
  3. Select “Protect the default branch of each repository” and click on “Create”.

4. Fill out the “Set build policy” form to use the pipeline we created in the previous step: “Secret scanning with Microsoft Security DevOps” and click on the “Save” icon.

Demonstration

The following screenshot illustrates a pull request that has been blocked by the build validation policy because secrets have been founded in the pull request source repository branch.

The following screenshot illustrates the “Scans” tab of the Azure DevOps pipeline, it prints the secret that have been founded revealing its path and its type, here it is an API Key.

The following screenshot is an Azure DevOps Workbook, it gives a summary of all secrets that have been founded on your organisation. This workbook is available if you go through this setup: “Quickstart: Connect your Azure DevOps repositories to Microsoft Defender for Cloud”.

Note: Microsoft Defender for DevOps is still in preview, changes should be expected while Microsoft improve the product.

Azure DevOps scan findings in Defender when the pipeline is located in another repository is currently not supported, a feature request has been done here for information, don’t hesitate to add your vote!

Because Credscan is no longer supported there is a dedicated article with a custom Azure Monitor Workbook that give you insights of your secrets finding:”Monitor git secrets on Azure DevOps with Gitleaks”.

Conclusion

Branch policies help teams protect their important branches of development. Policies enforce your team’s code quality and change management standards. This article was focused on analyzing secrets, but note that the Microsoft Security DevOps extension can be used to perform more general code analysis.

See You in the Cloud

Jamesdld

--

--