vault: add auto-unseal
All checks were successful
Run nix flake check / flake-check (push) Successful in 2m16s
All checks were successful
Run nix flake check / flake-check (push) Successful in 2m16s
This commit is contained in:
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 = {
|
||||
enable = true;
|
||||
@@ -25,5 +104,11 @@
|
||||
"key.pem:/var/lib/openbao/key.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