How to Unlock Encrypted Volume via API?

I want my computer that connects to TrueNAS to be able to unlock the volumes in the same script that I use to connect to the mounted volumes.

Solved

See my own comment below.

Original

I tried using the following script, but the problem is that unlocking the volume doesn’t trigger the mechanisms that make it available as a network share.

So how do I run a process that triggers the unlock, and triggers it to be available as a network share?

Only Unlocks, Does NOT Make Available on Network

#!/bin/sh
set -e
set -u

# USAGE
#     cat ./passphrase.txt | ssh '~/bin/zfs-unlock-all-encrypted-volumes'

echo "Enter the unlock phrase:"
read -r b_phrase

zfs get -t 'filesystem' encryption |
    grep -v -E 'NAME|off' |
    cut -d' ' -f1 |
    while read -r b_pool; do
        echo "Unlocking ${b_pool}..."
        echo "${b_phrase}" | zfs load-key -L file:///dev/stdin "${b_pool}" || true
done
1 Like

Those are vanilla zfs commands in your script. It’s not recommended to pursue that route, since you’ll also need to closely follow additional steps, such as mounting, mount location, etc.

The TrueNAS middleware API is different.

You need to use the CLI command (which is a terribly redundant name, by the way).


When you “unlock” a dataset using the GUI, it does the following (and possibly more):

  • Tests the key / passphrase in a dry-run
  • Unlocks the dataset(s) if the key / passphrase is correct
  • Mounts the dataset(s) under ALTROOT/<complete-dataset-name>)
  • The middleware / GUI is immediately aware of everything

You linked me to the entire manual, but what commands or APIs should I be looking for specifically?

I would imagine this is a fairly common task for anyone with encrypted volumes. Is there a repository of common recipes somewhere?

This will read TRUENAS_BASE_URL and TRUENAS_API_KEY from ~/.config/truenas/env, and passphrases from ~/.config/truenas/zfs-passphrases.conf and successfully unlock all encrypted volumes, triggering all relevant middleware.

#!/bin/sh
set -e
set -u

if ! test -s ~/.config/truenas/env; then
    mkdir -p ~/.config/truenas/
    {
        echo '# Example'
        echo '#     export TRUENAS_BASE_URL=https://truenas.local'
        echo '#     export TRUENAS_API_KEY=abc123 # from https://<truenas>/ui/apikeys'
    } > ~/.config/truenas/env
fi
if ! grep -q -v -E '^\s*(#.*)?$' ~/.config/truenas/env; then
    {
        echo ""
        echo "ERROR"
        echo "    Missing ~/.config/truenas/env"
        echo ""
        echo "SOLUTION"
        echo "    Create and save an API key from https://truenas.local/ui/apikeys"
        echo ""
    } >&2
    exit 1
fi

# shellcheck disable=SC1090
. ~/.config/truenas/env
if test -z "${TRUENAS_BASE_URL:-}" || test -z "${TRUENAS_API_KEY:-}"; then
    {
        echo ""
        echo "ERROR"
        echo "    Missing config from ~/.config/truenas/env"
        echo ""
        echo "SOLUTION"
        echo "    Set the config in this format:"
        echo "      export TRUENAS_BASE_URL=https://truenas.local # no trailing slash"
        echo "      export TRUENAS_API_KEY=abc123"
        echo ""
    } >&2
    exit 1
fi

if ! test -s ~/.config/truenas/zfs-passphrases.conf; then
    {
        echo ""
        echo "ERROR"
        echo "    Missing ~/.config/truenas/zfs-passphrases.conf"
        echo ""
        echo "SOLUTION"
        echo "    Set the passphrases in this format:"
        echo "      tank1/Data:foo bar baz"
        echo "      tankN/VolumeName:pass phrase goes here"
        echo ""
    } >&2
    exit 1
fi

fn_list() { (
    b_dataset_url="${TRUENAS_BASE_URL}/api/v2.0/pool/dataset"
    echo "    GET ${b_dataset_url} (listing dataset ids)..." >&2
    curl --fail-with-body -sS -k "${b_dataset_url}" \
        -H "Authorization: Bearer ${TRUENAS_API_KEY}" |
        jq -r '.[] | select(.encrypted == true) | .id'
); }

fn_unlock() { (
    b_dataset_id="${1}"
    b_dataset_phrase="${2}"

    b_unlock_url="${TRUENAS_BASE_URL}/api/v2.0/pool/dataset/unlock"
    printf "    POST %s (unlocking %s)..." "${b_unlock_url}" "${b_dataset_id}" >&2
    curl --fail-with-body -sS -k "${b_unlock_url}" \
        -H "Authorization: Bearer ${TRUENAS_API_KEY}" \
        -H "Content-Type: application/json" \
        -d '{ "id": "'"${b_dataset_id}"'"
            , "unlock_options": {
                "recursive": true,
                "datasets": [
                  { "name": "'"${b_dataset_id}"'"
                  , "passphrase": "'"${b_dataset_phrase}"'"}
                ]
              }
            }'
    echo " unlocked" >&2
); }

main() { (
    b_truenas_zfs_phrases="$(grep -v -E '^\s*(#.*)?$' ~/.config/truenas/zfs-passphrases.conf)"
    echo "Unlocking TrueNAS..." >&2
    fn_list | while read -r b_dataset_id; do
        b_dataset_phrase="$(
            echo "${b_truenas_zfs_phrases}" |
                grep -F "${b_dataset_id}:" |
                cut -d':' -f2
        )"
        if test -z "${b_dataset_phrase}"; then
            echo "    SKIP '${b_dataset_id}': no passphrase" >&2
            continue
        fi

        fn_unlock "${b_dataset_id}" "${b_dataset_phrase}"
    done
    echo "Done"
); }

main

See https://github.com/coolaj86/home-sweet-home/blob/main/bin/truenas-unlock-volumes for future updates.

1 Like