This chapter covers securing GitHub Actions pipelines, a critical topic for the AZ-500 exam under Security Operations objective 4.1. GitHub Actions is deeply integrated with Azure services, and understanding how to protect CI/CD workflows from credential leaks, supply chain attacks, and unauthorized access is essential. Approximately 10-15% of exam questions touch on CI/CD security, with a focus on OIDC authentication, secret management, and environment protection. By the end of this chapter, you will be able to design and audit secure GitHub Actions pipelines that comply with Azure security best practices.
Jump to a section
Imagine a pharmaceutical factory that produces custom drug batches. The factory has a secure receiving dock (GitHub repository), a quality control lab (CI/CD pipeline), and a shipping department (deployment). Each batch order is a workflow run triggered by a new prescription (code push). The factory manager (GitHub Actions) uses a set of standard operating procedures (workflow YAML) that specify which machines (runners) to use, which raw materials (secrets) to fetch from a locked cabinet (Azure Key Vault or GitHub Secrets), and which quality tests (jobs/steps) to run. Crucially, the factory has strict access controls: only authorized pharmacists (developers with write access) can submit prescriptions; the raw materials cabinet requires two-person authentication (approval gates) for high-risk materials; and every step of the process is logged on an immutable ledger (audit logs). If a machine (runner) tries to access a material it doesn't have permission for, the cabinet refuses (secret scanning and OIDC token validation). The factory also has a visitor badge system (OpenID Connect) so that external contractors (Azure resources) can verify the identity of the delivery truck (workload identity) without storing a shared key. This analogy mirrors how GitHub Actions pipelines use OIDC to authenticate to Azure, enforce branch protection rules, use environment secrets with required reviewers, and log all workflow runs to Azure Monitor for security auditing.
What is GitHub Actions and Why Secure It?
GitHub Actions is a CI/CD platform that automates software workflows directly from GitHub repositories. Workflows are defined in YAML files stored in the .github/workflows directory. They can build, test, and deploy code to any environment, including Azure. Securing these pipelines is critical because a compromised workflow can lead to credential theft, unauthorized deployment, or supply chain attacks. The AZ-500 exam tests your ability to implement security controls such as OpenID Connect (OIDC) authentication, secret scanning, environment protection rules, and audit logging.
How GitHub Actions Pipelines Work Internally
A workflow is triggered by events (e.g., push, pull_request, schedule). The workflow runs on a runner — either GitHub-hosted (Ubuntu, Windows, macOS) or self-hosted (your own VM). The runner executes jobs, each containing steps. Steps can run actions (prebuilt units of code) or shell commands. Security is enforced at multiple layers:
Authentication: The runner authenticates to Azure using either a service principal secret (stored as a GitHub secret) or OIDC tokens. OIDC is preferred because it eliminates static secrets. The runner requests a short-lived token from GitHub's OIDC provider, which Azure trusts via a federated identity credential.
Authorization: Workflows can reference secrets and environments. Environments have protection rules such as required reviewers and wait timers. Secrets are encrypted and only exposed to jobs that explicitly need them.
Network Security: Self-hosted runners can be placed in an Azure VNet, with network security groups controlling egress. GitHub-hosted runners use public IPs that change; you must allowlist the GitHub Actions IP ranges.
Audit: All workflow runs are logged. You can stream logs to Azure Monitor for analysis.
Key Components, Values, Defaults, and Timers
OIDC Token: The token issued by GitHub has a sub claim containing repo:owner/repo:environment:name. The token expires in 5 minutes by default (configurable up to 10 minutes). Azure validates the token using the federated identity credential.
GitHub Secrets: Max size 48 KB. Encrypted at rest with AES-256. Secrets are masked in logs. There is no default expiration; you must rotate them manually.
Environment Protection Rules: Required reviewers (up to 6) and wait timer (from 0 to 43,200 seconds, i.e., 12 hours). Default: no protection.
Self-Hosted Runner: Runs as a service on your VM. It polls GitHub for jobs using a personal access token (PAT) or a GitHub App token. The token must have repo and admin:org scopes for organization runners.
Audit Log: Retained for 90 days for GitHub Free/Pro, 180 days for Team, and 400 days for Enterprise. Azure Monitor retention is configurable.
Configuration and Verification Commands
To configure OIDC in Azure:
1. Create a user-assigned managed identity or app registration in Azure AD.
2. Add a federated identity credential with issuer https://token.actions.githubusercontent.com, subject repo:owner/repo:environment:name, and audience api://AzureADTokenExchange.
3. In the workflow YAML, use the azure/login action with client-id, tenant-id, and subscription-id.
Example workflow snippet:
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- run: az deployment group create ...To verify OIDC token exchange, check the workflow run log for Obtaining token using OIDC and Login successful.
Interaction with Related Technologies
Azure Key Vault: Use the Azure/keyvault-actions action to fetch secrets from Key Vault during workflows. Access is controlled by Key Vault access policies or RBAC.
Azure DevOps: GitHub Actions can trigger Azure Pipelines via webhooks. Secure the webhook secret.
Microsoft Defender for Cloud: Integrate with GitHub Actions to scan IaC templates and container images for vulnerabilities before deployment.
Azure Policy: Enforce policies on Azure resources deployed by GitHub Actions, e.g., require tags or specific SKUs.
Security Best Practices
Always use OIDC instead of service principal secrets.
Use environment-specific secrets and reviewers for production deployments.
Pin actions to a full-length commit SHA instead of a version tag to prevent supply chain attacks.
Use permissions: block to limit token scope to the minimum needed.
Enable Dependabot for dependency scanning.
For self-hosted runners, isolate them in a dedicated subnet with strict NSG rules.
Regularly rotate secrets and service principal credentials.
Common Pitfalls
Using pull_request_target event without proper checkout: This event runs in the context of the base repo, giving access to secrets. Attackers can submit a PR that modifies the workflow to exfiltrate secrets. Always use pull_request for untrusted forks, or if you must use pull_request_target, checkout the PR code only after careful review.
Storing secrets in plaintext in the YAML file: Never hardcode secrets. Always use ${{ secrets.NAME }}.
Not restricting permissions:: By default, workflows have write access to contents and issues. Always set explicit permissions.
Exam Relevance
AZ-500 objective 4.1 specifically covers implementing security for CI/CD pipelines. Know the difference between OIDC and service principal authentication, the purpose of federated identity credentials, and how to configure environment protection rules. Understand why pull_request_target is dangerous and how to mitigate it. Be able to interpret audit logs for unauthorized access attempts.
Define the workflow trigger
Start by defining what event triggers the workflow. Common triggers are `push`, `pull_request`, `workflow_dispatch` (manual), and `schedule`. For security, avoid triggers that allow external contributors to access secrets. For example, using `pull_request_target` runs the workflow in the context of the base repository, giving it access to secrets. Attackers can modify the workflow in a PR to exfiltrate secrets. Instead, use `pull_request` for untrusted forks, which runs in the context of the fork and has no secret access. If you must use `pull_request_target`, ensure you do not check out the PR code without validation.
Configure permissions for the job
Each job can specify a `permissions` block to set the token's scope. By default, the GITHUB_TOKEN has write access to contents, issues, and pull requests. For minimal privilege, set `permissions: {}` to grant no permissions, then add only what is needed. For OIDC authentication, you must include `id-token: write` to allow the job to request an OIDC token. For example: `permissions: id-token: write; contents: read`. This prevents the token from being used to modify the repository.
Set up OIDC authentication to Azure
To authenticate to Azure without storing long-lived secrets, configure OpenID Connect (OIDC). In Azure AD, create a federated identity credential for a user-assigned managed identity or app registration. The issuer is `https://token.actions.githubusercontent.com`. The subject must match the expected `sub` claim, e.g., `repo:myorg/myrepo:environment:prod`. The audience is `api://AzureADTokenExchange`. In the workflow, use the `azure/login` action with the client ID, tenant ID, and subscription ID of the Azure AD application. The action requests a token from GitHub's OIDC provider and exchanges it for an Azure access token. This token is short-lived (5 minutes default) and automatically refreshed.
Store secrets and environment variables
Store sensitive values like API keys, connection strings, and the Azure client ID as GitHub Secrets. Secrets are encrypted with AES-256 and are never exposed in logs. For environment-specific secrets, use GitHub Environments. Create an environment (e.g., 'prod') and add secrets that are only available to jobs referencing that environment. You can also require approval from designated reviewers before the job runs. The wait timer can be set from 0 to 12 hours. This prevents unauthorized deployments to production. Always reference secrets using `${{ secrets.SECRET_NAME }}` — never hardcode them.
Implement branch protection and code review
Protect the main branch by requiring pull request reviews and status checks before merging. In the repository settings, enable branch protection rules that require at least one approval, dismiss stale reviews, and require up-to-date branches. Additionally, use CODEOWNERS to require specific teams to approve changes to sensitive files like the workflow YAML. This ensures that any modification to the CI/CD pipeline is reviewed by security engineers. For extra security, enable required status checks that run a workflow to scan for secrets or vulnerabilities before merge.
Monitor and audit pipeline runs
Enable audit logging for GitHub Actions by streaming workflow logs to Azure Monitor or a SIEM. In Azure, you can create a diagnostic setting for the GitHub Actions app (if integrated via Azure AD) or use the GitHub Audit Log API. Monitor for suspicious activities such as failed OIDC token exchanges, unauthorized access to environments, or use of deprecated actions. Set up alerts for high-severity events like a workflow run triggered by a new contributor or a deployment to production outside of business hours. Regularly review the audit logs to detect misconfigurations.
Enterprise Scenario 1: Securing Production Deployments with OIDC and Environments
A large financial services company uses GitHub Actions to deploy microservices to Azure Kubernetes Service (AKS). They previously stored service principal secrets as GitHub secrets, but rotated them infrequently, leading to a credential leak. To fix this, they migrated to OIDC authentication. They created separate Azure AD applications for each environment (dev, test, prod) and configured federated identity credentials with subjects like repo:org/repo:environment:prod. In GitHub, they created a 'prod' environment that requires approval from two senior engineers and a 30-minute wait timer. The workflow uses the azure/login action with OIDC. Now, no static secrets are stored, and deployments to production require human approval. The wait timer gives time to abort if a mistake is detected. They also stream logs to Azure Sentinel for real-time monitoring. Misconfiguration: Initially they forgot to set id-token: write permission, causing the OIDC token request to fail. They learned to always include that permission.
Enterprise Scenario 2: Self-Hosted Runners in a VNet with NSG Restrictions
A healthcare SaaS provider processes PHI data and must comply with HIPAA. They cannot use GitHub-hosted runners because of data residency and network requirements. They deploy self-hosted runners on Azure VMs in a dedicated subnet. The VMs are registered as organization-level runners using a GitHub App token. The subnet has an NSG that only allows outbound traffic to api.github.com and *.actions.githubusercontent.com on port 443. Inbound traffic is blocked except from Azure Load Balancer health probes. The runners have no public IPs. They use Azure Private Link to connect to Azure SQL Database and Key Vault. The workflow YAML includes runs-on: self-hosted and references the runner labels. They rotate the runner registration token every 30 days. A common issue: when the runner VM is stopped and restarted, it fails to reconnect because the token expired. They now use a systemd service that automatically re-registers the runner on boot.
Enterprise Scenario 3: Supply Chain Security with Action Pinning and Dependabot
A software vendor uses hundreds of third-party actions from the GitHub Marketplace. They were vulnerable to a supply chain attack when a popular action was compromised via a tag update. They now pin all actions to a full-length commit SHA, e.g., actions/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675. They use Dependabot to automatically create PRs when a new SHA is available for a pinned action. They also have a workflow that scans for unpinned actions and fails the build. Additionally, they use the actions/dependency-review-action to block PRs that introduce new dependencies with known vulnerabilities. This proactive approach ensures that only vetted, immutable actions run in their pipelines.
What AZ-500 Tests on This Topic
AZ-500 objective 4.1 (Secure CI/CD pipelines) focuses on:
Configuring OIDC authentication between GitHub Actions and Azure (including federated identity credentials)
Managing secrets and environment protection rules
Understanding the risks of pull_request_target and how to mitigate them
Auditing and monitoring pipeline runs
Implementing branch protection and code review for workflow files
Common Wrong Answers and Why Candidates Choose Them
'Use a service principal secret stored as a GitHub secret for authentication': This is the most common wrong answer because it's simpler to set up. However, the exam favors OIDC because it eliminates long-lived secrets. Candidates choose this because they are unaware of OIDC or think static secrets are acceptable. Reality: OIDC is the recommended and more secure method.
'Set workflow permissions to write-all to ensure it can deploy': Overprivileged tokens are a common pitfall. Candidates think they need broad permissions to avoid errors. Reality: The principle of least privilege applies. You should set permissions: id-token: write; contents: read and nothing more.
'Use pull_request_target to run tests on forks': Candidates choose this because it allows secrets to be available for PRs from forks. However, this is dangerous because the workflow runs in the base repo context. The correct approach is to use pull_request for untrusted forks or carefully validate code in pull_request_target.
'Store secrets in the workflow YAML file using environment variables': Candidates may think that using env: is safe, but environment variables are visible in logs and to anyone with read access to the repo. Secrets must be stored in GitHub Secrets.
Specific Numbers, Values, and Terms
OIDC token lifetime: 5 minutes (default), max 10 minutes.
Environment wait timer: 0 to 43,200 seconds (12 hours).
Number of required reviewers per environment: up to 6.
GitHub secret max size: 48 KB.
Audit log retention: 90 days (Free/Pro), 180 days (Team), 400 days (Enterprise).
Federated credential subject format: repo:owner/repo:environment:name or repo:owner/repo:ref:refs/heads/main.
Audience value: api://AzureADTokenExchange.
Issuer URL: https://token.actions.githubusercontent.com.
Edge Cases and Exceptions
If you use pull_request_target, the workflow runs in the base repo context, but the code checkout is from the merge commit. To avoid malicious code execution, you must not run untrusted code in the same job. Use separate jobs: one to review, one to run.
Self-hosted runners can be compromised if not isolated. Always use a dedicated subnet and restrict network access.
OIDC tokens are only available for jobs that have id-token: write permission. Without it, the azure/login action fails.
If the federated identity credential's subject does not match the workflow's environment, OIDC authentication fails. For example, if the subject specifies environment:prod but the job does not reference an environment, the token exchange fails.
How to Eliminate Wrong Answers
If the question asks about authenticating to Azure, look for OIDC or federated identity. If the answer mentions a service principal secret, it is likely wrong unless the question explicitly says static credentials are acceptable.
For questions about securing secrets, any option that stores secrets in the YAML file or as plaintext is incorrect.
For trigger security, avoid pull_request_target unless the answer includes mitigation steps like separate jobs or no checkout of PR code.
Always check the permissions block: if it's missing id-token: write, OIDC won't work.
Always use OIDC authentication to Azure instead of service principal secrets to eliminate long-lived credentials.
Set explicit permissions in each job (id-token: write for OIDC, contents: read) to follow least privilege.
Use GitHub Environments with required reviewers and wait timers for production deployments.
Avoid pull_request_target for untrusted forks; use pull_request or separate jobs with careful code checkout.
Pin actions to a full commit SHA to prevent supply chain attacks via tag updates.
Store all sensitive values as GitHub Secrets, never in YAML or environment variables.
Stream workflow logs to Azure Monitor or SIEM for auditing and alerting.
Regularly rotate service principal secrets if OIDC is not used.
These come up on the exam all the time. Here's how to tell them apart.
OIDC Authentication
Uses short-lived tokens (5 min default) from GitHub's OIDC provider.
No static secrets stored in GitHub; reduces credential leak risk.
Requires federated identity credential in Azure AD.
Token scope is limited to specific workflow and environment via subject claim.
Recommended by Microsoft for CI/CD authentication.
Service Principal Secret Authentication
Uses a long-lived client secret stored as a GitHub secret.
Secret can be exposed in logs or through compromised runner.
No federation setup needed; just create a service principal.
Secret is static; rotation must be manual.
Easier to set up but less secure; not recommended for production.
Mistake
OIDC authentication is optional; using a service principal secret is equally secure.
Correct
OIDC eliminates the need for long-lived secrets, reducing the risk of credential theft. Service principal secrets can be leaked through logs or compromised runners. OIDC tokens are short-lived and scoped to specific workflows, making them more secure.
Mistake
GitHub Secrets are automatically rotated and expire.
Correct
GitHub Secrets do not expire. You must manually rotate them. There is no built-in expiration or rotation mechanism. Use OIDC to avoid managing static secrets.
Mistake
pull_request_target is safe because it only runs on trusted branches.
Correct
pull_request_target runs in the context of the base repository, giving access to secrets. An attacker can submit a PR that modifies the workflow YAML to exfiltrate secrets. It is only safe if you do not check out the PR code in the same job.
Mistake
Environment protection rules are only for production environments.
Correct
You can create environments for any stage (dev, test, staging) and apply protection rules. However, it is best practice to use them for sensitive environments like production. The number of environments is unlimited.
Mistake
Self-hosted runners are more secure than GitHub-hosted runners.
Correct
Self-hosted runners can be more secure if properly isolated (e.g., in a VNet with strict NSGs), but they also introduce maintenance overhead and can be a security risk if not hardened. GitHub-hosted runners are ephemeral and isolated per job, reducing the risk of cross-contamination.
Reveal each answer, then mark whether you got it right. Score 60%+ to unlock the next chapter.
Create a user-assigned managed identity or app registration in Azure AD. Add a federated identity credential with issuer https://token.actions.githubusercontent.com, subject repo:owner/repo:environment:name (or ref:refs/heads/main), and audience api://AzureADTokenExchange. In your workflow, use the azure/login action with client-id, tenant-id, and subscription-id. Ensure the job has permissions: id-token: write. The action will exchange the GitHub OIDC token for an Azure access token.
pull_request runs in the context of the merge commit from the fork, with secrets from the fork's repository (none for forks). pull_request_target runs in the context of the base repository, with access to its secrets. pull_request_target is dangerous because an attacker can modify the workflow in a PR to exfiltrate secrets. Use pull_request for untrusted forks. If you must use pull_request_target, do not check out the PR code in the same job.
Pin all third-party actions to a full-length commit SHA instead of a version tag. Use Dependabot to automatically update pinned actions. Enable dependency review to block PRs introducing vulnerable dependencies. Regularly scan your codebase for secrets and vulnerabilities using tools like secret scanning and code scanning.
Yes, you can trigger Azure Pipelines from GitHub Actions using webhooks. Secure the webhook secret stored as a GitHub secret. Alternatively, you can use the Azure DevOps GitHub Actions extension to interact with Azure Boards, Repos, and Pipelines.
GitHub provides audit logs for enterprise accounts. You can stream these logs to Azure Monitor or a SIEM using the GitHub Audit Log API. For individual runs, you can view logs in the Actions tab. Use Azure Monitor to create alerts for specific events like failed OIDC token exchanges or deployments to production.
By default, GITHUB_TOKEN has write access to contents, issues, pull requests, and other scopes. To restrict, set permissions: {} at the job level and then add only required permissions. For OIDC, you must include id-token: write.
Use GitHub Environments. Create an environment (e.g., dev, test, prod) and add secrets that are specific to that environment. In your workflow, reference the environment using environment: name. Only jobs that run in that environment can access its secrets. You can also require approval for sensitive environments.
You've just covered Securing GitHub Actions Pipelines — now see how well it sticks with free AZ-500 practice questions. Full explanations included, no account needed.
Done with this chapter?