Filters
How to search
  • Plain words find names, texts and examples.
  • "two words" finds the exact phrase.
  • --name or -x finds an option.
  • #word looks only inside commands.
  • @grep finds lessons and labs that use grep.
  • Filters: cat:network · in:labs · in:notes · level:rhcsa · ch:8 · root:yes. Put ! before a word to leave it out. OR between words means one of them.
  • ↑ ↓ to move · Enter to open · Esc to close · / to search

Chapter 8Name in the course: Continuous deployment to Azure

Deploy to Azure with OIDC and no stored passwords

The safest pipeline stores no cloud password at all. With OpenID Connect (OIDC), Azure trusts tokens that GitHub issues for one repository, branch or environment. The job logs in for minutes, runs Terraform or deploys the app, and the token expires.

  • 4 Lessons
  • 2 Commands

$ run as a normal user# run as root (or with sudo)

8.1

Understand OIDC login

GitHub signs a token that says "repo s0sta/infra-shop, environment production". Azure has a federated credential that trusts exactly that subject, and gives an access token for an app registration (or a managed identity).

GitHub job (id-token: write) --token--> Microsoft Entra ID --checks subject--> Azure access token (1 hour)
Subject (who may log in)Example
A branchrepo:s0sta/infra-shop:ref:refs/heads/main
An environmentrepo:s0sta/infra-shop:environment:production
Pull requestsrepo:s0sta/infra-shop:pull_request

8.2

Set up the trust in Azure

Create an identity, give it a role, and add federated credentials for GitHub.

az identity create -g rg-cicd -n id-github-infra
CLIENT_ID=$(az identity show -g rg-cicd -n id-github-infra --query clientId -o tsv)
PRINCIPAL_ID=$(az identity show -g rg-cicd -n id-github-infra --query principalId -o tsv)
az role assignment create --assignee-object-id "$PRINCIPAL_ID" --assignee-principal-type ServicePrincipal \
    --role Contributor --scope /subscriptions/SUB_ID/resourceGroups/rg-shop-staging
az identity federated-credential create -g rg-cicd --identity-name id-github-infra -n gh-staging \
    --issuer https://token.actions.githubusercontent.com \
    --subject repo:s0sta/infra-shop:environment:staging --audiences api://AzureADTokenExchange
gh variable set AZURE_CLIENT_ID --body "$CLIENT_ID"
gh variable set AZURE_TENANT_ID --body "$(az account show --query tenantId -o tsv)"
gh variable set AZURE_SUBSCRIPTION_ID --body "$(az account show --query id -o tsv)"

Remember

The client ID, tenant ID and subscription ID are not secrets — store them as variables. There is no password anywhere.

Commands in this lesson

8.3

Run Terraform with OIDC

azure/login logs the CLI in; Terraform's azurerm provider uses OIDC itself with ARM_USE_OIDC.

.github/workflows/terraform.yml
name: Terraform
on:
  pull_request:
    paths: ["envs/**", "modules/**"]
  push:
    branches: [main]
    paths: ["envs/**", "modules/**"]
permissions:
  id-token: write
  contents: read
  pull-requests: write
env:
  ARM_USE_OIDC: "true"
  ARM_CLIENT_ID: ${{ vars.AZURE_CLIENT_ID }}
  ARM_TENANT_ID: ${{ vars.AZURE_TENANT_ID }}
  ARM_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }}
jobs:
  plan:
    runs-on: ubuntu-latest
    environment: staging
    defaults: { run: { working-directory: envs/staging } }
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
      - run: terraform fmt -check -recursive ../..
      - run: terraform init
      - run: terraform validate
      - run: terraform plan -out=tfplan -var-file=staging.tfvars
      - uses: actions/upload-artifact@v4
        with: { name: tfplan, path: envs/staging/tfplan, retention-days: 3 }
  apply:
    if: github.ref == 'refs/heads/main'
    needs: plan
    runs-on: ubuntu-latest
    environment: staging
    concurrency: { group: tf-staging, cancel-in-progress: false }
    defaults: { run: { working-directory: envs/staging } }
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
      - uses: actions/download-artifact@v4
        with: { name: tfplan, path: envs/staging }
      - run: terraform init
      - run: terraform apply tfplan

8.4

Deploy an app to App Service

Log in with OIDC, then deploy the build to a slot and swap.

.github/workflows/deploy-app.yml
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    permissions: { id-token: write, contents: read }
    steps:
      - uses: actions/checkout@v4
      - uses: azure/login@v2
        with:
          client-id: ${{ vars.AZURE_CLIENT_ID }}
          tenant-id: ${{ vars.AZURE_TENANT_ID }}
          subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
      - uses: azure/webapps-deploy@v3
        with:
          app-name: app-shop-s0sta
          slot-name: staging
          package: .
      - run: az webapp deployment slot swap -g rg-apps -n app-shop-s0sta --slot staging --target-slot production

At work

Give the deploy identity rights only on the target resource group — not Contributor on the whole subscription.

All commands in this chapter