Skip to main content
All posts
DevSecOps7 min read

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.

Published

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.

Loading diagram...

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:

YAML
# 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:

YAML
          - 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:

YAML
          - 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:

YAML
          - 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:

YAML
  - 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:

YAML
  - 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:

JSON
{
  "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:

YAML
          - 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
YAML
# 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:

YAML
          - 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:

YAML
# .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

Loading diagram...

A complete secure pipeline stage sequence:

  1. Source — Code from version control with branch protection and required reviews
  2. Dependencies — Resolved from private feed with upstream proxy, scanned for vulnerabilities
  3. Build — Executed on hosted agents, SBOM generated alongside artifacts
  4. Test — Unit, integration, and security tests (SAST, DAST)
  5. Sign — Artifacts signed with Key Vault-backed certificate
  6. Attest — SLSA provenance generated and attached
  7. Store — Artifacts pushed to registry with SBOM and provenance attached
  8. Verify — Signature and provenance verified before deployment
  9. 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

supply chain securitySBOM generationartifact signingSLSA frameworkAzure DevOps security

Frequently Asked Questions

A Software Bill of Materials (SBOM) is a machine-readable inventory of all components, libraries, and dependencies in a software artifact. It matters because it enables vulnerability tracking, license compliance, and incident response when a new vulnerability is disclosed in a widely-used library — you can immediately determine which of your applications are affected.

Expert engagement

Need expert guidance?

Our team specializes in cloud architecture, security, AI platforms, and DevSecOps. Let's discuss how we can help your organization.

Get in touchNo commitment · No sales pressure

Related articles

All posts