fix: fde-lock tries clean for 20 s, then forces truenas kill and luks wipe
This commit is contained in:
1 parent
5d490e4734
commit
1e4bec6bfe
2 files changed
+106
-53
No files matched your search
+1
-1
@@ -27,7 +27,7 @@ let
|
|||||||
Type = "oneshot";
|
Type = "oneshot";
|
||||||
ExecStart = "${fde-lock}/bin/fde-lock ${mode}";
|
ExecStart = "${fde-lock}/bin/fde-lock ${mode}";
|
||||||
StandardInput = "null";
|
StandardInput = "null";
|
||||||
TimeoutStartSec = "6min";
|
TimeoutStartSec = "4min";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+105
-52
@@ -1,6 +1,9 @@
|
|||||||
# Puts Nethergarde (truenas VM) and ashenvale_crypt to rest.
|
# Puts Nethergarde (truenas VM) and ashenvale_crypt to rest.
|
||||||
# fde-lock lock lock both, never prompts, every step time-boxed
|
# fde-lock lock lock both, never prompts, every step time-boxed
|
||||||
# fde-lock check dry run: report what lock would act on, change nothing
|
# fde-lock check dry run: report what lock would act on, change nothing
|
||||||
|
# Each part first gets a clean attempt (unmount, lock/close, shutdown) with a shared budget of
|
||||||
|
# $clean_secs. Whatever fails or is still open after that is forced: truenas is killed,
|
||||||
|
# ashenvale's users are killed and its key is wiped even if the device stays busy.
|
||||||
# Status goes to /run/fde-lock/status (one "key<TAB>value" or "step<TAB>name<TAB>result<TAB>detail" per line).
|
# Status goes to /run/fde-lock/status (one "key<TAB>value" or "step<TAB>name<TAB>result<TAB>detail" per line).
|
||||||
|
|
||||||
# writeShellApplication enables errexit; failures are handled per step instead.
|
# writeShellApplication enables errexit; failures are handled per step instead.
|
||||||
@@ -17,6 +20,7 @@ tmp="$status.tmp"
|
|||||||
truenas_ip=192.168.0.16
|
truenas_ip=192.168.0.16
|
||||||
crypt=ashenvale_crypt
|
crypt=ashenvale_crypt
|
||||||
vg=ashenvale_pool
|
vg=ashenvale_pool
|
||||||
|
clean_secs=20
|
||||||
failed=0
|
failed=0
|
||||||
|
|
||||||
mkdir -p /run/fde-lock
|
mkdir -p /run/fde-lock
|
||||||
@@ -30,7 +34,7 @@ put "started $(date -Is)"
|
|||||||
step() {
|
step() {
|
||||||
local name="$1" secs="$2" out rc
|
local name="$1" secs="$2" out rc
|
||||||
shift 2
|
shift 2
|
||||||
out="$(timeout --kill-after=5 "$secs" "$@" < /dev/null 2>&1)"
|
out="$(timeout --kill-after=3 "$secs" "$@" < /dev/null 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
out="$(printf '%s' "$out" | tr '\t\n' ' ' | cut -c1-300)"
|
out="$(printf '%s' "$out" | tr '\t\n' ' ' | cut -c1-300)"
|
||||||
if [ "$rc" -eq 0 ]; then
|
if [ "$rc" -eq 0 ]; then
|
||||||
@@ -41,9 +45,25 @@ step() {
|
|||||||
echo "$name: rc=$rc $out"
|
echo "$name: rc=$rc $out"
|
||||||
return "$rc"
|
return "$rc"
|
||||||
}
|
}
|
||||||
note() { put "step $1 info $2"; echo "$1: $2"; }
|
note() { put "step $1 $2 $3"; echo "$1: $2 $3"; }
|
||||||
|
|
||||||
running() { [ "$(incus list "^$1\$" -c s -f csv 2> /dev/null)" = RUNNING ]; }
|
# Clean steps share one deadline per part; once it has passed they are skipped.
|
||||||
|
left() { local l=$(( $1 - $(date +%s) )); [ "$l" -gt 0 ] || l=0; echo "$l"; }
|
||||||
|
cstep() {
|
||||||
|
local name="$1" t
|
||||||
|
t="$(left "$2")"
|
||||||
|
shift 2
|
||||||
|
if [ "$t" -eq 0 ]; then note "$name" skip "clean budget used up"; return 1; fi
|
||||||
|
step "$name" "$t" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# incus queries must not hang the run either.
|
||||||
|
inc() { timeout --kill-after=3 10 incus "$@" < /dev/null 2> /dev/null; }
|
||||||
|
running() { [ "$(inc list "^$1\$" -c s -f csv)" = RUNNING ]; }
|
||||||
|
|
||||||
|
# truenas counts as gone only when its qemu process is gone, whatever incus says.
|
||||||
|
truenas_qemu() { pgrep -f -- "qemu-system[^ ]* .*-name truenas( |,|$)"; }
|
||||||
|
truenas_alive() { running truenas || truenas_qemu > /dev/null; }
|
||||||
|
|
||||||
# ---------------------------------------------------------------- Nethergarde
|
# ---------------------------------------------------------------- Nethergarde
|
||||||
lock_nethergarde() {
|
lock_nethergarde() {
|
||||||
@@ -53,34 +73,44 @@ lock_nethergarde() {
|
|||||||
findmnt -rn -t nfs,nfs4 -o TARGET,SOURCE | while read -r m src; do
|
findmnt -rn -t nfs,nfs4 -o TARGET,SOURCE | while read -r m src; do
|
||||||
case "$src" in
|
case "$src" in
|
||||||
"$truenas_ip":*)
|
"$truenas_ip":*)
|
||||||
if [ "$mode" = check ]; then note "nfs-umount" "would detach $m"
|
if [ "$mode" = check ]; then note "nfs-umount" info "would detach $m"
|
||||||
else step "nfs-umount $m" 15 umount -f -l "$m" || true; fi ;;
|
else step "nfs-umount $m" 5 umount -f -l "$m" || true; fi ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
if ! running truenas; then
|
if ! truenas_alive; then
|
||||||
note "truenas" "not running, keys not in memory"
|
note "truenas" info "not running, keys not in memory"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$mode" = check ]; then
|
if [ "$mode" = check ]; then
|
||||||
step "truenas-query" 30 incus exec -T truenas -- midclt call pool.dataset.query \
|
step "truenas-query" 15 incus exec -T truenas -- midclt call pool.dataset.query \
|
||||||
'[["id","=","Nethergarde"]]' '{"select":["id","encrypted","key_format","locked"]}' || failed=1
|
'[["id","=","Nethergarde"]]' '{"select":["id","encrypted","key_format","locked"]}'
|
||||||
|
note "truenas-qemu" info "pid $(truenas_qemu | tr '\n' ' ')"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Unlocked ZFS keys are zeroed by unload-key; force_umount stops the shares that hold the pool.
|
# Clean: unload-key zeroes the ZFS key, then a regular shutdown, both within the budget.
|
||||||
step "truenas-lock" 120 incus exec -T truenas -- midclt call -j pool.dataset.lock Nethergarde \
|
local dl t
|
||||||
'{"force_umount": true}' || failed=1
|
dl=$(( $(date +%s) + clean_secs ))
|
||||||
step "truenas-locked?" 30 incus exec -T truenas -- midclt call pool.dataset.query \
|
cstep "truenas-lock" "$dl" incus exec -T truenas -- midclt call -j pool.dataset.lock Nethergarde \
|
||||||
'[["id","=","Nethergarde"]]' '{"select":["id","locked"]}' || true
|
'{"force_umount": true}'
|
||||||
|
t="$(left "$dl")"
|
||||||
|
if [ "$t" -gt 3 ]; then
|
||||||
|
step "truenas-stop" "$t" incus stop truenas --timeout $(( t - 3 ))
|
||||||
|
else
|
||||||
|
note "truenas-stop" skip "clean budget used up"
|
||||||
|
fi
|
||||||
|
|
||||||
# Stop the VM in any case, so no key can stay in its memory (init_on_free zeroes it on the host).
|
# Forced: whatever is left of the VM goes, its memory (and every key in it) with it.
|
||||||
step "truenas-stop" 100 incus stop truenas --timeout 90 || true
|
if truenas_alive; then step "truenas-stop-force" 10 incus stop truenas --force; fi
|
||||||
if running truenas; then
|
if truenas_qemu > /dev/null; then step "truenas-kill" 5 pkill -9 -f -- "qemu-system[^ ]* .*-name truenas( |,|$)"; fi
|
||||||
step "truenas-stop-force" 30 incus stop truenas --force || failed=1
|
sleep 1
|
||||||
|
if truenas_qemu > /dev/null; then
|
||||||
|
note "truenas" fail "qemu still alive: $(truenas_qemu | tr '\n' ' ')"
|
||||||
|
else
|
||||||
|
note "truenas" ok "stopped, memory released"
|
||||||
fi
|
fi
|
||||||
running truenas && failed=1
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,57 +128,81 @@ ashenvale_mounts() {
|
|||||||
|
|
||||||
lock_ashenvale() {
|
lock_ashenvale() {
|
||||||
if [ ! -e "/dev/mapper/$crypt" ]; then
|
if [ ! -e "/dev/mapper/$crypt" ]; then
|
||||||
note "ashenvale" "$crypt not open, nothing to do"
|
note "ashenvale" info "$crypt not open, nothing to do"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Detach every ashenvale_pool volume from running instances (today: phonecam on varian).
|
# Volumes of the pool attached to instances (today: phonecam on varian), as "vol inst path".
|
||||||
local inst vol
|
local vol inst path attached=""
|
||||||
for vol in $(incus storage volume list "$vg" type=custom -c n -f csv 2> /dev/null); do
|
for vol in $(inc storage volume list "$vg" type=custom -c n -f csv); do
|
||||||
for inst in $(incus storage volume show "$vg" "custom/$vol" 2> /dev/null \
|
for inst in $(inc storage volume show "$vg" "custom/$vol" \
|
||||||
| sed -n 's#^ *- /1.0/instances/\([^?]*\).*#\1#p'); do
|
| sed -n 's#^ *- /1.0/instances/\([^?]*\).*#\1#p'); do
|
||||||
if [ "$mode" = check ]; then note "detach" "would detach $vol from $inst"; continue; fi
|
attached+="$vol $inst $(inc config device get "$inst" "$vol" path)"$'\n'
|
||||||
local path
|
|
||||||
path="$(incus config device get "$inst" "$vol" path 2> /dev/null)"
|
|
||||||
if [ -n "$path" ] && running "$inst"; then
|
|
||||||
step "guest-umount $inst:$path" 30 incus exec -T "$inst" -- umount "$path" \
|
|
||||||
|| step "guest-umount-lazy $inst:$path" 15 incus exec -T "$inst" -- umount -l "$path" || true
|
|
||||||
fi
|
|
||||||
step "detach $vol from $inst" 60 incus storage volume detach "$vg" "$vol" "$inst" || true
|
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
local m
|
local m
|
||||||
if [ "$mode" = check ]; then
|
if [ "$mode" = check ]; then
|
||||||
for m in $(ashenvale_mounts); do note "host-umount" "would umount $m"; done
|
while read -r vol inst path; do
|
||||||
note "ashenvale" "would close $crypt"
|
[ -n "$vol" ] && note "detach" info "would detach $vol from $inst ($path)"
|
||||||
|
done <<< "$attached"
|
||||||
|
for m in $(ashenvale_mounts); do note "host-umount" info "would umount $m"; done
|
||||||
|
note "ashenvale" info "would close $crypt"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Whatever still serves the volume (virtiofsd after a failed detach) keeps it busy.
|
# Clean: unmount in the guest, detach, unmount on the host, close, all within the budget.
|
||||||
for m in $(ashenvale_mounts); do
|
local dl
|
||||||
pkill -f -- "--shared-dir=$m( |$)" || true
|
dl=$(( $(date +%s) + clean_secs ))
|
||||||
done
|
while read -r vol inst path; do
|
||||||
|
[ -n "$vol" ] || continue
|
||||||
|
if [ -n "$path" ] && running "$inst"; then
|
||||||
|
cstep "guest-umount $inst:$path" "$dl" incus exec -T "$inst" -- umount "$path"
|
||||||
|
fi
|
||||||
|
cstep "detach $vol from $inst" "$dl" incus storage volume detach "$vg" "$vol" "$inst"
|
||||||
|
done <<< "$attached"
|
||||||
for m in $(ashenvale_mounts | sort -r); do
|
for m in $(ashenvale_mounts | sort -r); do
|
||||||
step "host-umount $m" 30 umount "$m" || step "host-umount-lazy $m" 15 umount -l "$m" || true
|
cstep "host-umount $m" "$dl" umount "$m"
|
||||||
done
|
done
|
||||||
|
cstep "vg-deactivate" "$dl" vgchange -an "$vg"
|
||||||
step "vg-deactivate" 30 vgchange -an "$vg" || true
|
cstep "luks-close" "$dl" cryptsetup close "$crypt"
|
||||||
step "luks-close" 30 cryptsetup close "$crypt" || true
|
|
||||||
|
|
||||||
if [ -e "/dev/mapper/$crypt" ]; then
|
if [ -e "/dev/mapper/$crypt" ]; then
|
||||||
# Still busy: drop the key now (suspend wipes it from the kernel), then replace the
|
# Forced: kill whatever still serves the volume (virtiofsd), detach the mounts, retry the close.
|
||||||
# stack with error targets so nothing waits on the suspended device.
|
for m in $(ashenvale_mounts); do
|
||||||
step "luks-suspend" 30 cryptsetup luksSuspend "$crypt" || true
|
if pkill -9 -f -- "--shared-dir=$m( |$)"; then note "virtiofsd-kill $m" ok ""; fi
|
||||||
for m in $(dmsetup ls 2> /dev/null | awk -v p="$vg-" 'index($1, p) == 1 {print $1}'); do
|
|
||||||
step "dm-remove-force $m" 15 dmsetup remove --force "$m" || true
|
|
||||||
done
|
done
|
||||||
step "dm-remove-force $crypt" 15 dmsetup remove --force "$crypt" || true
|
for m in $(ashenvale_mounts | sort -r); do
|
||||||
|
step "host-umount-lazy $m" 5 umount -l "$m"
|
||||||
|
done
|
||||||
|
step "vg-deactivate-force" 10 vgchange -an "$vg"
|
||||||
|
step "luks-close-force" 10 cryptsetup close "$crypt"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -e "/dev/mapper/$crypt" ]; then
|
if [ -e "/dev/mapper/$crypt" ]; then
|
||||||
failed=1
|
# Still busy: wipe the key now (suspend drops it from the kernel), then replace the
|
||||||
note "ashenvale" "$crypt still present: $(dmsetup info -c --noheadings -o attr "$crypt" 2>&1)"
|
# stack with error targets so nothing waits on the suspended device.
|
||||||
|
step "luks-suspend" 10 cryptsetup luksSuspend "$crypt"
|
||||||
|
for m in $(dmsetup ls 2> /dev/null | awk -v p="$vg-" 'index($1, p) == 1 {print $1}'); do
|
||||||
|
step "dm-remove-force $m" 5 dmsetup remove --force "$m"
|
||||||
|
done
|
||||||
|
step "dm-remove-force $crypt" 5 dmsetup remove --force "$crypt"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# At rest = device gone, or still listed but suspended (key wiped) or an error target (crypt table dropped).
|
||||||
|
if [ ! -e "/dev/mapper/$crypt" ]; then
|
||||||
|
note "ashenvale" ok "$crypt closed"
|
||||||
|
else
|
||||||
|
local attr target
|
||||||
|
attr="$(dmsetup info -c --noheadings -o attr "$crypt" 2>&1)"
|
||||||
|
target="$(dmsetup table "$crypt" 2> /dev/null | awk '{print $3; exit}')"
|
||||||
|
if [[ "$attr" == *s* ]]; then
|
||||||
|
note "ashenvale" ok "$crypt still listed but suspended, key wiped"
|
||||||
|
elif [ "$target" = error ]; then
|
||||||
|
note "ashenvale" ok "$crypt replaced by error target, key dropped"
|
||||||
|
else
|
||||||
|
note "ashenvale" fail "$crypt still active: attr $attr, target $target"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -159,10 +213,9 @@ lock_ashenvale
|
|||||||
wait
|
wait
|
||||||
|
|
||||||
# Subshell failures do not reach this shell; derive them from the recorded steps.
|
# Subshell failures do not reach this shell; derive them from the recorded steps.
|
||||||
if awk -F '\t' '$1 == "step" && $2 ~ /^truenas-(lock|stop-force|query)$/ && $3 ~ /^fail/ {f = 1} END {exit !f}' "$tmp"; then
|
if awk -F '\t' '$1 == "step" && ($2 == "truenas" || $2 == "ashenvale" || $2 == "truenas-query") && $3 ~ /^fail/ {f = 1} END {exit !f}' "$tmp"; then
|
||||||
failed=1
|
failed=1
|
||||||
fi
|
fi
|
||||||
running truenas && [ "$mode" = lock ] && failed=1
|
|
||||||
|
|
||||||
put "finished $(date -Is)"
|
put "finished $(date -Is)"
|
||||||
if [ "$failed" -eq 0 ]; then put "state done"; else put "state failed"; fi
|
if [ "$failed" -eq 0 ]; then put "state done"; else put "state failed"; fi
|
||||||
|
|||||||
Reference in new issue
Block a user