Security in Azure DevOps
Security in DevOps isn't about choosing between speed and safety — it's about achieving both simultaneously. A well-secured Azure DevOps environment moves faster because developers trust the guardrails, automated checks catch issues before they reach production, and audit trails mean you can prove compliance without slowing down releases.
The Threat Model
Before configuring security, understand what you're defending against:
| Threat | Example | Primary Defense |
|---|---|---|
| Insider risk | Disgruntled developer deletes production branch | Branch policies, RBAC, audit logs |
| Compromised credentials | Leaked PAT token grants repo access | Managed identities, short-lived tokens, conditional access |
| Supply chain attacks | Malicious dependency injected via upstream package | SCA scanning, upstream source pinning, signed packages |
| Misconfiguration | Service connection with Owner-level permissions | Least privilege, regular access reviews, policy enforcement |
| Pipeline abuse | Fork submits PR that exfiltrates secrets via modified YAML | Protected resources, extends templates, fork restrictions |
For supply chain security concepts in depth, see Part 23: Supply Chain Security.
Permissions Model
Azure DevOps uses a hierarchical permissions model where settings at higher levels cascade down to lower levels unless explicitly overridden. Understanding this hierarchy is essential for maintaining least-privilege access.
Permission Inheritance Chain
flowchart TD
A[Organization Level] --> B[Project Level]
B --> C[Repository]
B --> D[Pipelines]
B --> E[Boards]
B --> F[Artifacts]
C --> G[Branch Policies]
D --> H[Environments]
D --> I[Service Connections]
D --> J[Agent Pools]
style A fill:#132440,color:#fff
style B fill:#16476A,color:#fff
style C fill:#3B9797,color:#fff
style D fill:#3B9797,color:#fff
style E fill:#3B9797,color:#fff
style F fill:#3B9797,color:#fff
Permission States
Each permission can be in one of four states — and the interaction between them determines what a user can actually do:
| State | Meaning | Precedence |
|---|---|---|
| Allow | Explicitly grants the permission | Overridden by explicit Deny |
| Deny | Explicitly blocks the permission | Always wins (highest precedence) |
| Not set | Inherits from parent group/level | Effectively Deny if nothing grants it |
| Allow (inherited) | Granted by a parent group membership | Can be overridden by explicit Deny |
Checking Effective Permissions
To see what a user can actually do (after all inheritance and group memberships resolve):
- Navigate to Project Settings → Permissions
- Search for the user or group
- Click the Member of tab to see all group memberships
- Click the Permissions tab to see effective permissions with inheritance source
Principle of Least Privilege in Practice
- Start with Reader — grant additional permissions only when needed
- Use groups, not individual assignments — easier to audit and revoke
- Scope service connections to specific resource groups — not entire subscriptions
- Time-bound elevated access — use PIM (Privileged Identity Management) for admin roles
- Separate production from development — different service connections, different approval gates
Groups & RBAC
Role-Based Access Control (RBAC) in Azure DevOps is implemented through security groups. Rather than assigning permissions to individual users, you assign users to groups — and groups carry the permissions. This makes access management scalable.
Built-in Groups
| Group | Typical Members | Key Permissions |
|---|---|---|
| Project Administrators | Tech leads, DevOps engineers | Full control over project settings, pipelines, repos |
| Contributors | Developers | Push code, create branches, run pipelines, manage work items |
| Readers | Stakeholders, auditors | View-only access to all artifacts |
| Build Administrators | CI/CD engineers | Manage build pipelines, agent pools, service connections |
| Release Administrators | Release managers | Manage release pipelines, approve deployments |
| Project Valid Users | Everyone with any access | Basic project visibility (auto-assigned) |
Custom Groups
Create custom security groups when built-in groups don't match your access patterns:
- Security Reviewers — Can approve PRs to
/infrastructure/paths but can't push code - Database Admins — Access to database migration pipelines only
- External Contractors — Contributor on specific repos, no access to production pipelines
- Compliance Officers — Read access to audit logs, pipeline history, and test results
Entra ID (Azure AD) Group Synchronization
For organizations using Microsoft Entra ID, you can synchronize directory groups directly into Azure DevOps:
# Add an Entra ID group to an Azure DevOps project group
# This syncs membership automatically — when people join/leave the Entra group,
# their Azure DevOps access updates accordingly
az devops security group membership add \
--group-id "vssgp.Uy0xLTktMTY4MTM3NjAzOC..." \
--member-id "aadgp.Uy0xLTktMTY4MTM3NjAzOC..." \
--org https://dev.azure.com/yourorg
# List members of a security group
az devops security group membership list \
--id "vssgp.Uy0xLTktMTY4MTM3NjAzOC..." \
--org https://dev.azure.com/yourorg \
--output table
# Create a custom security group
az devops security group create \
--name "Security Reviewers" \
--description "Can approve PRs touching infrastructure code" \
--project "MyProject" \
--org https://dev.azure.com/yourorg
Teams vs Security Groups
A common source of confusion — Azure DevOps has both teams and security groups:
- Teams — Organize work (boards, backlogs, iterations, dashboards). Every team is also a security group, but their primary purpose is work management.
- Security groups — Control access (permissions to repos, pipelines, resources). Use these for access control decisions.
Service Principals & Managed Identities
When pipelines need to access Azure resources (deploy to AKS, read Key Vault secrets, push container images), they authenticate using non-human identities. There are three approaches, from least to most secure:
| Identity Type | Secret Management | Best For |
|---|---|---|
| Service Principal + Secret | You manage rotation (expires 1-2 years) | Legacy setups, cross-tenant access |
| Managed Identity | Azure manages credentials (no secrets to rotate) | Self-hosted agents running on Azure VMs |
| Workload Identity Federation (OIDC) | No secrets at all — uses short-lived tokens | Microsoft-hosted agents, modern setups ✅ |
Workload Identity Federation — The Modern Approach
Workload Identity Federation eliminates secrets entirely. Instead of storing a client secret, Azure DevOps presents a short-lived OIDC token that Azure trusts via a federated credential configuration:
sequenceDiagram
participant Pipeline as Azure Pipeline
participant ADO as Azure DevOps OIDC Provider
participant AAD as Microsoft Entra ID
participant Azure as Azure Resources
Pipeline->>ADO: Request OIDC token
ADO-->>Pipeline: Short-lived JWT (valid ~10 min)
Pipeline->>AAD: Present JWT + federated credential
AAD->>AAD: Validate issuer, subject, audience
AAD-->>Pipeline: Access token for Azure
Pipeline->>Azure: Deploy with access token
# Create a service connection using Workload Identity Federation (OIDC)
# This is the recommended approach — no secrets to rotate!
# Step 1: Create an app registration in Entra ID
az ad app create --display-name "azdo-prod-deployer"
# Step 2: Create a federated credential (trusts Azure DevOps)
az ad app federated-credential create \
--id <app-object-id> \
--parameters '{
"name": "ado-main-branch",
"issuer": "https://vstoken.dev.azure.com/<tenant-id>",
"subject": "sc://yourorg/yourproject/prod-azure-connection",
"audiences": ["api://AzureADTokenExchange"]
}'
# Step 3: Assign RBAC role to the app (scoped to resource group)
az role assignment create \
--assignee <app-client-id> \
--role "Contributor" \
--scope "/subscriptions/<sub-id>/resourceGroups/production-rg"
# Step 4: In Azure DevOps, create service connection
# Project Settings → Service connections → New → Azure Resource Manager
# Choose "Workload Identity federation (automatic)" or "manual"
Azure Key Vault Integration
Azure Key Vault is a centralized store for secrets, encryption keys, and certificates. Integrating Key Vault with Azure DevOps means your pipelines can access secrets at runtime without storing them in Azure DevOps at all — they're fetched fresh, and Key Vault handles access logging, rotation, and expiry notifications.
What Key Vault Provides
- Secrets — API keys, connection strings, passwords (with automatic version history)
- Keys — Cryptographic keys for signing/encryption (backed by HSMs)
- Certificates — TLS/SSL certificates with auto-renewal from CAs
- Access policies — Fine-grained control over who can read/write/manage each type
- Audit logging — Every access is logged to Azure Monitor
Linking Key Vault to Variable Groups
The simplest integration — a variable group backed by Key Vault secrets:
- Navigate to Pipelines → Library → + Variable group
- Toggle "Link secrets from an Azure key vault as variables"
- Select your Azure service connection and Key Vault
- Choose which secrets to map (each becomes a pipeline variable)
- Reference in YAML:
$(my-secret-name)
AzureKeyVault@2 Task — Runtime Fetching
# Fetch secrets from Key Vault at pipeline runtime
# Secrets become pipeline variables for subsequent steps
stages:
- stage: Deploy
jobs:
- job: DeployApp
steps:
# Fetch secrets from Key Vault
- task: AzureKeyVault@2
displayName: 'Fetch secrets from Key Vault'
inputs:
azureSubscription: 'prod-azure-connection' # Service connection name
KeyVaultName: 'myapp-prod-kv'
SecretsFilter: 'DatabaseConnectionString,ApiKey,StorageAccountKey'
# Each secret becomes a variable: $(DatabaseConnectionString), etc.
RunAsPreJob: true # Available to all steps in this job
# Use the secrets (they're automatically masked in logs)
- script: |
echo "Deploying with database connection..."
# $(DatabaseConnectionString) is available but masked in output
dotnet publish --configuration Release
displayName: 'Build application'
- task: AzureCLI@2
displayName: 'Deploy to App Service'
inputs:
azureSubscription: 'prod-azure-connection'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az webapp config appsettings set \
--resource-group production-rg \
--name myapp-prod \
--settings \
"ConnectionStrings__Default=$(DatabaseConnectionString)" \
"ApiSettings__Key=$(ApiKey)"
Access Policies vs RBAC for Key Vault
| Model | Granularity | Recommendation |
|---|---|---|
| Vault access policy | Per-vault, per-principal (get, list, set, delete) | Legacy — simpler but less flexible |
| Azure RBAC | Per-secret granularity, Entra ID integration | Recommended — consistent with Azure-wide RBAC ✅ |
Secret Rotation Strategies
- Key Vault auto-rotation — Configured per-secret with Event Grid notifications
- Near-expiry alerts — Azure Monitor alerts 30 days before expiry
- Dual-key pattern — Rotate by activating a second key, then deactivating the old one (zero-downtime)
- Pipeline-triggered rotation — Scheduled pipeline rotates secrets and updates dependent resources
Secure Pipeline Patterns
Pipelines are both your greatest productivity tool and your largest attack surface. A misconfigured pipeline can expose secrets, deploy malicious code, or destroy infrastructure. Azure DevOps provides multiple layers to lock pipelines down.
Protected Resources
Certain resources can be marked as protected, requiring explicit pipeline authorization before use:
- Environments — Production environment requires approval + checks
- Service connections — Production Azure connection restricted to specific pipelines
- Agent pools — Self-hosted production agents only accessible by release pipelines
- Variable groups — Key Vault-linked groups restricted to authorized pipelines
- Repositories — Shared template repos with controlled access
Extends Templates for Enforced Security
The most powerful security pattern — use extends templates to enforce that every pipeline runs required security checks, regardless of what developers put in their YAML:
# templates/secure-pipeline.yml (in a locked, shared repository)
# ALL project pipelines must extend this template
parameters:
- name: stages
type: stageList
stages:
# Enforced security scanning BEFORE any custom stages
- stage: SecurityGate
displayName: '🔒 Security Scanning (enforced)'
jobs:
- job: SecurityChecks
steps:
- task: CredScan@3
displayName: 'Credential scanning'
- task: SdtReport@2
displayName: 'Security report'
- script: |
echo "Running SAST analysis..."
# CodeQL or SonarQube analysis
displayName: 'Static analysis'
# Developer-defined stages run AFTER security passes
- ${{ each stage in parameters.stages }}:
- ${{ stage }}
# Enforced compliance gate AFTER all stages
- stage: ComplianceCheck
displayName: '📋 Compliance Verification'
dependsOn: ${{ each stage in parameters.stages }}
jobs:
- job: Verify
steps:
- script: echo "Verifying deployment compliance..."
displayName: 'Compliance attestation'
# azure-pipelines.yml (developer's pipeline)
# MUST extend the secure template — can't bypass security checks
trigger:
- main
resources:
repositories:
- repository: templates
type: git
name: SharedProject/pipeline-templates
ref: refs/heads/main
extends:
template: templates/secure-pipeline.yml@templates
parameters:
stages:
- stage: Build
jobs:
- job: BuildApp
steps:
- script: dotnet build
displayName: 'Build application'
- stage: Deploy
jobs:
- deployment: DeployProd
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploying..."
Fork and PR Validation Security
When accepting contributions from forks (open source projects), be cautious:
- Don't make secrets available to fork builds — A malicious PR could exfiltrate them
- Require a maintainer comment before running pipelines on fork PRs
- Use
prtriggers with restrictions — Only run limited checks (no deployment, no secret access) - Limit what fork pipelines can modify — No access to protected resources
Secret Management Best Practices
Secrets are everywhere in pipelines — database passwords, API keys, deployment tokens, SSH keys. Managing them properly is critical because a leaked secret can compromise your entire infrastructure.
Secret Storage Options Comparison
| Storage Method | Rotation | Audit | Scope | Best For |
|---|---|---|---|---|
| Pipeline variable (secret) | Manual | Limited | Single pipeline | Quick prototyping only |
| Variable group (secret) | Manual | Change history | Multiple pipelines | Shared non-critical secrets |
| Key Vault-linked variable group | Automated | Full audit trail | Multiple pipelines | Production secrets ✅ |
| Secure files | Manual upload | Download logged | Multiple pipelines | Certificates, SSH keys, .env files |
| AzureKeyVault@2 task | Automated | Full audit trail | Per-job | Dynamic secret fetching ✅ |
Secret Hygiene Rules
- Never echo secrets — Azure DevOps masks them, but
env | sortin a script can still leak them - Use
issecret=truefor dynamic secrets —echo "##vso[task.setvariable variable=myVar;issecret=true]$value" - Rotate regularly — Set calendar reminders or automate with Key Vault rotation policies
- Minimize blast radius — Separate secrets per environment (dev-kv, staging-kv, prod-kv)
- Detect leaks proactively — Enable GitHub Advanced Security secret scanning or use CredScan
Secure Files
For binary secrets that can't be stored as text (certificates, keystores, SSH keys):
# Using secure files in a pipeline
# Upload the file via Pipelines → Library → Secure files
steps:
# Download the secure file to the agent
- task: DownloadSecureFile@1
name: sshKey
displayName: 'Download SSH key'
inputs:
secureFile: 'deploy-key.pem'
# Use the file (path available via the task output variable)
- script: |
# Install the SSH key with correct permissions
mkdir -p ~/.ssh
cp $(sshKey.secureFilePath) ~/.ssh/deploy-key.pem
chmod 600 ~/.ssh/deploy-key.pem
# Use it for deployment
ssh -i ~/.ssh/deploy-key.pem user@server "cd /app && git pull"
displayName: 'Deploy via SSH'
# Clean up (the agent does this automatically, but be explicit)
- script: rm -f ~/.ssh/deploy-key.pem
displayName: 'Remove SSH key'
condition: always()
Audit Logging & Monitoring
Audit logs record who did what, when, and from where across your Azure DevOps organization. They're essential for security investigations, compliance evidence, and detecting suspicious behavior.
What Gets Audited
- Authentication events — Sign-ins, PAT creation/revocation, OAuth connections
- Permission changes — Group membership modifications, role assignments
- Pipeline events — Pipeline created/modified/deleted, runs, approvals
- Repository actions — Branch policy changes, force pushes, deletions
- Policy modifications — Branch policies, build validation rules, reviewer requirements
- Service connection changes — Creation, modification, authorization grants
Audit Event Flow
flowchart LR
A[User Action] --> B[Azure DevOps
Audit Service]
B --> C{Streaming
configured?}
C -->|Yes| D[Azure Monitor
Log Analytics]
C -->|Yes| E[Event Hubs]
C -->|Yes| F[Splunk/SIEM]
C -->|No| G[90-day retention
in Azure DevOps]
D --> H[KQL Queries
& Alerts]
E --> I[Real-time
Processing]
style B fill:#16476A,color:#fff
style D fill:#3B9797,color:#fff
style H fill:#BF092F,color:#fff
Querying Audit Logs via REST API
# Query Azure DevOps audit logs for the last 24 hours
# Useful for security monitoring scripts and compliance dashboards
# Set variables
ORG="https://auditservice.dev.azure.com/yourorg"
TOKEN=$(echo -n ":$PAT" | base64)
START_TIME=$(date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%SZ)
END_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)
# Fetch audit events
curl -s -H "Authorization: Basic $TOKEN" \
"$ORG/_apis/audit/auditlog?startTime=$START_TIME&endTime=$END_TIME&api-version=7.1-preview.1" \
| jq '.decoratedAuditLogEntries[] | {
timestamp: .timestamp,
actor: .actorDisplayName,
action: .actionId,
details: .details,
ipAddress: .actorClientId
}'
# Example output:
# {
# "timestamp": "2026-06-03T14:22:00Z",
# "actor": "jane.doe@company.com",
# "action": "Security.ModifyPermission",
# "details": "Added user to Project Administrators",
# "ipAddress": "203.0.113.42"
# }
Streaming to Log Analytics
For retention beyond 90 days and advanced querying, stream audit logs to Azure Monitor:
- Navigate to Organization Settings → Auditing
- Click Configure stream → New stream → Azure Monitor
- Select your Log Analytics workspace
- Events appear in the
AzureDevOpsAuditingtable within minutes
# KQL query in Log Analytics: Detect permission escalation
# Alert on any user being added to administrative groups
AzureDevOpsAuditing
| where TimeGenerated > ago(1h)
| where OperationName == "Security.ModifyPermission"
| where Details contains "Project Administrators"
or Details contains "Build Administrators"
or Details contains "Collection Administrators"
| project TimeGenerated, ActorDisplayName, Details, IpAddress, ProjectName
| order by TimeGenerated desc
Compliance Frameworks
Azure DevOps supports compliance with major frameworks by providing the tooling and evidence trails that auditors look for. The platform itself holds SOC 1/2/3, ISO 27001, and other certifications — but you're responsible for how you configure and use it.
Change Management Traceability
The most powerful compliance feature in Azure DevOps is automatic traceability — every deployment links back through a complete chain of evidence:
flowchart LR
A[Work Item
Requirement] --> B[Branch &
Commits]
B --> C[Pull Request
+ Reviewers]
C --> D[Build &
Tests]
D --> E[Approval
Gates]
E --> F[Deployment
+ Logs]
style A fill:#132440,color:#fff
style C fill:#16476A,color:#fff
style E fill:#BF092F,color:#fff
style F fill:#3B9797,color:#fff
Evidence Collection per Framework
| Framework | Key Requirement | Azure DevOps Evidence |
|---|---|---|
| SOC 2 | Change management controls | Branch policies, PR reviews, approval gates, audit logs |
| ISO 27001 | Access control (A.9) | RBAC groups, permission inheritance, periodic access reviews |
| HIPAA | Audit controls (§164.312) | Audit log streaming, encryption in transit/at rest |
| FedRAMP | Continuous monitoring (CA-7) | Security scanning in pipelines, audit log retention, alerts |
| PCI DSS | Secure development (Req 6) | Code reviews, SAST/DAST, test evidence, deployment approval |
Compliance Dashboards
Build dashboards that auditors can self-serve:
- Deployment frequency — How often releases go to production (with approvals)
- Change failure rate — Percentage of deployments rolled back
- PR review coverage — Percentage of changes with required reviewers
- Security scan pass rate — Percentage of builds passing SAST/SCA
- Mean time to remediate — How quickly security vulnerabilities are fixed
DevSecOps in Pipelines
DevSecOps integrates security checks directly into the CI/CD pipeline — shifting security left so vulnerabilities are caught during development rather than after deployment. The goal: every commit gets scanned automatically, and high-severity findings block the release.
Security Scanning Categories
| Category | What It Detects | Popular Tools |
|---|---|---|
| SAST (Static Analysis) | Code-level vulnerabilities (SQL injection, XSS, buffer overflows) | CodeQL, SonarQube, Checkmarx, Semgrep |
| SCA (Software Composition) | Vulnerable dependencies, license violations | Mend (WhiteSource), Snyk, Dependabot, OWASP Dependency-Check |
| DAST (Dynamic Analysis) | Runtime vulnerabilities in running applications | OWASP ZAP, Burp Suite, Nikto |
| Container Scanning | Vulnerabilities in base images and layers | Trivy, Aqua, Prisma Cloud, Grype |
| Secret Scanning | Committed credentials, API keys, tokens | CredScan, GitLeaks, TruffleHog, GitHub Advanced Security |
| IaC Scanning | Misconfigured infrastructure templates | Checkov, tfsec, KICS, Bridgecrew |
Complete Security Scanning Pipeline
# Full DevSecOps pipeline with layered security scanning
# Blocks release on critical/high findings
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
stages:
# ─── STAGE 1: SECURITY SCANNING ─────────────────────────
- stage: SecurityScan
displayName: '🔒 Security Scanning'
jobs:
- job: SAST
displayName: 'Static Analysis (SAST)'
steps:
- task: SonarQubePrepare@5
inputs:
SonarQube: 'sonarqube-connection'
scannerMode: 'CLI'
configMode: 'manual'
cliProjectKey: 'myapp'
cliSources: 'src/'
- script: dotnet build src/MyApp.sln
displayName: 'Build for analysis'
- task: SonarQubeAnalyze@5
displayName: 'Run SonarQube analysis'
- task: SonarQubePublish@5
displayName: 'Publish quality gate result'
- job: SCA
displayName: 'Dependency Scanning (SCA)'
steps:
- script: |
# Install and run Snyk for dependency scanning
npm install -g snyk
snyk auth $SNYK_TOKEN
snyk test --severity-threshold=high --json > snyk-results.json || true
snyk monitor # Upload results for dashboard tracking
displayName: 'Snyk dependency scan'
env:
SNYK_TOKEN: $(SnykApiToken)
- publish: snyk-results.json
artifact: 'sca-results'
condition: always()
- job: SecretScan
displayName: 'Secret Detection'
steps:
- script: |
# Install and run gitleaks for secret detection
wget -q https://github.com/gitleaks/gitleaks/releases/download/v8.18.0/gitleaks_8.18.0_linux_x64.tar.gz
tar -xzf gitleaks_8.18.0_linux_x64.tar.gz
./gitleaks detect --source . --report-format json --report-path gitleaks-report.json
displayName: 'Gitleaks secret scan'
- job: ContainerScan
displayName: 'Container Scanning'
steps:
- script: docker build -t myapp:$(Build.BuildId) .
displayName: 'Build container image'
- script: |
# Trivy container vulnerability scan
# Fail on CRITICAL or HIGH vulnerabilities
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt-get update && sudo apt-get install -y trivy
trivy image --exit-code 1 --severity CRITICAL,HIGH \
--format json --output trivy-results.json \
myapp:$(Build.BuildId)
displayName: 'Trivy vulnerability scan'
# ─── STAGE 2: BUILD (only runs if security passes) ──────
- stage: Build
displayName: '🔨 Build & Test'
dependsOn: SecurityScan
jobs:
- job: BuildAndTest
steps:
- script: dotnet build --configuration Release
displayName: 'Build'
- script: dotnet test --no-build --logger trx
displayName: 'Run tests'
# ─── STAGE 3: DAST (against deployed staging) ───────────
- stage: DAST
displayName: '🌐 Dynamic Analysis'
dependsOn: Build
jobs:
- job: ZAPScan
steps:
- script: |
# Run OWASP ZAP against staging environment
docker run --rm -v $(pwd):/zap/wrk:rw \
ghcr.io/zaproxy/zaproxy:stable \
zap-baseline.py -t https://myapp-staging.azurewebsites.net \
-r zap-report.html -J zap-results.json
displayName: 'OWASP ZAP baseline scan'
- publish: zap-report.html
artifact: 'dast-report'
Quality Gates for Security
Configure your pipeline to block deployments when security thresholds are breached:
- SAST — Block on any Critical severity finding; warn on High
- SCA — Block on Critical CVEs with known exploits; require remediation plan for High
- Container — Block if base image has Critical vulnerabilities; require update within 7 days for High
- Secrets — Block immediately on any committed credential (zero tolerance)
- DAST — Block on OWASP Top 10 High/Critical findings in staging
Exercises
Configure RBAC with Custom Security Group
Goal: Create a custom security group "Security Reviewers" that can approve PRs to infrastructure code but can't directly push to protected branches.
- Create a custom security group named
Security Reviewersin your project. - Add 2-3 users (or yourself with a different account) to the group.
- Configure repository permissions: grant Contribute to pull requests but deny Force push and Bypass policies when pushing.
- Create a branch policy on
mainrequiring at least 1 reviewer from the Security Reviewers group for changes to/infrastructure/**paths. - Submit a PR that modifies a file in
/infrastructure/and verify the group is automatically required as a reviewer. - Verify the group members cannot directly push to
main(only approve PRs).
Success criteria: Infrastructure changes require Security Reviewer approval. Group members can approve but cannot bypass branch policies or force push.
Azure Key Vault Integration with Variable Group
Goal: Set up Azure Key Vault, link it to a pipeline variable group, and consume secrets at runtime.
- Create an Azure Key Vault (e.g.,
bootcamp-secrets-kv) in your subscription. - Add three secrets:
DatabasePassword,ApiKey, andStorageConnectionString. - Create a service connection in Azure DevOps (preferably with Workload Identity Federation).
- Create a variable group linked to Key Vault — map all three secrets.
- Write a pipeline that references the variable group and uses the
AzureKeyVault@2task. - Verify that secrets are masked in logs (try
echo $(DatabasePassword)— should show***). - Check Key Vault access logs to confirm the pipeline accessed the secrets.
Success criteria: Pipeline successfully fetches secrets from Key Vault at runtime. Secrets are masked in pipeline logs. Key Vault audit log shows access events from the pipeline identity.
Secure Pipeline with Protected Resources & Extends Template
Goal: Create a mandatory security scanning template that all pipelines must extend, with protected resource enforcement.
- Create a shared repository
pipeline-templateswith a security template (secure-pipeline.yml) that runs credential scanning. - Mark your production service connection and production environment as protected resources.
- Configure the project to require extends templates from the shared repository (Organization Settings → Pipelines → Settings).
- Create a pipeline that extends the secure template and deploys to the production environment.
- Verify: Create a second pipeline that does NOT extend the template — confirm it cannot access protected resources.
- Verify: Attempt to modify the extends reference in the pipeline YAML — confirm it requires authorization.
Success criteria: Only pipelines extending the approved template can access production resources. Unauthorized pipelines are blocked. Template enforcement prevents bypass of security scanning.
Audit Log Streaming & Security Alerts
Goal: Enable audit log streaming to Azure Monitor Log Analytics and create an alert for permission escalation events.
- Create an Azure Log Analytics workspace (if you don't have one).
- Configure audit log streaming: Organization Settings → Auditing → New stream → Azure Monitor.
- Perform several auditable actions (create a group, add a member, modify a branch policy).
- Wait 5-10 minutes, then query the
AzureDevOpsAuditingtable in Log Analytics — confirm events appear. - Create a KQL alert rule that triggers when a user is added to "Project Administrators" or "Collection Administrators".
- Add yourself to a Project Administrators group and verify the alert fires (check Action Group notification).
Success criteria: Audit events stream to Log Analytics within minutes. Alert fires automatically when permission escalation is detected. You receive a notification (email/Teams/webhook).
Next in the Bootcamp
In Module 11: Enterprise Patterns & AI Integration, we'll explore multi-team governance at scale, large organization patterns, Azure DevOps + GitHub together, AI-assisted development with GitHub Copilot in pipelines, and emerging practices for enterprise DevOps.