Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Azure DevOps Pipelines

Validate pipelines Azure DevOps Security License

A multi-stage Azure DevOps pipeline — build, test, security scan, deploy to dev, gated deploy to production — built with reusable templates and no stored credentials anywhere.

What this badge means. Azure Pipelines YAML cannot execute on GitHub Actions; running it requires an Azure DevOps organisation, service connections and environments. The badge above is a validation badge: it proves the definitions are structurally sound and free of the security mistakes that matter. It does not prove a green deployment. docs/setup.md has the steps to wire it up, and states the same thing.

Every other repository in this portfolio proves its central claim by executing it. This one cannot, so it says so rather than implying otherwise.

What it actually produces

Real output from the latest CI run:

$ python3 scripts/validate_pipelines.py
checked 5 pipeline file(s)
all pipeline checks passed

The validator is itself tested against deliberately broken pipelines, so a check that stops detecting anything fails the build instead of quietly reporting success:

$ python3 scripts/validate_pipelines.py tests/fixtures
checked 2 pipeline file(s)
FAIL: hardcoded credential in stage 'Deploy' (clientSecret)
FAIL: production stage has no deployment gate

That second suite exists because the validator originally missed two of its own four test cases - \b(secret) does not match camelCase clientSecret, and the gate check only inspected top-level jobs rather than stages[].jobs. Both are fixed and both are now fixtures.

Why an Azure-first shop cares

Canadian banks, insurers and government run on Azure DevOps, frequently for a specific reason: an approval attached to an environment lives outside the repository. A developer cannot bypass a production gate by editing the pipeline in their own branch, because the gate is not in the file. That property is what auditors ask about.

The three security decisions

No credential exists to leak. The service connections use workload identity federation — Azure DevOps exchanges a short-lived token rather than holding a secret. There is no client secret to rotate, expire or steal.

Application secrets come from Key Vault through a variable group. The pipeline references names; values never appear in this repository or in the pipeline UI. Rotating a secret in the vault changes nothing here.

Security scans fail the build. None of them set continueOnError. A scanner in warn-only mode produces a number that goes up quietly for two years — if a finding is acceptable it gets suppressed explicitly with a reason in the tool's own config, not by ignoring the tool.

Structure

azure-pipelines.yml          # stages: Build -> Security -> DeployDev -> DeployProd
templates/
  build.yml                  # test + publish results and coverage
  security-scan.yml          # pip-audit, checkov, gitleaks over git history
  deploy.yml                 # deployment job with environment: gate
  steps/use-python.yml

Stages are templates rather than one long file. A 400-line pipeline nobody dares change is the normal failure mode.

Two details worth pointing at in an interview

The deploy verifies it is actually serving. After the Bicep deployment, the pipeline waits for the revision holding 100% of traffic to reach Running, then curls /health. This mirrors a real incident: a fix was deployed, the platform reported success, and the old revision served every request — so the "verified" measurement measured the old version. (the postmortem)

Gitleaks scans git history, not the working tree. A secret committed and then deleted in a later commit is still a leaked secret, and a working-tree scan reports clean.

What CI actually checks

scripts/validate_pipelines.py catches, in seconds, the classes of problem Azure DevOps only reports by failing a run:

Check Why
Hard-coded credentials A value assigned inline instead of coming from Key Vault or a service connection
Secrets echoed to logs Azure DevOps masks only the secrets it knows about
continueOnError on a security step A scan that cannot fail the build gates nothing
Deployment job without environment: No approval gate, no audit trail
Template parameters Declared and never passed, or passed and never declared

And it runs in both directions — CI points the validator at deliberately broken pipelines in tests/fixtures/ and fails if the validator accepts them.

That check earned its place immediately. The first version of the validator missed two of its own four test cases: the regex \b(secret) never matched clientSecret, because in camelCase the S is preceded by a word character and there is no word boundary; and the deployment-gate check only looked at top-level jobs, not the stages[].jobs shape a real pipeline uses. Both passed validation while a hard-coded credential and an ungated production deploy sat in the fixture. Fixed, and the negative fixture is why they were found.

Also here

GitHub Actions vs Azure DevOps — an honest comparison from having shipped the same workload through both, including where Actions is genuinely better and the three-expression-syntax trap that catches everyone.

Author

Sadhvi Sharma · Cloud & AI Engineer · Calgary, Alberta github.com/sadvi11

About

Multi-stage Azure DevOps pipeline with workload identity federation, Key Vault-backed variables and environment approval gates — plus a validator that catches hard-coded credentials and ungated deploys, tested in both directions.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages