290 lines
13 KiB
Bash
Executable File
290 lines
13 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# The README describes this repository accurately.
|
|
#
|
|
# It carries two facts that are cheap to state and easy to leave behind: how
|
|
# many contracts there are, and which `panama` subcommands exist. Both had
|
|
# already drifted -- the count said 121 when the suite had grown to 125, one day
|
|
# after it was written.
|
|
#
|
|
# A number in prose is not worth much on its own. It is worth something as a
|
|
# claim somebody might rely on, and worth nothing once it is wrong, so it is
|
|
# either checked or it should not be there.
|
|
|
|
set -uo pipefail
|
|
|
|
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
readme="$repo_dir/README.md"
|
|
panama="$repo_dir/bin/panama"
|
|
|
|
findings=()
|
|
note() { findings+=("$1"); }
|
|
work="$(mktemp -d)"
|
|
trap 'rm -rf -- "$work"' EXIT
|
|
|
|
# ── Verified bootstrap command ───────────────────────────────────────────────
|
|
|
|
if grep -qE 'bash[[:space:]]+<\(curl[^)]*/raw/branch/main/boot' "$readme"; then
|
|
note 'the README still executes the mutable main-branch bootstrap'
|
|
fi
|
|
if grep -q '/raw/branch/main/boot' "$readme"; then
|
|
note 'the README still names the mutable main-branch boot URL'
|
|
fi
|
|
|
|
require_bootstrap_occurrences() {
|
|
local pattern="$1" expected="$2" explanation="$3" actual
|
|
actual="$(grep -cE -- "$pattern" "$readme")"
|
|
(( actual == expected )) || note "$explanation"
|
|
}
|
|
|
|
mapfile -t documented_commits < <(
|
|
sed -nE "s/^bootstrap_commit=['\"]?([0-9a-f]{40})['\"]?$/\1/p" "$readme" | sort -u
|
|
)
|
|
mapfile -t documented_shas < <(
|
|
sed -nE "s/^bootstrap_sha=['\"]?([0-9a-f]{64})['\"]?$/\1/p" "$readme" | sort -u
|
|
)
|
|
|
|
if (( ${#documented_commits[@]} != 1 )); then
|
|
note 'the README does not declare one full lowercase 40-hex bootstrap commit'
|
|
else
|
|
documented_commit="${documented_commits[0]}"
|
|
if ! git -C "$repo_dir" cat-file -e "$documented_commit^{commit}" 2>/dev/null; then
|
|
note 'the documented bootstrap commit does not resolve to a repository commit'
|
|
fi
|
|
fi
|
|
|
|
if (( ${#documented_shas[@]} != 1 )); then
|
|
note 'the README does not declare one lowercase 64-hex bootstrap SHA-256'
|
|
else
|
|
documented_sha="${documented_shas[0]}"
|
|
fi
|
|
|
|
if [[ -n "${documented_commit:-}" && -n "${documented_sha:-}" ]] \
|
|
&& [[ "$(git -C "$repo_dir" show "$documented_commit:boot" 2>/dev/null | sha256sum | cut -d' ' -f1)" != "$documented_sha" ]]; then
|
|
note 'the documented SHA-256 does not match boot in the documented commit'
|
|
fi
|
|
|
|
require_bootstrap_occurrences '^bootstrap_commit=[0-9a-f]{40}$' 2 \
|
|
'the desktop and server commands do not declare the same full bootstrap commit'
|
|
require_bootstrap_occurrences '^bootstrap_sha=[0-9a-f]{64}$' 2 \
|
|
'the desktop and server commands do not declare the same full bootstrap SHA-256'
|
|
require_bootstrap_occurrences 'https://git\.gbrown\.org/gib/Panama/raw/commit/\$bootstrap_commit/boot' 2 \
|
|
'the desktop and server commands do not both use the commit-addressed boot URL'
|
|
require_bootstrap_occurrences '--connect-timeout 10' 2 \
|
|
'the desktop and server commands do not both use the 10-second connect timeout'
|
|
require_bootstrap_occurrences '--max-time 30' 2 \
|
|
'the desktop and server commands do not both use the 30-second total timeout'
|
|
require_bootstrap_occurrences '--max-filesize 262144' 2 \
|
|
'the desktop and server commands do not both use the 256 KiB response limit'
|
|
require_bootstrap_occurrences 'mktemp[[:space:]]+-d' 2 \
|
|
'the desktop and server commands do not both use a private temporary directory'
|
|
require_bootstrap_occurrences 'sha256sum[[:space:]]+-c' 2 \
|
|
'the desktop and server commands do not both verify with sha256sum -c'
|
|
require_bootstrap_occurrences 'PANAMA_BOOT_REVISION="?\$bootstrap_commit"?[[:space:]]+PANAMA_BOOT_SHA256="?\$bootstrap_sha"?' 2 \
|
|
'the desktop and server commands do not both pass the verified pins to boot'
|
|
grep -qE 'bash[[:space:]]+"?\$bootstrap"?([[:space:]]|$)' "$readme" \
|
|
|| note 'the README bootstrap does not execute the verified temporary file'
|
|
grep -qE 'bash[[:space:]]+"?\$bootstrap"?[[:space:]]+--server' "$readme" \
|
|
|| note 'the server bootstrap does not reuse the verified temporary file'
|
|
if grep -qE 'curl[^|]*\|[[:space:]]*(bash|sh)|bash[[:space:]]+<\(curl' "$readme"; then
|
|
note 'the README pipes a network response into a shell'
|
|
fi
|
|
|
|
# Run the two exact documented blocks with a successful download and a failing
|
|
# checksum. The Bash adapter records only the verified boot invocation; the
|
|
# contract itself uses /usr/bin/bash so the adapter cannot hide this behavior.
|
|
checksum_stub_dir="$work/checksum-bin"
|
|
checksum_boot_calls="$work/checksum-boot-calls"
|
|
mkdir -p "$checksum_stub_dir"
|
|
|
|
cat >"$checksum_stub_dir/curl" <<'STUB'
|
|
#!/usr/bin/bash
|
|
set -u
|
|
destination=""
|
|
while (( $# > 0 )); do
|
|
case "$1" in
|
|
--output)
|
|
destination="${2:-}"
|
|
shift 2
|
|
;;
|
|
*) shift ;;
|
|
esac
|
|
done
|
|
[[ -n "$destination" ]] || exit 97
|
|
printf 'tampered boot bytes\n' >"$destination"
|
|
STUB
|
|
chmod +x "$checksum_stub_dir/curl"
|
|
|
|
cat >"$checksum_stub_dir/sha256sum" <<'STUB'
|
|
#!/usr/bin/bash
|
|
[[ "${1:-}" == -c ]] || exit 97
|
|
exit 1
|
|
STUB
|
|
chmod +x "$checksum_stub_dir/sha256sum"
|
|
|
|
cat >"$checksum_stub_dir/bash" <<'STUB'
|
|
#!/usr/bin/bash
|
|
printf 'verified-boot %s\n' "$*" >>"$PANAMA_README_BOOT_CALLS"
|
|
exit 0
|
|
STUB
|
|
chmod +x "$checksum_stub_dir/bash"
|
|
|
|
mapfile -d $'\036' -t bootstrap_snippets < <(
|
|
awk '
|
|
/^```sh$/ { in_block = 1; block = ""; next }
|
|
/^```$/ && in_block {
|
|
if (block ~ /bootstrap_commit=/) printf "%s%c", block, 30
|
|
in_block = 0
|
|
next
|
|
}
|
|
in_block { block = block $0 "\n" }
|
|
' "$readme"
|
|
)
|
|
|
|
checksum_failure_stops_boot() {
|
|
local snippet="$1" status
|
|
: >"$checksum_boot_calls"
|
|
PATH="$checksum_stub_dir:/usr/bin:/bin" \
|
|
PANAMA_README_BOOT_CALLS="$checksum_boot_calls" \
|
|
/usr/bin/bash -c "$snippet" >/dev/null 2>&1
|
|
status=$?
|
|
(( status != 0 )) && [[ ! -s "$checksum_boot_calls" ]]
|
|
}
|
|
|
|
if (( ${#bootstrap_snippets[@]} != 2 )); then
|
|
note 'the README does not contain exactly two executable verified bootstrap blocks'
|
|
else
|
|
bootstrap_labels=(desktop server)
|
|
for index in "${!bootstrap_snippets[@]}"; do
|
|
snippet="${bootstrap_snippets[$index]}"
|
|
label="${bootstrap_labels[$index]}"
|
|
if ! checksum_failure_stops_boot "$snippet"; then
|
|
note "the $label command invoked boot after checksum failure"
|
|
fi
|
|
|
|
weakened_snippet="${snippet//$'set -euo pipefail\n'/}"
|
|
if [[ "$weakened_snippet" == "$snippet" ]]; then
|
|
note "the $label command has no fail-closed shell control to test"
|
|
elif checksum_failure_stops_boot "$weakened_snippet"; then
|
|
note "the $label checksum assertion accepts removal of fail-closed shell control"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# ── The contract count ───────────────────────────────────────────────────────
|
|
#
|
|
# Counted the way `panama test` collects the suite, so the README agrees with
|
|
# what the runner actually reports rather than with a second idea of it.
|
|
|
|
actual="$(find "$repo_dir/tests" -type f -not -path '*/fixtures/*' -not -path '*__pycache__*' \
|
|
\( -executable -o -name '*_test.py' \) | wc -l)"
|
|
|
|
claimed="$(grep -oE '^[0-9]+ of them, under' "$readme" | grep -oE '^[0-9]+')"
|
|
|
|
if [[ -z "$claimed" ]]; then
|
|
note 'the README no longer states a contract count in the form this checks'
|
|
elif (( claimed != actual )); then
|
|
note "the README says $claimed contracts; there are $actual"
|
|
fi
|
|
|
|
# ── Root server bootstrap ───────────────────────────────────────────────────
|
|
#
|
|
# This path runs before the repository exists on a fresh VPS. Its safety
|
|
# properties need to be stated beside the public `boot --server` example, not
|
|
# inferred from the shell implementation or buried in a fixture.
|
|
|
|
bootstrap_doc="$(sed -n '/^That command also works from a brand-new VPS/,/^`install` asks/p' "$readme" | tr '\n' ' ')"
|
|
|
|
require_bootstrap_doc() {
|
|
local pattern="$1" explanation="$2"
|
|
grep -qiE "$pattern" <<<"$bootstrap_doc" || note "$explanation"
|
|
}
|
|
|
|
assert_bootstrap_probe_rejected() {
|
|
local name="$1" pattern="$2" weakened_doc="$3"
|
|
if grep -qiE "$pattern" <<<"$weakened_doc"; then
|
|
note "the $name assertion accepts its weakened documentation probe"
|
|
fi
|
|
}
|
|
|
|
target_key_requirement='target user owns[^.]*\.ssh[^.]*mode[^.]*([^0-9]|^)0700([^0-9]|$)[^.]*authorized_keys[^.]*mode[^.]*([^0-9]|^)0600([^0-9]|$)'
|
|
parseable_key_requirement='(every|each)[^.]*non-?comment[^.]*authorized_keys[^.]*(OpenSSH|ssh-keygen)[^.]*(parse|valid)|(OpenSSH|ssh-keygen)[^-]*parse[^.]*every[^.]*non-?comment'
|
|
hardening_continues_requirement='hardening[[:space:]]+is[[:space:]]+unavailable[^.]*without[^.]*verified[^.]*key[^.]*install[[:space:]]+continues[^.]*without[[:space:]]+(it|SSH[[:space:]]+hardening)'
|
|
atomic_dropin_requirement='atomic[[:space:]]+same-directory[[:space:]]+drop-in'
|
|
rollback_requirement='validation[^.]*reload[^.]*fail[^.]*(restor|rollback)[^.]*previous[[:space:]]+drop-in'
|
|
effective_policy_requirement='sshd -T[^.]*root[^.]*target|sshd -T[^.]*target[^.]*root'
|
|
|
|
require_bootstrap_doc "$target_key_requirement" \
|
|
'the root bootstrap docs do not require target-user ownership with exact 0700/0600 SSH modes'
|
|
require_bootstrap_doc "$parseable_key_requirement" \
|
|
'the root bootstrap docs do not require OpenSSH to parse every non-comment key entry'
|
|
require_bootstrap_doc "$hardening_continues_requirement" \
|
|
'the root bootstrap docs do not say bootstrap continues without unavailable SSH hardening'
|
|
require_bootstrap_doc 'sshd -t' \
|
|
'the root bootstrap docs do not name sshd -t validation'
|
|
require_bootstrap_doc "$effective_policy_requirement" \
|
|
'the root bootstrap docs do not name sshd -T checks for root and target contexts'
|
|
require_bootstrap_doc "$atomic_dropin_requirement" \
|
|
'the root bootstrap docs do not describe the atomic same-directory drop-in'
|
|
require_bootstrap_doc '00-panama\.conf' \
|
|
'the root bootstrap docs do not name the precedence-safe 00-panama.conf drop-in'
|
|
require_bootstrap_doc 'PermitRootLogin[^.]*no[^.]*PasswordAuthentication[^.]*no[^.]*KbdInteractiveAuthentication[^.]*no' \
|
|
'the root bootstrap docs do not state all three effective authentication denials'
|
|
require_bootstrap_doc 'detected (SSH )?unit.*reload|reload.*detected (SSH )?unit' \
|
|
'the root bootstrap docs do not describe reloading the detected SSH unit'
|
|
require_bootstrap_doc "$rollback_requirement" \
|
|
'the root bootstrap docs do not promise rollback on validation or reload failure'
|
|
require_bootstrap_doc 'fixture contracts.*(these|this) (path|branch)|fixture contracts.*test' \
|
|
'the root bootstrap docs do not limit proof to fixture contracts'
|
|
require_bootstrap_doc 'no real daemon reload.*panama test --safe|panama test --safe.*no real daemon reload' \
|
|
'the root bootstrap docs imply a live daemon reload under the safe suite'
|
|
|
|
# These prove the semantic assertions above reject the precise omissions they
|
|
# guard against. They mutate only the scoped documentation string; README.md
|
|
# itself remains the real input that must satisfy the contract.
|
|
assert_bootstrap_probe_rejected 'exact SSH modes' "$target_key_requirement" \
|
|
"${bootstrap_doc//0700/700}"
|
|
assert_bootstrap_probe_rejected 'target-user ownership' "$target_key_requirement" \
|
|
"${bootstrap_doc//target user owns/someone owns}"
|
|
weakened_key_doc="${bootstrap_doc//OpenSSH/text tooling}"
|
|
weakened_key_doc="${weakened_key_doc//ssh-keygen/text parser}"
|
|
assert_bootstrap_probe_rejected 'OpenSSH key parsing' "$parseable_key_requirement" \
|
|
"$weakened_key_doc"
|
|
assert_bootstrap_probe_rejected 'hardening availability' "$hardening_continues_requirement" \
|
|
"${bootstrap_doc//unavailable/available}"
|
|
assert_bootstrap_probe_rejected 'hardening continuation' "$hardening_continues_requirement" \
|
|
"${bootstrap_doc//continues/stops}"
|
|
assert_bootstrap_probe_rejected 'atomic drop-in' "$atomic_dropin_requirement" \
|
|
"${bootstrap_doc//atomic /}"
|
|
assert_bootstrap_probe_rejected 'rollback after failure' "$rollback_requirement" \
|
|
"${bootstrap_doc//restores /keeps }"
|
|
assert_bootstrap_probe_rejected 'rollback trigger' "$rollback_requirement" \
|
|
"${bootstrap_doc//fails/works}"
|
|
assert_bootstrap_probe_rejected 'effective target policy' "$effective_policy_requirement" \
|
|
"${bootstrap_doc//target/root}"
|
|
|
|
if grep -qiE 'merely writes? (the )?(SSH )?(drop-in|file)|reload failure.*ignored|ignores? .*reload failure' <<<"$bootstrap_doc"; then
|
|
note 'the root bootstrap docs weaken the transaction by treating the write or reload failure as harmless'
|
|
fi
|
|
|
|
# ── Documented subcommands exist ─────────────────────────────────────────────
|
|
#
|
|
# A README listing a command the dispatcher does not have sends somebody to a
|
|
# 'Unknown command' error, which reads as a broken install rather than a stale
|
|
# document.
|
|
|
|
while read -r subcommand; do
|
|
[[ -n "$subcommand" ]] || continue
|
|
grep -qE "^\s+$subcommand\)" "$panama" \
|
|
|| note "the README documents 'panama $subcommand', which the dispatcher does not handle"
|
|
done < <(sed -n '/^panama [a-z]/s/^panama \([a-z-]*\).*/\1/p' "$readme" | sort -u)
|
|
|
|
if (( ${#findings[@]} > 0 )); then
|
|
mapfile -t findings < <(printf '%s\n' "${findings[@]}" | sort -u)
|
|
printf 'readme contract: %d finding(s)\n' "${#findings[@]}" >&2
|
|
printf ' - %s\n' "${findings[@]}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
printf 'readme contract: PASS (%d contracts, as documented)\n' "$actual"
|