Skip to main content

How to rotate your AZURE_CREDENTIALS in GitHub with Terraform

· 3 min read

If you're deploying your Azure infrastructure from GitHub, you'll need Azure service principal credentials stored as secret variables. You may also want to rotate those credentials. This entry describes how you can use Terraform in your GitHub actions to configure and rotate your Azure service principal credentials.

In a previous post, I describe how to safely rotate credentials using Terraform. This post builds on that by showing you how to create client secrets for your Azure service principals and store them as secrets in GitHub.

First off, we'll create a Terraform module that will manage the secret rotation in Azure and GitHub. This stores everything you need to be able to deploy from GitHub to Azure, including information about the credentials, service principal, tenant and subscription.

It stores them both as an AZURE_CREDENTIALS json block that matches the output of az ad sp create-for-rbac --sdk-auth which is useful for the Azure Login GitHub Action under the variable AZURE_CREDENTIALS as well as broken out into ARM_CLIENT_ID, ARM_TENANT_ID, ARM_SUBSCRIPTION_ID and ARM_CLIENT_SECRET which is useful for running Terraform in GitHub Actions.

The format of the sdk-auth that the Azure Login GitHub Action requires is as follows:

{  "clientId": "APPLICATION_ID",  "clientSecret": "CLIENT_SECRET",  "subscriptionId": "SUBSCRIPTION_ID",  "tenantId": "TENANT_ID",  "activeDirectoryEndpointUrl": "https://login.microsoftonline.com",  "resourceManagerEndpointUrl": "https://management.azure.com/",  "activeDirectoryGraphResourceId": "https://graph.windows.net/",  "sqlManagementEndpointUrl": "https://management.core.windows.net:8443/",  "galleryEndpointUrl": "https://gallery.azure.com/",  "managementEndpointUrl": "https://management.core.windows.net/"}

The following files will create a Terraform module that will allow you to safely rotate secrets in Azure and store the currently active secret in GitHub secrets for use in GitHub Actions:

provider.tf

provider "azurerm" {  features {}}

variables.tf

variable "subscription_id" { type = string }variable "tenant_id" { type = string }variable "repository" { type = string }variable "application_id" { type = string }variable "application_object_id" { type = string }variable "date" { type = string }

main.tf

locals {  date        = tonumber(var.date)  odd_keeper  = floor((local.date + 1) / 2)  even_keeper = floor(local.date / 2)  use_even    = local.date % 2 == 0}
resource "random_uuid" "odd" {}
resource "azuread_application_password" "odd" {  application_object_id = var.application_object_id  description           = "odd"  value                 = random_password.odd.result  end_date_relative     = "1440h"  key_id                = random_uuid.odd.result}
resource "random_password" "odd" {  keepers = {    "date" = local.odd_keeper  }  length  = 64}
resource "random_uuid" "even" {}
resource "azuread_application_password" "even" {  application_object_id = var.application_object_id  description           = "even"  value                 = random_password.even.result  end_date_relative     = "1440h"  key_id                = random_uuid.even.result}
resource "random_password" "even" {  keepers = {    "date" = local.even_keeper  }  length  = 64}
resource "github_actions_secret" "terraform" {  repository      = var.repository  secret_name     = "AZURE_CREDENTIALS"  plaintext_value = <<-EOT{  "clientId": "${var.application_id}",  "clientSecret": "${local.use_even ? random_password.even.result : random_password.odd.result}",  "subscriptionId": "${var.subscription_id}",  "tenantId": "${var.tenant_id}",  "activeDirectoryEndpointUrl": "https://login.microsoftonline.com",  "resourceManagerEndpointUrl": "https://management.azure.com/",  "activeDirectoryGraphResourceId": "https://graph.windows.net/",  "sqlManagementEndpointUrl": "https://management.core.windows.net:8443/",  "galleryEndpointUrl": "https://gallery.azure.com/",  "managementEndpointUrl": "https://management.core.windows.net/"}EOT}
resource "github_actions_secret" "arm_client_id" {  repository = var.repository  secret_name = "ARM_CLIENT_ID"  plaintext_value = var.application_id}
resource "github_actions_secret" "arm_client_secret" {  repository = var.repository  secret_name = "ARM_CLIENT_SECRET"  plaintext_value = local.use_even ? random_password.even.result : random_password.odd.result}
resource "github_actions_secret" "arm_subscription_id" {  repository = var.repository  secret_name = "ARM_SUBSCRIPTION_ID"  plaintext_value = var.subscription_id}
resource "github_actions_secret" "arm_tenant_id" {  repository = var.repository  secret_name = "ARM_TENANT_ID"  plaintext_value = var.tenant_id}

Using the module#

module "example_github_azure" {  source                = "LOCATION_OF_MODULE"  subscription_id       = AZURE_SUBSCRIPTION_ID  tenant_id             = AZURE_TENANT_ID  repository            = GITHUB_REPO_NAME  application_id        = AZURE_APPLICATION_ID  application_object_id = AZURE_APPLICATION_OBJECT_ID  date                  = var.date}

To see it in action, have a look at this repository https://github.com/jamiemccrindle/azure-rotate-service-principal-github-secrets