Authenticate GITHUB actions to AZURE using OIDC — Short term credentials
Objective —
Everyone have only one main concern in cloud is “Secrets” :) , No one want’s to expose their secrets to the out side world.
GitHub or any other versioning control tools requires a secret to authenticate with your cloud to create / Modify / Delete the resources. But, we can’t monitor all the time to make sure those secrets are safe and secure.
Scope —
So, let’s follow the below steps to make your GitHub actions authenticate with Azure without any permanent credentials / Secrets.
We can call this as secret less authentication, every platform will use OpenIDConnect [OIDC].
1. Authentication with IAM identity provider in AWS.
2. Authentication with workload identity federation in GCP.
3. Authentication with federated credentials in Azure.
I’m always thankful to my leaders for making my hands dirty on cloud security engineering
Cdr Praveen Kumar — https://www.linkedin.com/in/cdr-praveen-kumar-0532ba6/
Saurabh Arora — https://www.linkedin.com/in/saurabh-arora-a72b4922/
Procedure —
In this demo we will deploy [create] a “resource group” with terraform using GitHub Actions :)
- Login to Azure portal and open the “App registrations” & click on “New registration”.
2. Give a name and click on register.
3. Click on “certificates & Secrets” under “Manage”
4. Click on “Federated credentials” and click on “Add credential”.
5. Under federated credential scenario , select the “GitHub Actions deploying Azure resources”.
6. Provide your GitHub org name, Repo name that you want to integrate, select Entity type as “Branch” & GitHub branch name as “main”.
7. Under “Credential details” provide a name and description, leave the “Audience” default value & click on “Add”.
8. Now we have a Federated credentials as below
9. Create a storage account and container > attach the permissions to your application with Storage Blob Data Owner role.
10. Go to your subscription > IAM > click on “Add” > Add roles assignments> Provide a “contributor” permissions to your application.
11. Now create a secrets in GitHub repo > settings > Security > Secrets & Variables > actions.
Under “secrets” create below variables:
These are requried for GitHub workflow files to run the pipes :)
AZURE_CLIENT_ID
AZURE_SUBSCRIPTION_ID
AZURE_TENANT_ID
RESOURCE_GROUP_NAME
10. Now let’s create a github workflow files and add the “pull request” and “push files”
“Pull Request”
name: Pull Request
on:
pull_request:
branches:
- main
env:
TF_LOG: INFO
permissions:
id-token: write
issues: write
pull-requests: write
contents: read
jobs:
pr-infra-check:
runs-on: ubuntu-latest
steps:
# Checkout the repository to the GitHub Actions runner
- name: Checkout
uses: actions/checkout@v3
# Install the latest version of Terraform CLI
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
# Log into Azure with OIDC integration
- name: 'Az CLI login'
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
# Run az commands to confirm sub access
- name: 'Run az commands'
run: |
az account show
# Run Terraform init
- name: Terraform Init
id: init
env:
STORAGE_ACCOUNT: ${{ secrets.STORAGE_ACCOUNT }}
CONTAINER_NAME: ${{ secrets.CONTAINER_NAME }}
RESOURCE_GROUP_NAME: ${{ secrets.RESOURCE_GROUP_NAME }}
ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
run: terraform init -backend-config="storage_account_name=<stotage account name>" -backend-config="container_name=<container name>" -backend-config="resource_group_name=$RESOURCE_GROUP_NAME"
# Run a Terraform fmt
- name: Terraform format
id: fmt
run: terraform fmt -check
# Run a Terraform validate
- name: Terraform validate
id: validate
if: success() || failure()
env:
ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
run: terraform validate -no-color
# Run a Terraform plan
- name: Terraform plan
id: plan
env:
ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
run: terraform plan -no-color
# Add a comment to pull requests with plan results
- name: Add Plan Comment
id: comment
uses: actions/github-script@v6
env:
PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
#### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
#### Terraform Validation 🤖${{ steps.validate.outputs.stdout }}
#### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
<details><summary>Show Plan</summary>
\`\`\`${process.env.PLAN}\`\`\`
</details>
*Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Working Directory: \`${{ env.tf_actions_working_dir }}\`, Workflow: \`${{ github.workflow }}\`*`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})
“Push”
name: Push
on:
push:
branches:
- main
env:
TF_LOG: INFO
permissions:
id-token: write
contents: read
jobs:
deploy-infra:
runs-on: ubuntu-latest
steps:
# Checkout the repository to the GitHub Actions runner
- name: Checkout
uses: actions/checkout@v3
# Install the latest version of Terraform CLI
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
# Log into Azure with OIDC integration
- name: 'Az CLI login'
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
# Run az commands to confirm sub access
- name: 'Run az commands'
run: |
az account show
# Run Terraform init
- name: Terraform Init
id: init
env:
STORAGE_ACCOUNT: ${{ secrets.STORAGE_ACCOUNT }}
CONTAINER_NAME: ${{ secrets.CONTAINER_NAME }}
RESOURCE_GROUP_NAME: ${{ secrets.RESOURCE_GROUP_NAME }}
ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
run: terraform init -backend-config="storage_account_name=<storage account name>" -backend-config="container_name=< container name> " -backend-config="resource_group_name=$RESOURCE_GROUP_NAME"
# Run a Terraform apply
- name: Terraform apply
id: apply
env:
ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
run: terraform apply -auto-approve
11. Now let’s create a simple terraform code
main.tf
provider "azurerm" {
features {}
use_oidc = true
}
resource "azurerm_resource_group" "oidc" {
name = var.resource_group_name
location = var.location
}
terraform.tf
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~>3.0"
}
}
backend "azurerm" {
key = "terraform.tfstate"
use_oidc = true
use_azuread_auth = true
}
}
variables.tf
variable "resource_group_name" {
type = string
description = "(Optional) Name of resource group to create. Defaults to oidc-test."
default = "oidc-simple-somevalue"
}
variable "location" {
type = string
description = "(Optional) Azure region to use. Defaults to East US."
default = "eastus"
}
11. After commit, you will see the successful AZ login as below:
State file will be created inside the container as below:
As I said, we are creating resource group in this demo ;)