diff --git a/hosts/vaulttest01/configuration.nix b/hosts/vaulttest01/configuration.nix index 76342ff..96baf1e 100644 --- a/hosts/vaulttest01/configuration.nix +++ b/hosts/vaulttest01/configuration.nix @@ -105,6 +105,17 @@ }; }; + # Test ACME certificate issuance from OpenBao PKI + # Override the global ACME server (from system/acme.nix) to use OpenBao instead of step-ca + security.acme.defaults.server = lib.mkForce "https://vault01.home.2rjus.net:8200/v1/pki_int/acme/directory"; + + # Request a certificate for this host + # Using HTTP-01 challenge with standalone listener on port 80 + security.acme.certs."vaulttest01.home.2rjus.net" = { + listenHTTP = ":80"; + enableDebugLogs = true; + }; + system.stateVersion = "25.11"; # Did you read the comment? } diff --git a/services/vault/default.nix b/services/vault/default.nix index bb30d60..3439a67 100644 --- a/services/vault/default.nix +++ b/services/vault/default.nix @@ -77,8 +77,89 @@ let fi ''; }; + + bootstrapCertScript = pkgs.writeShellApplication { + name = "bootstrap-vault-cert"; + runtimeInputs = with pkgs; [ + openbao + jq + openssl + coreutils + ]; + text = '' + # Bootstrap vault01 with a proper certificate from its own PKI + # This solves the chicken-and-egg problem where ACME clients can't trust + # vault01's self-signed certificate. + + echo "=== Bootstrapping vault01 certificate ===" + + # Use Unix socket to avoid TLS issues + export BAO_ADDR='unix:///run/openbao/openbao.sock' + + # ACME certificate directory + CERT_DIR="/var/lib/acme/vault01.home.2rjus.net" + + # Issue certificate for vault01 with vault as SAN + echo "Issuing certificate for vault01.home.2rjus.net (with SAN: vault.home.2rjus.net)..." + OUTPUT=$(bao write -format=json pki_int/issue/homelab \ + common_name="vault01.home.2rjus.net" \ + alt_names="vault.home.2rjus.net" \ + ttl="720h") + + # Create ACME directory structure + echo "Creating ACME certificate directory..." + mkdir -p "$CERT_DIR" + + # Extract certificate components to temp files + echo "$OUTPUT" | jq -r '.data.certificate' > /tmp/vault01-cert.pem + echo "$OUTPUT" | jq -r '.data.private_key' > /tmp/vault01-key.pem + echo "$OUTPUT" | jq -r '.data.issuing_ca' > /tmp/vault01-ca.pem + + # Create fullchain (cert + CA) + cat /tmp/vault01-cert.pem /tmp/vault01-ca.pem > /tmp/vault01-fullchain.pem + + # Backup old certificates if they exist + if [ -f "$CERT_DIR/fullchain.pem" ]; then + echo "Backing up old certificate..." + cp "$CERT_DIR/fullchain.pem" "$CERT_DIR/fullchain.pem.backup" + cp "$CERT_DIR/key.pem" "$CERT_DIR/key.pem.backup" + fi + + # Install new certificates + echo "Installing new certificate..." + mv /tmp/vault01-fullchain.pem "$CERT_DIR/fullchain.pem" + mv /tmp/vault01-cert.pem "$CERT_DIR/cert.pem" + mv /tmp/vault01-ca.pem "$CERT_DIR/chain.pem" + mv /tmp/vault01-key.pem "$CERT_DIR/key.pem" + + # Set proper ownership and permissions (ACME-style) + chown -R acme:acme "$CERT_DIR" + chmod 750 "$CERT_DIR" + chmod 640 "$CERT_DIR"/*.pem + + echo "Certificate installed successfully!" + echo "" + echo "Certificate details:" + openssl x509 -in "$CERT_DIR/cert.pem" -noout -subject -issuer -dates + echo "" + echo "Subject Alternative Names:" + openssl x509 -in "$CERT_DIR/cert.pem" -noout -ext subjectAltName + + echo "" + echo "Now restart openbao service:" + echo " systemctl restart openbao" + echo "" + echo "After restart, verify ACME endpoint is accessible:" + echo " curl https://vault01.home.2rjus.net:8200/v1/pki_int/acme/directory" + echo "" + echo "Once working, ACME will automatically manage certificate renewals." + ''; + }; in { + # Make bootstrap script available as a command + environment.systemPackages = [ bootstrapCertScript ]; + services.openbao = { enable = true; @@ -101,8 +182,8 @@ in systemd.services.openbao.serviceConfig = { LoadCredential = [ - "key.pem:/var/lib/openbao/key.pem" - "cert.pem:/var/lib/openbao/cert.pem" + "key.pem:/var/lib/acme/vault01.home.2rjus.net/key.pem" + "cert.pem:/var/lib/acme/vault01.home.2rjus.net/fullchain.pem" ]; # TPM2-encrypted unseal key (created manually, see setup instructions) LoadCredentialEncrypted = [ @@ -110,5 +191,16 @@ in ]; # Auto-unseal on service start ExecStartPost = "${unsealScript}/bin/openbao-unseal"; + # Add openbao user to acme group to read certificates + SupplementaryGroups = [ "acme" ]; + }; + + # ACME certificate management + # Bootstrapped with bootstrap-vault-cert, now managed by ACME + security.acme.certs."vault01.home.2rjus.net" = { + server = "https://vault01.home.2rjus.net:8200/v1/pki_int/acme/directory"; + listenHTTP = ":80"; + reloadServices = [ "openbao" ]; + extraDomainNames = [ "vault.home.2rjus.net" ]; }; } diff --git a/system/default.nix b/system/default.nix index 7957c30..3c59c8c 100644 --- a/system/default.nix +++ b/system/default.nix @@ -7,7 +7,7 @@ ./packages.nix ./nix.nix ./root-user.nix - ./root-ca.nix + ./pki/root-ca.nix ./sops.nix ./sshd.nix ./vault-secrets.nix diff --git a/system/root-ca.crt b/system/pki/root-ca.crt similarity index 100% rename from system/root-ca.crt rename to system/pki/root-ca.crt diff --git a/system/root-ca.nix b/system/pki/root-ca.nix similarity index 84% rename from system/root-ca.nix rename to system/pki/root-ca.nix index 5e5ff78..0397351 100644 --- a/system/root-ca.nix +++ b/system/pki/root-ca.nix @@ -4,6 +4,7 @@ certificateFiles = [ "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" ./root-ca.crt + ./vault-root-ca.crt ]; }; } diff --git a/system/pki/vault-root-ca.crt b/system/pki/vault-root-ca.crt new file mode 100644 index 0000000..c45d391 --- /dev/null +++ b/system/pki/vault-root-ca.crt @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICIjCCAaigAwIBAgIUQ/Bd/4kNvkPjQjgGLUMynIVzGeAwCgYIKoZIzj0EAwMw +QDELMAkGA1UEBhMCTk8xEDAOBgNVBAoTB0hvbWVsYWIxHzAdBgNVBAMTFmhvbWUu +MnJqdXMubmV0IFJvb3QgQ0EwHhcNMjYwMjAxMjIxODA5WhcNMzYwMTMwMjIxODM5 +WjBAMQswCQYDVQQGEwJOTzEQMA4GA1UEChMHSG9tZWxhYjEfMB0GA1UEAxMWaG9t +ZS4ycmp1cy5uZXQgUm9vdCBDQTB2MBAGByqGSM49AgEGBSuBBAAiA2IABH8xhIOl +Nd1Yb1OFhgIJQZM+OkwoFenOQiKfuQ4oPMxaF+fnXdKc77qPDVRjeDy61oGS38X3 +CjPOZAzS9kjo7FmVbzdqlYK7ut/OylF+8MJkCT8mFO1xvuzIXhufnyAD4aNjMGEw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEimBeAg +3JVeF4BqdC9hMZ8MYKw2MB8GA1UdIwQYMBaAFEimBeAg3JVeF4BqdC9hMZ8MYKw2 +MAoGCCqGSM49BAMDA2gAMGUCMQCvhRElHBra/XyT93SKcG6ZzIG+K+DH3J5jm6Xr +zaGj2VtdhBRVmEKaUcjU7htgSxcCMA9qHKYFcUH72W7By763M6sy8OOiGQNDSERY +VgnNv9rLCvCef1C8G2bYh/sKGZTPGQ== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/terraform/vault/pki.tf b/terraform/vault/pki.tf index 7583d41..23d47c8 100644 --- a/terraform/vault/pki.tf +++ b/terraform/vault/pki.tf @@ -62,6 +62,13 @@ resource "vault_mount" "pki_int" { description = "Intermediate CA" default_lease_ttl_seconds = 157680000 # 5 years max_lease_ttl_seconds = 157680000 # 5 years + + # Required for ACME support - allow ACME-specific response headers + allowed_response_headers = [ + "Replay-Nonce", + "Link", + "Location" + ] } resource "vault_pki_secret_backend_intermediate_cert_request" "intermediate" { @@ -139,6 +146,33 @@ resource "vault_pki_secret_backend_config_urls" "config_urls" { ] } +# Configure cluster path (required for ACME) +resource "vault_pki_secret_backend_config_cluster" "cluster" { + backend = vault_mount.pki_int.path + path = "${var.vault_address}/v1/${vault_mount.pki_int.path}" + aia_path = "${var.vault_address}/v1/${vault_mount.pki_int.path}" +} + +# Enable ACME support +resource "vault_generic_endpoint" "acme_config" { + depends_on = [ + vault_pki_secret_backend_config_cluster.cluster, + vault_pki_secret_backend_role.homelab + ] + + path = "${vault_mount.pki_int.path}/config/acme" + ignore_absent_fields = true + disable_read = true + disable_delete = true + + data_json = jsonencode({ + enabled = true + allowed_issuers = ["*"] + allowed_roles = ["*"] + default_directory_policy = "sign-verbatim" + }) +} + # ============================================================================ # Direct Certificate Issuance (Non-ACME) # ============================================================================ diff --git a/terraform/vms.tf b/terraform/vms.tf index cf15eef..de8e2af 100644 --- a/terraform/vms.tf +++ b/terraform/vms.tf @@ -50,8 +50,8 @@ locals { cpu_cores = 2 memory = 2048 disk_size = "20G" - flake_branch = "vault-bootstrap-integration" - vault_wrapped_token = "s.HwNenAYvXBsPs8uICh4CbE11" + flake_branch = "pki-migration" + vault_wrapped_token = "s.UCpQCOp7cOKDdtGGBvfRWwAt" } }