vault: implement bootstrap integration
Some checks failed
Run nix flake check / flake-check (push) Successful in 2m31s
Run nix flake check / flake-check (pull_request) Failing after 14m16s

This commit is contained in:
2026-02-02 22:27:28 +01:00
parent b5364d2ccc
commit 01d4812280
28 changed files with 2305 additions and 84 deletions

View File

@@ -10,18 +10,25 @@ resource "proxmox_cloud_init_disk" "ci" {
pve_node = each.value.target_node
storage = "local" # Cloud-init disks must be on storage that supports ISO/snippets
# User data includes SSH keys and optionally NIXOS_FLAKE_BRANCH
# User data includes SSH keys and optionally NIXOS_FLAKE_BRANCH and Vault credentials
user_data = <<-EOT
#cloud-config
ssh_authorized_keys:
- ${each.value.ssh_public_key}
${each.value.flake_branch != null ? <<-BRANCH
${each.value.flake_branch != null || each.value.vault_wrapped_token != null ? <<-FILES
write_files:
- path: /etc/environment
- path: /run/cloud-init-env
content: |
%{~if each.value.flake_branch != null~}
NIXOS_FLAKE_BRANCH=${each.value.flake_branch}
append: true
BRANCH
%{~endif~}
%{~if each.value.vault_wrapped_token != null~}
VAULT_ADDR=https://vault01.home.2rjus.net:8200
VAULT_WRAPPED_TOKEN=${each.value.vault_wrapped_token}
VAULT_SKIP_VERIFY=1
%{~endif~}
permissions: '0600'
FILES
: ""}
EOT

View File

@@ -33,7 +33,7 @@ variable "default_target_node" {
variable "default_template_name" {
description = "Default template VM name to clone from"
type = string
default = "nixos-25.11.20260128.fa83fd8"
default = "nixos-25.11.20260131.41e216c"
}
variable "default_ssh_public_key" {

View File

@@ -19,7 +19,7 @@ Manages the following OpenBao resources:
2. **Edit `terraform.tfvars` with your OpenBao credentials:**
```hcl
vault_address = "https://vault.home.2rjus.net:8200"
vault_address = "https://vault01.home.2rjus.net:8200"
vault_token = "hvs.your-root-token-here"
vault_skip_tls_verify = true
```
@@ -120,7 +120,7 @@ bao write pki_int/config/acme enabled=true
ACME directory endpoint:
```
https://vault.home.2rjus.net:8200/v1/pki_int/acme/directory
https://vault01.home.2rjus.net:8200/v1/pki_int/acme/directory
```
Use with ACME clients (lego, certbot, cert-manager, etc.):
@@ -128,7 +128,7 @@ Use with ACME clients (lego, certbot, cert-manager, etc.):
# Example with lego
lego --email admin@home.2rjus.net \
--dns manual \
--server https://vault.home.2rjus.net:8200/v1/pki_int/acme/directory \
--server https://vault01.home.2rjus.net:8200/v1/pki_int/acme/directory \
--accept-tos \
run -d test.home.2rjus.net
```
@@ -239,18 +239,18 @@ 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_ADDR='https://vault01.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
bao write pki_int/config/cluster path=https://vault01.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
curl -k https://vault01.home.2rjus.net:8200/v1/pki_int/acme/directory
```
### 2. Download Root CA Certificate

View File

@@ -0,0 +1,48 @@
# WARNING: Auto-generated by create-host tool
# Manual edits will be overwritten when create-host is run
# Generated host policies
# Each host gets access to its own secrets under hosts/<hostname>/*
locals {
generated_host_policies = {
"vaulttest01" = {
paths = [
"secret/data/hosts/vaulttest01/*",
]
}
}
# Placeholder secrets - user should add actual secrets manually or via tofu
generated_secrets = {
}
}
# Create policies for generated hosts
resource "vault_policy" "generated_host_policies" {
for_each = local.generated_host_policies
name = "host-${each.key}"
policy = <<-EOT
# Allow host to read its own secrets
%{for path in each.value.paths~}
path "${path}" {
capabilities = ["read", "list"]
}
%{endfor~}
EOT
}
# Create AppRoles for generated hosts
resource "vault_approle_auth_backend_role" "generated_hosts" {
for_each = local.generated_host_policies
backend = vault_auth_backend.approle.path
role_name = each.key
token_policies = ["host-${each.key}"]
secret_id_ttl = 0 # Never expire (wrapped tokens provide time limit)
token_ttl = 3600
token_max_ttl = 3600
secret_id_num_uses = 0 # Unlimited uses
}

View File

@@ -16,7 +16,7 @@
#
# 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
# - 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.
#
@@ -149,7 +149,7 @@ locals {
static_certificates = {
# Example: Issue a certificate for a specific service
# "vault" = {
# common_name = "vault.home.2rjus.net"
# common_name = "vault01.home.2rjus.net"
# alt_names = ["vault01.home.2rjus.net"]
# ip_sans = ["10.69.13.19"]
# ttl = "8760h" # 1 year
@@ -169,7 +169,7 @@ resource "vault_pki_secret_backend_cert" "static_certs" {
ip_sans = lookup(each.value, "ip_sans", [])
ttl = lookup(each.value, "ttl", "720h") # 30 days default
auto_renew = true
auto_renew = true
min_seconds_remaining = 604800 # Renew 7 days before expiry
}
@@ -178,12 +178,12 @@ 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
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

View File

@@ -46,7 +46,11 @@ locals {
auto_generate = true
password_length = 24
}
# TODO: Remove after testing
"hosts/vaulttest01/test-service" = {
auto_generate = true
password_length = 32
}
}
}

View File

@@ -1,6 +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_address = "https://vault01.home.2rjus.net:8200"
vault_token = "hvs.XXXXXXXXXXXXXXXXXXXX"
vault_skip_tls_verify = true

View File

@@ -1,7 +1,7 @@
variable "vault_address" {
description = "OpenBao server address"
type = string
default = "https://vault.home.2rjus.net:8200"
default = "https://vault01.home.2rjus.net:8200"
}
variable "vault_token" {

View File

@@ -45,6 +45,14 @@ locals {
disk_size = "20G"
flake_branch = "vault-setup" # Bootstrap from this branch instead of master
}
"vaulttest01" = {
ip = "10.69.13.150/24"
cpu_cores = 2
memory = 2048
disk_size = "20G"
flake_branch = "vault-bootstrap-integration"
vault_wrapped_token = "s.HwNenAYvXBsPs8uICh4CbE11"
}
}
# Compute VM configurations with defaults applied
@@ -66,6 +74,8 @@ locals {
gateway = lookup(vm, "gateway", var.default_gateway)
# Branch configuration for bootstrap (optional, uses master if not set)
flake_branch = lookup(vm, "flake_branch", null)
# Vault configuration (optional, for automatic secret provisioning)
vault_wrapped_token = lookup(vm, "vault_wrapped_token", null)
}
}
}
@@ -138,4 +148,12 @@ resource "proxmox_vm_qemu" "vm" {
source = "/dev/urandom"
period = 1000
}
# Lifecycle configuration
lifecycle {
ignore_changes = [
clone, # Template name can change without recreating VMs
startup_shutdown, # Proxmox sets defaults (-1) that we don't need to manage
]
}
}