Skip to content

thomasbeste/vault-tpm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Vault-TPM

Hardware-backed secrets management using TPM 2.0 and LUKS encryption.

Unlike cloud-based secrets managers, Vault-TPM stores your master key in a TPM chip - physically bound to your hardware. Even if someone steals your disk, they can't decrypt your secrets without the original TPM.

Features

  • Hardware-bound security - Master key sealed to TPM 2.0, bound to boot state (PCR 0+7)
  • Encryption at rest - LUKS2 encrypted volume with AES-XTS-256
  • Double encryption - Secrets encrypted again with AES-256-GCM inside the vault
  • Pluggable backends - SQLite (default), PostgreSQL, or simple JSON file
  • Full audit trail - Every access logged with timestamps
  • Zero cloud dependencies - Everything runs on your hardware

Quick Start

1. Install TPM + LUKS Infrastructure (one-time)

# Requires root and TPM 2.0 hardware
sudo ./scripts/install.sh

This creates:

  • LUKS encrypted volume at /opt/vault-tpm/secrets.img
  • Master key sealed to TPM at handle 0x81000002
  • Auto-unlock systemd service
  • Recovery passphrase (backup unlock method if TPM fails)

2. Install Python Package

pip install vault-tpm

# With PostgreSQL support
pip install vault-tpm[postgres]

# Everything
pip install vault-tpm[all]

3. Use It

# Store a secret
vault-tpm set API_KEY "sk_live_xxx" --type api_key

# Retrieve it
vault-tpm get API_KEY

# List all secrets
vault-tpm list

# Check status
vault-tpm status

Usage

CLI

# Basic operations
vault-tpm set <KEY> <VALUE>
vault-tpm get <KEY>
vault-tpm delete <KEY>
vault-tpm list

# With secret types
vault-tpm set DB_PASSWORD "hunter2" --type password
vault-tpm set STRIPE_KEY "sk_live_xxx" --type api_key
vault-tpm set AES_KEY "base64..." --type encryption_key

# Read from stdin (for scripts)
echo "secret" | vault-tpm set MY_SECRET --stdin
cat private.pem | vault-tpm set PRIVATE_KEY --type private_key --stdin

# Different backends
vault-tpm --backend sqlite --db ./vault.db set KEY value
vault-tpm --backend postgres --db "postgresql://localhost/vault" set KEY value
vault-tpm --backend file --db ./secrets.json set KEY value

# Export/Import (for backup)
vault-tpm export -o backup.txt
vault-tpm import backup.txt --overwrite

Python Library

from vault_tpm import Vault, SecretType
from vault_tpm.backends import SqliteBackend, PostgresBackend

# SQLite (default, zero config)
vault = Vault(SqliteBackend())

# PostgreSQL (production)
vault = Vault(PostgresBackend("postgresql://localhost/vault"))

# Store secrets
vault.set("API_KEY", "sk_live_xxx", SecretType.API_KEY)
vault.set("DB_PASSWORD", "hunter2", SecretType.PASSWORD)

# Retrieve secrets
api_key = vault.get("API_KEY")

# Safe get with default
optional = vault.get_or_default("MAYBE_EXISTS", default=None)

# List and check
keys = vault.list()
exists = vault.exists("API_KEY")

# Rotate a secret
vault.rotate("API_KEY", "sk_live_new_value")

# Metadata
vault.set("KEY", "value", metadata={"purpose": "Production API"})
meta = vault.get_metadata("KEY")

Environment Variables

export VAULT_TPM_BACKEND=sqlite          # sqlite, postgres, file
export VAULT_TPM_DB=/path/to/vault.db    # Database path/connection string
export VAULT_TPM_MASTER_KEY=/path/to/key # Master key location

Master Key Resolution

The master key is searched in this order:

  1. VAULT_TPM_MASTER_KEY environment variable
  2. /opt/vault-tpm/secrets/master.key (default LUKS mount)
  3. /run/secrets/master_key (Docker/Kubernetes secrets)
  4. ~/.vault-tpm/master.key (user-local fallback)

