Supply Chain Security for Azure DevOps: SBOMs, Signing, and Attestation
A practical guide to securing the software supply chain in Azure DevOps with SBOM generation, artifact signing, SLSA framework compliance, and dependency scanning.
The SolarWinds attack demonstrated that compromising a single build pipeline can cascade to thousands of organisations. Supply chain security is no longer optional — it is a fundamental requirement for any software organisation. Regulators (DORA, NIS2, the US Executive Order 14028) now mandate specific supply chain controls including SBOMs, provenance attestation, and dependency management.
This guide covers practical implementation of supply chain security controls in Azure DevOps, with pipeline YAML examples you can adapt to your environment.
The Supply Chain Threat Model
Before implementing controls, understand what you are defending against:
Dependency confusion: An attacker publishes a malicious package with the same name as your internal package to a public registry. If your build system checks public registries first, it pulls the malicious version.
Compromised upstream dependencies: A legitimate open-source library is compromised (through maintainer account takeover, malicious pull request, or typosquatting). Your build system pulls the compromised version.
Build system compromise: An attacker gains access to your build pipeline and injects malicious code during the build process, after source code review but before artifact creation.
Artifact tampering: A built artifact is modified after creation but before deployment. Without signing and verification, the modification goes undetected.
Each threat requires a different control. A comprehensive supply chain security programme addresses all four.
SBOM Generation
CycloneDX in Azure DevOps Pipelines
CycloneDX provides tooling for most ecosystems. Here is a pipeline configuration that generates SBOMs for a .NET application:
# azure-pipelines.yml — SBOM generation stage
stages:
- stage: Build
jobs:
- job: BuildAndSBOM
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UseDotNet@2
inputs:
packageType: 'sdk'
version: '8.x'
- script: |
dotnet restore
dotnet build --configuration Release --no-restore
displayName: 'Build application'
- script: |
dotnet tool install --global CycloneDX
dotnet CycloneDX $(Build.SourcesDirectory)/src/MyApp.csproj \
--output $(Build.ArtifactStagingDirectory) \
--filename sbom.cyclonedx.json \
--json \
--include-project-references \
--set-version $(Build.BuildNumber)
displayName: 'Generate CycloneDX SBOM'
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)/sbom.cyclonedx.json'
ArtifactName: 'sbom'For container images, use Syft which produces comprehensive SBOMs including OS packages:
- script: |
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
syft $(containerRegistry)/$(imageRepository):$(tag) \
--output cyclonedx-json=$(Build.ArtifactStagingDirectory)/container-sbom.cyclonedx.json
displayName: 'Generate container SBOM'SPDX Generation
If your compliance requirements specify SPDX (common in automotive and government), Microsoft's SBOM tool generates SPDX natively:
- script: |
curl -Lo sbom-tool https://github.com/microsoft/sbom-tool/releases/latest/download/sbom-tool-linux-x64
chmod +x sbom-tool
./sbom-tool generate \
-b $(Build.ArtifactStagingDirectory) \
-bc $(Build.SourcesDirectory)/src \
-pn MyApp \
-pv $(Build.BuildNumber) \
-ps "MyOrganisation" \
-nsb https://myorg.com/sbom
displayName: 'Generate SPDX SBOM'SBOM Storage and Querying
Store SBOMs alongside the artifacts they describe. For container images, attach the SBOM as an OCI artifact reference using ORAS:
- script: |
oras attach $(containerRegistry)/$(imageRepository):$(tag) \
--artifact-type application/vnd.cyclonedx+json \
$(Build.ArtifactStagingDirectory)/container-sbom.cyclonedx.json
displayName: 'Attach SBOM to container image'For queryable vulnerability tracking across all SBOMs, ingest them into Dependency-Track or GUAC (Graph for Understanding Artifact Composition). This gives you the ability to answer: "Which of our deployed applications use log4j version X?"
Artifact Signing
Container Image Signing with Notation
Notation (from the Notary Project) is the recommended signing tool for OCI artifacts in Azure environments:
- stage: Sign
dependsOn: Build
jobs:
- job: SignArtifacts
pool:
vmImage: 'ubuntu-latest'
steps:
- script: |
# Install Notation
curl -Lo notation.tar.gz https://github.com/notaryproject/notation/releases/download/v1.1.0/notation_1.1.0_linux_amd64.tar.gz
tar xzf notation.tar.gz -C /usr/local/bin notation
# Install Azure Key Vault plugin
notation plugin install --url https://github.com/Azure/notation-azure-kv/releases/download/v1.2.0/notation-azure-kv_1.2.0_linux_amd64.tar.gz
# Sign the container image using Key Vault certificate
notation sign $(containerRegistry)/$(imageRepository):$(tag) \
--plugin azure-kv \
--id $(signingKeyId) \
--plugin-config self_signed=false
displayName: 'Sign container image with Notation'
env:
AZURE_CLIENT_ID: $(servicePrincipalId)
AZURE_TENANT_ID: $(tenantId)
AZURE_CLIENT_SECRET: $(servicePrincipalKey)The signing certificate should be stored in Azure Key Vault with:
- HSM-backed key (Key Vault Premium or Managed HSM)
- Access restricted to the build service principal only
- Certificate rotation automated with a 12-month validity
Signature Verification in Deployment
Verify signatures before deployment using Azure Policy for Kubernetes or a Notation verification step in your deployment pipeline:
- stage: Deploy
dependsOn: Sign
jobs:
- job: VerifyAndDeploy
steps:
- script: |
# Verify signature before deployment
notation verify $(containerRegistry)/$(imageRepository):$(tag) \
--trust-policy ./trust-policy.json \
--trust-store ./trust-store
if [ $? -ne 0 ]; then
echo "##vso[task.logissue type=error]Signature verification failed. Aborting deployment."
exit 1
fi
displayName: 'Verify container signature'The trust policy JSON defines which registries and signing identities you trust:
{
"version": "1.0",
"trustPolicies": [
{
"name": "production-images",
"registryScopes": ["myregistry.azurecr.io/production/*"],
"signatureVerification": {
"level": "strict"
},
"trustStores": ["ca:my-org-signing-ca"],
"trustedIdentities": [
"x509.subject: CN=my-org-signing,O=My Organisation"
]
}
]
}SLSA Framework Compliance
SLSA Level 1: Provenance
Generate build provenance that documents how an artifact was built:
- script: |
# Generate SLSA provenance
cat > $(Build.ArtifactStagingDirectory)/provenance.json << PROVENANCE
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "$(containerRegistry)/$(imageRepository)",
"digest": { "sha256": "$(imageDigest)" }
}
],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"buildDefinition": {
"buildType": "https://dev.azure.com/pipeline/v1",
"externalParameters": {
"repository": "$(Build.Repository.Uri)",
"ref": "$(Build.SourceBranch)"
}
},
"runDetails": {
"builder": {
"id": "https://dev.azure.com/$(System.TeamFoundationCollectionUri)"
},
"metadata": {
"invocationId": "$(Build.BuildUri)",
"startedOn": "$(System.PipelineStartTime)"
}
}
}
}
PROVENANCE
displayName: 'Generate SLSA provenance'SLSA Level 2: Hosted Build Service
Azure DevOps hosted agents satisfy SLSA Level 2's requirement for a hosted build service. Ensure:
- All production builds run on Microsoft-hosted agents (not self-hosted)
- Pipeline definitions are stored in version control (YAML pipelines, not classic editor)
- Build logs are retained for your compliance retention period
SLSA Level 3: Hardened Build Platform
Achieving Level 3 requires:
- Non-falsifiable provenance: Use Azure DevOps service connections with workload identity federation (no shared secrets)
- Isolated builds: Each build runs in a fresh, ephemeral environment (hosted agents provide this)
- Parameterless builds: The pipeline YAML fully defines the build — no runtime parameters that could alter the output
- Pinned dependencies: All dependencies reference exact versions or digests, not floating tags
Dependency Scanning and Management
Azure Artifacts as a Private Registry
Configure Azure Artifacts feeds with upstream sources to proxy public registries. This gives you:
- A cache of approved packages (survives public registry outages)
- Visibility into which public packages are consumed
- The ability to block specific package versions
# nuget.config — use Azure Artifacts feed with upstream
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="MyOrgFeed" value="https://pkgs.dev.azure.com/myorg/_packaging/approved-packages/nuget/v3/index.json" />
</packageSources>
</configuration>Dependency Scanning in Pipelines
Integrate dependency scanning as a build gate:
- task: AdvancedSecurity-Dependency-Scanning@1
displayName: 'Scan dependencies for vulnerabilities'
# Or using Snyk
- script: |
npx snyk test --severity-threshold=high --json > snyk-results.json
if [ $? -ne 0 ]; then
echo "##vso[task.logissue type=error]High or critical vulnerabilities found"
exit 1
fi
displayName: 'Snyk dependency scan'Dependabot for Azure DevOps
Configure Dependabot to automatically create pull requests for dependency updates:
# .github/dependabot.yml (works with Azure DevOps via GitHub Advanced Security)
version: 2
updates:
- package-ecosystem: "nuget"
directory: "/src"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
reviewers:
- "security-team"
labels:
- "dependencies"
- "security"Putting It All Together: The Secure Pipeline
A complete secure pipeline stage sequence:
- Source — Code from version control with branch protection and required reviews
- Dependencies — Resolved from private feed with upstream proxy, scanned for vulnerabilities
- Build — Executed on hosted agents, SBOM generated alongside artifacts
- Test — Unit, integration, and security tests (SAST, DAST)
- Sign — Artifacts signed with Key Vault-backed certificate
- Attest — SLSA provenance generated and attached
- Store — Artifacts pushed to registry with SBOM and provenance attached
- Verify — Signature and provenance verified before deployment
- Deploy — Admission controller verifies signature before allowing container to run
Each step produces evidence that feeds into your compliance reporting and audit trail.
Conclusion
Supply chain security is not a single tool or process — it is a layered defence that spans from dependency management through build integrity to deployment verification. The controls described here align with SLSA, DORA, NIS2, and the NIST Secure Software Development Framework.
Start with SBOM generation and dependency scanning — these provide immediate visibility. Then add artifact signing and provenance to build towards SLSA Level 3.
If you need help designing and implementing a secure software supply chain for your Azure DevOps environment, contact us at mbrahim@conceptualise.de. We help engineering teams build supply chain security into their pipelines without destroying developer velocity.
Topics