vault: implement bootstrap integration
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user