Secrets & Secure Pipelines: Key Vault & Workload Federation
Manage secrets with Azure Key Vault and workload identity federation. Implement secretless authentication with OIDC, protect sensitive files, and prevent secret leakage in pipelines.
Why secrets management is the foundation of secure DevOps
Think of your house keys.
You would not tape your house key to the front door. You would not post a photo of it on social media. You would not make 50 copies and hand them out to everyone who visits. Yet developers do the equivalent with passwords, API keys, and certificates every day β hardcoding them in source code, pasting them in chat, or storing them in plain text config files.
Secrets management gives you a secure lockbox (Azure Key Vault) and a way to let your pipelines open the lockbox without ever holding a key themselves (workload identity federation).
Azure Key Vault
Azure Key Vault is the centralised secret store for Azure environments. It stores three types of objects:
| Object Type | Purpose | Example |
|---|---|---|
| Secrets | Any string value β passwords, connection strings, API keys | Database password, SaaS API key |
| Keys | Cryptographic keys for encryption and signing (HSM-backed optional) | Data encryption key, JWT signing key |
| Certificates | TLS/SSL certificates with automatic renewal | App Service TLS cert, code signing cert |
Access control: RBAC vs Access Policies
Key Vault supports two permission models. Azure RBAC is recommended for new vaults.
| Feature | Azure RBAC (Recommended) | Vault Access Policies (Legacy) |
|---|---|---|
| Permission granularity | Per-object-type roles (Secrets Officer, Key Vault Reader, etc.) | Per-vault policy with key/secret/certificate permissions |
| Scope | Can be applied at management group, subscription, resource group, or vault level | Vault level only |
| Central management | Yes β same RBAC system as all Azure resources | No β each vault has its own access policy list |
| Conditional access | Supports Entra ID Conditional Access policies | No conditional access support |
| Maximum principals | Thousands (Azure RBAC limits) | 1,024 access policy entries per vault |
| Audit | Azure RBAC audit logs + Key Vault diagnostic logs | Key Vault diagnostic logs only |
Key Vault RBAC roles
| Role | Permissions |
|---|---|
| Key Vault Administrator | Full management of all vault objects |
| Key Vault Secrets Officer | All operations on secrets (but not keys or certificates) |
| Key Vault Secrets User | Read secret values β the role you give to pipelines |
| Key Vault Certificates Officer | Manage certificates (issue, renew, import) |
| Key Vault Crypto Officer | All operations on keys |
| Key Vault Reader | Read vault metadata (not secret values) |
Key Vault in pipelines
Azure Pipelines: AzureKeyVault task
The AzureKeyVault@2 task fetches secrets from Key Vault and maps them to pipeline variables:
steps:
- task: AzureKeyVault@2
inputs:
azureSubscription: 'my-service-connection'
KeyVaultName: 'my-keyvault'
SecretsFilter: 'db-password,api-key' # comma-separated, or * for all
RunAsPreJob: true # fetch before any other steps
Fetched secrets are automatically masked in logs. They can be referenced as $(db-password) in subsequent steps.
Azure Pipelines: Variable group linked to Key Vault
Instead of fetching per-pipeline, link a variable group to a Key Vault. All secrets from the vault become available as variables in any pipeline that references the group. Changes in Key Vault propagate automatically β no pipeline edits needed.
GitHub Actions: Key Vault with azure/login
steps:
- uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- uses: azure/cli@v2
with:
inlineScript: |
az keyvault secret show --vault-name my-kv --name db-password --query value -o tsv
Alternatively, use azure/cli@v2 with az keyvault secret show to retrieve individual secrets β this is the currently recommended approach.
Workload identity federation in depth
Module 20 introduced OIDC concepts. Here we cover the implementation details.
GitHub Actions OIDC claims
When a GitHub Actions workflow requests an OIDC token, the token contains claims that identify:
- Issuer:
https://token.actions.githubusercontent.com - Subject: Encodes the repo, branch, environment, or tag β e.g.,
repo:org/repo:ref:refs/heads/main - Audience:
api://AzureADTokenExchange(default for Azure)
The federated credential on the Entra ID app registration specifies which subject claims to trust. This is how you restrict which branches or environments can authenticate.
Subject claim filtering
| Filter | Subject Claim | Use Case |
|---|---|---|
| Branch | repo:org/repo:ref:refs/heads/main | Only main branch can deploy to production |
| Environment | repo:org/repo:environment:production | Only the βproductionβ environment can access prod resources |
| Tag | repo:org/repo:ref:refs/tags/v* | Only tagged releases can publish packages |
| Pull request | repo:org/repo:pull_request | PR workflows can access read-only resources |
Scenario: Amira locks down OIDC for defence pipeline
ποΈ Major Collins requires that only the main branch of the deployment repository can access the production Azure subscription β no feature branches, no PRs, no tags.
Amiraβs implementation:
- Creates an Entra ID app registration
defence-prod-deploy - Adds a federated credential:
- Issuer:
https://token.actions.githubusercontent.com - Subject:
repo:defence-org/deploy-infra:ref:refs/heads/main - Audience:
api://AzureADTokenExchange
- Issuer:
- Assigns the app
Contributorrole scoped to the production resource group only - The GitHub Actions workflow uses
azure/login@v2with the appβs client-id - Any attempt to authenticate from a feature branch fails β the subject claim does not match
Result: Even if an attacker compromises a feature branch workflow, they cannot access production Azure resources.
GitHub Actions secrets
GitHub provides three levels of secret storage:
| Level | Scope | Set By | Use Case |
|---|---|---|---|
| Repository secrets | Single repo, all environments | Repo admin | Repo-specific API keys |
| Environment secrets | Single repo, specific environment only | Repo admin | Production credentials (with environment protection rules) |
| Organisation secrets | Multiple repos (configurable: all, private, or selected) | Org owner | Shared secrets like container registry credentials |
Environment secrets are particularly powerful when combined with environment protection rules β require reviewer approval before a workflow job can access the environmentβs secrets.
Secret masking in GitHub Actions
GitHub automatically masks any secret value that appears in workflow logs. However, masking has limitations:
- Structured formats (JSON, XML) may partially expose values if the secret appears as a substring
echoorprintstatements that transform secrets (base64 encode, substring) may bypass masking- Multi-line secrets need special handling with
add-mask
Azure Pipelines secure files
Secure files store binary files (certificates, provisioning profiles, SSH keys) that pipelines need during execution but should not be stored in source control.
Key characteristics:
- Uploaded to the Azure DevOps project library
- Encrypted at rest
- Downloaded by pipelines using the
DownloadSecureFile@1task - Pipeline permissions control which pipelines can access each file
- Approval checks can require human approval before download
- Files are placed in a temporary directory and deleted after the pipeline completes
steps:
- task: DownloadSecureFile@1
name: sshKey
inputs:
secureFile: 'deploy_rsa'
- script: |
mkdir -p ~/.ssh
cp $(sshKey.secureFilePath) ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
displayName: 'Install SSH key'
Preventing secret leakage
Pipeline design patterns
- Never echo secrets β avoid
echo $(secret)orprint(secret). Even with masking, transformations can leak values. - Use secret variables β mark pipeline variables as secret. Azure Pipelines masks them and prevents them from being logged.
- Audit pipeline outputs β review pipeline logs for accidental exposure. Secrets in error messages are a common leak vector.
- Restrict fork PR builds β forks can modify workflows to exfiltrate secrets. Require approval for fork PR builds.
- Separate contexts β production secrets should only be accessible from production deployment stages, not from CI build stages.
Azure DevOps specific controls
- Variable groups with permissions β restrict which pipelines can reference a variable group
- Secret masking β variables marked as secret are masked in all log output
- Audit logs β Azure DevOps audit stream captures who accessed what and when
- Pipeline decorators β inject security scanning steps into all pipelines (org-wide enforcement)
GitHub Actions specific controls
- Required reviewers on environments β secrets in a protected environment cannot be accessed until a reviewer approves
- Deployment branches β restrict which branches can deploy to an environment
CODEOWNERSβ require specific reviewers for workflow file changes (.github/workflows/)- OpenSSF Scorecards β automated security health check for GitHub repositories
Secret rotation strategy
| Secret Type | Rotation Approach | Frequency |
|---|---|---|
| Service principal client secrets | Automate with Azure Automation or Logic Apps. Create new secret, update consumers, delete old. | Every 90 days (or eliminate with OIDC) |
| Key Vault secrets | Enable expiry notifications, automate rotation with Event Grid + Azure Functions | Per compliance policy (30-365 days) |
| GitHub PATs | Set short expiry, use fine-grained PATs, replace with GitHub Apps where possible | Every 90 days |
| Certificates | Use Key Vault auto-renewal for supported CAs (DigiCert, GlobalSign) | Before expiry (auto with Key Vault) |
| SSH keys | Rotate and replace in secure files and Key Vault | Every 6-12 months |
Exam tip: Secretless is always the best answer
When the exam presents a scenario asking how to improve pipeline security, the answer hierarchy is:
- Eliminate the secret entirely β workload identity federation, managed identities
- Centralise in Key Vault β with RBAC, audit logging, and rotation
- Use platform secret storage β GitHub Secrets, Azure DevOps secret variables
- Rotate frequently β short expiry, automated rotation
If an answer option mentions βworkload identity federationβ or βmanaged identityβ and the scenario supports it, that is almost certainly correct.
Knowledge check
Dr. Amira's client stores database passwords as Azure DevOps pipeline variables (not marked as secret). A junior developer's PR accidentally prints the connection string in build logs. What TWO changes should Amira make first?
Kai wants his GitHub Actions workflow to deploy to a staging Azure environment from any branch, but only the 'main' branch should be able to deploy to production. He is using workload identity federation. How should he configure this?
π¬ Video coming soon
Secrets & Secure Pipelines: Key Vault & Workload Federation
Next up: Security Scanning: GHAS, Defender and Dependabot