From 5d513fd5affafdcd6013358443b6e0f5c54c58cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torjus=20H=C3=A5kestad?= Date: Sun, 1 Feb 2026 23:07:47 +0100 Subject: [PATCH] terraform: add vault secret managment to terraform --- .gitignore | 9 + terraform/vault/.terraform.lock.hcl | 37 +++++ terraform/vault/README.md | 201 +++++++++++++++++++++++ terraform/vault/approle.tf | 74 +++++++++ terraform/vault/main.tf | 19 +++ terraform/vault/secrets.tf | 76 +++++++++ terraform/vault/terraform.tfvars.example | 6 + terraform/vault/variables.tf | 26 +++ 8 files changed, 448 insertions(+) create mode 100644 terraform/vault/.terraform.lock.hcl create mode 100644 terraform/vault/README.md create mode 100644 terraform/vault/approle.tf create mode 100644 terraform/vault/main.tf create mode 100644 terraform/vault/secrets.tf create mode 100644 terraform/vault/terraform.tfvars.example create mode 100644 terraform/vault/variables.tf diff --git a/.gitignore b/.gitignore index 8068363..fa65636 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,12 @@ terraform/terraform.tfvars terraform/*.auto.tfvars terraform/crash.log terraform/crash.*.log + +terraform/vault/.terraform/ +terraform/vault/.terraform.lock.hcl +terraform/vault/*.tfstate +terraform/vault/*.tfstate.* +terraform/vault/terraform.tfvars +terraform/vault/*.auto.tfvars +terraform/vault/crash.log +terraform/vault/crash.*.log diff --git a/terraform/vault/.terraform.lock.hcl b/terraform/vault/.terraform.lock.hcl new file mode 100644 index 0000000..dc102ca --- /dev/null +++ b/terraform/vault/.terraform.lock.hcl @@ -0,0 +1,37 @@ +# This file is maintained automatically by "tofu init". +# Manual edits may be lost in future updates. + +provider "registry.opentofu.org/hashicorp/random" { + version = "3.8.1" + constraints = "~> 3.6" + hashes = [ + "h1:EHn3jsqOKhWjbg0X+psk0Ww96yz3N7ASqEKKuFvDFwo=", + "zh:25c458c7c676f15705e872202dad7dcd0982e4a48e7ea1800afa5fc64e77f4c8", + "zh:2edeaf6f1b20435b2f81855ad98a2e70956d473be9e52a5fdf57ccd0098ba476", + "zh:44becb9d5f75d55e36dfed0c5beabaf4c92e0a2bc61a3814d698271c646d48e7", + "zh:7699032612c3b16cc69928add8973de47b10ce81b1141f30644a0e8a895b5cd3", + "zh:86d07aa98d17703de9fbf402c89590dc1e01dbe5671dd6bc5e487eb8fe87eee0", + "zh:8c411c77b8390a49a8a1bc9f176529e6b32369dd33a723606c8533e5ca4d68c1", + "zh:a5ecc8255a612652a56b28149994985e2c4dc046e5d34d416d47fa7767f5c28f", + "zh:aea3fe1a5669b932eda9c5c72e5f327db8da707fe514aaca0d0ef60cb24892f9", + "zh:f56e26e6977f755d7ae56fa6320af96ecf4bb09580d47cb481efbf27f1c5afff", + ] +} + +provider "registry.opentofu.org/hashicorp/vault" { + version = "4.8.0" + constraints = "~> 4.0" + hashes = [ + "h1:SQkjClJDo6SETUnq912GO8BdEExhU1ko8IG2mr4X/2A=", + "zh:0c07ef884c03083b08a54c2cf782f3ff7e124b05e7a4438a0b90a86e60c8d080", + "zh:13dcf2ed494c79e893b447249716d96b665616a868ffaf8f2c5abef07c7eee6f", + "zh:6f15a29fae3a6178e5904e3c95ba22b20f362d8ee491da816048c89f30e6b2de", + "zh:94b92a4bf7a2d250d9698a021f1ab60d1957d01b5bab81f7d9c00c2d6a9b3747", + "zh:a9e207540ef12cd2402e37b3b7567e08de14061a0a2635fd2f4fd09e0a3382aa", + "zh:b41667938ba541e8492036415b3f51fbd1758e456f6d5f0b63e26f4ad5728b21", + "zh:df0b73aff5f4b51e08fc0c273db7f677994db29a81deda66d91acfcfe3f1a370", + "zh:df904b217dc79b71a8b5f5f3ab2e52316d0f890810383721349cc10a72f7265b", + "zh:f0e0b3e6782e0126c40f05cf87ec80978c7291d90f52d7741300b5de1d9c01ba", + "zh:f8e599718b0ea22658eaa3e590671d3873aa723e7ce7d00daf3460ab41d3af14", + ] +} diff --git a/terraform/vault/README.md b/terraform/vault/README.md new file mode 100644 index 0000000..74c330c --- /dev/null +++ b/terraform/vault/README.md @@ -0,0 +1,201 @@ +# OpenBao Terraform Configuration + +This directory contains Terraform/OpenTofu configuration for managing OpenBao (Vault) infrastructure as code. + +## Overview + +Manages the following OpenBao resources: +- **AppRole Authentication**: For host-based authentication +- **PKI Infrastructure**: Root CA + Intermediate CA for TLS certificates +- **KV Secrets Engine**: Key-value secret storage (v2) +- **Policies**: Access control policies + +## Setup + +1. **Copy the example tfvars file:** + ```bash + cp terraform.tfvars.example terraform.tfvars + ``` + +2. **Edit `terraform.tfvars` with your OpenBao credentials:** + ```hcl + vault_address = "https://vault.home.2rjus.net:8200" + vault_token = "hvs.your-root-token-here" + vault_skip_tls_verify = true + ``` + +3. **Initialize Terraform:** + ```bash + tofu init + ``` + +4. **Review the plan:** + ```bash + tofu plan + ``` + +5. **Apply the configuration:** + ```bash + tofu apply + ``` + +## Files + +- `main.tf` - Provider configuration +- `variables.tf` - Variable definitions +- `approle.tf` - AppRole authentication backend and roles +- `pki.tf` - PKI engines (root CA and intermediate CA) +- `secrets.tf` - KV secrets engine and test secrets +- `terraform.tfvars` - Credentials (gitignored) +- `terraform.tfvars.example` - Example configuration + +## Resources Created + +### AppRole Authentication +- AppRole backend at `approle/` +- Host-based roles and policies (defined in `locals.host_policies`) + +### PKI Infrastructure +- Root CA at `pki/` (10 year TTL) +- Intermediate CA at `pki_int/` (5 year TTL) +- Role `homelab` for issuing certificates to `*.home.2rjus.net` +- Certificate max TTL: 30 days + +### Secrets +- KV v2 engine at `secret/` +- Secrets and policies defined in `locals.secrets` and `locals.host_policies` + +## Usage Examples + +### Adding a New Host + +1. **Define the host policy in `approle.tf`:** +```hcl +locals { + host_policies = { + "monitoring01" = { + paths = [ + "secret/data/hosts/monitoring01/*", + "secret/data/services/prometheus/*", + ] + } + } +} +``` + +2. **Add secrets in `secrets.tf`:** +```hcl +locals { + secrets = { + "hosts/monitoring01/grafana-admin" = { + auto_generate = true + password_length = 32 + } + } +} +``` + +3. **Apply changes:** +```bash +tofu apply +``` + +4. **Get AppRole credentials:** +```bash +# Get role_id +bao read auth/approle/role/monitoring01/role-id + +# Generate secret_id +bao write -f auth/approle/role/monitoring01/secret-id +``` + +### Issue a certificate from PKI + +```bash +# Issue certificate for a host +bao write pki_int/issue/homelab \ + common_name="test.home.2rjus.net" \ + ttl="720h" +``` + +### Read a secret + +```bash +# Authenticate with AppRole first +bao write auth/approle/login \ + role_id="..." \ + secret_id="..." + +# Read the test secret +bao kv get secret/test/example +``` + +## Managing Secrets + +Secrets are defined in the `locals.secrets` block in `secrets.tf` using a declarative pattern: + +### Auto-Generated Secrets (Recommended) + +Most secrets can be auto-generated using the `random_password` provider: + +```hcl +locals { + secrets = { + "hosts/monitoring01/grafana-admin" = { + auto_generate = true + password_length = 32 + } + } +} +``` + +### Manual Secrets + +For secrets that must have specific values (external services, etc.): + +```hcl +# In variables.tf +variable "smtp_password" { + type = string + sensitive = true +} + +# In secrets.tf locals block +locals { + secrets = { + "shared/smtp/credentials" = { + auto_generate = false + data = { + username = "notifications@2rjus.net" + password = var.smtp_password + server = "smtp.gmail.com" + } + } + } +} + +# In terraform.tfvars +smtp_password = "super-secret-password" +``` + +### Path Structure + +Secrets follow a three-tier hierarchy: +- `hosts/{hostname}/*` - Host-specific secrets +- `services/{service}/*` - Service-wide secrets (any host running the service) +- `shared/{category}/*` - Shared secrets (SMTP, backup, etc.) + +## Security Notes + +- `terraform.tfvars` is gitignored to prevent credential leakage +- Root token should be stored securely (consider using a limited admin token instead) +- `skip_tls_verify = true` is acceptable for self-signed certs in homelab +- AppRole secret_ids can be scoped to specific CIDR ranges for additional security + +## Next Steps + +1. Add more AppRoles for different host types +2. Create policies for different service tiers +3. Migrate existing sops-nix secrets to OpenBao KV +4. Enable ACME support on PKI intermediate CA (OpenBao 2.0+) +5. Set up SSH CA for host and user certificates diff --git a/terraform/vault/approle.tf b/terraform/vault/approle.tf new file mode 100644 index 0000000..cb9aac1 --- /dev/null +++ b/terraform/vault/approle.tf @@ -0,0 +1,74 @@ +# Enable AppRole auth backend +resource "vault_auth_backend" "approle" { + type = "approle" + path = "approle" +} + +# Define host access policies +locals { + host_policies = { + # Example: monitoring01 host + # "monitoring01" = { + # paths = [ + # "secret/data/hosts/monitoring01/*", + # "secret/data/services/prometheus/*", + # "secret/data/services/grafana/*", + # "secret/data/shared/smtp/*" + # ] + # } + + # Example: ha1 host + # "ha1" = { + # paths = [ + # "secret/data/hosts/ha1/*", + # "secret/data/shared/mqtt/*" + # ] + # } + + # TODO: actually use this policy + "ha1" = { + paths = [ + "secret/data/hosts/ha1/*", + ] + } + + # TODO: actually use this policy + "monitoring01" = { + paths = [ + "secret/data/hosts/monitoring01/*", + ] + } + } +} + +# Generate policies for each host +resource "vault_policy" "host_policies" { + for_each = local.host_policies + + name = "${each.key}-policy" + + policy = < v + if lookup(v, "auto_generate", false) + } + + length = each.value.password_length + special = true +} + +# Create all secrets in Vault +resource "vault_kv_secret_v2" "secrets" { + for_each = local.secrets + + mount = vault_mount.kv.path + name = each.key + + data_json = jsonencode( + lookup(each.value, "auto_generate", false) + ? { password = random_password.auto_secrets[each.key].result } + : each.value.data + ) +} diff --git a/terraform/vault/terraform.tfvars.example b/terraform/vault/terraform.tfvars.example new file mode 100644 index 0000000..23f9f87 --- /dev/null +++ b/terraform/vault/terraform.tfvars.example @@ -0,0 +1,6 @@ +# Copy this file to terraform.tfvars and fill in your values +# terraform.tfvars is gitignored to keep credentials safe + +vault_address = "https://vault.home.2rjus.net:8200" +vault_token = "hvs.XXXXXXXXXXXXXXXXXXXX" +vault_skip_tls_verify = true diff --git a/terraform/vault/variables.tf b/terraform/vault/variables.tf new file mode 100644 index 0000000..be0a9d1 --- /dev/null +++ b/terraform/vault/variables.tf @@ -0,0 +1,26 @@ +variable "vault_address" { + description = "OpenBao server address" + type = string + default = "https://vault.home.2rjus.net:8200" +} + +variable "vault_token" { + description = "OpenBao root or admin token" + type = string + sensitive = true +} + +variable "vault_skip_tls_verify" { + description = "Skip TLS verification (for self-signed certs)" + type = bool + default = true +} + +# Example variables for manual secrets +# Uncomment and add to terraform.tfvars as needed + +# variable "smtp_password" { +# description = "SMTP password for notifications" +# type = string +# sensitive = true +# }