From 3f2f91aeddf5da39bcfc281fba0349c72e56de03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torjus=20H=C3=A5kestad?= Date: Sun, 1 Feb 2026 23:23:03 +0100 Subject: [PATCH] terraform: add vault pki management to terraform --- terraform/vault/README.md | 89 +++++++++++++++++- terraform/vault/pki.tf | 190 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 274 insertions(+), 5 deletions(-) create mode 100644 terraform/vault/pki.tf diff --git a/terraform/vault/README.md b/terraform/vault/README.md index 74c330c..eb4d3b7 100644 --- a/terraform/vault/README.md +++ b/terraform/vault/README.md @@ -109,7 +109,48 @@ bao read auth/approle/role/monitoring01/role-id bao write -f auth/approle/role/monitoring01/secret-id ``` -### Issue a certificate from PKI +### Issue Certificates from PKI + +**Method 1: ACME (Recommended for automated services)** + +First, enable ACME support: +```bash +bao write pki_int/config/acme enabled=true +``` + +ACME directory endpoint: +``` +https://vault.home.2rjus.net:8200/v1/pki_int/acme/directory +``` + +Use with ACME clients (lego, certbot, cert-manager, etc.): +```bash +# Example with lego +lego --email admin@home.2rjus.net \ + --dns manual \ + --server https://vault.home.2rjus.net:8200/v1/pki_int/acme/directory \ + --accept-tos \ + run -d test.home.2rjus.net +``` + +**Method 2: Static certificates via Terraform** + +Define in `pki.tf`: +```hcl +locals { + static_certificates = { + "monitoring" = { + common_name = "monitoring.home.2rjus.net" + alt_names = ["grafana.home.2rjus.net", "prometheus.home.2rjus.net"] + ttl = "720h" + } + } +} +``` + +Terraform will auto-issue and auto-renew these certificates. + +**Method 3: Manual CLI issuance** ```bash # Issue certificate for a host @@ -192,10 +233,48 @@ Secrets follow a three-tier hierarchy: - `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 +## Initial Setup Steps + +After deploying this configuration, perform these one-time setup tasks: + +### 1. Enable ACME +```bash +export BAO_ADDR='https://vault.home.2rjus.net:8200' +export BAO_TOKEN='your-root-token' +export BAO_SKIP_VERIFY=1 + +# Configure cluster path (required for ACME) +bao write pki_int/config/cluster path=https://vault.home.2rjus.net:8200/v1/pki_int + +# Enable ACME on intermediate CA +bao write pki_int/config/acme enabled=true + +# Verify ACME is enabled +curl -k https://vault.home.2rjus.net:8200/v1/pki_int/acme/directory +``` + +### 2. Download Root CA Certificate + +For trusting the internal CA on clients: +```bash +# Download root CA certificate +bao read -field=certificate pki/cert/ca > homelab-root-ca.crt + +# Install on NixOS hosts (add to system/default.nix or similar) +security.pki.certificateFiles = [ ./homelab-root-ca.crt ]; +``` + +### 3. Test Certificate Issuance + +```bash +# Manual test +bao write pki_int/issue/homelab common_name="test.home.2rjus.net" ttl="24h" +``` + ## Next Steps -1. Add more AppRoles for different host types -2. Create policies for different service tiers +1. Replace step-ca ACME endpoint with OpenBao in `system/acme.nix` +2. Add more AppRoles for different host types 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 +4. Set up SSH CA for host and user certificates +5. Configure auto-unseal for vault01 diff --git a/terraform/vault/pki.tf b/terraform/vault/pki.tf new file mode 100644 index 0000000..d8b1560 --- /dev/null +++ b/terraform/vault/pki.tf @@ -0,0 +1,190 @@ +# ============================================================================ +# PKI Infrastructure Configuration +# ============================================================================ +# +# This file configures a two-tier PKI hierarchy: +# - Root CA (pki/) - 10 year validity, EC P-384, kept offline (internal to Vault) +# - Intermediate CA (pki_int/) - 5 year validity, EC P-384, used for issuing certificates +# - Leaf certificates - Default to EC P-256 for optimal performance +# +# Key Type Choices: +# - Root/Intermediate: EC P-384 (secp384r1) for long-term security +# - Leaf certificates: EC P-256 (secp256r1) for performance and compatibility +# - EC provides smaller keys, faster operations, and lower CPU usage vs RSA +# +# Certificate Issuance Methods: +# +# 1. ACME (Automated Certificate Management Environment) +# - Services fetch certificates automatically using ACME protocol +# - ACME directory: https://vault.home.2rjus.net:8200/v1/pki_int/acme/directory +# - Enable ACME: bao write pki_int/config/acme enabled=true +# - Compatible with cert-manager, lego, certbot, etc. +# +# 2. Direct Issuance (Non-ACME) +# - Certificates defined in locals.static_certificates +# - Terraform manages lifecycle (issuance, renewal) +# - Useful for services without ACME support +# - Certificates auto-renew 7 days before expiry +# +# 3. Manual Issuance (CLI) +# - bao write pki_int/issue/homelab common_name="service.home.2rjus.net" +# - Useful for one-off certificates or testing +# +# ============================================================================ + +# Root CA +resource "vault_mount" "pki_root" { + path = "pki" + type = "pki" + description = "Root CA" + default_lease_ttl_seconds = 315360000 # 10 years + max_lease_ttl_seconds = 315360000 # 10 years +} + +resource "vault_pki_secret_backend_root_cert" "root" { + backend = vault_mount.pki_root.path + type = "internal" + common_name = "home.2rjus.net Root CA" + ttl = "315360000" # 10 years + format = "pem" + private_key_format = "der" + key_type = "ec" + key_bits = 384 # P-384 curve (NIST P-384, secp384r1) + exclude_cn_from_sans = true + organization = "Homelab" + country = "NO" +} + +# Intermediate CA +resource "vault_mount" "pki_int" { + path = "pki_int" + type = "pki" + description = "Intermediate CA" + default_lease_ttl_seconds = 157680000 # 5 years + max_lease_ttl_seconds = 157680000 # 5 years +} + +resource "vault_pki_secret_backend_intermediate_cert_request" "intermediate" { + backend = vault_mount.pki_int.path + type = "internal" + common_name = "home.2rjus.net Intermediate CA" + key_type = "ec" + key_bits = 384 # P-384 curve (NIST P-384, secp384r1) + organization = "Homelab" + country = "NO" +} + +resource "vault_pki_secret_backend_root_sign_intermediate" "intermediate" { + backend = vault_mount.pki_root.path + csr = vault_pki_secret_backend_intermediate_cert_request.intermediate.csr + common_name = "Homelab Intermediate CA" + ttl = "157680000" # 5 years + exclude_cn_from_sans = true + organization = "Homelab" + country = "NO" +} + +resource "vault_pki_secret_backend_intermediate_set_signed" "intermediate" { + backend = vault_mount.pki_int.path + certificate = vault_pki_secret_backend_root_sign_intermediate.intermediate.certificate +} + +# PKI Role for issuing certificates via ACME and direct issuance +resource "vault_pki_secret_backend_role" "homelab" { + backend = vault_mount.pki_int.path + name = "homelab" + allowed_domains = ["home.2rjus.net"] + allow_subdomains = true + max_ttl = 2592000 # 30 days + ttl = 2592000 # 30 days default + + # Key configuration - EC (Elliptic Curve) by default + key_type = "ec" + key_bits = 256 # P-256 curve (NIST P-256, secp256r1) + + # ACME-friendly settings + allow_ip_sans = true # Allow IP addresses in SANs + allow_localhost = false # Disable localhost + allow_bare_domains = false # Require subdomain or FQDN + allow_glob_domains = false # Don't allow glob patterns in domain names + + # Server authentication + server_flag = true + client_flag = false + code_signing_flag = false + email_protection_flag = false + + # Key usage (appropriate for EC certificates) + key_usage = [ + "DigitalSignature", + "KeyAgreement", + ] + ext_key_usage = ["ServerAuth"] + + # Certificate properties + require_cn = false # ACME doesn't always use CN +} + +# Configure CRL and issuing URLs +resource "vault_pki_secret_backend_config_urls" "config_urls" { + backend = vault_mount.pki_int.path + issuing_certificates = [ + "${var.vault_address}/v1/pki_int/ca" + ] + crl_distribution_points = [ + "${var.vault_address}/v1/pki_int/crl" + ] + ocsp_servers = [ + "${var.vault_address}/v1/pki_int/ocsp" + ] +} + +# ============================================================================ +# Direct Certificate Issuance (Non-ACME) +# ============================================================================ + +# Define static certificates to be issued directly (not via ACME) +# Useful for services that don't support ACME or need long-lived certificates +locals { + static_certificates = { + # Example: Issue a certificate for a specific service + # "vault" = { + # common_name = "vault.home.2rjus.net" + # alt_names = ["vault01.home.2rjus.net"] + # ip_sans = ["10.69.13.19"] + # ttl = "8760h" # 1 year + # } + } +} + +# Issue static certificates +resource "vault_pki_secret_backend_cert" "static_certs" { + for_each = local.static_certificates + + backend = vault_mount.pki_int.path + name = vault_pki_secret_backend_role.homelab.name + common_name = each.value.common_name + + alt_names = lookup(each.value, "alt_names", []) + ip_sans = lookup(each.value, "ip_sans", []) + ttl = lookup(each.value, "ttl", "720h") # 30 days default + + auto_renew = true + min_seconds_remaining = 604800 # Renew 7 days before expiry +} + +# Output static certificate data for use in configurations +output "static_certificates" { + description = "Static certificates issued by Vault PKI" + value = { + for k, v in vault_pki_secret_backend_cert.static_certs : k => { + common_name = v.common_name + serial = v.serial_number + expiration = v.expiration + issuing_ca = v.issuing_ca + certificate = v.certificate + private_key = v.private_key + } + } + sensitive = true +}