The shift toward Infrastructure as Code (IaC) has transformed the way organizations manage their cloud footprints. By treating infrastructure configuration with the same rigor as application code, DevOps teams can achieve consistency, scalability, and repeatability. In the Azure ecosystem, the combination of HashiCorp Terraform and GitHub Actions provides a robust framework for establishing a Continuous Integration and Continuous Deployment (CI/CD) pipeline. This integration allows for the programmatic provisioning of resources—ranging from simple storage accounts to complex Azure Kubernetes Service (AKS) clusters—while maintaining a strict audit trail and reducing the risks associated with manual configuration.
Modern cloud engineering demands more than just the ability to deploy; it requires secure, automated, and validated deployments. The industry is moving away from long-lived secrets stored in repository settings toward identity-based authentication. By leveraging OpenID Connect (OIDC), organizations can establish a trust relationship between GitHub and Microsoft Entra ID, eliminating the need to store static client secrets that could be leaked or require tedious rotation.
The Architectural Framework for Automated Provisioning
A professional-grade automation pipeline for Azure and Terraform consists of several interlocking components, each serving a specific role in the lifecycle of a resource. The goal is to create a flow where code changes are validated, planned, and then applied to the target environment with minimal manual intervention.
Core Components of the Pipeline
The architecture is centered around the interaction between the GitHub repository, the GitHub Actions runner, and the Azure cloud environment.
- GitHub Actions Workflows: These are YAML-defined processes that orchestrate the execution of Terraform commands. Typically, these are split into a "Plan" workflow for validation and an "Apply" workflow for deployment.
- Azure App Registration (Service Principal): This acts as the identity for the GitHub Action. Rather than using a user account, a Service Principal is created to provide the necessary permissions to create and manage resources.
- Federated Credentials (OIDC): This is the security layer that allows GitHub to request a short-lived access token from Azure, removing the dependency on stored passwords or secrets.
- Azure Storage Account (Remote State): Terraform requires a state file to track the current condition of the infrastructure. Storing this in a centralized Azure Blob Storage container ensures that multiple team members and automated workflows are working from a single source of truth.
Recommended Project Folder Structure
Organization is critical for maintaining Terraform projects as they grow in complexity. A standard directory structure ensures that variables, outputs, and workflows are easily discoverable.
text
├── main.tf # Primary resource definitions
├── variables.tf # Input variable declarations
├── terraform.tfvars # Environment-specific variable values
├── outputs.tf # Exported resource attributes
└── .github/
└── workflows/
└── deploy.yml # GitHub Actions workflow definitions
Secure Authentication via OpenID Connect (OIDC)
Traditionally, automating Azure deployments required creating a Service Principal and storing a JSON block containing a client secret in GitHub Secrets. This method introduces security risks, as secrets can expire or be compromised. OpenID Connect (OIDC) solves this by implementing a workload identity federation.
How OIDC Operates in the Pipeline
OIDC is an identity authentication protocol extending OAuth 2.0. Instead of providing a secret, GitHub Actions requests a short-lived access token directly from Azure. This process creates a trust relationship between Microsoft Entra ID and GitHub.
When a workflow runs, it presents a token to Azure. Azure verifies the token's "subject" to ensure the request is coming from a trusted repository and branch. If the subject matches the federated credential configuration, Azure issues a temporary token valid only for that specific job. This eliminates the need for secret rotation and provides granular control over which environments (e.g., Dev vs. Prod) can access specific Azure subscriptions.
Configuring Federated Credentials
The subject of the federated credential defines exactly who is allowed to authenticate. Different subjects can be used for different stages of the pipeline:
- Environment-based:
repo:${var.github_organization_target}/${var.github_repository}:environment:${var.environment}allows authentication when deploying to a specific GitHub environment (e.g., development). - Pull Request-based:
repo:${var.github_organization_target}/${var.github_repository}:pull_requestallows the workflow to authenticate during the validation phase of a PR. - Branch-based:
repo:my-github-user/my-repo:ref:refs/heads/mainrestricts authentication to the main branch.
Managing Terraform State in Azure
Terraform uses a state file (terraform.tfstate) to map your configuration to real-world resources and improve performance. By default, this file is stored locally. In a CI/CD environment, local storage is impossible because each GitHub Action run occurs on a fresh runner.
The Risk of Local State
Committing the terraform.tfstate file to source control is strictly forbidden. State files often contain sensitive information in plain text, including passwords, private keys, and internal IP addresses. Furthermore, concurrent runs on the same state file in a git repository would lead to state corruption and merge conflicts.
Azure Blob Storage as a Remote Backend
The professional solution is to use an Azure Storage Account and a specific container (e.g., tfstatefiles) to hold the state. This provides:
- Shared State: Every single workflow run accesses the same state file, ensuring consistency across the team.
- Locking: Azure Blob Storage helps prevent concurrent executions from corrupting the state.
- Security: Access to the state file is controlled via Azure Role-Based Access Control (RBAC).
Implementing the CI/CD Workflow
A robust pipeline is divided into two primary stages: the Plan stage (CI) and the Apply stage (CD). This separation ensures that no changes are ever pushed to production without a peer review and a documented plan.
The Terraform Plan Workflow
The terraform-plan.yml workflow is typically triggered by pull requests to the main branch. Its primary purpose is validation and risk assessment.
The workflow executes the following sequence:
1. Azure Login via OIDC: Authenticates using the azure/login@v1 action.
2. Terraform Init: Initializes the backend and downloads providers.
3. Validation: Runs terraform validate to ensure the configuration is syntactically correct.
4. Security Scanning: Integrates tools like Checkov and tfsec to identify security misconfigurations before they are deployed.
5. Terraform Plan: Generates an execution plan showing exactly what resources will be added, changed, or destroyed.
6. PR Feedback: The plan results are commented directly back into the pull request for reviewers to see.
The Terraform Apply Workflow
The terraform-apply.yml workflow is triggered only when code is merged or pushed to the main branch. It transforms the theoretical plan into actual infrastructure.
This workflow performs the following:
- Authentication: Re-establishes the OIDC connection to Azure.
- Terraform Apply: Executes the changes. In a production environment, this is often paired with a manual approval gate within GitHub Environments.
Workflow Permissions and Requirements
For OIDC to function, the GitHub Actions YAML must explicitly define permissions to allow the runner to request a token and write to pull requests.
yaml
permissions:
id-token: write # Required for OIDC authentication
contents: read # Required to read the repository code
pull-requests: write # Required to post plan results to the PR
Azure Identity and Access Management (IAM)
To enable the GitHub Action to manage resources, the associated Azure App Registration must be granted specific roles. Granting too much power violates the principle of least privilege, while too little prevents the pipeline from completing its task.
Required Roles for Automation
| Role Name | Scope | Purpose |
|---|---|---|
| Contributor | Subscription Level | Allows the creation, modification, and deletion of most Azure resources (e.g., VMs, AKS). |
| Storage Blob Data Contributor | Storage Account Level | Specifically allows the pipeline to read and write the terraform.tfstate file in the blob container. |
Manual Service Principal Creation
For those not using OIDC and opting for the legacy secret-based approach, a Service Principal is created via the Azure CLI using the following command:
az ad sp create-for-rbac --name github-terraform-sp --role Contributor --scopes /subscriptions/[subscription-id] --sdk-auth
The resulting JSON output must be stored as a GitHub Action secret (e.g., AZURE_CREDENTIALS) to be accessed by the workflow.
Advanced Terraform Techniques for Azure
When building complex infrastructure, such as an Azure Kubernetes Service (AKS) cluster, leveraging Terraform's data sources and modules is essential for maintainability.
Utilizing Data Sources
Data sources allow Terraform to fetch information about existing resources that were not created by the current Terraform project. This prevents the need to hardcode values like Subscription IDs.
For example, the azurerm_subscription data block can be used to retrieve the current subscription's identity. This result can be exported under a local name (e.g., sub) and referenced elsewhere:
data.azurerm_subscription.sub.id
Bootstrapping and Modularization
Large-scale deployments often follow a "bootstrap" pattern. This involves a separate, small Terraform project dedicated to creating the foundation required for the main pipeline.
- Bootstrap Phase: Provisions the Azure Storage Account for the state file and configures the Azure App Registration and OIDC federated credentials.
- Resource Phase: Deploys the actual application infrastructure (e.g., Virtual Networks, AKS clusters, Database servers) using the identity established in the bootstrap phase.
Comparison of Authentication Methods
Choosing between OIDC and Service Principal secrets depends on the security requirements and the technical maturity of the environment.
| Feature | OIDC (Workload Identity) | Service Principal (Secrets) |
|---|---|---|
| Secret Storage | No secrets stored in GitHub | Client Secret stored in GitHub Secrets |
| Token Lifespan | Short-lived (Job-based) | Long-lived until expiration |
| Rotation | Automatic/Not required | Manual rotation required |
| Setup Complexity | Medium (Requires Federated Credentials) | Low (Simple JSON secret) |
| Security Risk | Very Low | Medium (Secret leakage risk) |
Conclusion
Implementing a CI/CD pipeline for Azure using Terraform and GitHub Actions represents a significant leap in operational maturity. By transitioning from manual deployments to a structured workflow—characterized by the separation of plan and apply phases—teams can ensure that their infrastructure is not only automated but also secure and validated.
The adoption of OpenID Connect (OIDC) is the most critical security enhancement in this architecture. By replacing static secrets with short-lived tokens, the attack surface is dramatically reduced. Furthermore, the use of Azure Blob Storage for remote state management ensures that the infrastructure remains consistent regardless of which runner or developer initiates the change.
For engineers building complex systems like AKS clusters, the combination of modular Terraform code, data sources for dynamic lookups, and rigorous security scanning via Checkov and tfsec creates a resilient foundation. The transition toward this "GitOps" style of infrastructure management allows for faster iteration cycles, easier disaster recovery, and a transparent history of every change made to the cloud environment.
Sources
- https://simonvedder.com/automated-terraform-deployments-with-github/
- https://www.linkedin.com/pulse/automate-azure-deployments-using-github-actions-terraform-patil-cr1kf/
- https://learn.microsoft.com/en-us/samples/azure-samples/github-terraform-oidc-ci-cd/github-terraform-oidc-ci-cd/
- https://dev.to/willvelida/deploying-to-azure-with-terraform-and-github-actions-5191