Files
nixos-servers/terraform/vault/pki.tf
Torjus Håkestad f5904738b0
Some checks failed
Run nix flake check / flake-check (push) Has been cancelled
Run nix flake check / flake-check (pull_request) Successful in 2m30s
vault: implement bootstrap integration
2026-02-03 01:09:43 +01:00

191 lines
6.5 KiB
HCL

# ============================================================================
# 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://vault01.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 = "vault01.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
}