LUKS passphrase rotation agent for Linux, integrated with HashiCorp Vault KV v2.
luks-vault runs as a background service, reads LUKS key metadata from Vault, and rotates passphrases when the configured TTL expires. It alternates between LUKS key slots 0 and 1, updates the local key file used by crypttab, and stores the new passphrase back in Vault.
- Automatic LUKS passphrase rotation based on Vault TTL
- Slot
0/1swap strategy for safer key rollover - HashiCorp Vault KV v2 integration with TLS/mTLS and AppRole or token auth
- Rotation state file for crash recovery and step resume
- Config reload via
SIGHUP/systemctl reloadwithout restart - Exponential backoff when rotation fails
- Runs as non-root user (
luks-vault) with limitedsudoaccess tocryptsetup - Systemd service included
.deband.rpmpackages viamake package- GitHub Actions CI for test, build, and release artifacts
┌─────────────┐ poll TTL ┌──────────────┐
│ HashiCorp │ ◄─────────────── │ luks-vault │
│ Vault KV v2 │ ───────────────► │ (daemon) │
└─────────────┘ write new key └──────┬───────┘
│
sudo cryptsetup │ write key file
▼
┌──────────────┐
│ LUKS device │
│ /etc/data- │
│ at-rest/key │
└──────────────┘
When TTL expires, the agent:
- Generates a new passphrase
- Adds it to the alternate LUKS key slot
- Verifies the new passphrase
- Updates
/etc/data-at-rest/key - Writes metadata to Vault
- Removes the old LUKS key slot
- Deletes the temporary key file
Each successful step is persisted to agent.state_file. If the process crashes mid-rotation, the next run resumes from the last completed step instead of starting over.
- Linux with LUKS-encrypted block device
- HashiCorp Vault with KV v2 enabled
cryptsetupsudosystemd- LUKS device already initialized with a key in slot
0or1
For LUKS setup reference, see data-at-rest guide.
Build packages locally:
make package VERSION=1.0.0Install on Debian/Ubuntu:
sudo dpkg -i dist/luks-vault_1.0.0-1_amd64.deb
sudo apt-get install -fInstall on RHEL/CentOS/Fedora:
sudo rpm -ivh dist/luks-vault-1.0.0-1.x86_64.rpmThe package installs:
| Path | Description |
|---|---|
/usr/local/bin/luks-vault |
Agent binary |
/lib/systemd/system/luks-vault.service |
Systemd unit |
/etc/sudoers.d/luks-vault |
Limited sudo rules for cryptsetup |
/etc/luks-vault/config.yaml.example |
Example configuration |
/var/lib/luks-vault/ |
Rotation state directory |
/var/log/luks-vault/ |
Log directory |
After install:
sudo vi /etc/luks-vault/config.yaml
sudo install -o luks-vault -g luks-vault -m 600 /path/to/approle-secret-id /etc/luks-vault/approle-secret-id
sudo cp vault-ca.pem vault-client.pem vault-client-key.pem /etc/luks-vault/
sudo systemctl enable --now luks-vault
sudo systemctl status luks-vaultOn first install, the package creates /etc/luks-vault/config.yaml from the example if it does not exist yet.
Release packages are also published as GitHub Actions artifacts and attached to Git tags (v*).
Configuration file: /etc/luks-vault/config.yaml
agent:
poll_interval: 10s
pid_file: /run/luks-vault/luks-vault.pid
log_file: /var/log/luks-vault/agent.log
state_file: /var/lib/luks-vault/rotation.state
vault:
address: https://vault.example.com:8200
auth_method: approle
approle:
role_id: change-me-role-id
secret_id_file: /etc/luks-vault/approle-secret-id
kv2_mount: luks/hostname
secret_path: dev/sda
tls:
ca_cert: /etc/luks-vault/vault-ca.pem
client_cert: /etc/luks-vault/vault-client.pem
client_key: /etc/luks-vault/vault-client-key.pem
skip_verify: false
luks:
enabled: true
device: /dev/sda
passphrase_dir: /etc/data-at-rest
cryptsetup_use_sudo: true| Key | Description |
|---|---|
agent.poll_interval |
How often the agent polls Vault |
agent.pid_file |
PID file for legacy daemon mode |
agent.log_file |
Log file path |
agent.state_file |
Rotation state file for crash recovery |
| Key | Description |
|---|---|
vault.address |
Vault API URL, use https:// in production |
vault.auth_method |
token or approle |
vault.token |
Static token when auth_method=token |
vault.approle.role_id |
AppRole role ID |
vault.approle.secret_id |
AppRole secret ID |
vault.approle.secret_id_file |
File containing AppRole secret ID |
vault.kv2_mount |
KV v2 mount path |
vault.secret_path |
Secret path under the mount |
vault.tls.ca_cert |
CA certificate for Vault TLS |
vault.tls.client_cert |
Client certificate for mTLS |
vault.tls.client_key |
Client private key for mTLS |
vault.tls.skip_verify |
Skip TLS verification, default false |
| Key | Description |
|---|---|
luks.enabled |
Enable LUKS rotation |
luks.device |
LUKS block device, e.g. /dev/sda |
luks.passphrase_dir |
Directory for current and temporary key files |
luks.cryptsetup_use_sudo |
Run cryptsetup via sudo -n |
Example files:
packaging/config/config.yaml.examplefor production with AppRole + mTLSconfig/config.yaml.examplefor local development with token auth
Note: Configuration uses nested YAML keys such as
vault.address,luks.device, andagent.log_file. Older flat keys likevault_addressordevice_pathare no longer supported.
Reload config without restart:
sudo systemctl reload luks-vault
# or
sudo kill -HUP $(pidof luks-vault)Minimal token-auth example for development:
vault:
address: https://127.0.0.1:8200
auth_method: token
token: change_me
kv2_mount: luks/hostname
secret_path: dev/sda
tls:
ca_cert: /etc/luks-vault/vault-ca.pem
skip_verify: falseCreate a KV v2 secret with these fields:
{
"created": "2026-07-05T12:00:00+07:00",
"key": "current_passphrase_on_luks",
"slot": "1",
"ttl": "30m"
}| Field | Description |
|---|---|
created |
RFC3339 timestamp of the current key |
key |
Current LUKS passphrase |
slot |
Active LUKS key slot (0 or 1) |
ttl |
Rotation interval (ns, us, ms, s, m, h) |
The slot value must match the active LUKS key slot on the device.
Create the local key file used by the agent and crypttab:
sudo mkdir -p /etc/data-at-rest
echo "current_passphrase_on_luks" | sudo tee /etc/data-at-rest/key
sudo chown root:luks-vault /etc/data-at-rest
sudo chmod 750 /etc/data-at-rest
sudo chown root:luks-vault /etc/data-at-rest/key
sudo chmod 640 /etc/data-at-rest/keyConfigure /etc/crypttab:
# <target name> <source device> <key file> <options>
data01 /dev/mapper/data-data01 /etc/data-at-rest/key luks
If you initialize an extra LUKS slot manually:
sudo cryptsetup -v -q luksAddKey /dev/sda -d /path/to/init-key -S 1The packaged service runs as user luks-vault, not root.
- Vault access and key file updates run as
luks-vault cryptsetupruns throughsudowith a restricted rule set in/etc/sudoers.d/luks-vault- Only these commands are allowed:
luksAddKeyluksKillSlotopen --test-passphrase
Verify sudo access:
sudo -u luks-vault sudo -n /usr/sbin/cryptsetup --versionFor legacy root-based deployment, set luks.cryptsetup_use_sudo: false and run the binary without --foreground.
Requires Go 1.26+.
git clone https://github.com/bienkma/luks-vault.git
cd luks-vault
make test
make buildCross-compile for Linux:
make build GOOS=linux GOARCH=amd64
make build GOOS=linux GOARCH=arm64Build inside Docker:
make docker-buildBuild install packages:
make package VERSION=1.0.0
make package VERSION=1.0.0 GOARCH=arm64Useful Makefile targets:
make help
make test
make vet
make deb
make rpm
make cleanForeground mode for systemd:
/usr/local/bin/luks-vault --foregroundLegacy daemon mode:
/usr/local/bin/luks-vault
/usr/local/bin/luks-vault -s quit
/usr/local/bin/luks-vault -s reloadsudo systemctl start luks-vault
sudo systemctl stop luks-vault
sudo systemctl reload luks-vault
sudo systemctl status luks-vault
sudo tail -f /var/log/luks-vault/agent.logWhen running with --foreground, logs are written to agent.log_file from the config (default: /var/log/luks-vault/agent.log). You can also inspect systemd status with:
sudo journalctl -u luks-vault -fRun tests:
go test ./...
go vet ./...CI runs on push and pull requests:
go mod tidyverification- unit tests and
go vet - linux
amd64/arm64builds .deb/.rpmpackage build- GitHub Release assets on tags
v*
.
├── agent/ # daemon, rotation workflow, TTL, state, backoff
├── config/ # configuration loader and examples
├── module/ # Vault and LUKS integrations
├── packaging/ # systemd, sudoers, nfpm, postinstall scripts
├── main.go
├── Makefile
└── .github/workflows/ # CI pipeline
Apache-2.0