Skip to content

ADR-007: Centralized DevSecOps Pipeline

Status

Accepted

Date

2023-Q3


Context

As the Microtec ERP platform grew to 14+ microservices and 10 frontend applications, each team maintained its own CI/CD pipeline YAML. This created:

  • Security inconsistency: Some pipelines ran SAST scans; others did not. Secret scanning was optional.
  • Duplication: 80% of every pipeline YAML was boilerplate (checkout, NuGet restore, Docker build, ACA deploy). Changes had to be replicated across 20+ pipeline files manually.
  • Compliance drift: Security tooling (Trivy, SonarCloud, Gitleaks) was at different versions across pipelines.
  • Audit gaps: There was no single place to verify that every deployment went through a mandatory security gate.

The team evaluated two approaches:

OptionDescriptionConcerns
Template per teamEach team owns their pipeline; central templates for common stepsTeams may skip steps; no enforcement mechanism
Centralized 16-stage pipelineOne master pipeline template; all repos must use itMore upfront work; single point of change

Decision

Build and mandate a centralized 16-stage DevSecOps pipeline template in Azure DevOps.

Every service (backend and frontend) that is deployed to any Microtec environment must use this template. Teams cannot opt out of any stage.

The 16 Stages

StageToolBlocks on failure?
1. Secret scanGitleaksYes — hard block
2. Dependency auditOWASP Dependency-CheckYes (CVSS ≥ 7.0)
3. SASTSonarCloud (Quality Gate)Yes
4. License checkFOSSA / manual allowlistYes (GPL detected)
5. Unit testsdotnet test / ng testYes
6. Coverage gate80% minimumYes
7. Build artifactdotnet publish / ng buildYes
8. Docker builddocker buildYes
9. Container scanTrivyYes (CRITICAL CVE)
10. Push to ACRdocker pushYes
11. Deploy devaz containerapp updateYes
12. Integration testsPostman / NewmanWarning only
13. DAST scanOWASP ZAP (baseline)Warning only
14. Deploy stageaz containerapp updateYes
15. Smoke testscurl / health checksYes
16. Approval gateAzure DevOps manual approvalRequired for prod

Toolchain Selection Rationale

ToolSelected ForAlternatives Considered
GitleaksOSS, fast, regex-based, 800+ built-in rulesTruffleHog (slower), git-secrets (fewer rules)
SonarCloudDeep .NET + Angular integration, PR decorationSonarQube self-hosted (infra overhead), Semgrep (weaker .NET)
TrivyMulti-target (image + IaC + filesystem), fast, freeAnchore Grype (similar), Snyk (cost at scale)
OWASP ZAPIndustry standard DAST, Azure DevOps task availableBurp Suite (cost), Nikto (limited)
OWASP Dependency-CheckJava/NuGet/npm support, integrates with ADODependabot (GitHub-only at time of decision)

Consequences

Positive

  • Audit trail: Every deployment to every environment passes through all 16 stages. The pipeline run log is the audit record.
  • Consistency: All 24+ services use identical security tooling at the same version. Tooling updates propagate to all services by updating one template file.
  • Secret leak prevention: Gitleaks blocks the pipeline at Stage 1 before any code is built or deployed — secrets cannot reach any environment.
  • Compliance evidence: The Stage 3 SonarCloud gate and Stage 9 Trivy scan produce exportable reports for audit purposes.
  • Reduced pipeline YAML per service: Each service's pipeline YAML is ~30 lines (trigger + template reference); the template handles everything else.

Negative

  • Longer pipeline runtime: The full 16 stages take 25–40 minutes end-to-end. Fast feedback requires running only the first 6 stages for PRs.
  • Template ownership: The central template is a single point of failure. A breaking change in the template affects all 24+ services simultaneously. Mitigated by the template being versioned and tested before rollout.
  • False positives from DAST: ZAP baseline scan generates noise on endpoints that intentionally return 4xx. Custom suppressions must be maintained per service.
  • Coverage gate friction: New services bootstrapped during early development cannot realistically reach 80% coverage immediately. A coverageOverride parameter exists for bootstrapping but requires team lead approval.

Neutral

  • Stage 12 (integration tests) and Stage 13 (DAST) are Warning rather than hard failures because their false positive rates make them unsuitable for hard gates at this time. This is reviewed quarterly.

Implementation Notes

Template Location

Devops/azure/templates/devsecops/
├── pipeline-template.yml      ← Master 16-stage template
├── stages/
│   ├── 01-secret-scan.yml
│   ├── 02-dependency-audit.yml
│   ├── 03-sonarcloud.yml
│   ├── 04-license-check.yml
│   ├── 05-unit-tests.yml
│   ├── 06-coverage-gate.yml
│   ├── 07-build-artifact.yml
│   ├── 08-docker-build.yml
│   ├── 09-trivy-scan.yml
│   ├── 10-push-acr.yml
│   ├── 11-deploy-dev.yml
│   ├── 12-integration-tests.yml
│   ├── 13-dast-zap.yml
│   ├── 14-deploy-stage.yml
│   ├── 15-smoke-tests.yml
│   └── 16-approval-gate.yml
└── configs/
    ├── gitleaks.toml
    ├── trivy.yaml
    └── zap-baseline.conf

Service Pipeline Entrypoint

Each service references the template with minimal configuration:

yaml
# Devops/azure/pipelines/my-service/deploy-my-service.yml
trigger:
  branches: { include: [main, stage, PreProd, production] }
  paths: { include: [Platforms/Src/AppsPortal/MyService/**] }

extends:
  template: ../../templates/devsecops/pipeline-template.yml
  parameters:
    serviceName: my-service
    dockerfilePath: Platforms/Src/AppsPortal/MyService/MyService.Apis/Dockerfile
    testProject: Platforms/Src/AppsPortal/MyService/MyService.Tests/MyService.Tests.csproj
    sonarProjectKey: microtec_my-service

Onboarding a New Repository

See the Runbook: Onboard Repo to DevSecOps for the step-by-step process.


  • ADR-001: Microservices Architecture (the pipeline operates per-service)
  • ADR-002: Azure Container Apps (Stage 11 and Stage 14 deploy targets)

Internal Documentation — Microtec Platform Team