terraform: add vault secret managment to terraform
This commit is contained in:
9
.gitignore
vendored
9
.gitignore
vendored
@@ -10,3 +10,12 @@ terraform/terraform.tfvars
|
||||
terraform/*.auto.tfvars
|
||||
terraform/crash.log
|
||||
terraform/crash.*.log
|
||||
|
||||
terraform/vault/.terraform/
|
||||
terraform/vault/.terraform.lock.hcl
|
||||
terraform/vault/*.tfstate
|
||||
terraform/vault/*.tfstate.*
|
||||
terraform/vault/terraform.tfvars
|
||||
terraform/vault/*.auto.tfvars
|
||||
terraform/vault/crash.log
|
||||
terraform/vault/crash.*.log
|
||||
|
||||
37
terraform/vault/.terraform.lock.hcl
generated
Normal file
37
terraform/vault/.terraform.lock.hcl
generated
Normal file
@@ -0,0 +1,37 @@
|
||||
# This file is maintained automatically by "tofu init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.opentofu.org/hashicorp/random" {
|
||||
version = "3.8.1"
|
||||
constraints = "~> 3.6"
|
||||
hashes = [
|
||||
"h1:EHn3jsqOKhWjbg0X+psk0Ww96yz3N7ASqEKKuFvDFwo=",
|
||||
"zh:25c458c7c676f15705e872202dad7dcd0982e4a48e7ea1800afa5fc64e77f4c8",
|
||||
"zh:2edeaf6f1b20435b2f81855ad98a2e70956d473be9e52a5fdf57ccd0098ba476",
|
||||
"zh:44becb9d5f75d55e36dfed0c5beabaf4c92e0a2bc61a3814d698271c646d48e7",
|
||||
"zh:7699032612c3b16cc69928add8973de47b10ce81b1141f30644a0e8a895b5cd3",
|
||||
"zh:86d07aa98d17703de9fbf402c89590dc1e01dbe5671dd6bc5e487eb8fe87eee0",
|
||||
"zh:8c411c77b8390a49a8a1bc9f176529e6b32369dd33a723606c8533e5ca4d68c1",
|
||||
"zh:a5ecc8255a612652a56b28149994985e2c4dc046e5d34d416d47fa7767f5c28f",
|
||||
"zh:aea3fe1a5669b932eda9c5c72e5f327db8da707fe514aaca0d0ef60cb24892f9",
|
||||
"zh:f56e26e6977f755d7ae56fa6320af96ecf4bb09580d47cb481efbf27f1c5afff",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.opentofu.org/hashicorp/vault" {
|
||||
version = "4.8.0"
|
||||
constraints = "~> 4.0"
|
||||
hashes = [
|
||||
"h1:SQkjClJDo6SETUnq912GO8BdEExhU1ko8IG2mr4X/2A=",
|
||||
"zh:0c07ef884c03083b08a54c2cf782f3ff7e124b05e7a4438a0b90a86e60c8d080",
|
||||
"zh:13dcf2ed494c79e893b447249716d96b665616a868ffaf8f2c5abef07c7eee6f",
|
||||
"zh:6f15a29fae3a6178e5904e3c95ba22b20f362d8ee491da816048c89f30e6b2de",
|
||||
"zh:94b92a4bf7a2d250d9698a021f1ab60d1957d01b5bab81f7d9c00c2d6a9b3747",
|
||||
"zh:a9e207540ef12cd2402e37b3b7567e08de14061a0a2635fd2f4fd09e0a3382aa",
|
||||
"zh:b41667938ba541e8492036415b3f51fbd1758e456f6d5f0b63e26f4ad5728b21",
|
||||
"zh:df0b73aff5f4b51e08fc0c273db7f677994db29a81deda66d91acfcfe3f1a370",
|
||||
"zh:df904b217dc79b71a8b5f5f3ab2e52316d0f890810383721349cc10a72f7265b",
|
||||
"zh:f0e0b3e6782e0126c40f05cf87ec80978c7291d90f52d7741300b5de1d9c01ba",
|
||||
"zh:f8e599718b0ea22658eaa3e590671d3873aa723e7ce7d00daf3460ab41d3af14",
|
||||
]
|
||||
}
|
||||
201
terraform/vault/README.md
Normal file
201
terraform/vault/README.md
Normal file
@@ -0,0 +1,201 @@
|
||||
# OpenBao Terraform Configuration
|
||||
|
||||
This directory contains Terraform/OpenTofu configuration for managing OpenBao (Vault) infrastructure as code.
|
||||
|
||||
## Overview
|
||||
|
||||
Manages the following OpenBao resources:
|
||||
- **AppRole Authentication**: For host-based authentication
|
||||
- **PKI Infrastructure**: Root CA + Intermediate CA for TLS certificates
|
||||
- **KV Secrets Engine**: Key-value secret storage (v2)
|
||||
- **Policies**: Access control policies
|
||||
|
||||
## Setup
|
||||
|
||||
1. **Copy the example tfvars file:**
|
||||
```bash
|
||||
cp terraform.tfvars.example terraform.tfvars
|
||||
```
|
||||
|
||||
2. **Edit `terraform.tfvars` with your OpenBao credentials:**
|
||||
```hcl
|
||||
vault_address = "https://vault.home.2rjus.net:8200"
|
||||
vault_token = "hvs.your-root-token-here"
|
||||
vault_skip_tls_verify = true
|
||||
```
|
||||
|
||||
3. **Initialize Terraform:**
|
||||
```bash
|
||||
tofu init
|
||||
```
|
||||
|
||||
4. **Review the plan:**
|
||||
```bash
|
||||
tofu plan
|
||||
```
|
||||
|
||||
5. **Apply the configuration:**
|
||||
```bash
|
||||
tofu apply
|
||||
```
|
||||
|
||||
## Files
|
||||
|
||||
- `main.tf` - Provider configuration
|
||||
- `variables.tf` - Variable definitions
|
||||
- `approle.tf` - AppRole authentication backend and roles
|
||||
- `pki.tf` - PKI engines (root CA and intermediate CA)
|
||||
- `secrets.tf` - KV secrets engine and test secrets
|
||||
- `terraform.tfvars` - Credentials (gitignored)
|
||||
- `terraform.tfvars.example` - Example configuration
|
||||
|
||||
## Resources Created
|
||||
|
||||
### AppRole Authentication
|
||||
- AppRole backend at `approle/`
|
||||
- Host-based roles and policies (defined in `locals.host_policies`)
|
||||
|
||||
### PKI Infrastructure
|
||||
- Root CA at `pki/` (10 year TTL)
|
||||
- Intermediate CA at `pki_int/` (5 year TTL)
|
||||
- Role `homelab` for issuing certificates to `*.home.2rjus.net`
|
||||
- Certificate max TTL: 30 days
|
||||
|
||||
### Secrets
|
||||
- KV v2 engine at `secret/`
|
||||
- Secrets and policies defined in `locals.secrets` and `locals.host_policies`
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Adding a New Host
|
||||
|
||||
1. **Define the host policy in `approle.tf`:**
|
||||
```hcl
|
||||
locals {
|
||||
host_policies = {
|
||||
"monitoring01" = {
|
||||
paths = [
|
||||
"secret/data/hosts/monitoring01/*",
|
||||
"secret/data/services/prometheus/*",
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **Add secrets in `secrets.tf`:**
|
||||
```hcl
|
||||
locals {
|
||||
secrets = {
|
||||
"hosts/monitoring01/grafana-admin" = {
|
||||
auto_generate = true
|
||||
password_length = 32
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **Apply changes:**
|
||||
```bash
|
||||
tofu apply
|
||||
```
|
||||
|
||||
4. **Get AppRole credentials:**
|
||||
```bash
|
||||
# Get role_id
|
||||
bao read auth/approle/role/monitoring01/role-id
|
||||
|
||||
# Generate secret_id
|
||||
bao write -f auth/approle/role/monitoring01/secret-id
|
||||
```
|
||||
|
||||
### Issue a certificate from PKI
|
||||
|
||||
```bash
|
||||
# Issue certificate for a host
|
||||
bao write pki_int/issue/homelab \
|
||||
common_name="test.home.2rjus.net" \
|
||||
ttl="720h"
|
||||
```
|
||||
|
||||
### Read a secret
|
||||
|
||||
```bash
|
||||
# Authenticate with AppRole first
|
||||
bao write auth/approle/login \
|
||||
role_id="..." \
|
||||
secret_id="..."
|
||||
|
||||
# Read the test secret
|
||||
bao kv get secret/test/example
|
||||
```
|
||||
|
||||
## Managing Secrets
|
||||
|
||||
Secrets are defined in the `locals.secrets` block in `secrets.tf` using a declarative pattern:
|
||||
|
||||
### Auto-Generated Secrets (Recommended)
|
||||
|
||||
Most secrets can be auto-generated using the `random_password` provider:
|
||||
|
||||
```hcl
|
||||
locals {
|
||||
secrets = {
|
||||
"hosts/monitoring01/grafana-admin" = {
|
||||
auto_generate = true
|
||||
password_length = 32
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Manual Secrets
|
||||
|
||||
For secrets that must have specific values (external services, etc.):
|
||||
|
||||
```hcl
|
||||
# In variables.tf
|
||||
variable "smtp_password" {
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
# In secrets.tf locals block
|
||||
locals {
|
||||
secrets = {
|
||||
"shared/smtp/credentials" = {
|
||||
auto_generate = false
|
||||
data = {
|
||||
username = "notifications@2rjus.net"
|
||||
password = var.smtp_password
|
||||
server = "smtp.gmail.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# In terraform.tfvars
|
||||
smtp_password = "super-secret-password"
|
||||
```
|
||||
|
||||
### Path Structure
|
||||
|
||||
Secrets follow a three-tier hierarchy:
|
||||
- `hosts/{hostname}/*` - Host-specific secrets
|
||||
- `services/{service}/*` - Service-wide secrets (any host running the service)
|
||||
- `shared/{category}/*` - Shared secrets (SMTP, backup, etc.)
|
||||
|
||||
## Security Notes
|
||||
|
||||
- `terraform.tfvars` is gitignored to prevent credential leakage
|
||||
- Root token should be stored securely (consider using a limited admin token instead)
|
||||
- `skip_tls_verify = true` is acceptable for self-signed certs in homelab
|
||||
- AppRole secret_ids can be scoped to specific CIDR ranges for additional security
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Add more AppRoles for different host types
|
||||
2. Create policies for different service tiers
|
||||
3. Migrate existing sops-nix secrets to OpenBao KV
|
||||
4. Enable ACME support on PKI intermediate CA (OpenBao 2.0+)
|
||||
5. Set up SSH CA for host and user certificates
|
||||
74
terraform/vault/approle.tf
Normal file
74
terraform/vault/approle.tf
Normal file
@@ -0,0 +1,74 @@
|
||||
# Enable AppRole auth backend
|
||||
resource "vault_auth_backend" "approle" {
|
||||
type = "approle"
|
||||
path = "approle"
|
||||
}
|
||||
|
||||
# Define host access policies
|
||||
locals {
|
||||
host_policies = {
|
||||
# Example: monitoring01 host
|
||||
# "monitoring01" = {
|
||||
# paths = [
|
||||
# "secret/data/hosts/monitoring01/*",
|
||||
# "secret/data/services/prometheus/*",
|
||||
# "secret/data/services/grafana/*",
|
||||
# "secret/data/shared/smtp/*"
|
||||
# ]
|
||||
# }
|
||||
|
||||
# Example: ha1 host
|
||||
# "ha1" = {
|
||||
# paths = [
|
||||
# "secret/data/hosts/ha1/*",
|
||||
# "secret/data/shared/mqtt/*"
|
||||
# ]
|
||||
# }
|
||||
|
||||
# TODO: actually use this policy
|
||||
"ha1" = {
|
||||
paths = [
|
||||
"secret/data/hosts/ha1/*",
|
||||
]
|
||||
}
|
||||
|
||||
# TODO: actually use this policy
|
||||
"monitoring01" = {
|
||||
paths = [
|
||||
"secret/data/hosts/monitoring01/*",
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Generate policies for each host
|
||||
resource "vault_policy" "host_policies" {
|
||||
for_each = local.host_policies
|
||||
|
||||
name = "${each.key}-policy"
|
||||
|
||||
policy = <<EOT
|
||||
%{~for path in each.value.paths~}
|
||||
path "${path}" {
|
||||
capabilities = ["read", "list"]
|
||||
}
|
||||
%{~endfor~}
|
||||
EOT
|
||||
}
|
||||
|
||||
# Generate AppRoles for each host
|
||||
resource "vault_approle_auth_backend_role" "hosts" {
|
||||
for_each = local.host_policies
|
||||
|
||||
backend = vault_auth_backend.approle.path
|
||||
role_name = each.key
|
||||
token_policies = ["${each.key}-policy"]
|
||||
|
||||
# Token configuration
|
||||
token_ttl = 3600 # 1 hour
|
||||
token_max_ttl = 86400 # 24 hours
|
||||
|
||||
# Security settings
|
||||
bind_secret_id = true
|
||||
secret_id_ttl = 0 # Never expire (we'll rotate manually)
|
||||
}
|
||||
19
terraform/vault/main.tf
Normal file
19
terraform/vault/main.tf
Normal file
@@ -0,0 +1,19 @@
|
||||
terraform {
|
||||
required_version = ">= 1.0"
|
||||
required_providers {
|
||||
vault = {
|
||||
source = "hashicorp/vault"
|
||||
version = "~> 4.0"
|
||||
}
|
||||
random = {
|
||||
source = "hashicorp/random"
|
||||
version = "~> 3.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "vault" {
|
||||
address = var.vault_address
|
||||
token = var.vault_token
|
||||
skip_tls_verify = var.vault_skip_tls_verify
|
||||
}
|
||||
76
terraform/vault/secrets.tf
Normal file
76
terraform/vault/secrets.tf
Normal file
@@ -0,0 +1,76 @@
|
||||
# Enable KV v2 secrets engine
|
||||
resource "vault_mount" "kv" {
|
||||
path = "secret"
|
||||
type = "kv"
|
||||
options = { version = "2" }
|
||||
description = "KV Version 2 secret store"
|
||||
}
|
||||
|
||||
# Define all secrets with auto-generation support
|
||||
locals {
|
||||
secrets = {
|
||||
# Example host-specific secrets
|
||||
# "hosts/monitoring01/grafana-admin" = {
|
||||
# auto_generate = true
|
||||
# password_length = 32
|
||||
# }
|
||||
# "hosts/ha1/mqtt-password" = {
|
||||
# auto_generate = true
|
||||
# password_length = 24
|
||||
# }
|
||||
|
||||
# Example service secrets
|
||||
# "services/prometheus/remote-write" = {
|
||||
# auto_generate = true
|
||||
# password_length = 40
|
||||
# }
|
||||
|
||||
# Example shared secrets with manual values
|
||||
# "shared/smtp/credentials" = {
|
||||
# auto_generate = false
|
||||
# data = {
|
||||
# username = "notifications@2rjus.net"
|
||||
# password = var.smtp_password # Define in variables.tf and set in terraform.tfvars
|
||||
# server = "smtp.gmail.com"
|
||||
# }
|
||||
# }
|
||||
|
||||
# TODO: actually use the secret
|
||||
"hosts/monitoring01/grafana-admin" = {
|
||||
auto_generate = true
|
||||
password_length = 32
|
||||
}
|
||||
|
||||
# TODO: actually use the secret
|
||||
"hosts/ha1/mqtt-password" = {
|
||||
auto_generate = true
|
||||
password_length = 24
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
# Auto-generate passwords for secrets with auto_generate = true
|
||||
resource "random_password" "auto_secrets" {
|
||||
for_each = {
|
||||
for k, v in local.secrets : k => v
|
||||
if lookup(v, "auto_generate", false)
|
||||
}
|
||||
|
||||
length = each.value.password_length
|
||||
special = true
|
||||
}
|
||||
|
||||
# Create all secrets in Vault
|
||||
resource "vault_kv_secret_v2" "secrets" {
|
||||
for_each = local.secrets
|
||||
|
||||
mount = vault_mount.kv.path
|
||||
name = each.key
|
||||
|
||||
data_json = jsonencode(
|
||||
lookup(each.value, "auto_generate", false)
|
||||
? { password = random_password.auto_secrets[each.key].result }
|
||||
: each.value.data
|
||||
)
|
||||
}
|
||||
6
terraform/vault/terraform.tfvars.example
Normal file
6
terraform/vault/terraform.tfvars.example
Normal file
@@ -0,0 +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_token = "hvs.XXXXXXXXXXXXXXXXXXXX"
|
||||
vault_skip_tls_verify = true
|
||||
26
terraform/vault/variables.tf
Normal file
26
terraform/vault/variables.tf
Normal file
@@ -0,0 +1,26 @@
|
||||
variable "vault_address" {
|
||||
description = "OpenBao server address"
|
||||
type = string
|
||||
default = "https://vault.home.2rjus.net:8200"
|
||||
}
|
||||
|
||||
variable "vault_token" {
|
||||
description = "OpenBao root or admin token"
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "vault_skip_tls_verify" {
|
||||
description = "Skip TLS verification (for self-signed certs)"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
# Example variables for manual secrets
|
||||
# Uncomment and add to terraform.tfvars as needed
|
||||
|
||||
# variable "smtp_password" {
|
||||
# description = "SMTP password for notifications"
|
||||
# type = string
|
||||
# sensitive = true
|
||||
# }
|
||||
Reference in New Issue
Block a user