This repository is a collection of GitHub Actions useful for deploying Terraform using OIDC authentication.
It supports multi-environment solutions (i.e. those that need to deploy similar code to dev, test, prod, etc), with optional support for commonly required checks such as linting and code security static analysis enabled by default.
Check the Optional Features section below to configure settings for:
- Terraform Lint
- Checkov infrastructure scanning (tfplan enriched)
- Additional TFVARs at run time
- Terraform backend options
- Automatic creation of Terraform backend
- Unlock private networking resource firewalls if not using runners
Below are manual instructions for getting started. If you're looking to automate this process, check out Az-Bootstrap.
The following steps should be completed in the calling repository:
-
Create Environments: Navigate to
Settings
->Environments
, create two for each target environment:<env_name>-iac-plan
(e.g.,dev-iac-plan
)<env_name>-iac-apply
(e.g.,dev-iac-apply
)
-
Add Required Secrets: Add the following secrets to both the
-plan
and-apply
environments you just created:ARM_CLIENT_ID
: Client ID for the User Assigned Managed Identity used for deployment.ARM_SUBSCRIPTION_ID
: Target Azure Subscription ID for resource deployment.ARM_TENANT_ID
: Azure Tenant ID.
-
For Terraform only, create the following (also in both plan and apply environments):
TF_STATE_RESOURCE_GROUP_NAME
: Resource group name containing the Terraform state storage account.TF_STATE_STORAGE_ACCOUNT_NAME
: Storage account name for Terraform state.
Create a workflow file (e.g., .github/workflows/deploy.yml
) in your repository to call the deploy template.
The example below uses workflow_dispatch
for manual triggering:
name: Terraform Deployment
on:
workflow_dispatch:
inputs:
terraform_action:
description: 'Terraform Action'
default: apply
type: choice
options:
- apply
- destroy
- plan
target_environment:
description: 'Select target environment'
required: true
type: choice
default: dev
options: # options should match your configured environments (e.g., dev, test, prod)
- dev
- test
- prod
destroy_resources:
description: 'Actually destroy resources?'
type: boolean
default: false
run-name: Terraform ${{ inputs.terraform_action }} (${{ inputs.target_environment }}) by @${{ github.actor }}
permissions:
id-token: write # Required for OIDC authentication
contents: read # Required to checkout code
pull-requests: write # Terraform Plan summaries can be written to PRs as comments
security-events: write # Allow upload of sarif outputs
jobs:
call-terraform-deploy:
name: "Run terraform ${{ inputs.terraform_action }} for ${{ inputs.target_environment }}"
uses: kewalaka/github-azure-iac-templates/.github/workflows/terraform-deploy-template.yml@main
with:
terraform_action: ${{ inputs.terraform_action }}
environment_name_plan: "${{ inputs.target_environment }}-iac-plan"
environment_name_apply: "${{ inputs.target_environment }}-iac-apply"
tfvars_file: "./environments/${{ inputs.target_environment }}.terraform.tfvars"
destroy_resources: ${{ inputs.destroy_resources == true || inputs.terraform_action == 'destroy' }}
secrets: inherit
Create a workflow file (e.g., .github/workflows/deploy-bicep.yml
) in your repository with the following content. This example uses workflow_dispatch
for manual triggering:
name: Bicep Deployment Stacks
on:
workflow_dispatch:
inputs:
bicep_action:
description: 'Bicep Action'
default: deploy
type: choice
options:
- deploy
- plan
target_environment:
description: 'Select environment'
required: true
type: choice
default: dev
options: # options should match your configured environments (e.g., dev, test, prod)
- dev
- test
- prod
deployment_scope:
description: 'Deployment scope'
required: true
type: choice
default: resourceGroup
options:
- resourceGroup
- subscription
- managementGroup
run-name: Bicep ${{ inputs.bicep_action }} (${{ inputs.target_environment }}) by @${{ github.actor }}
permissions:
id-token: write # Required for OIDC authentication
contents: read # Required to checkout code
pull-requests: write # Required for PR commenting during plan
jobs:
call-bicep-deploy:
name: "Run bicep ${{ inputs.bicep_action }} for ${{ inputs.target_environment }}"
uses: kewalaka/github-azure-iac-templates/.github/workflows/[email protected]
with:
bicep_action: ${{ inputs.bicep_action }}
environment_name_plan: "${{ inputs.target_environment }}_plan"
environment_name_apply: "${{ inputs.target_environment }}_apply"
deployment_scope: ${{ inputs.deployment_scope }}
deployment_stack_name: "${{ inputs.target_environment }}-stack" # Optional: auto-generated if not provided
root_iac_folder_relative_path: "./iac"
parameters_file_path: "parameters/${{ inputs.target_environment }}.parameters.json"
resource_group_name: "${{ inputs.deployment_scope == 'resourceGroup' && format('rg-{0}', inputs.target_environment) || '' }}"
management_group_id: "${{ inputs.deployment_scope == 'managementGroup' && 'your-mg-id' || '' }}"
location: "eastus"
action_on_unmanage: "detachAll" # Options: detachAll, deleteAll
deny_settings_mode: "none" # Options: none, denyDelete, denyWriteAndDelete
secrets: inherit
To prevent accidental deployments, configure protection rules on your -apply
environments:
- Go to Repository
Settings
->Environments
-><env_name>-iac-apply
. - Under Deployment protection rules, enable Required reviewers.
- Configure reviewers (users or teams) who must approve deployments to this environment.
- Save the protection rules.
Az-Bootstrap performs this step automatically by default.
The following section provides details of how to tune the configuration of the deployment templates.
The default folder for IaC is ./iac
. This can be modified using root_iac_folder_relative_path
This is enabled by default, can be disabled using enable_checkov: false
Check out the actions README.md for more details.
Infracost is disabled by default, can be enabled using enable_infracost: true
, and supplying the INFRACOST_API_KEY via GitHub secrets.
Secret Name | Description |
---|---|
INFRACOST_API_KEY |
API key for Infracost. Sign up for free at infracost.io to get your API key. |
It is possible to specify a list of resource firewalls to unlock during the pipeline run, however we recommend using self-hosted or managed runners instead of this feature:
Variable Name | Description | Default |
---|---|---|
EXTRA_FIREWALL_UNLOCKS |
Comma-separated list of additional storageaccountname or keyvaultname resources whose firewalls should be temporarily opened. |
(none) |
Check out the actions README.md for more details.
This is enabled by default, can be disabled using enable_static_analysis_checks: false
TFLint can further be configured in the calling repository by
placing a file .tflint.hcl
in the IaC root.
Check out the actions README.md for more details.
If the pipeline principal has sufficient permissions, it is possible to make the Terraform backend automatically. This action can also be used to check the backend is available.
This is disabled by default, can be enabled using deploy_backend: true
Check out the actions README.md for more details.
You can add these optional secrets to your environments (-plan
and -apply
) to customize behavior:
Secret Name | Description | Default |
---|---|---|
TF_STATE_SUBSCRIPTION_ID |
Subscription ID for the Terraform state storage, only required if it is not the same as the deployment subscription account. | ARM_SUBSCRIPTION_ID |
TF_STATE_STORAGE_CONTAINER_NAME |
Container name within the state storage account. | tfstate |
ARTIFACT_STORAGE_CONTAINER_NAME |
Container name for storing the Terraform plan artifact. | tfartifact |
These are supplied using TF_VAR_ environment variables, using this:
Variable Name | Description | Default |
---|---|---|
EXTRA_TF_VARS |
Comma-separated key=value pairs passed as additional -var arguments to Terraform (e.g., containertag=<SHA>,subid=<GUID> ) This should be used sparingly, only for variables that need to be computed by previous steps. |
(none) |
For Bicep deployment stacks, you can customize stack behavior using the following parameters:
Parameter Name | Description | Default | Options |
---|---|---|---|
deployment_stack_name |
Name for the deployment stack | Auto-generated from repository name | Any valid Azure resource name |
action_on_unmanage |
What happens to resources no longer managed by the stack | detachAll |
detachAll , deleteAll |
deny_settings_mode |
Operations denied on stack-managed resources | none |
none , denyDelete , denyWriteAndDelete |
Note: Stack names are automatically generated based on the calling repository name if not explicitly provided. The what-if functionality uses standard Azure deployment commands since stacks don't support what-if operations directly.
- For Bicep, Azure deployment stacks are used across resource group, subscription, and management group scopes.
- For Terraform, Azure Blob Storage is used for state and plan artifacts, to provide stronger RBAC than is available via GitHub packages.
To use these templates from another private repository within the same organization:
- Enable Access: In this template repository (
github-azure-iac-templates
), go toSettings
->Actions
->General
. Under Access, ensure "Accessible from repositories in the<your_org_name>
organization" is selected. - Update
uses
Path: In the calling workflow of the other repository, update theuses:
path to the full path of this template repository, pinning to a specific version tag (recommended):
# Replace 'your-org-name' and use a real tag
uses: your-org-name/github-azure-iac-templates/.github/workflows/[email protected]
(Reference: Managing access for Actions in an organization)