Architecture

PCR Values Used:

  • PCR 0: Firmware measurement (BIOS/UEFI code)
  • PCR 7: Secure Boot state (ensures boot chain integrity)
flowchart TB
    subgraph TPM["TPM 2.0 Chip"]
        sealed["Sealed Object @ 0x81000002\n(Bound to PCR 0+7)"]
    end

    subgraph LUKS["LUKS2 Encrypted Volume"]
        volume["/opt/vault-tpm/secrets.img\nAES-XTS-256"]
        masterkey["master.key (32 bytes)"]
        volume --> masterkey
    end

    subgraph Backend["Storage Backend"]
        db[("SQLite / PostgreSQL / JSON")]
        secrets["Encrypted Secrets\nAES-256-GCM\n(ciphertext + nonce + tag)"]
        db --- secrets
    end

    boot{{"System Boot"}} -->|"Valid firmware +\nSecure Boot state"| TPM
    TPM -->|"Unseals LUKS key"| LUKS
    LUKS -->|"Master key decrypts"| Backend

    user(["vault-tpm CLI"])
    user -->|"get / set / delete"| Backend
Loading

Data Flow

sequenceDiagram
    participant User
    participant CLI as vault-tpm
    participant Enc as Encryption Engine
    participant DB as Storage Backend
    participant LUKS as LUKS Volume
    participant TPM as TPM 2.0

    Note over TPM,LUKS: Boot-time (once)
    TPM->>LUKS: Unseal LUKS key (PCR 0+7 valid)
    LUKS->>Enc: Load master.key

    Note over User,DB: Runtime operations
    User->>CLI: vault-tpm set KEY "secret"
    CLI->>Enc: Encrypt with master key
    Enc->>Enc: AES-256-GCM + random nonce
    Enc->>DB: Store (ciphertext, nonce, tag)
    DB-->>CLI: Success
    CLI-->>User: Secret stored

    User->>CLI: vault-tpm get KEY
    CLI->>DB: Fetch encrypted data
    DB-->>Enc: (ciphertext, nonce, tag)
    Enc->>Enc: Decrypt with master key
    Enc-->>CLI: Plaintext
    CLI-->>User: secret
Loading

Storage Backends

Backend Use Case Concurrency Audit Log
SQLite Single machine, dev Single writer
PostgreSQL Production, multi-service Full
File Minimal/embedded None

Requirements

  • Linux (TPM support required)
  • TPM 2.0 hardware (or virtual TPM for VMs)
  • Python 3.11+
  • Root access for initial setup (TPM + LUKS)
  • System packages: tpm2-tools, cryptsetup (installed by setup script)

Security Model

What's Protected

  • At rest: Master key in LUKS volume (AES-XTS-256), secrets in DB (AES-256-GCM)
  • Hardware binding: Master key only accessible when TPM unseals (correct boot state)
  • Key derivation: PBKDF2-SHA256 with 100k iterations
  • Recovery: Manual passphrase if TPM fails or PCR values change

What's NOT Protected

  • Memory: Decrypted secrets live in RAM during use
  • Root access: Root on running system can read mounted secrets
  • Physical access + boot: If attacker can boot your OS, they can read secrets

Threat Model

Threat Protected?
Disk theft (cold) ✓ LUKS + TPM seal
Database dump ✓ AES-256-GCM encrypted
Network sniffing ✓ Never leaves machine
Root on running system ✗ Can read mounted volume
Evil maid (boot tampering) ✓ PCR binding detects
Physical TPM extraction ✗ Possible with lab equipment

Comparison

Feature Vault-TPM HashiCorp Vault AWS Secrets Manager
Hardware-backed ✓ TPM Optional (HSM $$$)
Self-hosted
Zero cloud
Complexity Low High Low
Cost Free Free/$$ $$$
HA/Clustering

License

Apache 2.0

Contributing

PRs welcome. Please:

  1. Add tests for new features
  2. Run ruff check and mypy
  3. Update docs if needed

About

TPM backed security vault

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published