vault: implement bootstrap integration
Some checks failed
Run nix flake check / flake-check (push) Has been cancelled
Run nix flake check / flake-check (pull_request) Successful in 2m30s

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

View File

@@ -86,3 +86,114 @@ def generate_host_files(config: HostConfig, repo_root: Path) -> None:
state_version=config.state_version,
)
(host_dir / "configuration.nix").write_text(config_content)
def generate_vault_terraform(hostname: str, repo_root: Path) -> None:
"""
Generate or update Vault Terraform configuration for a new host.
Creates/updates terraform/vault/hosts-generated.tf with:
- Host policy granting access to hosts/<hostname>/* secrets
- AppRole configuration for the host
- Placeholder secret entry (user adds actual secrets separately)
Args:
hostname: Hostname for the new host
repo_root: Path to repository root
"""
vault_tf_path = repo_root / "terraform" / "vault" / "hosts-generated.tf"
# Read existing file if it exists, otherwise start with empty structure
if vault_tf_path.exists():
content = vault_tf_path.read_text()
else:
# Create initial file structure
content = """# 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 = {
}
# 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
}
"""
# Parse existing policies from the file
import re
policies_match = re.search(
r'generated_host_policies = \{(.*?)\n \}',
content,
re.DOTALL
)
if policies_match:
policies_content = policies_match.group(1)
else:
policies_content = ""
# Check if hostname already exists
if f'"{hostname}"' in policies_content:
# Already exists, don't duplicate
return
# Add new policy entry
new_policy = f'''
"{hostname}" = {{
paths = [
"secret/data/hosts/{hostname}/*",
]
}}'''
# Insert before the closing brace
if policies_content.strip():
# There are existing entries, add after them
new_policies_content = policies_content.rstrip() + new_policy + "\n "
else:
# First entry
new_policies_content = new_policy + "\n "
# Replace the policies map
new_content = re.sub(
r'(generated_host_policies = \{)(.*?)(\n \})',
rf'\1{new_policies_content}\3',
content,
flags=re.DOTALL
)
# Write the updated file
vault_tf_path.write_text(new_content)