vault-setup #10
178
docs/vault/auto-unseal.md
Normal file
178
docs/vault/auto-unseal.md
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
# OpenBao TPM2 Auto-Unseal Setup
|
||||||
|
|
||||||
|
This document describes the one-time setup process for enabling TPM2-based auto-unsealing on vault01.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The auto-unseal feature uses systemd's `LoadCredentialEncrypted` with TPM2 to securely store and retrieve an unseal key. On service start, systemd automatically decrypts the credential using the VM's TPM, and the service unseals OpenBao.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- OpenBao must be initialized (`bao operator init` completed)
|
||||||
|
- You must have at least one unseal key from the initialization
|
||||||
|
- vault01 must have a TPM2 device (virtual TPM for Proxmox VMs)
|
||||||
|
|
||||||
|
## Initial Setup
|
||||||
|
|
||||||
|
Perform these steps on vault01 after deploying the service configuration:
|
||||||
|
|
||||||
|
### 1. Save Unseal Key
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create temporary file with one of your unseal keys
|
||||||
|
echo "paste-your-unseal-key-here" > /tmp/unseal-key.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Encrypt with TPM2
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Encrypt the key using TPM2 binding
|
||||||
|
systemd-creds encrypt \
|
||||||
|
--with-key=tpm2 \
|
||||||
|
--name=unseal-key \
|
||||||
|
/tmp/unseal-key.txt \
|
||||||
|
/var/lib/openbao/unseal-key.cred
|
||||||
|
|
||||||
|
# Set proper ownership and permissions
|
||||||
|
chown openbao:openbao /var/lib/openbao/unseal-key.cred
|
||||||
|
chmod 600 /var/lib/openbao/unseal-key.cred
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Cleanup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Securely delete the plaintext key
|
||||||
|
shred -u /tmp/unseal-key.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Test Auto-Unseal
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Restart the service - it should auto-unseal
|
||||||
|
systemctl restart openbao
|
||||||
|
|
||||||
|
# Verify it's unsealed
|
||||||
|
bao status
|
||||||
|
# Should show: Sealed = false
|
||||||
|
```
|
||||||
|
|
||||||
|
## TPM PCR Binding
|
||||||
|
|
||||||
|
The default `--with-key=tpm2` binds the credential to PCR 7 (Secure Boot state). For stricter binding that includes firmware and boot state:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemd-creds encrypt \
|
||||||
|
--with-key=tpm2 \
|
||||||
|
--tpm2-pcrs=0+7+14 \
|
||||||
|
--name=unseal-key \
|
||||||
|
/tmp/unseal-key.txt \
|
||||||
|
/var/lib/openbao/unseal-key.cred
|
||||||
|
```
|
||||||
|
|
||||||
|
PCR meanings:
|
||||||
|
- **PCR 0**: BIOS/UEFI firmware measurements
|
||||||
|
- **PCR 7**: Secure Boot state (UEFI variables)
|
||||||
|
- **PCR 14**: MOK (Machine Owner Key) state
|
||||||
|
|
||||||
|
**Trade-off**: Stricter PCR binding improves security but may require re-encrypting the credential after firmware updates or kernel changes.
|
||||||
|
|
||||||
|
## Re-provisioning
|
||||||
|
|
||||||
|
If you need to reprovision vault01 from scratch:
|
||||||
|
|
||||||
|
1. **Before destroying**: Back up your root token and all unseal keys (stored securely offline)
|
||||||
|
2. **After recreating the VM**:
|
||||||
|
- Initialize OpenBao: `bao operator init`
|
||||||
|
- Follow the setup steps above to encrypt a new unseal key with TPM2
|
||||||
|
3. **Restore data** (if migrating): Copy `/var/lib/openbao` from backup
|
||||||
|
|
||||||
|
## Handling System Changes
|
||||||
|
|
||||||
|
**After firmware updates, kernel updates, or boot configuration changes**, PCR values may change, causing TPM decryption to fail.
|
||||||
|
|
||||||
|
### Symptoms
|
||||||
|
- Service fails to start
|
||||||
|
- Logs show: `Failed to decrypt credentials`
|
||||||
|
- OpenBao remains sealed after reboot
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
1. Unseal manually with one of your offline unseal keys:
|
||||||
|
```bash
|
||||||
|
bao operator unseal
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Re-encrypt the credential with updated PCR values:
|
||||||
|
```bash
|
||||||
|
echo "your-unseal-key" > /tmp/unseal-key.txt
|
||||||
|
systemd-creds encrypt \
|
||||||
|
--with-key=tpm2 \
|
||||||
|
--name=unseal-key \
|
||||||
|
/tmp/unseal-key.txt \
|
||||||
|
/var/lib/openbao/unseal-key.cred
|
||||||
|
chown openbao:openbao /var/lib/openbao/unseal-key.cred
|
||||||
|
chmod 600 /var/lib/openbao/unseal-key.cred
|
||||||
|
shred -u /tmp/unseal-key.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Restart the service:
|
||||||
|
```bash
|
||||||
|
systemctl restart openbao
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
### What This Protects Against
|
||||||
|
- **Data at rest**: Vault data is encrypted and cannot be accessed without unsealing
|
||||||
|
- **VM snapshot theft**: An attacker with a VM snapshot cannot decrypt the unseal key without the TPM state
|
||||||
|
- **TPM binding**: The key can only be decrypted by the same VM with matching PCR values
|
||||||
|
|
||||||
|
### What This Does NOT Protect Against
|
||||||
|
- **Compromised host**: If an attacker gains root access to vault01 while running, they can access unsealed data
|
||||||
|
- **Boot-time attacks**: If an attacker can modify the boot process to match PCR values, they may retrieve the key
|
||||||
|
- **VM console access**: An attacker with VM console access during boot could potentially access the unsealed vault
|
||||||
|
|
||||||
|
### Recommendations
|
||||||
|
- **Keep offline backups** of root token and all unseal keys in a secure location (password manager, encrypted USB, etc.)
|
||||||
|
- **Use Shamir secret sharing**: The default 5-key threshold means even if the TPM key is compromised, an attacker needs the other keys
|
||||||
|
- **Monitor access**: Use OpenBao's audit logging to detect unauthorized access
|
||||||
|
- **Consider stricter PCR binding** (PCR 0+7+14) for production, accepting the maintenance overhead
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Check if credential exists
|
||||||
|
```bash
|
||||||
|
ls -la /var/lib/openbao/unseal-key.cred
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test credential decryption manually
|
||||||
|
```bash
|
||||||
|
# Should output your unseal key if TPM decryption works
|
||||||
|
systemd-creds decrypt /var/lib/openbao/unseal-key.cred -
|
||||||
|
```
|
||||||
|
|
||||||
|
### View service logs
|
||||||
|
```bash
|
||||||
|
journalctl -u openbao -n 50
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual unseal
|
||||||
|
```bash
|
||||||
|
bao operator unseal
|
||||||
|
# Enter one of your offline unseal keys when prompted
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check TPM status
|
||||||
|
```bash
|
||||||
|
# Check if TPM2 is available
|
||||||
|
ls /dev/tpm*
|
||||||
|
|
||||||
|
# View TPM PCR values
|
||||||
|
tpm2_pcrread
|
||||||
|
```
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [systemd.exec - Credentials](https://www.freedesktop.org/software/systemd/man/systemd.exec.html#Credentials)
|
||||||
|
- [systemd-creds man page](https://www.freedesktop.org/software/systemd/man/systemd-creds.html)
|
||||||
|
- [TPM2 PCR Documentation](https://uapi-group.org/specifications/specs/linux_tpm_pcr_registry/)
|
||||||
|
- [OpenBao Documentation](https://openbao.org/docs/)
|
||||||
38
services/vault/README.md
Normal file
38
services/vault/README.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# OpenBao Service Module
|
||||||
|
|
||||||
|
NixOS service module for OpenBao (open-source Vault fork) with TPM2-based auto-unsealing.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- TLS-enabled TCP listener on `0.0.0.0:8200`
|
||||||
|
- Unix socket listener at `/run/openbao/openbao.sock`
|
||||||
|
- File-based storage at `/var/lib/openbao`
|
||||||
|
- TPM2 auto-unseal on service start
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The module expects:
|
||||||
|
- TLS certificate: `/var/lib/openbao/cert.pem`
|
||||||
|
- TLS private key: `/var/lib/openbao/key.pem`
|
||||||
|
- TPM2-encrypted unseal key: `/var/lib/openbao/unseal-key.cred`
|
||||||
|
|
||||||
|
Certificates are loaded via systemd `LoadCredential`, and the unseal key via `LoadCredentialEncrypted`.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
For initial setup and configuration instructions, see:
|
||||||
|
- **Auto-unseal setup**: `/docs/vault/auto-unseal.md`
|
||||||
|
- **Terraform configuration**: `/terraform/vault/README.md`
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check seal status
|
||||||
|
bao status
|
||||||
|
|
||||||
|
# Manually seal (for maintenance)
|
||||||
|
bao operator seal
|
||||||
|
|
||||||
|
# Service will auto-unseal on restart
|
||||||
|
systemctl restart openbao
|
||||||
|
```
|
||||||
@@ -1,4 +1,83 @@
|
|||||||
{ ... }:
|
{ pkgs, ... }:
|
||||||
|
let
|
||||||
|
unsealScript = pkgs.writeShellApplication {
|
||||||
|
name = "openbao-unseal";
|
||||||
|
runtimeInputs = with pkgs; [
|
||||||
|
openbao
|
||||||
|
coreutils
|
||||||
|
gnugrep
|
||||||
|
getent
|
||||||
|
];
|
||||||
|
text = ''
|
||||||
|
# Set environment to use Unix socket
|
||||||
|
export BAO_ADDR='unix:///run/openbao/openbao.sock'
|
||||||
|
SOCKET_PATH="/run/openbao/openbao.sock"
|
||||||
|
CREDS_DIR="''${CREDENTIALS_DIRECTORY:-}"
|
||||||
|
|
||||||
|
# Wait for socket to exist
|
||||||
|
echo "Waiting for OpenBao socket..."
|
||||||
|
for _ in {1..30}; do
|
||||||
|
if [ -S "$SOCKET_PATH" ]; then
|
||||||
|
echo "Socket exists"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
# Wait for OpenBao to accept connections
|
||||||
|
echo "Waiting for OpenBao to be ready..."
|
||||||
|
for _ in {1..30}; do
|
||||||
|
output=$(timeout 2 bao status 2>&1 || true)
|
||||||
|
|
||||||
|
if echo "$output" | grep -q "Sealed.*false"; then
|
||||||
|
# Already unsealed
|
||||||
|
echo "OpenBao is already unsealed"
|
||||||
|
exit 0
|
||||||
|
elif echo "$output" | grep -qE "(Sealed|Initialized)"; then
|
||||||
|
# Got a valid response, OpenBao is ready (sealed)
|
||||||
|
echo "OpenBao is ready"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check if already unsealed
|
||||||
|
if output=$(timeout 2 bao status 2>&1 || true); then
|
||||||
|
if echo "$output" | grep -q "Sealed.*false"; then
|
||||||
|
echo "OpenBao is already unsealed"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Unseal using the TPM-decrypted keys (one per line)
|
||||||
|
if [ -n "$CREDS_DIR" ] && [ -f "$CREDS_DIR/unseal-key" ]; then
|
||||||
|
echo "Unsealing OpenBao..."
|
||||||
|
while IFS= read -r key; do
|
||||||
|
# Skip empty lines
|
||||||
|
[ -z "$key" ] && continue
|
||||||
|
|
||||||
|
echo "Applying unseal key..."
|
||||||
|
bao operator unseal "$key"
|
||||||
|
|
||||||
|
# Check if unsealed after each key
|
||||||
|
if output=$(timeout 2 bao status 2>&1 || true); then
|
||||||
|
if echo "$output" | grep -q "Sealed.*false"; then
|
||||||
|
echo "OpenBao unsealed successfully"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done < "$CREDS_DIR/unseal-key"
|
||||||
|
|
||||||
|
echo "WARNING: Applied all keys but OpenBao is still sealed"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "WARNING: Unseal key credential not found, OpenBao remains sealed"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
in
|
||||||
{
|
{
|
||||||
services.openbao = {
|
services.openbao = {
|
||||||
enable = true;
|
enable = true;
|
||||||
@@ -25,5 +104,11 @@
|
|||||||
"key.pem:/var/lib/openbao/key.pem"
|
"key.pem:/var/lib/openbao/key.pem"
|
||||||
"cert.pem:/var/lib/openbao/cert.pem"
|
"cert.pem:/var/lib/openbao/cert.pem"
|
||||||
];
|
];
|
||||||
|
# TPM2-encrypted unseal key (created manually, see setup instructions)
|
||||||
|
LoadCredentialEncrypted = [
|
||||||
|
"unseal-key:/var/lib/openbao/unseal-key.cred"
|
||||||
|
];
|
||||||
|
# Auto-unseal on service start
|
||||||
|
ExecStartPost = "${unsealScript}/bin/openbao-unseal";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user