Compare commits
3 Commits
4133eafc4e
...
3f2f91aedd
| Author | SHA1 | Date | |
|---|---|---|---|
|
3f2f91aedd
|
|||
|
5d513fd5af
|
|||
|
b6f1e80c2a
|
9
.gitignore
vendored
9
.gitignore
vendored
@@ -10,3 +10,12 @@ terraform/terraform.tfvars
|
|||||||
terraform/*.auto.tfvars
|
terraform/*.auto.tfvars
|
||||||
terraform/crash.log
|
terraform/crash.log
|
||||||
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",
|
||||||
|
]
|
||||||
|
}
|
||||||
280
terraform/vault/README.md
Normal file
280
terraform/vault/README.md
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
# 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 Certificates from PKI
|
||||||
|
|
||||||
|
**Method 1: ACME (Recommended for automated services)**
|
||||||
|
|
||||||
|
First, enable ACME support:
|
||||||
|
```bash
|
||||||
|
bao write pki_int/config/acme enabled=true
|
||||||
|
```
|
||||||
|
|
||||||
|
ACME directory endpoint:
|
||||||
|
```
|
||||||
|
https://vault.home.2rjus.net:8200/v1/pki_int/acme/directory
|
||||||
|
```
|
||||||
|
|
||||||
|
Use with ACME clients (lego, certbot, cert-manager, etc.):
|
||||||
|
```bash
|
||||||
|
# Example with lego
|
||||||
|
lego --email admin@home.2rjus.net \
|
||||||
|
--dns manual \
|
||||||
|
--server https://vault.home.2rjus.net:8200/v1/pki_int/acme/directory \
|
||||||
|
--accept-tos \
|
||||||
|
run -d test.home.2rjus.net
|
||||||
|
```
|
||||||
|
|
||||||
|
**Method 2: Static certificates via Terraform**
|
||||||
|
|
||||||
|
Define in `pki.tf`:
|
||||||
|
```hcl
|
||||||
|
locals {
|
||||||
|
static_certificates = {
|
||||||
|
"monitoring" = {
|
||||||
|
common_name = "monitoring.home.2rjus.net"
|
||||||
|
alt_names = ["grafana.home.2rjus.net", "prometheus.home.2rjus.net"]
|
||||||
|
ttl = "720h"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Terraform will auto-issue and auto-renew these certificates.
|
||||||
|
|
||||||
|
**Method 3: Manual CLI issuance**
|
||||||
|
|
||||||
|
```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
|
||||||
|
|
||||||
|
## Initial Setup Steps
|
||||||
|
|
||||||
|
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_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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Download Root CA Certificate
|
||||||
|
|
||||||
|
For trusting the internal CA on clients:
|
||||||
|
```bash
|
||||||
|
# Download root CA certificate
|
||||||
|
bao read -field=certificate pki/cert/ca > homelab-root-ca.crt
|
||||||
|
|
||||||
|
# Install on NixOS hosts (add to system/default.nix or similar)
|
||||||
|
security.pki.certificateFiles = [ ./homelab-root-ca.crt ];
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Test Certificate Issuance
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Manual test
|
||||||
|
bao write pki_int/issue/homelab common_name="test.home.2rjus.net" ttl="24h"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Replace step-ca ACME endpoint with OpenBao in `system/acme.nix`
|
||||||
|
2. Add more AppRoles for different host types
|
||||||
|
3. Migrate existing sops-nix secrets to OpenBao KV
|
||||||
|
4. Set up SSH CA for host and user certificates
|
||||||
|
5. Configure auto-unseal for vault01
|
||||||
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
|
||||||
|
}
|
||||||
190
terraform/vault/pki.tf
Normal file
190
terraform/vault/pki.tf
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# PKI Infrastructure Configuration
|
||||||
|
# ============================================================================
|
||||||
|
#
|
||||||
|
# This file configures a two-tier PKI hierarchy:
|
||||||
|
# - Root CA (pki/) - 10 year validity, EC P-384, kept offline (internal to Vault)
|
||||||
|
# - Intermediate CA (pki_int/) - 5 year validity, EC P-384, used for issuing certificates
|
||||||
|
# - Leaf certificates - Default to EC P-256 for optimal performance
|
||||||
|
#
|
||||||
|
# Key Type Choices:
|
||||||
|
# - Root/Intermediate: EC P-384 (secp384r1) for long-term security
|
||||||
|
# - Leaf certificates: EC P-256 (secp256r1) for performance and compatibility
|
||||||
|
# - EC provides smaller keys, faster operations, and lower CPU usage vs RSA
|
||||||
|
#
|
||||||
|
# Certificate Issuance Methods:
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
# - Enable ACME: bao write pki_int/config/acme enabled=true
|
||||||
|
# - Compatible with cert-manager, lego, certbot, etc.
|
||||||
|
#
|
||||||
|
# 2. Direct Issuance (Non-ACME)
|
||||||
|
# - Certificates defined in locals.static_certificates
|
||||||
|
# - Terraform manages lifecycle (issuance, renewal)
|
||||||
|
# - Useful for services without ACME support
|
||||||
|
# - Certificates auto-renew 7 days before expiry
|
||||||
|
#
|
||||||
|
# 3. Manual Issuance (CLI)
|
||||||
|
# - bao write pki_int/issue/homelab common_name="service.home.2rjus.net"
|
||||||
|
# - Useful for one-off certificates or testing
|
||||||
|
#
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Root CA
|
||||||
|
resource "vault_mount" "pki_root" {
|
||||||
|
path = "pki"
|
||||||
|
type = "pki"
|
||||||
|
description = "Root CA"
|
||||||
|
default_lease_ttl_seconds = 315360000 # 10 years
|
||||||
|
max_lease_ttl_seconds = 315360000 # 10 years
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "vault_pki_secret_backend_root_cert" "root" {
|
||||||
|
backend = vault_mount.pki_root.path
|
||||||
|
type = "internal"
|
||||||
|
common_name = "home.2rjus.net Root CA"
|
||||||
|
ttl = "315360000" # 10 years
|
||||||
|
format = "pem"
|
||||||
|
private_key_format = "der"
|
||||||
|
key_type = "ec"
|
||||||
|
key_bits = 384 # P-384 curve (NIST P-384, secp384r1)
|
||||||
|
exclude_cn_from_sans = true
|
||||||
|
organization = "Homelab"
|
||||||
|
country = "NO"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Intermediate CA
|
||||||
|
resource "vault_mount" "pki_int" {
|
||||||
|
path = "pki_int"
|
||||||
|
type = "pki"
|
||||||
|
description = "Intermediate CA"
|
||||||
|
default_lease_ttl_seconds = 157680000 # 5 years
|
||||||
|
max_lease_ttl_seconds = 157680000 # 5 years
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "vault_pki_secret_backend_intermediate_cert_request" "intermediate" {
|
||||||
|
backend = vault_mount.pki_int.path
|
||||||
|
type = "internal"
|
||||||
|
common_name = "home.2rjus.net Intermediate CA"
|
||||||
|
key_type = "ec"
|
||||||
|
key_bits = 384 # P-384 curve (NIST P-384, secp384r1)
|
||||||
|
organization = "Homelab"
|
||||||
|
country = "NO"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "vault_pki_secret_backend_root_sign_intermediate" "intermediate" {
|
||||||
|
backend = vault_mount.pki_root.path
|
||||||
|
csr = vault_pki_secret_backend_intermediate_cert_request.intermediate.csr
|
||||||
|
common_name = "Homelab Intermediate CA"
|
||||||
|
ttl = "157680000" # 5 years
|
||||||
|
exclude_cn_from_sans = true
|
||||||
|
organization = "Homelab"
|
||||||
|
country = "NO"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "vault_pki_secret_backend_intermediate_set_signed" "intermediate" {
|
||||||
|
backend = vault_mount.pki_int.path
|
||||||
|
certificate = vault_pki_secret_backend_root_sign_intermediate.intermediate.certificate
|
||||||
|
}
|
||||||
|
|
||||||
|
# PKI Role for issuing certificates via ACME and direct issuance
|
||||||
|
resource "vault_pki_secret_backend_role" "homelab" {
|
||||||
|
backend = vault_mount.pki_int.path
|
||||||
|
name = "homelab"
|
||||||
|
allowed_domains = ["home.2rjus.net"]
|
||||||
|
allow_subdomains = true
|
||||||
|
max_ttl = 2592000 # 30 days
|
||||||
|
ttl = 2592000 # 30 days default
|
||||||
|
|
||||||
|
# Key configuration - EC (Elliptic Curve) by default
|
||||||
|
key_type = "ec"
|
||||||
|
key_bits = 256 # P-256 curve (NIST P-256, secp256r1)
|
||||||
|
|
||||||
|
# ACME-friendly settings
|
||||||
|
allow_ip_sans = true # Allow IP addresses in SANs
|
||||||
|
allow_localhost = false # Disable localhost
|
||||||
|
allow_bare_domains = false # Require subdomain or FQDN
|
||||||
|
allow_glob_domains = false # Don't allow glob patterns in domain names
|
||||||
|
|
||||||
|
# Server authentication
|
||||||
|
server_flag = true
|
||||||
|
client_flag = false
|
||||||
|
code_signing_flag = false
|
||||||
|
email_protection_flag = false
|
||||||
|
|
||||||
|
# Key usage (appropriate for EC certificates)
|
||||||
|
key_usage = [
|
||||||
|
"DigitalSignature",
|
||||||
|
"KeyAgreement",
|
||||||
|
]
|
||||||
|
ext_key_usage = ["ServerAuth"]
|
||||||
|
|
||||||
|
# Certificate properties
|
||||||
|
require_cn = false # ACME doesn't always use CN
|
||||||
|
}
|
||||||
|
|
||||||
|
# Configure CRL and issuing URLs
|
||||||
|
resource "vault_pki_secret_backend_config_urls" "config_urls" {
|
||||||
|
backend = vault_mount.pki_int.path
|
||||||
|
issuing_certificates = [
|
||||||
|
"${var.vault_address}/v1/pki_int/ca"
|
||||||
|
]
|
||||||
|
crl_distribution_points = [
|
||||||
|
"${var.vault_address}/v1/pki_int/crl"
|
||||||
|
]
|
||||||
|
ocsp_servers = [
|
||||||
|
"${var.vault_address}/v1/pki_int/ocsp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Direct Certificate Issuance (Non-ACME)
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Define static certificates to be issued directly (not via ACME)
|
||||||
|
# Useful for services that don't support ACME or need long-lived certificates
|
||||||
|
locals {
|
||||||
|
static_certificates = {
|
||||||
|
# Example: Issue a certificate for a specific service
|
||||||
|
# "vault" = {
|
||||||
|
# common_name = "vault.home.2rjus.net"
|
||||||
|
# alt_names = ["vault01.home.2rjus.net"]
|
||||||
|
# ip_sans = ["10.69.13.19"]
|
||||||
|
# ttl = "8760h" # 1 year
|
||||||
|
# }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Issue static certificates
|
||||||
|
resource "vault_pki_secret_backend_cert" "static_certs" {
|
||||||
|
for_each = local.static_certificates
|
||||||
|
|
||||||
|
backend = vault_mount.pki_int.path
|
||||||
|
name = vault_pki_secret_backend_role.homelab.name
|
||||||
|
common_name = each.value.common_name
|
||||||
|
|
||||||
|
alt_names = lookup(each.value, "alt_names", [])
|
||||||
|
ip_sans = lookup(each.value, "ip_sans", [])
|
||||||
|
ttl = lookup(each.value, "ttl", "720h") # 30 days default
|
||||||
|
|
||||||
|
auto_renew = true
|
||||||
|
min_seconds_remaining = 604800 # Renew 7 days before expiry
|
||||||
|
}
|
||||||
|
|
||||||
|
# Output static certificate data for use in configurations
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
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