Skip to main content

Safely rotate secrets with terraform

· 2 min read

Rotating secrets is a Good Thing™ as it limits the length of a time a compromised set of credentials can be used for.

It's quite difficult to make secret rotation atomic i.e. changing a secret in your identity provider at exactly the same time you change the secret in the system that uses it for authentication. Mismatches between the values will result in authentication failures.

The ideal is where the identity provider supports multiple valid secrets for a single identity e.g. Azure Service Principals. If that's the case, you can have 2 secrets active at the same time and rotate them on offset time periods e.g.:

password rotation

info

This example uses secrets that expire after 60 days and rotates them each month. The mechanism supports rotating secrets more frequently so pick an expiry window that meets your risk appetite. The limiting factor is often when infrastructure needs to be redeployed after a secret is rotated.

In the terraform below, I've set up 2 passwords, one called even and one called odd.

The odd password rotates at the beginning of the odd months and is the current password for those months i.e.:

  • January, March, May, July, September, November

And the even password rotates at the beginning of the even months and is the current password for those months i.e.:

  • February, April, June, August, October, December

And now for the terraform:

variable "date" {    type = string}
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_password" "odd" {  keepers = {    "date" = local.odd_keeper  }  length           = 64  special          = true}
resource "random_password" "even" {  keepers = {    "date" = local.even_keeper  }  length           = 64  special          = true}
# this example just outputs the password# but you'd typically use this as a property of# the system(s) that need the password.output "current_secret" {    value = local.use_even                 ? random_password.even.result                 : random_password.odd.result}

For this to work, you need to supply a date variable when you call terraform that contains the current year and month e.g.:

terraform apply -auto-approve -var="date=`date +%Y%m`"
caution

Terraform will store these secrets in terraform state, so make sure you're using a backend that is appropriately secured.