This repository is a demonstration of how to deploy Azure infrastructure using Terraform, automated by a GitHub Actions workflow that authenticates via Microsoft Entra ID's OpenID Connect (OIDC) support.
This project provides a complete example of a secure, automated CI/CD pipeline for Infrastructure as Code (IaC). It leverages passwordless authentication between GitHub Actions and Azure, which is a modern best practice that eliminates the need to store long-lived cloud credentials (like service principal secrets) in GitHub.
- Infrastructure as Code: Manages Azure infrastructure using Terraform.
- CI/CD Automation: Automates deployment with a GitHub Actions workflow.
- Secure Authentication: Uses OpenID Connect (OIDC) for passwordless authentication to Azure.
- Persistent State: Stores Terraform state remotely and securely in an Azure Storage Account.
- Dynamic & Reusable Code: Employs Terraform variables and loops (
count
) to keep the code DRY (Don't Repeat Yourself).
The OIDC authentication flow is seamless and secure:
- A push to the
main
branch or the creation/update of a pull request targetingmain
starts the GitHub Actions workflow. - The workflow requests a short-lived JSON Web Token (JWT) from GitHub's internal OIDC provider.
- The
azure/login
action presents this JWT to Microsoft Entra ID. - Entra ID verifies the token's signature and checks its claims (like the repository and branch name) against a pre-configured "Federated Credential". This credential establishes a trust relationship between an Azure App Registration and your specific GitHub repository.
- Upon successful verification, Entra ID issues a standard, short-lived Azure access token to the GitHub Actions runner.
- The workflow, now securely authenticated, executes
terraform plan
. If the trigger was a push to themain
branch, it proceeds toterraform apply
the changes.
graph TD
subgraph "One-Time Setup"
A[Developer] -- runs --> B(setup-azure-oidc.sh);
B -- creates --> C{Entra ID App &<br>Federated Credentials};
A -- runs --> D(setup-terraform-backend.sh);
D -- creates --> E{Azure Storage Account<br>for Terraform State};
A -- configures --> F(GitHub Secrets & Variables);
end
subgraph "CI/CD Pipeline"
G[Developer pushes<br>code to GitHub] --> H{GitHub Actions<br>Workflow Triggered};
H -- 1. requests JWT --> I[GitHub OIDC Provider];
I -- 2. issues JWT --> H;
H -- 3. presents JWT --> C;
C -- 4. validates & issues token --> H;
H -- "5. runs 'terraform plan' & 'apply'<br>(plan on PR, apply on merge)" --> K[Azure Resources];
K -- state updated --> E;
end
- An active Azure Subscription.
- Azure CLI installed.
- Terraform CLI installed locally.
- A GitHub account with a repository created from this template or forked/cloned.
Follow these steps to configure and deploy the infrastructure.
This project includes a setup script to automate the creation of the Azure identity (App Registration and Service Principal) and establish the OIDC trust relationship with your GitHub repository.
-
Configure the Setup Script: Open the
setup-azure-oidc.sh
file and update the configuration variables at the top with your GitHub organization/username and repository name.# --- Configuration --- # Replace these with your actual GitHub organization/username and repository name. GITHUB_ORG="YourGitHubOrg" GITHUB_REPO="YourGitHubRepo"
-
Run the Setup Script: Log in to Azure using the CLI, then run the script from your terminal.
# Login to your Azure account az login # Make the script executable chmod +x ./setup-azure-oidc.sh # Run the script ./setup-azure-oidc.sh
The script will create all necessary Azure resources and output the values you need for the next step.
Terraform needs a persistent location to store the state file. This project includes a script to create the necessary Azure Storage Account.
-
Run the Backend Setup Script: This script will create a resource group, a globally unique storage account, and a blob container.
# Make the script executable chmod +x ./setup-terraform-backend.sh # Run the script ./setup-terraform-backend.sh
-
Update
backend.tf
: Copy thestorage_account_name
from the script's output and update thebackend.tf
file accordingly.
Navigate to your GitHub repository's Settings > Secrets and variables > Actions
.
-
Create Repository Secrets (these are encrypted):
VM_ADMIN_PASSWORD
: Create a strong password for the Windows virtual machines.
-
Create Repository Variables (these are stored in plain text):
- Copy the three values (
AZURE_CLIENT_ID
,AZURE_TENANT_ID
,AZURE_SUBSCRIPTION_ID
) from the output of thesetup-azure-oidc.sh
script and create a repository variable for each.
- Copy the three values (
Commit and push your changes. Creating a pull request will trigger a terraform plan
, and merging to main
will trigger a terraform apply
.
git add .
git commit -m "Configure project for deployment"
git push origin main
This push will trigger the Deploy Terraform with OIDC
workflow. You can monitor its progress in the "Actions" tab of your repository.
This Terraform configuration will create the following Azure resources:
- 1 x Resource Group
- 1 x Virtual Network (VNet)
- 2 x Subnets
- 1 x Network Security Group (NSG)
- 2 x Public IP Addresses
- 2 x Network Interfaces (NIC)
- 2 x Windows Server 2019 Virtual Machines
- NSG Rule: The Network Security Group (
azurerm_network_security_group.app_nsg
) is configured to allow RDP traffic (port 3389) from any source (*
). This is highly insecure and for demonstration purposes only. In a production environment, you must restrict thesource_address_prefix
to a specific, trusted IP address or range. - VM Password: The VM administrator password is provided via a GitHub Secret, which is the correct and secure practice. Never hardcode secrets in your Terraform files.
To destroy all the resources created by this project and avoid ongoing Azure costs, you can run terraform destroy
from your local machine.
-
Ensure you are in the project directory and authenticated to Azure (
az login
). -
Initialize Terraform to connect to your remote backend.
terraform init
-
Run the destroy command. You will need to pass the VM password.
# You will be prompted for the password, or you can set it as an environment variable # export TF_VAR_vm_admin_password="your_secret_password" terraform destroy
-
Delete the Azure AD Application: You can find the name of the application in the
setup-azure-oidc.sh
script (AAD_APP_NAME
).# Replace the display name if you changed it in the script APP_ID=$(az ad app list --display-name "github-oidc-demo-app" --query "[].appId" -o tsv) # Delete the App Registration and its associated Service Principal az ad app delete --id $APP_ID