@consiliency/agent-board-cli 1.24.0 → 1.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets.d.ts +22 -0
- package/dist/assets.js +61 -0
- package/dist/assets.json +6 -0
- package/dist/cli.d.ts +37 -3
- package/dist/cli.js +68 -26
- package/dist/config_edit.d.ts +37 -0
- package/dist/config_edit.js +549 -0
- package/dist/doctor.d.ts +7 -1
- package/dist/doctor.js +144 -2
- package/dist/identity_writer.d.ts +9 -7
- package/dist/identity_writer.js +18 -30
- package/dist/setup.d.ts +116 -0
- package/dist/setup.js +603 -0
- package/dist/setup_storage.d.ts +117 -0
- package/dist/setup_storage.js +447 -0
- package/dist/setup_writers.d.ts +128 -0
- package/dist/setup_writers.js +653 -0
- package/keyslot/agent-board-keyslot-load@.service +20 -0
- package/keyslot/agent-board-keyslot@.service +20 -0
- package/keyslot/constants.sh +73 -0
- package/keyslot/keygen.mjs +67 -0
- package/keyslot/keyslot-load.sh +78 -0
- package/keyslot/lib.sh +599 -0
- package/keyslot/optin.sh +185 -0
- package/keyslot/probe.sh +135 -0
- package/keyslot/provision.sh +530 -0
- package/keyslot/seam.sh +48 -0
- package/keyslot/status.sh +254 -0
- package/package.json +8 -4
package/keyslot/optin.sh
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# shellcheck shell=bash
|
|
2
|
+
# The unsealed slot's validation (A1), ONE predicate shared by keyslot-load.sh, which runs it
|
|
3
|
+
# before every load (boot included), and by status.sh and provision.sh (through lib.sh). The
|
|
4
|
+
# loader therefore never loads a key that status would call a contradiction. In the install
|
|
5
|
+
# set, so it runs with the checkout gone. Needs constants.sh and seam.sh sourced first.
|
|
6
|
+
#
|
|
7
|
+
# Principle 4 in bash: every ancestor directory is a real directory (never a symlink), owned by
|
|
8
|
+
# the user or root, and not group/other-writable unless root-owned and sticky (/tmp). The file
|
|
9
|
+
# itself is refused unless its lstat is a regular file (so a FIFO is never opened, and never
|
|
10
|
+
# blocks a boot), then OPENED, and judged on the opened descriptor (/proc/self/fd/N): the same
|
|
11
|
+
# inode as the lstat, a regular file, the user's, no group/other bit. Every consumer reads that
|
|
12
|
+
# descriptor: the opt-in's lines, ssh-keygen's derivation (/proc/self/fd/N), and the loader's
|
|
13
|
+
# ssh-add, which is fed the descriptor it checked. File types come from stat's numeric mode
|
|
14
|
+
# (%f), never its translated names.
|
|
15
|
+
#
|
|
16
|
+
# Every function prints its problems, one per line, and returns 0 only when there are none.
|
|
17
|
+
|
|
18
|
+
# keyseal_trusted_ancestors <dir>: <dir> and every directory above it.
|
|
19
|
+
keyseal_trusted_ancestors() {
|
|
20
|
+
local dir=$1 path=/ part uid rc=0
|
|
21
|
+
local -a parts
|
|
22
|
+
uid=$("$KEYSEAL_BIN_ID" -u)
|
|
23
|
+
[[ $dir == /* ]] || { printf '%s is not absolute\n' "$dir"; return 1; }
|
|
24
|
+
IFS=/ read -ra parts <<<"${dir#/}"
|
|
25
|
+
keyseal_trusted_dir_entry / "$uid" || rc=1
|
|
26
|
+
for part in "${parts[@]}"; do
|
|
27
|
+
[[ -n $part ]] || continue
|
|
28
|
+
path=${path%/}/$part
|
|
29
|
+
keyseal_trusted_dir_entry "$path" "$uid" || { rc=1; [[ -e $path ]] || return 1; }
|
|
30
|
+
done
|
|
31
|
+
return "$rc"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# One directory, not followed: a real directory, the user's or root's, not writable by others
|
|
35
|
+
# unless root-owned and sticky.
|
|
36
|
+
# The file type of a raw stat mode (%f, hex): dir, link, reg, or other.
|
|
37
|
+
keyseal_ftype() {
|
|
38
|
+
case $(((16#$1) & 16#F000)) in
|
|
39
|
+
$((16#4000))) printf dir ;;
|
|
40
|
+
$((16#A000))) printf link ;;
|
|
41
|
+
$((16#8000))) printf reg ;;
|
|
42
|
+
*) printf other ;;
|
|
43
|
+
esac
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
keyseal_trusted_dir_entry() {
|
|
47
|
+
local path=$1 uid=$2 info type owner mode
|
|
48
|
+
info=$("$KEYSEAL_BIN_STAT" -c '%f|%u|%a' -- "$path" 2>/dev/null) || { printf 'ancestor %s: missing\n' "$path"; return 1; }
|
|
49
|
+
type=$(keyseal_ftype "${info%%|*}") info=${info#*|}
|
|
50
|
+
owner=${info%%|*} mode=$((8#${info#*|}))
|
|
51
|
+
if [[ $type == link ]]; then printf 'ancestor %s is a symlink\n' "$path"; return 1; fi
|
|
52
|
+
if [[ $type != dir ]]; then printf 'ancestor %s is not a directory\n' "$path"; return 1; fi
|
|
53
|
+
local rc=0
|
|
54
|
+
if [[ $owner != "$uid" && $owner != 0 ]]; then printf 'ancestor %s is owned by uid %s\n' "$path" "$owner"; rc=1; fi
|
|
55
|
+
if ((mode & 8#022)) && ! { ((mode & 8#1000)) && [[ $owner == 0 ]]; }; then
|
|
56
|
+
printf 'ancestor %s is group/other-writable\n' "$path"; rc=1
|
|
57
|
+
fi
|
|
58
|
+
return "$rc"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# keyseal_trusted_open <path> <max-bytes>: principle 4 on the file. On success, KEYSEAL_FD is
|
|
62
|
+
# an open read descriptor on it (the caller closes it).
|
|
63
|
+
keyseal_trusted_open() {
|
|
64
|
+
local p=$1 max=$2 name=${1##*/} before after info uid rc=0
|
|
65
|
+
uid=$("$KEYSEAL_BIN_ID" -u)
|
|
66
|
+
KEYSEAL_FD=
|
|
67
|
+
keyseal_trusted_ancestors "${p%/*}" || rc=1
|
|
68
|
+
if [[ -L $p ]]; then printf '%s is a symlink\n' "$name"; return 1; fi
|
|
69
|
+
before=$("$KEYSEAL_BIN_STAT" -c '%f|%d:%i' -- "$p" 2>/dev/null) || { printf '%s: missing\n' "$name"; return 1; }
|
|
70
|
+
# Judged BEFORE the open: opening a FIFO blocks until a writer appears.
|
|
71
|
+
[[ $(keyseal_ftype "${before%%|*}") == reg ]] || { printf '%s is not a regular file\n' "$name"; return 1; }
|
|
72
|
+
before=${before#*|}
|
|
73
|
+
exec {KEYSEAL_FD}<"$p" || { printf '%s: unreadable\n' "$name"; KEYSEAL_FD=; return 1; }
|
|
74
|
+
info=$("$KEYSEAL_BIN_STAT" -L -c '%d:%i|%f|%u|%a|%s' -- "/proc/self/fd/$KEYSEAL_FD" 2>/dev/null) || info=
|
|
75
|
+
after=${info%%|*}
|
|
76
|
+
local rest=${info#*|}
|
|
77
|
+
local type=${rest%%|*}; rest=${rest#*|}
|
|
78
|
+
local owner=${rest%%|*}; rest=${rest#*|}
|
|
79
|
+
local mode=${rest%%|*} size=${rest#*|}
|
|
80
|
+
if [[ -z $info || $after != "$before" ]]; then printf '%s changed while it was opened\n' "$name"; rc=1
|
|
81
|
+
else
|
|
82
|
+
[[ $(keyseal_ftype "$type") == reg ]] || { printf '%s is not a regular file\n' "$name"; rc=1; }
|
|
83
|
+
[[ $owner == "$uid" ]] || { printf '%s is owned by uid %s\n' "$name" "$owner"; rc=1; }
|
|
84
|
+
(((8#$mode & 8#077) == 0)) || { printf '%s has mode %s, not 0600\n' "$name" "$mode"; rc=1; }
|
|
85
|
+
((size <= max)) || { printf '%s is over %s bytes\n' "$name" "$max"; rc=1; }
|
|
86
|
+
fi
|
|
87
|
+
if ((rc)); then exec {KEYSEAL_FD}<&-; KEYSEAL_FD=; fi
|
|
88
|
+
return "$rc"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# keyseal_machine_id_of <file>: the opt-in's host comparand, sha256(file)[:16].
|
|
92
|
+
keyseal_machine_id_of() {
|
|
93
|
+
local h
|
|
94
|
+
h=$("$KEYSEAL_BIN_SHA256SUM" <"$1" 2>/dev/null) || return 1
|
|
95
|
+
printf '%s' "${h:0:16}"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# keyseal_optin_verify <optin> <pub> <slot> <uid> <machine_id>: the opt-in record, opened under
|
|
99
|
+
# principle 4, holds exactly the frozen fields, and each matches (IF-0-KEYSEAL-6).
|
|
100
|
+
keyseal_optin_verify() {
|
|
101
|
+
local path=$1 pub=$2 slot=$3 uid=$4 mid=$5 line k v rc=0 h
|
|
102
|
+
local -A rec=()
|
|
103
|
+
local fields=(version machine_id uid slot pub_sha256 granted_at)
|
|
104
|
+
keyseal_trusted_open "$path" 4096 || return 1
|
|
105
|
+
while IFS= read -r line <&"$KEYSEAL_FD" || [[ -n $line ]]; do
|
|
106
|
+
[[ -z $line ]] && continue
|
|
107
|
+
k=${line%%=*} v=${line#*=}
|
|
108
|
+
if [[ $line != *=* || " ${fields[*]} " != *" $k "* ]]; then printf 'unknown or malformed line "%s"\n' "$k"; rc=1; continue; fi
|
|
109
|
+
if [[ -n ${rec[$k]+x} ]]; then printf 'duplicate %s\n' "$k"; rc=1; continue; fi
|
|
110
|
+
rec[$k]=$v
|
|
111
|
+
done
|
|
112
|
+
exec {KEYSEAL_FD}<&-
|
|
113
|
+
local -A grammar=([version]='^1$' [machine_id]='^[0-9a-f]{16}$' [uid]='^[0-9]+$' [slot]="$KEYSEAL_SLOT_RE"
|
|
114
|
+
[pub_sha256]='^[0-9a-f]{64}$' [granted_at]='^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$')
|
|
115
|
+
for k in "${fields[@]}"; do
|
|
116
|
+
if [[ -z ${rec[$k]+x} ]]; then printf 'missing %s\n' "$k"; rc=1
|
|
117
|
+
elif [[ ! ${rec[$k]} =~ ${grammar[$k]} ]]; then printf 'malformed %s\n' "$k"; rc=1
|
|
118
|
+
fi
|
|
119
|
+
done
|
|
120
|
+
h=
|
|
121
|
+
if [[ -f $pub && ! -L $pub ]]; then h=$("$KEYSEAL_BIN_SHA256SUM" <"$pub" 2>/dev/null) && h=${h%% *} || h=; fi
|
|
122
|
+
[[ -z ${rec[machine_id]+x} || ${rec[machine_id]} == "$mid" ]] || { printf "machine_id is another machine's\n"; rc=1; }
|
|
123
|
+
[[ -z ${rec[uid]+x} || ${rec[uid]} == "$uid" ]] || { printf "uid is another user's\n"; rc=1; }
|
|
124
|
+
[[ -z ${rec[slot]+x} || ${rec[slot]} == "$slot" ]] || { printf "slot is another slot's\n"; rc=1; }
|
|
125
|
+
[[ -z ${rec[pub_sha256]+x} || ${rec[pub_sha256]} == "$h" ]] || { printf 'pub_sha256 does not match the pin file\n'; rc=1; }
|
|
126
|
+
return "$rc"
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
# keyseal_keyfile_pub <key>: the key file under principle 4, then the raw Ed25519 public key
|
|
130
|
+
# (43-char base64url) ssh-keygen derives from it, read through the opened descriptor.
|
|
131
|
+
keyseal_keyfile_pub() {
|
|
132
|
+
local key=$1 rc=0
|
|
133
|
+
keyseal_trusted_open "$key" 65536 || return 1
|
|
134
|
+
keyseal_fd_pub "$KEYSEAL_FD" "${key##*/}" || rc=1
|
|
135
|
+
exec {KEYSEAL_FD}<&-
|
|
136
|
+
return "$rc"
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# keyseal_fd_pub <fd> <name>: the raw Ed25519 public key ssh-keygen derives from the open
|
|
140
|
+
# descriptor <fd>. ssh-keygen is resolved through the seam, as probe.sh resolves it (N5).
|
|
141
|
+
keyseal_fd_pub() {
|
|
142
|
+
local fd=$1 name=$2 out wire raw keygen
|
|
143
|
+
keygen=$(seam_resolve KEYSEAL_TEST_SSH_KEYGEN "$KEYSEAL_BIN_SSH_KEYGEN") || { printf '%s: ssh-keygen refused by the seam\n' "$name"; return 1; }
|
|
144
|
+
out=$("$keygen" -y -f "/proc/self/fd/$fd" 2>/dev/null) || out=
|
|
145
|
+
[[ $out == "ssh-ed25519 "* ]] || { printf '%s is not a usable Ed25519 key\n' "$name"; return 1; }
|
|
146
|
+
wire=${out#ssh-ed25519 }
|
|
147
|
+
wire=${wire%% *}
|
|
148
|
+
# The wire blob is string("ssh-ed25519") + string(32 raw bytes): 51 bytes, 68 base64
|
|
149
|
+
# characters, with this fixed prefix; the raw key is its last 32 bytes.
|
|
150
|
+
[[ ${#wire} == 68 && $wire == AAAAC3NzaC1lZDI1NTE5AAAAI* ]] || { printf '%s is not a usable Ed25519 key\n' "$name"; return 1; }
|
|
151
|
+
raw=$(printf '%s' "$wire" | "$KEYSEAL_BIN_BASE64" -d 2>/dev/null | "$KEYSEAL_BIN_TAIL" -c 32 | "$KEYSEAL_BIN_BASE64" -w0) || raw=
|
|
152
|
+
raw=${raw//+/-}
|
|
153
|
+
raw=${raw//\//_}
|
|
154
|
+
raw=${raw%%=*}
|
|
155
|
+
[[ $raw =~ ^[A-Za-z0-9_-]{43}$ ]] || { printf '%s is not a usable Ed25519 key\n' "$name"; return 1; }
|
|
156
|
+
printf '%s' "$raw"
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
# keyseal_unsealed_verify <slot> <uid> <machine_id>: the whole unsealed predicate. A valid
|
|
160
|
+
# opt-in, a 0600 key of this user, and the key's public key equal to the pin in <slot>.pub.
|
|
161
|
+
# On success prints the pin it verified (the loader compares its own descriptor with THAT value,
|
|
162
|
+
# never a re-read of <slot>.pub); otherwise prints the problems. rc 0 only when the slot may be
|
|
163
|
+
# loaded. The pin file is read under principle 4 too, so a FIFO there never blocks.
|
|
164
|
+
keyseal_unsealed_verify() {
|
|
165
|
+
local slot=$1 uid=$2 mid=$3 K pin= kp line rc=0
|
|
166
|
+
K=$(keyseal_key_dir)
|
|
167
|
+
# Called directly (not in $(...)): the descriptor must be open in THIS shell to be read.
|
|
168
|
+
if keyseal_trusted_open "$K/$slot.pub" 256; then
|
|
169
|
+
IFS= read -r pin <&"$KEYSEAL_FD" || true
|
|
170
|
+
exec {KEYSEAL_FD}<&-
|
|
171
|
+
fi
|
|
172
|
+
[[ $pin =~ ^[A-Za-z0-9_-]{43}$ ]] || { printf 'pin missing or malformed\n'; rc=1; }
|
|
173
|
+
if [[ -e $K/$slot.unsealed-optin || -L $K/$slot.unsealed-optin ]]; then
|
|
174
|
+
keyseal_optin_verify "$K/$slot.unsealed-optin" "$K/$slot.pub" "$slot" "$uid" "$mid" || rc=1
|
|
175
|
+
else
|
|
176
|
+
printf 'no opt-in record\n'; rc=1
|
|
177
|
+
fi
|
|
178
|
+
if kp=$(keyseal_keyfile_pub "$K/$slot.key"); then
|
|
179
|
+
[[ -z $pin || $kp == "$pin" ]] || { printf 'the pin does not derive from %s.key\n' "$slot"; rc=1; }
|
|
180
|
+
else
|
|
181
|
+
printf '%s\n' "$kp"; rc=1
|
|
182
|
+
fi
|
|
183
|
+
((rc)) || printf '%s' "$pin"
|
|
184
|
+
return "$rc"
|
|
185
|
+
}
|
package/keyslot/probe.sh
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# probe.sh [--json]: can this host hold a key slot, and in which mode? (IF-0-KEYSEAL-4,
|
|
3
|
+
# IF-0-KEYSEAL-6)
|
|
4
|
+
#
|
|
5
|
+
# Seals a random canary under a throwaway credential name, decrypts it IN A PIPE and
|
|
6
|
+
# compares the bytes. Also reports linger, the user manager's state, the systemd version and
|
|
7
|
+
# the absolute binaries the loader and the operator tools use, then removes its artefacts.
|
|
8
|
+
#
|
|
9
|
+
# Four outcomes, frozen (A1), in this precedence:
|
|
10
|
+
# error anything the probe cannot judge: no XDG_RUNTIME_DIR, a user manager
|
|
11
|
+
# that is not running/degraded, linger unreadable, an operator binary
|
|
12
|
+
# missing, an unreadable systemd version, and ANY failed round trip
|
|
13
|
+
# on systemd >= 256. Never `incapable`: provision refuses.
|
|
14
|
+
# incapable-for-keyslot linger off, or ssh-agent/ssh-add/ssh-keygen missing: no mode survives
|
|
15
|
+
# a reboot.
|
|
16
|
+
# incapable ONLY systemd-creds absent (systemd 249), or `--user` refused on
|
|
17
|
+
# systemd < 256, both measured: the unsealed opt-in is admissible.
|
|
18
|
+
# capable a byte-equal `systemd-creds --user` round trip, with linger on.
|
|
19
|
+
# Exit 0 capable, 2 for each other outcome (the JSON's `outcome` says which), 1 on a usage or
|
|
20
|
+
# seam error. Never 0 without a byte-equal round trip. dev0's manager reads `degraded` and is
|
|
21
|
+
# capable. `machine_id` is the opt-in record's host comparand, sha256(/etc/machine-id)[:16].
|
|
22
|
+
set -euo pipefail
|
|
23
|
+
# The measured refusal is matched as text, so every child speaks the C locale (review round 2, N4).
|
|
24
|
+
export LC_ALL=C
|
|
25
|
+
|
|
26
|
+
here=${BASH_SOURCE[0]%/*}
|
|
27
|
+
[[ $here == "${BASH_SOURCE[0]}" ]] && here=.
|
|
28
|
+
# shellcheck source=lib.sh
|
|
29
|
+
. "$here/lib.sh"
|
|
30
|
+
|
|
31
|
+
json=no
|
|
32
|
+
for arg in "$@"; do
|
|
33
|
+
case $arg in
|
|
34
|
+
--json) json=yes ;;
|
|
35
|
+
*) keyseal_die "usage: probe.sh [--json]" ;;
|
|
36
|
+
esac
|
|
37
|
+
done
|
|
38
|
+
keyseal_resolve_bins || exit 1
|
|
39
|
+
|
|
40
|
+
linger=$(keyseal_linger)
|
|
41
|
+
manager_state=$("$SYSTEMCTL" --user is-system-running 2>/dev/null) || true
|
|
42
|
+
manager_state=${manager_state:-unreachable}
|
|
43
|
+
|
|
44
|
+
bins_json=
|
|
45
|
+
declare -A present
|
|
46
|
+
for b in "$CREDS" "$SSH_ADD" "$SLEEP" "$SYSTEMCTL" "$LOGINCTL" "$FLOCK" "$KEYSEAL_BIN_SSH_AGENT" "$SSH_KEYGEN"; do
|
|
47
|
+
if [[ -x $b ]]; then present[$b]=true; else present[$b]=false; fi
|
|
48
|
+
bins_json+="${bins_json:+,}$(keyseal_json_str "$b"):${present[$b]}"
|
|
49
|
+
done
|
|
50
|
+
|
|
51
|
+
# The systemd version, as systemd-creds reports it. Empty when absent or unreadable.
|
|
52
|
+
systemd_version=
|
|
53
|
+
if [[ ${present[$CREDS]} == true ]]; then
|
|
54
|
+
v=$("$CREDS" --version 2>/dev/null) || v=
|
|
55
|
+
v=${v%%$'\n'*}
|
|
56
|
+
# Unanchored (review round 1, Gemini S2): a distro prefix before "systemd N" still parses.
|
|
57
|
+
[[ $v =~ (^|[^A-Za-z0-9_-])systemd\ ([0-9]+)([^0-9]|$) ]] && systemd_version=${BASH_REMATCH[2]}
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
round_trip=not_run
|
|
61
|
+
user_refused=no
|
|
62
|
+
dir=
|
|
63
|
+
cleanup() { if [[ -n $dir ]]; then rm -rf -- "$dir"; fi; }
|
|
64
|
+
trap cleanup EXIT
|
|
65
|
+
if [[ ${present[$CREDS]} == true && -n ${XDG_RUNTIME_DIR-} && -d $XDG_RUNTIME_DIR ]] &&
|
|
66
|
+
dir=$(mktemp -d "$XDG_RUNTIME_DIR/keyseal-probe.XXXXXX"); then
|
|
67
|
+
round_trip=failed
|
|
68
|
+
canary=$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')
|
|
69
|
+
name="keyseal-probe-$(head -c 6 /dev/urandom | od -An -tx1 | tr -d ' \n')"
|
|
70
|
+
if enc_err=$(printf '%s' "$canary" | "$CREDS" --user encrypt --name="$name" - "$dir/canary.cred" 2>&1 >/dev/null); then
|
|
71
|
+
if got=$("$CREDS" --user decrypt --name="$name" "$dir/canary.cred" - 2>/dev/null | sha256sum); then
|
|
72
|
+
if [[ ${got%% *} == "$(keyseal_sha256_str "$canary")" ]]; then round_trip=equal; else round_trip=mismatch; fi
|
|
73
|
+
fi
|
|
74
|
+
else
|
|
75
|
+
# The measured refusal (systemd < 256), not merely any failure (review round 1, Opus S4).
|
|
76
|
+
if [[ $enc_err == *"unrecognized option '--user'"* || $enc_err == *'unrecognized option "--user"'* ]]; then user_refused=yes; fi
|
|
77
|
+
fi
|
|
78
|
+
fi
|
|
79
|
+
cleanup
|
|
80
|
+
dir=
|
|
81
|
+
|
|
82
|
+
outcome= reason=
|
|
83
|
+
if [[ -z ${XDG_RUNTIME_DIR-} || ! -d $XDG_RUNTIME_DIR ]]; then
|
|
84
|
+
outcome=error reason=no_runtime_dir
|
|
85
|
+
elif [[ $manager_state != running && $manager_state != degraded ]]; then
|
|
86
|
+
outcome=error reason="manager_$manager_state"
|
|
87
|
+
elif [[ $linger != yes && $linger != no ]]; then
|
|
88
|
+
outcome=error reason=linger_unreadable
|
|
89
|
+
elif [[ ${present[$SYSTEMCTL]} != true || ${present[$LOGINCTL]} != true || ${present[$FLOCK]} != true || ${present[$SLEEP]} != true ]]; then
|
|
90
|
+
outcome=error reason=operator_binary_missing
|
|
91
|
+
elif [[ ${present[$CREDS]} != true ]]; then
|
|
92
|
+
outcome=incapable reason=systemd_creds_absent
|
|
93
|
+
elif [[ -z $systemd_version ]]; then
|
|
94
|
+
outcome=error reason=systemd_version_unreadable
|
|
95
|
+
elif [[ $round_trip == equal ]]; then
|
|
96
|
+
outcome=capable reason=round_trip_equal
|
|
97
|
+
elif ((systemd_version < 256)) && [[ $round_trip == failed && $user_refused == yes ]]; then
|
|
98
|
+
outcome=incapable reason=user_scope_refused
|
|
99
|
+
else
|
|
100
|
+
# Every other non-equal round trip is an error, never a reason to accept an unsealed key: any
|
|
101
|
+
# failure on systemd >= 256 (expected to seal at user scope), and below 256 anything but the
|
|
102
|
+
# measured `--user` refusal (bytes that came back different, or another failure).
|
|
103
|
+
outcome=error reason="round_trip_$round_trip"
|
|
104
|
+
fi
|
|
105
|
+
if [[ $outcome == capable || $outcome == incapable ]]; then
|
|
106
|
+
if [[ $linger != yes ]]; then
|
|
107
|
+
outcome=incapable-for-keyslot reason="linger_off (fix: loginctl enable-linger $(id -un))"
|
|
108
|
+
elif [[ ${present[$SSH_ADD]} != true || ${present[$KEYSEAL_BIN_SSH_AGENT]} != true || ${present[$SSH_KEYGEN]} != true ]]; then
|
|
109
|
+
# ssh-keygen: the loader derives an unsealed key's public key with it before loading.
|
|
110
|
+
outcome=incapable-for-keyslot reason="ssh_agent_or_ssh_add_missing (fix: install openssh-client)"
|
|
111
|
+
fi
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
capable=false
|
|
115
|
+
rc=2
|
|
116
|
+
if [[ $outcome == capable ]]; then
|
|
117
|
+
capable=true
|
|
118
|
+
rc=0
|
|
119
|
+
fi
|
|
120
|
+
machine_id=$(keyseal_machine_id) || machine_id=
|
|
121
|
+
mid_json=null
|
|
122
|
+
[[ -n $machine_id ]] && mid_json=$(keyseal_json_str "$machine_id")
|
|
123
|
+
ver_json=null
|
|
124
|
+
[[ -n $systemd_version ]] && ver_json=$systemd_version
|
|
125
|
+
|
|
126
|
+
if [[ $json == yes ]]; then
|
|
127
|
+
printf '{"tool":"probe","seam":%s,"host":%s,"uid":%s,"machine_id":%s,"linger":%s,"manager_state":%s,"systemd_version":%s,"binaries":{%s},"round_trip":%s,"outcome":%s,"reason":%s,"capable":%s,"exit":%s}\n' \
|
|
128
|
+
"$(keyseal_seam_json)" "$(keyseal_json_str "$(uname -n)")" "$(keyseal_uid)" "$mid_json" "$(keyseal_json_str "$linger")" \
|
|
129
|
+
"$(keyseal_json_str "$manager_state")" "$ver_json" "$bins_json" "$(keyseal_json_str "$round_trip")" \
|
|
130
|
+
"$(keyseal_json_str "$outcome")" "$(keyseal_json_str "$reason")" "$capable" "$rc"
|
|
131
|
+
else
|
|
132
|
+
printf 'probe: outcome=%s reason=%s round_trip=%s linger=%s manager=%s systemd=%s seam=%s\n' \
|
|
133
|
+
"$outcome" "$reason" "$round_trip" "$linger" "$manager_state" "${systemd_version:-?}" "$(keyseal_seam_json)"
|
|
134
|
+
fi
|
|
135
|
+
exit "$rc"
|