@consiliency/agent-board-cli 1.27.1 → 1.29.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/claude-marketplace/plugins/agent-board/.claude-plugin/plugin.json +2 -2
- package/dist/assets.json +4 -4
- package/dist/cli.d.ts +5 -2
- package/dist/cli.js +41 -13
- package/dist/doctor.d.ts +143 -2
- package/dist/doctor.js +346 -39
- package/dist/identity_writer.d.ts +1 -46
- package/dist/identity_writer.js +5 -174
- package/dist/keyslot_status.d.ts +134 -0
- package/dist/keyslot_status.js +348 -0
- package/dist/setup.d.ts +112 -5
- package/dist/setup.js +345 -73
- package/dist/setup_all.d.ts +51 -0
- package/dist/setup_all.js +164 -0
- package/dist/setup_storage.d.ts +74 -11
- package/dist/setup_storage.js +173 -30
- package/dist/setup_writers.d.ts +7 -0
- package/dist/setup_writers.js +11 -2
- package/keyslot/lib.sh +835 -38
- package/keyslot/status.sh +439 -173
- package/package.json +2 -2
package/keyslot/status.sh
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# status.sh --harness <h> [--slot <s>] [--json]
|
|
3
|
-
#
|
|
2
|
+
# status.sh --harness <h> [--slot <s>] [--json] [--scan-cache fresh|use|only] [--scan-timeout <s>]
|
|
3
|
+
# or status.sh --slots <a,b,...> --json [--scan-cache fresh|use|only] [--scan-timeout <s>]:
|
|
4
|
+
# what does slot <s> hold, measured, and is it chained and loaded? (IF-0-KEYSEAL-3,
|
|
5
|
+
# IF-0-KEYSEAL-6). <s> defaults to <h>.
|
|
6
|
+
#
|
|
7
|
+
# SCAN CACHE (IF-552-2, #552): check 6's result is cached per slot under
|
|
8
|
+
# <state root>/agent-board/keyslot-scan/<slot>.json, valid for 24 h and only for the same seam,
|
|
9
|
+
# uid, machine, slot, mode, exemption, pin, blob, scope and scanner. `fresh` (the default, a hand
|
|
10
|
+
# run) never reads it and scans; `use` reads it and scans on a miss; `only` never scans, and a
|
|
11
|
+
# miss is exit 2 `scan_not_cached`. A match is never cached: it writes a tombstone. The whole
|
|
12
|
+
# scan stage runs under one `timeout` (--scan-timeout, default 1800 s); a scan that gives up is
|
|
13
|
+
# exit 2 `scan_timed_out` and is never cached. Progress goes to stderr; stdout stays JSON.
|
|
14
|
+
#
|
|
15
|
+
# --slots <a,b,...> (#552-ONBOARD-SL3, IF-0-ONBOARD-1 "a key scan at most once per host"): judge N
|
|
16
|
+
# slots in one run. Checks 1-5 run per slot, unchanged; check 6 walks the filesystem ONCE for
|
|
17
|
+
# every slot named (one `find`/`xargs`/`grep` stage under one `timeout`), then classifies each
|
|
18
|
+
# slot's candidates separately against that single walk's output, so N slots never cost N walks.
|
|
19
|
+
# `--slots` requires --json and cannot combine with --harness/--slot; each name is both the
|
|
20
|
+
# harness and the slot. Output is one JSON object per line, in the order given, each carrying its
|
|
21
|
+
# own "exit"; the process exit is the worst of the per-slot exits (1 beats 2 beats 0, as below). A
|
|
22
|
+
# single name behaves exactly as --harness <name>: N=1 is "N slots sharing one walk" with N=1, so
|
|
23
|
+
# it is the SAME code path, not a special case, and its output and cache are byte-identical to
|
|
24
|
+
# today's.
|
|
4
25
|
#
|
|
5
26
|
# MODE, from provenance only, never from what provision was asked (A1):
|
|
6
27
|
# sealed <s>.cred + <s>.pub, the pin derives from a PIPED decrypt of the blob, no
|
|
@@ -34,6 +55,8 @@
|
|
|
34
55
|
# legacy_loader_present flat-layout loader files (pre-A1) are present, or the installed
|
|
35
56
|
# template still runs them
|
|
36
57
|
# nothing_committed, undeterminable
|
|
58
|
+
# scan_not_cached --scan-cache only, and no valid cache (the scan is not judged)
|
|
59
|
+
# scan_timed_out the scan stage hit --scan-timeout (the scan is not judged)
|
|
37
60
|
# The remedy for the three loader causes is provision.sh --refresh-loader, which never re-keys.
|
|
38
61
|
# The JSON carries hashes only: never the pin, the reference or any key byte.
|
|
39
62
|
set -euo pipefail
|
|
@@ -43,29 +66,59 @@ here=${BASH_SOURCE[0]%/*}
|
|
|
43
66
|
# shellcheck source=lib.sh
|
|
44
67
|
. "$here/lib.sh"
|
|
45
68
|
|
|
46
|
-
harness= slot= json=no
|
|
69
|
+
harness= slot= slots_arg= json=no scan_cache=fresh scan_timeout=$KEYSEAL_SCAN_TIMEOUT_S
|
|
70
|
+
usage="usage: status.sh --harness <h> [--slot <s>] [--json] [--scan-cache fresh|use|only] [--scan-timeout <s>] | status.sh --slots <a,b,...> --json [--scan-cache fresh|use|only] [--scan-timeout <s>]"
|
|
47
71
|
while (($#)); do
|
|
48
72
|
case $1 in
|
|
49
73
|
--harness) harness=${2-}; shift 2 || keyseal_die "usage: --harness <h>" ;;
|
|
50
74
|
--slot) slot=${2-}; shift 2 || keyseal_die "usage: --slot <s>" ;;
|
|
75
|
+
--slots) slots_arg=${2-}; shift 2 || keyseal_die "usage: --slots <a,b,...>" ;;
|
|
51
76
|
--json) json=yes; shift ;;
|
|
52
|
-
|
|
77
|
+
--scan-cache) scan_cache=${2-}; shift 2 || keyseal_die "$usage" ;;
|
|
78
|
+
--scan-timeout) scan_timeout=${2-}; shift 2 || keyseal_die "$usage" ;;
|
|
79
|
+
*) keyseal_die "$usage" ;;
|
|
53
80
|
esac
|
|
54
81
|
done
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
[[
|
|
58
|
-
|
|
82
|
+
|
|
83
|
+
slots=() harness_of=()
|
|
84
|
+
if [[ -n $slots_arg ]]; then
|
|
85
|
+
[[ -z $harness && -z $slot ]] || keyseal_die "usage: --slots cannot combine with --harness/--slot"
|
|
86
|
+
[[ $json == yes ]] || keyseal_die "usage: --slots requires --json"
|
|
87
|
+
IFS=, read -ra slots <<<"$slots_arg"
|
|
88
|
+
((${#slots[@]} >= 1)) || keyseal_die "usage: --slots <a,b,...>"
|
|
89
|
+
declare -A seen_slot=()
|
|
90
|
+
for s in "${slots[@]}"; do
|
|
91
|
+
[[ $s =~ $KEYSEAL_SLOT_RE ]] || keyseal_die "bad_slot: $s"
|
|
92
|
+
[[ -z ${seen_slot[$s]-} ]] || keyseal_die "duplicate_slot: $s"
|
|
93
|
+
seen_slot[$s]=1
|
|
94
|
+
done
|
|
95
|
+
harness_of=("${slots[@]}")
|
|
96
|
+
else
|
|
97
|
+
[[ $harness =~ $KEYSEAL_SLOT_RE ]] || keyseal_die "bad_harness"
|
|
98
|
+
slot=${slot:-$harness}
|
|
99
|
+
[[ $slot =~ $KEYSEAL_SLOT_RE ]] || keyseal_die "bad_slot"
|
|
100
|
+
slots=("$slot")
|
|
101
|
+
harness_of=("$harness")
|
|
102
|
+
fi
|
|
103
|
+
n=${#slots[@]}
|
|
104
|
+
[[ $scan_cache == fresh || $scan_cache == use || $scan_cache == only ]] || keyseal_die "bad_scan_cache: $usage"
|
|
105
|
+
[[ $scan_timeout =~ ^[1-9][0-9]{0,6}$ ]] || keyseal_die "bad_scan_timeout: $usage"
|
|
106
|
+
[[ -n ${XDG_RUNTIME_DIR-} ]] || keyseal_die "runtime_dir_unset: XDG_RUNTIME_DIR is unset, so the slot's agent socket cannot be located; run from a login session or export XDG_RUNTIME_DIR=/run/user/$(id -u)"
|
|
59
107
|
: "${HOME:?status: HOME unset}"
|
|
60
108
|
keyseal_resolve_bins || exit 1
|
|
109
|
+
# The scan's cleanup (IF-552-2): the ticker, the stage group and every temp file, on any exit.
|
|
110
|
+
trap keyseal_scan_cleanup EXIT
|
|
111
|
+
trap 'exit 130' INT
|
|
112
|
+
trap 'exit 143' TERM
|
|
61
113
|
|
|
62
|
-
K=$(keyseal_key_dir)
|
|
63
|
-
cred=$K/$slot.cred pub=$K/$slot.pub key=$K/$slot.key optin=$K/$slot.unsealed-optin
|
|
64
|
-
base_unit=$KEYSEAL_BASE_UNIT@$slot.service
|
|
65
|
-
load_unit=$KEYSEAL_LOAD_UNIT@$slot.service
|
|
66
|
-
declare -A ok detail
|
|
67
|
-
exit2=()
|
|
68
114
|
has() { [[ -e $1 || -L $1 ]]; }
|
|
115
|
+
str_or_null() { if [[ -n $1 ]]; then keyseal_json_str "$1"; else printf null; fi; }
|
|
116
|
+
bool() { if [[ $1 == yes ]]; then printf true; else printf false; fi; }
|
|
117
|
+
state() { "$SYSTEMCTL" --user "$1" "$2" 2>/dev/null || true; }
|
|
118
|
+
|
|
119
|
+
# ---- host-level, computed at most once per run (IF-0-ONBOARD-1) --------------------------------
|
|
120
|
+
|
|
121
|
+
K=$(keyseal_key_dir)
|
|
69
122
|
|
|
70
123
|
probe_out=$("$here/probe.sh" --json 2>/dev/null) && probe_rc=0 || probe_rc=$?
|
|
71
124
|
outcome=error
|
|
@@ -73,182 +126,395 @@ outcome=error
|
|
|
73
126
|
round_trip=unknown
|
|
74
127
|
[[ $probe_out =~ \"round_trip\":\"([a-z_]+)\" ]] && round_trip=${BASH_REMATCH[1]}
|
|
75
128
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
fi
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
129
|
+
boot_id=$(</proc/sys/kernel/random/boot_id)
|
|
130
|
+
machine_id=$(keyseal_machine_id) || machine_id=
|
|
131
|
+
seam_j=$(keyseal_seam_json)
|
|
132
|
+
host_j=$(keyseal_json_str "$(uname -n)")
|
|
133
|
+
uid_j=$(keyseal_uid)
|
|
134
|
+
machine_id_j=$(str_or_null "$machine_id")
|
|
135
|
+
boot_id_j=$(keyseal_json_str "$boot_id")
|
|
136
|
+
|
|
137
|
+
stale_temp=$(keyseal_scan_stale_temp)
|
|
138
|
+
keyseal_scan_roots
|
|
139
|
+
scope_sha_j=$(keyseal_json_str "$(keyseal_sha256_str "$(keyseal_scan_scope_json)")")
|
|
140
|
+
scanner_sha_j=$(str_or_null "$(keyseal_files_sha "$here" lib.sh status.sh || true)")
|
|
141
|
+
|
|
142
|
+
linger=$(keyseal_linger)
|
|
143
|
+
if [[ $linger == yes ]]; then ok_linger=true detail_linger=""; else ok_linger=false detail_linger="linger is $linger"; fi
|
|
144
|
+
|
|
145
|
+
# ---- phase A: checks 1-5 and the cache judge, per slot ------------------------------------------
|
|
146
|
+
|
|
147
|
+
declare -a A_MODE=() A_PIN=() A_EXEMPT=() A_EXEMPT_J=() A_PINSHA_J=() A_BLOBSHA_J=()
|
|
148
|
+
declare -a A_OK_COMMITTED=() A_DET_COMMITTED=()
|
|
149
|
+
declare -a A_OK_CHAIN=() A_DET_CHAIN=() A_CHAIN_LEGACY=() A_LEGACY=() A_LOADER_STATE=() A_LOADER_REC=() A_LOADER_RUN=()
|
|
150
|
+
declare -a A_OK_UNITS=() A_DET_UNITS=()
|
|
151
|
+
declare -a A_OK_AGENT=() A_DET_AGENT=() A_AGENT_EMPTY=()
|
|
152
|
+
declare -a A_OPTIN_J=() A_UPGRADE=() A_EXIT2=()
|
|
153
|
+
declare -a A_CACHE_TOKEN=() A_CACHE_STATE=() A_CACHE_REASON=() A_CACHE_SCAN=() A_CACHE_SCANNED_AT=() A_CACHE_DURATION=() A_CACHE_LAST=()
|
|
154
|
+
declare -a A_NEEDS_SCAN=()
|
|
155
|
+
|
|
156
|
+
for i in "${!slots[@]}"; do
|
|
157
|
+
slot=${slots[i]}
|
|
158
|
+
cred=$K/$slot.cred pub=$K/$slot.pub key=$K/$slot.key optin=$K/$slot.unsealed-optin
|
|
159
|
+
base_unit=$KEYSEAL_BASE_UNIT@$slot.service
|
|
160
|
+
load_unit=$KEYSEAL_LOAD_UNIT@$slot.service
|
|
161
|
+
|
|
162
|
+
# 1 committed: the provenance table. The pin is read whatever the provenance, so a
|
|
163
|
+
# contradiction still scans for copies of the pinned key (review round 1, Opus S3).
|
|
164
|
+
contra=()
|
|
165
|
+
mode=undetermined optin_json=null
|
|
166
|
+
pin=$(keyseal_read_pin "$pub") || pin=
|
|
167
|
+
has "$cred" && has "$key" && contra+=("a sealed blob and an unsealed key are both present")
|
|
168
|
+
has "$cred" && has "$optin" && contra+=("an opt-in record beside a sealed blob")
|
|
169
|
+
has "$optin" && ! has "$key" && contra+=("an opt-in record without $slot.key")
|
|
170
|
+
if has "$optin"; then
|
|
171
|
+
optin_ok=true
|
|
172
|
+
optin_problems=$(keyseal_optin_check "$slot") || optin_ok=false
|
|
173
|
+
plist=
|
|
174
|
+
while IFS= read -r line; do [[ -n $line ]] && plist+="${plist:+,}$(keyseal_json_str "$line")"; done <<<"$optin_problems"
|
|
175
|
+
optin_json="{\"ok\":$optin_ok,\"problems\":[$plist]}"
|
|
176
|
+
fi
|
|
177
|
+
if has "$key" && ! has "$optin"; then contra+=("$slot.key without an opt-in record"); fi
|
|
178
|
+
if has "$K/$slot.cred.staging" || has "$K/$slot.pub.staging" || has "$K/$slot.unsealed-optin.staging"; then
|
|
179
|
+
contra+=("staging file present")
|
|
180
|
+
fi
|
|
181
|
+
exit2_i=()
|
|
182
|
+
if ((${#contra[@]} == 0)); then
|
|
183
|
+
if ! has "$cred" && ! has "$pub" && ! has "$key" && ! has "$optin"; then
|
|
184
|
+
exit2_i+=(nothing_committed)
|
|
185
|
+
elif has "$key"; then
|
|
186
|
+
# The loader's own predicate (optin.sh): what it would refuse to load is a contradiction.
|
|
187
|
+
if problems=$(keyseal_unsealed_check "$slot"); then mode=unsealed
|
|
188
|
+
else contra+=("the unsealed slot does not verify: ${problems//$'\n'/; }")
|
|
189
|
+
fi
|
|
190
|
+
elif ! has "$cred"; then contra+=("blob missing")
|
|
191
|
+
elif [[ -z $pin ]]; then contra+=("pin missing or malformed")
|
|
112
192
|
else
|
|
113
|
-
|
|
193
|
+
drc=0
|
|
194
|
+
derived=$(keyseal_derive_pub "$slot") || drc=$?
|
|
195
|
+
if ((drc == 0)); then
|
|
196
|
+
if [[ $derived == "$pin" ]]; then mode=sealed; else contra+=("pin does not derive from the blob"); fi
|
|
197
|
+
elif [[ $outcome == capable ]]; then
|
|
198
|
+
contra+=("the blob does not decrypt to a key, though sealing works here")
|
|
199
|
+
else
|
|
200
|
+
exit2_i+=(undeterminable)
|
|
201
|
+
fi
|
|
114
202
|
fi
|
|
115
203
|
fi
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
fi
|
|
204
|
+
if ((${#contra[@]})); then
|
|
205
|
+
mode=contradiction ok_committed_i=false
|
|
206
|
+
detail_committed_i=$(printf '%s; ' "${contra[@]}")
|
|
207
|
+
detail_committed_i=${detail_committed_i%; }
|
|
208
|
+
else
|
|
209
|
+
ok_committed_i=true detail_committed_i=""
|
|
210
|
+
fi
|
|
124
211
|
|
|
125
|
-
# 2 chain, including the slot's loader version
|
|
126
|
-
if keyseal_chain_check "$slot" "$here"; then
|
|
127
|
-
else
|
|
128
|
-
fi
|
|
129
|
-
|
|
130
|
-
if [[ $KEYSEAL_CHAIN_LEGACY == yes ]] || keyseal_legacy_present; then
|
|
131
|
-
if [[ $KEYSEAL_CHAIN_LEGACY == no ]]; then
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
212
|
+
# 2 chain, including the slot's loader version
|
|
213
|
+
if keyseal_chain_check "$slot" "$here"; then ok_chain_i=true detail_chain_i=""
|
|
214
|
+
else ok_chain_i=false detail_chain_i=${KEYSEAL_CHAIN_PROBLEMS%$'\n'}
|
|
215
|
+
fi
|
|
216
|
+
legacy_i=no
|
|
217
|
+
if [[ $KEYSEAL_CHAIN_LEGACY == yes ]] || keyseal_legacy_present; then legacy_i=yes exit2_i+=(legacy_loader_present); fi
|
|
218
|
+
if [[ $KEYSEAL_CHAIN_LEGACY == no ]]; then
|
|
219
|
+
case $KEYSEAL_LOADER_STATE in
|
|
220
|
+
outdated) exit2_i+=(loader_outdated) ;;
|
|
221
|
+
unrecorded) exit2_i+=(loader_unrecorded) ;;
|
|
222
|
+
esac
|
|
223
|
+
fi
|
|
224
|
+
|
|
225
|
+
# 3 units
|
|
226
|
+
sub=$("$SYSTEMCTL" --user show -p SubState "$load_unit" 2>/dev/null || true)
|
|
227
|
+
if [[ $(state is-enabled "$base_unit") == enabled && $(state is-enabled "$load_unit") == enabled &&
|
|
228
|
+
$(state is-active "$base_unit") == active && $(state is-active "$load_unit") == active && $sub == SubState=exited ]]; then
|
|
229
|
+
ok_units_i=true detail_units_i=""
|
|
230
|
+
else
|
|
231
|
+
ok_units_i=false detail_units_i="base $(state is-enabled "$base_unit")/$(state is-active "$base_unit"), loader $(state is-enabled "$load_unit")/$(state is-active "$load_unit") ${sub#SubState=}"
|
|
232
|
+
fi
|
|
233
|
+
|
|
234
|
+
# 4 agent: exactly one identity, and it is the pin. `empty` is told apart from a wrong or
|
|
235
|
+
# extra key: an empty agent can be the CONSEQUENCE of an exit-2 cause (below).
|
|
236
|
+
agent_empty=no
|
|
237
|
+
if [[ -n $pin ]] && keyseal_agent_holds_exactly "$slot" "$pin"; then ok_agent_i=true detail_agent_i=""
|
|
238
|
+
else
|
|
239
|
+
held=$(keyseal_agent_keys "$slot" 2>/dev/null) || held="?"
|
|
240
|
+
if [[ -z $held ]]; then agent_empty=yes; fi
|
|
241
|
+
nheld=$(printf '%s' "$held" | "$KEYSEAL_BIN_GREP" -c . || true)
|
|
242
|
+
[[ $held == "?" ]] && nheld=
|
|
243
|
+
ok_agent_i=false detail_agent_i="the slot agent holds ${nheld:-an unreadable set of} identities, not exactly the pin"
|
|
244
|
+
fi
|
|
245
|
+
|
|
246
|
+
# 5 linger (shared, host-level), and capability for the measured mode
|
|
247
|
+
[[ $linger == yes ]] || exit2_i+=(linger_off)
|
|
248
|
+
upgrade_i=no
|
|
249
|
+
case $mode in
|
|
250
|
+
sealed) [[ $outcome == capable ]] || exit2_i+=("capability_$outcome") ;;
|
|
251
|
+
unsealed)
|
|
252
|
+
if [[ $outcome == capable ]]; then upgrade_i=yes exit2_i+=(upgrade_available)
|
|
253
|
+
elif [[ $outcome != incapable ]]; then exit2_i+=("capability_$outcome")
|
|
254
|
+
fi
|
|
255
|
+
;;
|
|
135
256
|
esac
|
|
136
|
-
fi
|
|
137
257
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if [[ $(state is-enabled "$base_unit") == enabled && $(state is-enabled "$load_unit") == enabled &&
|
|
142
|
-
$(state is-active "$base_unit") == active && $(state is-active "$load_unit") == active && $sub == SubState=exited ]]; then
|
|
143
|
-
ok[units]=true detail[units]=""
|
|
144
|
-
else
|
|
145
|
-
ok[units]=false detail[units]="base $(state is-enabled "$base_unit")/$(state is-active "$base_unit"), loader $(state is-enabled "$load_unit")/$(state is-active "$load_unit") ${sub#SubState=}"
|
|
146
|
-
fi
|
|
258
|
+
pin_sha=null blob_sha=null
|
|
259
|
+
[[ -n $pin ]] && pin_sha=$(keyseal_json_str "$(keyseal_sha256_str "$pin")")
|
|
260
|
+
[[ -f $cred ]] && blob_sha=$(keyseal_json_str "$(keyseal_sha256_file "$cred")")
|
|
147
261
|
|
|
148
|
-
#
|
|
149
|
-
#
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
else
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
262
|
+
# 6 scan setup: the unsealed slot's own key file is the one exemption. Judge the cache now (IF-
|
|
263
|
+
# 552-2); the walk, shared across every slot needing one, runs once after this loop.
|
|
264
|
+
exempt=
|
|
265
|
+
[[ $mode == unsealed ]] && exempt=$key
|
|
266
|
+
exempt_j=$(if [[ -n $exempt ]]; then keyseal_json_str "$(keyseal_sha256_str "$exempt")"; else printf null; fi)
|
|
267
|
+
KEYSEAL_INPUT=(
|
|
268
|
+
[seam]=$seam_j
|
|
269
|
+
[uid]=$uid_j
|
|
270
|
+
[machine_id]=$machine_id_j
|
|
271
|
+
[slot]=$(keyseal_json_str "$slot")
|
|
272
|
+
[mode]=$(keyseal_json_str "$mode")
|
|
273
|
+
[exempt]=$exempt_j
|
|
274
|
+
[pin_sha256]=$pin_sha
|
|
275
|
+
[blob_sha256]=$blob_sha
|
|
276
|
+
[scope_sha256]=$scope_sha_j
|
|
277
|
+
[scanner_sha256]=$scanner_sha_j
|
|
278
|
+
)
|
|
279
|
+
keyseal_cache_read "$slot" "$scan_cache"
|
|
280
|
+
|
|
281
|
+
needs_scan=no
|
|
282
|
+
if [[ $KEYSEAL_CACHE_STATE != hit && $scan_cache != only ]]; then needs_scan=yes; fi
|
|
283
|
+
[[ $KEYSEAL_CACHE_STATE != hit && $scan_cache == only ]] && exit2_i+=(scan_not_cached)
|
|
284
|
+
|
|
285
|
+
A_MODE[i]=$mode A_PIN[i]=$pin A_EXEMPT[i]=$exempt A_EXEMPT_J[i]=$exempt_j
|
|
286
|
+
A_PINSHA_J[i]=$pin_sha A_BLOBSHA_J[i]=$blob_sha
|
|
287
|
+
A_OK_COMMITTED[i]=$ok_committed_i A_DET_COMMITTED[i]=$detail_committed_i
|
|
288
|
+
A_OK_CHAIN[i]=$ok_chain_i A_DET_CHAIN[i]=$detail_chain_i
|
|
289
|
+
A_CHAIN_LEGACY[i]=$KEYSEAL_CHAIN_LEGACY A_LEGACY[i]=$legacy_i
|
|
290
|
+
A_LOADER_STATE[i]=$KEYSEAL_LOADER_STATE A_LOADER_REC[i]=$KEYSEAL_LOADER_RECORDED A_LOADER_RUN[i]=$KEYSEAL_LOADER_RUNNING
|
|
291
|
+
A_OK_UNITS[i]=$ok_units_i A_DET_UNITS[i]=$detail_units_i
|
|
292
|
+
A_OK_AGENT[i]=$ok_agent_i A_DET_AGENT[i]=$detail_agent_i A_AGENT_EMPTY[i]=$agent_empty
|
|
293
|
+
A_OPTIN_J[i]=$optin_json A_UPGRADE[i]=$upgrade_i
|
|
294
|
+
A_EXIT2[i]="${exit2_i[*]-}"
|
|
295
|
+
A_CACHE_TOKEN[i]=$KEYSEAL_CACHE_TOKEN A_CACHE_STATE[i]=$KEYSEAL_CACHE_STATE A_CACHE_REASON[i]=$KEYSEAL_CACHE_REASON
|
|
296
|
+
A_CACHE_SCAN[i]=$KEYSEAL_CACHE_SCAN A_CACHE_SCANNED_AT[i]=$KEYSEAL_CACHE_SCANNED_AT
|
|
297
|
+
A_CACHE_DURATION[i]=$KEYSEAL_CACHE_DURATION A_CACHE_LAST[i]=$KEYSEAL_CACHE_LAST
|
|
298
|
+
A_NEEDS_SCAN[i]=$needs_scan
|
|
299
|
+
done
|
|
300
|
+
|
|
301
|
+
# ---- the ONE shared walk, for every slot phase A marked needs_scan=yes --------------------------
|
|
302
|
+
|
|
303
|
+
scan_idx=()
|
|
304
|
+
for i in "${!slots[@]}"; do
|
|
305
|
+
[[ ${A_NEEDS_SCAN[i]} == yes ]] && scan_idx+=("$i")
|
|
306
|
+
done
|
|
307
|
+
|
|
308
|
+
scan_start=0 scan_duration=0
|
|
309
|
+
if ((${#scan_idx[@]})); then
|
|
310
|
+
KEYSEAL_SCAN_PINS=() KEYSEAL_SCAN_EXEMPTS=()
|
|
311
|
+
for k in "${!scan_idx[@]}"; do
|
|
312
|
+
i=${scan_idx[k]}
|
|
313
|
+
KEYSEAL_SCAN_PINS[k]=${A_PIN[i]}
|
|
314
|
+
KEYSEAL_SCAN_EXEMPTS[k]=${A_EXEMPT[i]}
|
|
315
|
+
done
|
|
316
|
+
# scanned_at is the scan's START: a file created after it may be missed.
|
|
317
|
+
scan_start=$(keyseal_now) || scan_start=0
|
|
318
|
+
for k in "${!scan_idx[@]}"; do
|
|
319
|
+
i=${scan_idx[k]}
|
|
320
|
+
if ((n > 1)); then
|
|
321
|
+
printf 'status: scan: starting (%s:%s) slot=%s\n' "${A_CACHE_STATE[i]}" "${A_CACHE_REASON[i]}" "${slots[i]}" >&2
|
|
322
|
+
else
|
|
323
|
+
printf 'status: scan: starting (%s:%s)\n' "${A_CACHE_STATE[i]}" "${A_CACHE_REASON[i]}" >&2
|
|
324
|
+
fi
|
|
325
|
+
done
|
|
326
|
+
keyseal_scan_run "$scan_timeout"
|
|
327
|
+
scan_end=$(keyseal_now) || scan_end=$scan_start
|
|
328
|
+
scan_duration=$((scan_end - scan_start))
|
|
329
|
+
((scan_duration >= 0)) || scan_duration=0
|
|
158
330
|
fi
|
|
159
331
|
|
|
160
|
-
#
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
332
|
+
# ---- phase B: interpret the scan, commit the cache and emit, per slot ---------------------------
|
|
333
|
+
|
|
334
|
+
saw1=no saw2=no
|
|
335
|
+
for i in "${!slots[@]}"; do
|
|
336
|
+
slot=${slots[i]} harness=${harness_of[i]} mode=${A_MODE[i]}
|
|
337
|
+
read -ra exit2_i <<<"${A_EXIT2[i]:-}"
|
|
338
|
+
cache_read_i="${A_CACHE_STATE[i]}:${A_CACHE_REASON[i]}"
|
|
339
|
+
cache_state=${A_CACHE_STATE[i]} cache_reason=${A_CACHE_REASON[i]}
|
|
340
|
+
base_unit=$KEYSEAL_BASE_UNIT@$slot.service
|
|
341
|
+
load_unit=$KEYSEAL_LOAD_UNIT@$slot.service
|
|
342
|
+
|
|
343
|
+
KEYSEAL_INPUT=(
|
|
344
|
+
[seam]=$seam_j
|
|
345
|
+
[uid]=$uid_j
|
|
346
|
+
[machine_id]=$machine_id_j
|
|
347
|
+
[slot]=$(keyseal_json_str "$slot")
|
|
348
|
+
[mode]=$(keyseal_json_str "$mode")
|
|
349
|
+
[exempt]=${A_EXEMPT_J[i]}
|
|
350
|
+
[pin_sha256]=${A_PINSHA_J[i]}
|
|
351
|
+
[blob_sha256]=${A_BLOBSHA_J[i]}
|
|
352
|
+
[scope_sha256]=$scope_sha_j
|
|
353
|
+
[scanner_sha256]=$scanner_sha_j
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
scan_source=none scan_scanned_at=null scan_age=null scan_dur=null scan_core=
|
|
357
|
+
scan_judged=yes scan_timed_out=no detail_scan=""
|
|
358
|
+
if [[ $cache_state == hit ]]; then
|
|
359
|
+
scan_source=cached scan_core=${A_CACHE_SCAN[i]}
|
|
360
|
+
scan_now=$(keyseal_now) || scan_now=${A_CACHE_SCANNED_AT[i]}
|
|
361
|
+
scan_scanned_at=$(keyseal_json_str "$(keyseal_iso "${A_CACHE_SCANNED_AT[i]}")")
|
|
362
|
+
scan_age=$((scan_now - A_CACHE_SCANNED_AT[i]))
|
|
363
|
+
scan_dur=${A_CACHE_DURATION[i]}
|
|
364
|
+
elif [[ ${A_NEEDS_SCAN[i]} == no ]]; then
|
|
365
|
+
scan_judged=no
|
|
366
|
+
detail_scan="not judged: no valid scan cache ($cache_state:$cache_reason)"
|
|
367
|
+
else
|
|
368
|
+
case $KEYSEAL_SCAN_OUTCOME in
|
|
369
|
+
ok)
|
|
370
|
+
k=-1
|
|
371
|
+
for kk in "${!scan_idx[@]}"; do [[ ${scan_idx[kk]} == "$i" ]] && k=$kk && break; done
|
|
372
|
+
scan_source=fresh scan_core=$(keyseal_scan_core_json "$k")
|
|
373
|
+
scan_scanned_at=$(keyseal_json_str "$(keyseal_iso "$scan_start")") scan_age=$scan_duration
|
|
374
|
+
scan_dur=$scan_duration
|
|
375
|
+
;;
|
|
376
|
+
timed_out)
|
|
377
|
+
scan_judged=no scan_timed_out=yes
|
|
378
|
+
detail_scan="not judged: the scan gave up after $scan_timeout s (scan_timed_out)"
|
|
379
|
+
exit2_i+=(scan_timed_out)
|
|
380
|
+
cache_state=not_written cache_reason=timed_out
|
|
381
|
+
;;
|
|
382
|
+
*)
|
|
383
|
+
detail_scan="the scan did not run to completion"
|
|
384
|
+
cache_state=not_written cache_reason=incomplete
|
|
385
|
+
;;
|
|
386
|
+
esac
|
|
387
|
+
fi
|
|
388
|
+
if [[ -n $scan_core ]]; then
|
|
389
|
+
if [[ $scan_core == *'"plaintext_matches":[]'* ]]; then ok_scan=true detail_scan=""
|
|
390
|
+
else ok_scan=false detail_scan="plaintext copy of the slot key found"
|
|
391
|
+
fi
|
|
392
|
+
else
|
|
393
|
+
ok_scan=false
|
|
394
|
+
fi
|
|
395
|
+
# A match is never cached: it writes a tombstone. Only a clean, completed, fresh scan commits.
|
|
396
|
+
if [[ $scan_source == fresh ]]; then
|
|
397
|
+
KEYSEAL_CACHE_TOKEN=${A_CACHE_TOKEN[i]}
|
|
398
|
+
if [[ $ok_scan == true ]]; then keyseal_cache_commit_clean "$slot" "$scan_start" "$scan_duration" "$scan_core"
|
|
399
|
+
else keyseal_cache_commit_tombstone "$slot" "$harness"
|
|
400
|
+
fi
|
|
401
|
+
cache_state=$KEYSEAL_CACHE_STATE cache_reason=$KEYSEAL_CACHE_REASON
|
|
402
|
+
fi
|
|
403
|
+
|
|
404
|
+
if [[ $scan_source == fresh ]]; then
|
|
405
|
+
n_matches=0 n_candidates=0
|
|
406
|
+
[[ $scan_core =~ \"candidates\":([0-9]+) ]] && n_candidates=${BASH_REMATCH[1]}
|
|
407
|
+
[[ $ok_scan == true ]] || n_matches=$(printf '%s' "$scan_core" | keyseal_node count-matches 2>/dev/null || printf '?')
|
|
408
|
+
if ((n > 1)); then
|
|
409
|
+
printf 'status: scan: done in %s s (%s folders, %s candidates, %s matches) slot=%s\n' "$scan_duration" "$KEYSEAL_SCAN_FOLDERS" "$n_candidates" "$n_matches" "$slot" >&2
|
|
410
|
+
else
|
|
411
|
+
printf 'status: scan: done in %s s (%s folders, %s candidates, %s matches)\n' "$scan_duration" "$KEYSEAL_SCAN_FOLDERS" "$n_candidates" "$n_matches" >&2
|
|
412
|
+
fi
|
|
413
|
+
elif [[ $scan_source == none && $scan_judged == yes || $scan_timed_out == yes ]]; then
|
|
414
|
+
if ((n > 1)); then
|
|
415
|
+
printf 'status: scan: done in %s s (%s folders) slot=%s: %s\n' "$scan_duration" "$KEYSEAL_SCAN_FOLDERS" "$slot" "$detail_scan" >&2
|
|
416
|
+
else
|
|
417
|
+
printf 'status: scan: done in %s s (%s folders): %s\n' "$scan_duration" "$KEYSEAL_SCAN_FOLDERS" "$detail_scan" >&2
|
|
169
418
|
fi
|
|
170
|
-
|
|
171
|
-
esac
|
|
419
|
+
fi
|
|
172
420
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if
|
|
177
|
-
|
|
178
|
-
else ok[scan]=false detail[scan]="plaintext copy of the slot key found"
|
|
421
|
+
cache_last=null
|
|
422
|
+
[[ -n ${A_CACHE_LAST[i]} ]] && cache_last=$(keyseal_json_str "$(keyseal_iso "${A_CACHE_LAST[i]}")")
|
|
423
|
+
scan_meta="\"source\":\"$scan_source\",\"scanned_at\":$scan_scanned_at,\"age_s\":$scan_age,\"duration_s\":$scan_dur,\"inputs\":$(keyseal_cache_inputs_json),\"stale_temp\":$stale_temp,\"cache\":{\"mode\":\"$scan_cache\",\"state\":\"$cache_state\",\"reason\":$(str_or_null "$cache_reason"),\"read\":$(keyseal_json_str "$cache_read_i"),\"last_scanned_at\":$cache_last}"
|
|
424
|
+
if [[ -n $scan_core ]]; then scan_json=$(keyseal_scan_with_headers "${scan_core%\}},$scan_meta}")
|
|
425
|
+
else scan_json="{\"roots\":$(keyseal_json_list ${KEYSEAL_ROOTS[@]+"${KEYSEAL_ROOTS[@]}"}),\"roots_skipped\":$KEYSEAL_ROOTS_SKIPPED_JSON,$scan_meta}"
|
|
179
426
|
fi
|
|
180
|
-
else
|
|
181
|
-
scan='{}' ok[scan]=false detail[scan]="the scan did not run to completion"
|
|
182
|
-
fi
|
|
183
427
|
|
|
184
|
-
# Nothing committed is 2 by the provenance table: there is no unit, agent or chain to judge.
|
|
185
|
-
hard=(committed chain units agent scan)
|
|
186
|
-
[[ ${
|
|
187
|
-
#
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
for r in "${
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
428
|
+
# Nothing committed is 2 by the provenance table: there is no unit, agent or chain to judge.
|
|
429
|
+
hard=(committed chain units agent scan)
|
|
430
|
+
[[ "${exit2_i[*]-}" == *nothing_committed* ]] && hard=(committed scan)
|
|
431
|
+
# A scan that was not judged (no valid cache under `only`, or the deadline) is 2, never 1.
|
|
432
|
+
if [[ $scan_judged == no ]]; then
|
|
433
|
+
judged=()
|
|
434
|
+
for c in "${hard[@]}"; do [[ $c == scan ]] || judged+=("$c"); done
|
|
435
|
+
hard=("${judged[@]}")
|
|
436
|
+
fi
|
|
437
|
+
soft=no
|
|
438
|
+
for r in "${exit2_i[@]}"; do
|
|
439
|
+
case $r in
|
|
440
|
+
undeterminable) soft=yes ;;
|
|
441
|
+
capability_error | capability_incapable) [[ $mode == sealed ]] && soft=yes ;;
|
|
442
|
+
capability_incapable-for-keyslot) [[ $mode == sealed && $round_trip != equal ]] && soft=yes ;;
|
|
443
|
+
esac
|
|
444
|
+
done
|
|
445
|
+
if [[ $soft == yes ]]; then
|
|
446
|
+
still_hard=()
|
|
447
|
+
for c in "${hard[@]}"; do
|
|
448
|
+
case $c in
|
|
449
|
+
agent) [[ ${A_OK_AGENT[i]} == true || ${A_AGENT_EMPTY[i]} == yes ]] || still_hard+=(agent) ;;
|
|
450
|
+
units)
|
|
451
|
+
[[ ${A_OK_UNITS[i]} == true ]] ||
|
|
452
|
+
{ [[ $(state is-enabled "$base_unit") == enabled && $(state is-enabled "$load_unit") == enabled ]] || still_hard+=(units); }
|
|
453
|
+
;;
|
|
454
|
+
*) still_hard+=("$c") ;;
|
|
455
|
+
esac
|
|
456
|
+
done
|
|
457
|
+
hard=("${still_hard[@]}")
|
|
458
|
+
fi
|
|
459
|
+
rc=0
|
|
206
460
|
for c in "${hard[@]}"; do
|
|
207
461
|
case $c in
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
*) still_hard+=("$c") ;;
|
|
462
|
+
committed) v=${A_OK_COMMITTED[i]} ;;
|
|
463
|
+
chain) v=${A_OK_CHAIN[i]} ;;
|
|
464
|
+
units) v=${A_OK_UNITS[i]} ;;
|
|
465
|
+
agent) v=${A_OK_AGENT[i]} ;;
|
|
466
|
+
scan) v=$ok_scan ;;
|
|
214
467
|
esac
|
|
468
|
+
[[ $v == true ]] || rc=1
|
|
215
469
|
done
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
rc=
|
|
219
|
-
for c in "${hard[@]}"; do [[ ${ok[$c]} == true ]] || rc=1; done
|
|
220
|
-
if ((rc == 0 && ${#exit2[@]})); then rc=2; fi
|
|
470
|
+
if ((rc == 0 && ${#exit2_i[@]})); then rc=2; fi
|
|
471
|
+
((rc == 1)) && saw1=yes
|
|
472
|
+
((rc == 2)) && saw2=yes
|
|
221
473
|
|
|
222
|
-
|
|
223
|
-
[[ -
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
machine_id=$(keyseal_machine_id) || machine_id=
|
|
227
|
-
bool() { if [[ $1 == yes ]]; then printf true; else printf false; fi; }
|
|
228
|
-
reasons=
|
|
229
|
-
for r in "${exit2[@]}"; do reasons+="${reasons:+,}$(keyseal_json_str "$r")"; done
|
|
230
|
-
outdated=no
|
|
231
|
-
[[ ${exit2[*]} == *loader_outdated* ]] && outdated=yes
|
|
232
|
-
str_or_null() { if [[ -n $1 ]]; then keyseal_json_str "$1"; else printf null; fi; }
|
|
474
|
+
outdated_i=no
|
|
475
|
+
[[ "${exit2_i[*]-}" == *loader_outdated* ]] && outdated_i=yes
|
|
476
|
+
reasons=
|
|
477
|
+
for r in "${exit2_i[@]}"; do reasons+="${reasons:+,}$(keyseal_json_str "$r")"; done
|
|
233
478
|
|
|
234
|
-
if [[ $json == yes ]]; then
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
479
|
+
if [[ $json == yes ]]; then
|
|
480
|
+
checks=
|
|
481
|
+
for c in committed chain units agent linger scan; do
|
|
482
|
+
case $c in
|
|
483
|
+
committed) cok=${A_OK_COMMITTED[i]} cdet=${A_DET_COMMITTED[i]} ;;
|
|
484
|
+
chain) cok=${A_OK_CHAIN[i]} cdet=${A_DET_CHAIN[i]} ;;
|
|
485
|
+
units) cok=${A_OK_UNITS[i]} cdet=${A_DET_UNITS[i]} ;;
|
|
486
|
+
agent) cok=${A_OK_AGENT[i]} cdet=${A_DET_AGENT[i]} ;;
|
|
487
|
+
linger) cok=$ok_linger cdet=$detail_linger ;;
|
|
488
|
+
scan) cok=$ok_scan cdet=$detail_scan ;;
|
|
489
|
+
esac
|
|
490
|
+
checks+="${checks:+,}\"$c\":{\"ok\":$cok,\"detail\":$(keyseal_json_str "$cdet")}"
|
|
491
|
+
done
|
|
492
|
+
printf '{"tool":"status","seam":%s,"host":%s,"uid":%s,"machine_id":%s,"harness":%s,"slot":%s,"mode":%s,"boot_id":%s,"pin_sha256":%s,"blob_sha256":%s,"capability_probe_exit":%s,"probe_outcome":%s,"optin":%s,"loader":{"recorded_sha256":%s,"running_sha256":%s,"state":%s},"upgrade_available":%s,"loader_outdated":%s,"legacy_loader_present":%s,"exit2_reasons":[%s],"checks":{%s},"scan":%s,"exit":%s}\n' \
|
|
493
|
+
"$seam_j" "$host_j" "$uid_j" "$machine_id_j" \
|
|
494
|
+
"$(keyseal_json_str "$harness")" "$(keyseal_json_str "$slot")" "$(keyseal_json_str "$mode")" \
|
|
495
|
+
"$boot_id_j" "${A_PINSHA_J[i]}" "${A_BLOBSHA_J[i]}" "$probe_rc" "$(keyseal_json_str "$outcome")" "${A_OPTIN_J[i]}" \
|
|
496
|
+
"$(str_or_null "${A_LOADER_REC[i]}")" "$(str_or_null "${A_LOADER_RUN[i]}")" \
|
|
497
|
+
"$(keyseal_json_str "$([[ ${A_CHAIN_LEGACY[i]} == yes ]] && echo legacy || echo "${A_LOADER_STATE[i]}")")" \
|
|
498
|
+
"$(bool "${A_UPGRADE[i]}")" "$(bool "$outdated_i")" "$(bool "${A_LEGACY[i]}")" "$reasons" "$checks" "$scan_json" "$rc"
|
|
499
|
+
else
|
|
500
|
+
for c in committed chain units agent linger scan; do
|
|
501
|
+
case $c in
|
|
502
|
+
committed) cok=${A_OK_COMMITTED[i]} cdet=${A_DET_COMMITTED[i]} ;;
|
|
503
|
+
chain) cok=${A_OK_CHAIN[i]} cdet=${A_DET_CHAIN[i]} ;;
|
|
504
|
+
units) cok=${A_OK_UNITS[i]} cdet=${A_DET_UNITS[i]} ;;
|
|
505
|
+
agent) cok=${A_OK_AGENT[i]} cdet=${A_DET_AGENT[i]} ;;
|
|
506
|
+
linger) cok=$ok_linger cdet=$detail_linger ;;
|
|
507
|
+
scan) cok=$ok_scan cdet=$detail_scan ;;
|
|
508
|
+
esac
|
|
509
|
+
printf '%-9s %s %s\n' "$c" "$([[ $cok == true ]] && echo ok || echo FAIL)" "$cdet"
|
|
510
|
+
done
|
|
511
|
+
printf 'mode %s\n' "$mode"
|
|
512
|
+
((${#exit2_i[@]})) && printf 'exit 2 %s\n' "${exit2_i[*]}"
|
|
513
|
+
printf 'status: exit %s (seam=%s)\n' "$rc" "$seam_j"
|
|
514
|
+
fi
|
|
515
|
+
done
|
|
516
|
+
|
|
517
|
+
if [[ $saw1 == yes ]]; then exit 1
|
|
518
|
+
elif [[ $saw2 == yes ]]; then exit 2
|
|
519
|
+
else exit 0
|
|
253
520
|
fi
|
|
254
|
-
exit "$rc"
|