Skip to content

josh/tofu-age-encryption

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tofu-age-encryption

tofu-age-encryption provides an external encryption method for OpenTofu using age.

OpenTofu encrypts state with a symmetric key derived from a shared passphrase that every operator must share and rotate together. This project replaces that workflow with age's asymmetric key pairs so operators can keep private keys, specify their own recipients, and rotate access without redistributing a secret.

Usage

  1. Provide the age recipient and identity file using either environment variables or CLI flags:

    Environment variables:

    • AGE_IDENTITY_FILE (alias: AGE_KEY_FILE): path to your age identity file
    • AGE_IDENTITY (alias: AGE_KEY): age identity string; supports file:PATH, cmd:COMMAND, and command:COMMAND
    • AGE_IDENTITY_COMMAND (alias: AGE_IDENTITY_CMD): command whose output is the age identity
    • AGE_RECIPIENT or AGE_RECIPIENTS: comma-separated list of age recipients
    • AGE_RECIPIENTS_FILE: path to a file with newline-separated age recipients

    The following SOPS_-prefixed variables are also supported as aliases for compatibility with tools that expect them:

    • SOPS_AGE_KEY_FILE: alias for AGE_IDENTITY_FILE
    • SOPS_AGE_KEY: age identity string
    • SOPS_AGE_KEY_CMD: alias for AGE_IDENTITY_COMMAND
    • SOPS_AGE_RECIPIENTS: alias for AGE_RECIPIENT/AGE_RECIPIENTS (values from both variables are merged; duplicates are ignored)

    CLI flags:

  • --identity-file: path to your age identity file
  • --identity: age identity string or file:PATH, cmd:COMMAND, command:COMMAND
  • --identity-command: command whose output is the age identity
  • --recipient: may be provided multiple times or as a comma-separated list of recipients
  • --recipients-file: path to a file with newline-separated age recipients
  1. Configure OpenTofu to use the external method. For multiple recipients, build the command dynamically:
terraform {
  encryption {
    method "external" "age" {
      encrypt_command = concat(
        ["tofu-age-encryption", "--encrypt"],
        flatten([for recipient in local.age_recipients : ["--recipient", recipient]])
      )
      decrypt_command = ["tofu-age-encryption", "--decrypt", "--identity", nonsensitive(var.age_identity)]
    }

    state {
      method   = method.external.age
      enforced = true
    }

    plan {
      method   = method.external.age
      enforced = true
    }
  }
}

locals {
  age_recipients = [
    "KEY_A",
    "KEY_B",
    "KEY_C",
  ]
}

variable "age_identity" {
  type      = string
  sensitive = true
}

resource "random_pet" "example" {}

output "pet" {
  value = random_pet.example.id
}
  1. Run OpenTofu as usual. The state and plan files are encrypted with the given age recipients:
$ tofu init
$ tofu apply

Manual state recovery

If the tofu-age-encryption binary is unavailable, you can decrypt an existing state file using common command-line tools:

jq --raw-output '.encrypted_data' terraform.tfstate | base64 --decode >state.age
age --decrypt --identity key.txt state.age >state.json
jq . state.json

This process does not require tofu-age-encryption and can be used to recover the plain text state for backup or debugging.

About

Encrypt OpenTofu state data with age encryption keys

Topics

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •  

Languages