Truenas-unlock: tiny CLI for automatic ZFS dataset unlocking from a remote device

I’ve been using encrypted ZFS datasets on TrueNAS for a few years now. The encryption works great, but there are two annoyances:

  1. After every reboot, I need to manually unlock each dataset through the web UI
  2. If I store the keys on the NAS itself, the encryption is kind of pointless. If someone steals the server, they get both the data and the keys.

To solve this, I set up a Raspberry Pi hidden elsewhere in my house that holds the encryption keys and unlocks the datasets automatically via the TrueNAS API. I’ve been running this setup for a couple of years using a shell script, but recently rewrote it as a Python tool that’s easier to install and configure.

See it in action:

http://files.nijho.lt/truenas-unlock.mp4

What it does

  • Runs as a daemon on a separate machine (Raspberry Pi, home server, VM, etc.)
  • Polls TrueNAS every N seconds and unlocks any locked datasets
  • Stores encryption keys only on the remote device, not on the NAS

This gives you a kind of “poor man’s second factor”. The NAS can reboot and come back up automatically, but the data stays encrypted until the Pi (or whatever device you use) unlocks it. If someone steals just the NAS, the data is useless.

Installation

# Install with uv (or pip)
uv tool install truenas-unlock

Create a config file at ~/.config/truenas-unlock/config.yaml:

host: 192.168.1.214:443
api_key: ~/.secrets/truenas-api-key
skip_cert_verify: true
datasets:
  tank/photos: ~/.secrets/photos-key
  tank/documents: ~/.secrets/documents-key

Then test it and install the service:

# Test it
truenas-unlock --dry-run
# Install as a service (systemd on Linux, launchd on macOS)
truenas-unlock service install

Other commands

# Check status of all datasets
truenas-unlock status
# Lock specific datasets
truenas-unlock lock --dataset photos
# Unlock specific datasets only
truenas-unlock --dataset photos --dataset documents

Links

The original inspiration came from ThorpeJosh/truenas-zfs-unlock, which I used for a while before rewriting it. However, Docker is a little heavy for a tiny Raspberry Pi 3, so I rewrote it.

Let me know if you have questions or run into any issues.

2 Likes

That looks like you’re using the rest API. Since this is a python script, why not use our python websocket client?

I didn’t consider it but looking into it now!

Generally, you can do something like this:

from truenas_api_client import Client

with Client(“wss://<ip address>/api/current”) as c:
    assert c.call(‘auth.login_with_api_key’, key)
    c.call(‘pool.dataset.lock’, payload, job=True)

Thanks! I did try implementing a websocket client (using the /websocket legacy endpoint) and got it working for basic operations.

(Side note: I implemented my own minimal client using websocket-client since truenas_api_client isn’t on PyPI, I prefer not to rely on installing packages from Git.)

My main question is about event subscriptions, is there a way to subscribe and get notified when a dataset is locked/unlocked, rather than polling? I tried core.subscribe('pool.dataset.query') but didn’t see events when lock state changed.

If that’s possible, websockets would be a clear win. Otherwise it’s essentially polling with less HTTP overhead.

A better way to do this (IMO), instead of another device needing to unlock the TrueNAS appliance on your network, is to have the TrueNAS appliance on start-up run a script which pulls an encrypted keybag (can be just an encrypted .7z file) from a cloud storage like google drive, one drive anything, down into a ramdisk and this way you are not reliant on yet another device that can fail. Use multiple cloud providers to store the encrypted keybag if you want redundancy, TrueNAS appliance discard the keybag upon unlock, no passphrases reside on the TrueNAS appliance or even anywhere on your local network.

You can control access in the event of a device theft or whatever by killing the keybag cloud side.

OFC your publicly available keybag should be AES encrypted with a sufficiently long password (like > 40 chars), use the inbuilt 7za binary to decrypt, wget to fetch the keybag, no third party tool install necessary, all done with a living off the land shell script.

I am doing this now, except instead of cloud storage I’m hosting the keybags via nginx on cloud VPS and I have cronjob to run script on TrueNAS that fires a heartbeat http request to the cloud server occasionally. If my cloud server(s) does not hear from the TrueNAS appliance for 30 minutes, they automatically scrubs the keybag so if someone steals my NAS they have 30 minutes to get it out of my prem and get it powered up and connected to a network or my keys are gone.