Encrypted Vault for VPN Secrets and Residual Logs

TL;DR: A LUKS2 file-backed vault holds VPN configuration files, private keys, PKI certificates, and any leftover runtime logs. It protects sensitive assets if a powered-off or decommissioned VPS disk is imaged or inspected.


Series Navigation: Building a VPS VPN Chain

This article is Part 2 of a 7-part series on building a multi-hop, fail-closed VPS VPN chain with Ansible, LUKS encrypted vaults, Telegram monitoring, and zero-log policy routing.

  1. Part 1: VPS Baseline: Install, Harden, and Disable Logs
  2. Part 2: Encrypted Vault for VPN Secrets and Residual Logs
  3. Part 3: Telegram Alerts Without Leaking the Exit IP
  4. Part 4: Ansible Playbook for Edge Hop Installation
  5. Part 5: VPN Chain and End-to-End Encryption Without Persisted Logs
  6. Part 6: VPN Failover Between VPS Nodes
  7. Part 7: Operational Tasks: Heal, Add, Remove, and Replace Hops

What the Encrypted Vault Achieves

In Part 1, we hardened the OS baseline and redirected system logs to volatile RAM. However, cryptographic secrets—such as WireGuard private keys and OpenVPN CA certificates—must reside on the node to terminate tunnels.

By creating a LUKS2 file-backed container, /etc/wireguard, OpenVPN PKI data, and /var/log are mounted inside an encrypted block device:

/var/lib/vpn-vault.img   LUKS2 container (~5 GB)
        └── /vault
              ├── wireguard  -> bind mounted to /etc/wireguard
              ├── openvpn    -> bind mounted to /opt/openvpn-vpn
              ├── secrets    -> Telegram bot tokens & API keys (/vault/secrets)
              ├── bin        -> custom administrative & monitoring scripts (/vault/bin)
              └── log        -> bind mounted to /var/log

If the underlying VPS host is powered down, snapshotted, or returned to the hosting provider, the disk contains only encrypted ciphertext.

Vault Initialization and Storage Layout

Create a 5 GB file container and format it with LUKS2 encryption:

# Create the image container
fallocate -l 5G /var/lib/vpn-vault.img
cryptsetup luksFormat --type luks2 /var/lib/vpn-vault.img

# Open the mapped device
cryptsetup open /var/lib/vpn-vault.img vpn-vault
mkfs.ext4 /dev/mapper/vpn-vault
mkdir -p /vault
mount /dev/mapper/vpn-vault /vault

Auto-Unlocking via Keyfile

Configure automatic boot unlocking via /etc/crypttab:

# Generate a secure keyfile
dd if=/dev/urandom of=/etc/vpn-vault.key bs=512 count=1
chmod 0400 /etc/vpn-vault.key
cryptsetup luksAddKey /var/lib/vpn-vault.img /etc/vpn-vault.key

# Append to /etc/crypttab
echo "vpn-vault /var/lib/vpn-vault.img /etc/vpn-vault.key luks" >> /etc/crypttab

Note: If the root partition is unencrypted, keep a backup of /etc/vpn-vault.key in a secure offline password manager. Without this key, vault data cannot be recovered after an OS reinstall.

Directory Migration and Bind Mounting

Migrate existing configurations into the vault and configure /etc/fstab bind mounts:

mkdir -p /vault/wireguard /vault/openvpn /vault/secrets /vault/bin /vault/log

rsync -aH /etc/wireguard/ /vault/wireguard/
rsync -aH /opt/openvpn-vpn/ /vault/openvpn/
rsync -aH /var/log/ /vault/log/

# Add bind mounts to /etc/fstab
cat <<'EOF' >> /etc/fstab
/vault/wireguard /etc/wireguard none bind 0 0
/vault/openvpn /opt/openvpn-vpn none bind 0 0
/vault/log /var/log none bind 0 0
EOF

systemctl daemon-reload
mount -a

Ensure systemd service units for WireGuard ([email protected]) and OpenVPN load after cryptsetup.target and /vault mounts are active.

Limitations and Threat Model

A LUKS vault protects offline data at rest. It does not protect against a root compromise on an active, running server. If an attacker gains root access while the server is live, they can read /etc/vpn-vault.key and access /vault directly.

A live compromise must be treated as a total loss of that specific hop node.

Automated Dead-Man Wipe (Optional)

For high-security nodes, implement an automated wipe script that destroys the vault header and keyfile if the node loses internet connectivity for an extended period:

  1. A cron job executes every 2 minutes, probing reliable external IP targets (1.1.1.1, 8.8.8.8).
  2. If connectivity fails continuously for more than 15 minutes, the script wipes the LUKS header using cryptsetup erase and shreds /etc/vpn-vault.key.
  3. To prevent false triggers during DNS failures, probe raw IP targets directly rather than hostnames.
  4. Provide an override lockfile (/etc/vpn-vault-disarm) to temporarily suspend wipes during scheduled maintenance.

Ansible Playbook Deployment

Deploy vault configuration idempotently:

ansible-playbook -i hosts.yaml playbooks/vpn-node-vault.yaml \
  -e target_vpn=vpn-1

The playbook verifies that the vault image exists, /etc/crypttab is configured, bind mounts are active, and systemd units order VPN startup after unlock.

Verification

Validate the encrypted vault status:

cryptsetup status vpn-vault
findmnt /vault
findmnt /etc/wireguard
systemctl is-active wg-quick@wg0

Reboot the server once to verify that the keyfile unlocks the volume automatically and VPN interfaces initialize without manual intervention.


Next in the series: Part 3: Telegram Alerts Without Leaking the Exit IP - Monitoring SSH logins, daily node status, and bandwidth caps via out-of-band Telegram notifications.