The operator no longer answers an "offline?" question and no longer needs network access: the installer finds the package set that ships with the checkout, so the only input left is the dealer number. Installer - Auto-discover the local APT repository (BF_OFFLINE_REPO, then provisioning/offline-repo/built, then /opt/blueforce/offline-repo) and enter offline mode on its own; --offline stays for compatibility. - Fail closed with an explicit Turkish message when nothing is found: the searched locations, what to do, and a named exit reason -- no state touched. - Distinguish "repository absent" (operator error, actionable) from "repository present but invalid" (OFFLINE BLOCKED, verification gate). - Network is informational only in precheck/network modules; no probe runs in offline mode and archive.ubuntu.com is no longer mandatory. ISO builder - Look for built/ first, which is what build-index.sh produces, and accept build/ as a warned legacy path. The two disagreed, so a medium could be built without the package set and fail only at install time. Docs - docs/27, scripts/install/README, provisioning/offline-repo/README and provisioning/firstboot/README describe the discovered flow, the two failure modes, and that the installer downloads nothing. Tests - check-admin-static exercised the "console missing from the clone" branch by deleting a tracked file; git clone restored it from history, so the branch never ran and the warning was effectively untested. Build the stripped source as a real commit instead; verified by mutation.
785 lines
34 KiB
Bash
Executable file
785 lines
34 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
#
|
|
# Static + behavioural contract tests for the git-based bootstrap and the
|
|
# optional credential tool:
|
|
#
|
|
# admin/bf-bootstrap.sh one-command install from git
|
|
# admin/bf-creds optional, default-OFF credential generation/delivery
|
|
# admin/lib/creds.sh shared helpers
|
|
#
|
|
# Everything here is mock/sandboxed: no network, no real apt, no root, and no
|
|
# write outside a mktemp sandbox. The bootstrap apply path is exercised with
|
|
# `id` and `apt-get` shims placed first in PATH, every PATH entry providing
|
|
# ansible-playbook removed (so the dependency branch runs deterministically and
|
|
# still only reaches the apt-get shim), and BF_INSTALL_DIR / BF_BIN_DIR /
|
|
# BF_REPO_URL pointed at the sandbox — a real package install or a real
|
|
# /usr/local/bin link can never happen from this suite.
|
|
#
|
|
# Every load-bearing check is paired with a negative control: the same
|
|
# predicate is run against a deliberately broken copy and MUST trip, so a
|
|
# check that has quietly stopped testing anything fails the suite instead of
|
|
# passing silently.
|
|
#
|
|
set -euo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
BOOTSTRAP="$REPO_ROOT/admin/bf-bootstrap.sh"
|
|
CREDS="$REPO_ROOT/admin/bf-creds"
|
|
CREDS_LIB="$REPO_ROOT/admin/lib/creds.sh"
|
|
GITIGNORE="$REPO_ROOT/.gitignore"
|
|
|
|
FAIL=0
|
|
CHECKS=0
|
|
fail() { printf 'FAIL: %s\n' "$*" >&2; FAIL=1; }
|
|
pass() { printf 'OK: %s\n' "$*"; }
|
|
note() { printf 'NOTE: %s\n' "$*"; }
|
|
check() { CHECKS=$((CHECKS + 1)); }
|
|
|
|
WORK="$(mktemp -d "${TMPDIR:-/tmp}/check-admin.XXXXXX")"
|
|
cleanup() { rm -rf "$WORK"; }
|
|
trap cleanup EXIT
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Predicates and assertion helpers.
|
|
# --------------------------------------------------------------------------- #
|
|
has() { [[ -f "$1" ]] && grep -qE -- "$2" "$1"; }
|
|
lacks() { [[ -f "$1" ]] && ! grep -qE -- "$2" "$1"; }
|
|
# Case-insensitive variants, used for the secret-material scan (a planted
|
|
# `DEMO_TOKEN=...` must be found just like a lowercase one).
|
|
has_i() { [[ -f "$1" ]] && grep -qiE -- "$2" "$1"; }
|
|
lacks_i() { [[ -f "$1" ]] && ! grep -qiE -- "$2" "$1"; }
|
|
|
|
# contract <desc> <file> <predicate> <regex> -- the predicate must hold.
|
|
contract() {
|
|
check
|
|
if "$3" "$2" "$4"; then
|
|
pass "$1"
|
|
else
|
|
fail "$1 [file=${2#"$REPO_ROOT"/} pattern=$4]"
|
|
fi
|
|
}
|
|
|
|
# control <desc> <broken-file> <predicate> <regex> -- the predicate must NOT hold.
|
|
control() {
|
|
check
|
|
if "$3" "$2" "$4"; then
|
|
fail "negative control did not trip: $1"
|
|
else
|
|
pass "negative control trips: $1"
|
|
fi
|
|
}
|
|
|
|
expect_rc() { # <desc> <expected-rc> <actual-rc>
|
|
check
|
|
if [[ "$2" -eq "$3" ]]; then
|
|
pass "$1"
|
|
else
|
|
fail "$1 (expected exit $2, got $3)"
|
|
fi
|
|
}
|
|
|
|
expect_absent() { # <desc> <path>
|
|
check
|
|
if [[ ! -e "$2" ]]; then
|
|
pass "$1"
|
|
else
|
|
fail "$1 (path exists: $2)"
|
|
fi
|
|
}
|
|
|
|
expect_present() { # <desc> <path>
|
|
check
|
|
if [[ -e "$2" ]]; then
|
|
pass "$1"
|
|
else
|
|
fail "$1 (path missing: $2)"
|
|
fi
|
|
}
|
|
|
|
expect_mode() { # <desc> <path> <mode>
|
|
check
|
|
local mode=""
|
|
mode="$(stat -c '%a' "$2" 2>/dev/null || true)"
|
|
if [[ "$mode" == "$3" ]]; then
|
|
pass "$1"
|
|
else
|
|
fail "$1 (mode is '${mode:-missing}', expected $3)"
|
|
fi
|
|
}
|
|
|
|
expect_contains() { # <desc> <file> <literal>
|
|
check
|
|
if grep -qF -- "$3" "$2" 2>/dev/null; then
|
|
pass "$1"
|
|
else
|
|
fail "$1 (literal not found: $3)"
|
|
fi
|
|
}
|
|
|
|
expect_absent_text() { # <desc> <file> <literal>
|
|
check
|
|
if grep -qF -- "$3" "$2" 2>/dev/null; then
|
|
fail "$1 (literal found: $3)"
|
|
else
|
|
pass "$1"
|
|
fi
|
|
}
|
|
|
|
# secret_leaked <output-file> <secret> -- 0 when the secret is visible.
|
|
# This is the leak detector used for the generated credential; its negative
|
|
# control below proves it can actually find a secret.
|
|
secret_leaked() { grep -qF -- "$2" "$1" 2>/dev/null; }
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# 1. Presence, shebang, strict mode, syntax.
|
|
# --------------------------------------------------------------------------- #
|
|
for target in "$BOOTSTRAP" "$CREDS" "$CREDS_LIB"; do
|
|
check
|
|
if [[ -f "$target" ]]; then
|
|
pass "present: ${target#"$REPO_ROOT"/}"
|
|
else
|
|
fail "missing: ${target#"$REPO_ROOT"/}"
|
|
fi
|
|
done
|
|
|
|
for target in "$BOOTSTRAP" "$CREDS" "$CREDS_LIB"; do
|
|
[[ -f "$target" ]] || continue
|
|
name="${target#"$REPO_ROOT"/}"
|
|
check
|
|
if [[ "$(head -n 1 "$target")" == '#!/usr/bin/env bash' ]]; then
|
|
pass "bash shebang: $name"
|
|
else
|
|
fail "bash shebang missing: $name"
|
|
fi
|
|
check
|
|
if grep -q '^set -euo pipefail$' "$target"; then
|
|
pass "strict mode (set -euo pipefail): $name"
|
|
else
|
|
fail "strict mode missing: $name"
|
|
fi
|
|
check
|
|
if bash -n "$target" 2>/dev/null; then
|
|
pass "bash parses: $name"
|
|
else
|
|
fail "bash parse error: $name"
|
|
fi
|
|
done
|
|
|
|
# The two entry points are invoked directly and symlinked into /usr/local/bin.
|
|
for target in "$BOOTSTRAP" "$CREDS"; do
|
|
[[ -f "$target" ]] || continue
|
|
check
|
|
if [[ -x "$target" ]]; then
|
|
pass "executable bit: ${target#"$REPO_ROOT"/}"
|
|
else
|
|
fail "not executable: ${target#"$REPO_ROOT"/}"
|
|
fi
|
|
done
|
|
|
|
# Negative control for the syntax gate: a broken script must be rejected.
|
|
printf '#!/usr/bin/env bash\nif true; then\n' > "$WORK/broken.sh"
|
|
check
|
|
if bash -n "$WORK/broken.sh" 2>/dev/null; then
|
|
fail 'negative control did not trip: bash -n accepted broken syntax'
|
|
else
|
|
pass 'negative control trips: bash -n rejects broken syntax'
|
|
fi
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# 2. --help works and documents the documented interface.
|
|
# --------------------------------------------------------------------------- #
|
|
check
|
|
if bash "$BOOTSTRAP" --help > "$WORK/bootstrap-help.out" 2>&1; then
|
|
pass 'bf-bootstrap.sh --help exits 0'
|
|
else
|
|
fail 'bf-bootstrap.sh --help failed'
|
|
fi
|
|
for flag in '--check' '--dir' '--branch' '--yes'; do
|
|
contract "bf-bootstrap --help documents $flag" "$WORK/bootstrap-help.out" has "\\${flag}"
|
|
done
|
|
|
|
check
|
|
if bash "$CREDS" --help > "$WORK/creds-help.out" 2>&1; then
|
|
pass 'bf-creds --help exits 0'
|
|
else
|
|
fail 'bf-creds --help failed'
|
|
fi
|
|
for flag in '--generate' '--fetch' '--repo' '--dealer-id'; do
|
|
contract "bf-creds --help documents $flag" "$WORK/creds-help.out" has "\\${flag}"
|
|
done
|
|
|
|
# Unknown arguments are refused with exit 2 (and never mutate anything).
|
|
set +e
|
|
bash "$BOOTSTRAP" --definitely-not-a-flag > "$WORK/bootstrap-bad.out" 2>&1
|
|
bootstrap_bad_rc=$?
|
|
bash "$CREDS" --definitely-not-a-flag > "$WORK/creds-bad.out" 2>&1
|
|
creds_bad_rc=$?
|
|
set -e
|
|
expect_rc 'bf-bootstrap.sh rejects an unknown argument with exit 2' 2 "$bootstrap_bad_rc"
|
|
expect_rc 'bf-creds rejects an unknown argument with exit 2' 2 "$creds_bad_rc"
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# 3. bf-bootstrap.sh static contracts (+ negative controls).
|
|
# --------------------------------------------------------------------------- #
|
|
contract 'bf-bootstrap checks for root before mutating' "$BOOTSTRAP" has 'id -u'
|
|
contract 'bf-bootstrap installs prerequisites with apt-get' "$BOOTSTRAP" has 'apt-get install'
|
|
contract 'bf-bootstrap prepares git/curl/ansible-core' "$BOOTSTRAP" has 'ansible-playbook'
|
|
contract 'bf-bootstrap clones the repository' "$BOOTSTRAP" has 'git clone'
|
|
contract 'bf-bootstrap updates an existing checkout fast-forward only' "$BOOTSTRAP" has 'merge --ff-only'
|
|
contract 'bf-bootstrap never rewrites the checkout history' "$BOOTSTRAP" lacks 'reset --hard|git clean'
|
|
contract 'bf-bootstrap links tools into /usr/local/bin' "$BOOTSTRAP" has '/usr/local/bin'
|
|
contract 'bf-bootstrap links idempotently' "$BOOTSTRAP" has 'ln -sfn'
|
|
contract 'bf-bootstrap discovers the admin tools' "$BOOTSTRAP" has 'admin/bf-\*'
|
|
contract 'bf-bootstrap discovers the diagnostics tools' "$BOOTSTRAP" has 'scripts/diagnostics/bf-\*'
|
|
contract 'bf-bootstrap discovers the maintenance tools' "$BOOTSTRAP" has 'scripts/maintenance/bf-\*'
|
|
contract 'bf-bootstrap wires up bf-menu' "$BOOTSTRAP" has 'bf-menu'
|
|
contract 'bf-bootstrap supports Ubuntu 24.04' "$BOOTSTRAP" has '24\.04'
|
|
contract 'bf-bootstrap supports Ubuntu 26.04' "$BOOTSTRAP" has '26\.04'
|
|
contract 'bf-bootstrap has a read-only check mode' "$BOOTSTRAP" has 'CHECK_MODE'
|
|
contract 'bf-bootstrap prints the completion contract' "$BOOTSTRAP" has 'Kurulum tamam\. Başlamak için: sudo bf'
|
|
contract 'bf-bootstrap carries no private key material' "$BOOTSTRAP" lacks 'BEGIN [A-Z ]*PRIVATE KEY'
|
|
contract 'bf-bootstrap generates no WireGuard key' "$BOOTSTRAP" lacks 'wg genkey'
|
|
|
|
# Negative controls: break each load-bearing property and prove the check trips.
|
|
sed -E 's#/usr/local/bin#/usr/bin#g' "$BOOTSTRAP" > "$WORK/b-ins-local-bin.sh"
|
|
chmod 0755 "$WORK/b-ins-local-bin.sh"
|
|
control 'bf-bootstrap target directory is checked' "$WORK/b-ins-local-bin.sh" has '/usr/local/bin'
|
|
|
|
sed -E 's#merge --ff-only#reset --hard#g' "$BOOTSTRAP" > "$WORK/b-reset.sh"
|
|
chmod 0755 "$WORK/b-reset.sh"
|
|
control 'bf-bootstrap fast-forward requirement is checked' "$WORK/b-reset.sh" has 'merge --ff-only'
|
|
control 'bf-bootstrap history-rewrite ban is checked' "$WORK/b-reset.sh" lacks 'reset --hard|git clean'
|
|
|
|
sed -E 's#id -u#true#g' "$BOOTSTRAP" > "$WORK/b-no-root.sh"
|
|
chmod 0755 "$WORK/b-no-root.sh"
|
|
control 'bf-bootstrap root check is checked' "$WORK/b-no-root.sh" has 'id -u'
|
|
|
|
sed -E '/Kurulum tamam/d' "$BOOTSTRAP" > "$WORK/b-no-summary.sh"
|
|
chmod 0755 "$WORK/b-no-summary.sh"
|
|
control 'bf-bootstrap completion contract is checked' "$WORK/b-no-summary.sh" has 'Kurulum tamam\. Başlamak için: sudo bf'
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# 4. bf-bootstrap.sh runtime: --check is mutation-free, apply mode works.
|
|
# --------------------------------------------------------------------------- #
|
|
# A sandbox PATH shim: `id` reports uid 0 (so the apply path can run unprivileged
|
|
# inside the sandbox) and `apt-get` only records the call. Nothing else changes.
|
|
SHIM="$WORK/shims"
|
|
mkdir -p "$SHIM"
|
|
cat > "$SHIM/id" <<'SHIMEOF'
|
|
#!/usr/bin/env bash
|
|
if [[ "${1:-}" == "-u" ]]; then printf '0\n'; exit 0; fi
|
|
printf 'uid=0(root) gid=0(root) groups=0(root)\n'
|
|
SHIMEOF
|
|
cat > "$SHIM/apt-get" <<'SHIMEOF'
|
|
#!/usr/bin/env bash
|
|
printf 'shim apt-get: %s\n' "$*" >> "${BF_SHIM_APT_LOG:-/dev/null}"
|
|
exit 0
|
|
SHIMEOF
|
|
chmod 0755 "$SHIM/id" "$SHIM/apt-get"
|
|
|
|
# Deterministic dependency-install branch: drop every PATH entry that provides
|
|
# ansible-playbook, so the bootstrap really sees a missing prerequisite and
|
|
# takes its apt-get path (against the shim, never the real package manager).
|
|
# git and curl must stay resolvable; when they would be filtered out too, the
|
|
# unmodified PATH is kept and the assertion below falls back to a note.
|
|
PATH_NO_ANSIBLE=""
|
|
while IFS= read -r path_dir; do
|
|
[[ -z "$path_dir" ]] && continue
|
|
[[ -x "$path_dir/ansible-playbook" ]] && continue
|
|
PATH_NO_ANSIBLE="${PATH_NO_ANSIBLE:+$PATH_NO_ANSIBLE:}$path_dir"
|
|
done < <(printf '%s\n' "$PATH" | tr ':' '\n')
|
|
APPLY_PATH="$SHIM:$PATH_NO_ANSIBLE"
|
|
if ! PATH="$APPLY_PATH" command -v git >/dev/null 2>&1 || ! PATH="$APPLY_PATH" command -v curl >/dev/null 2>&1; then
|
|
APPLY_PATH="$SHIM:$PATH"
|
|
fi
|
|
|
|
bootstrap_apply() { # <checkout-dir> <bin-dir> <out-file> <apt-log>
|
|
PATH="$APPLY_PATH" \
|
|
BF_SHIM_APT_LOG="$4" \
|
|
BF_BIN_DIR="$2" \
|
|
BF_INSTALL_DIR="$1" \
|
|
BF_REPO_URL="$REPO_ROOT" \
|
|
bash "$BOOTSTRAP" --yes < /dev/null > "$3" 2>&1
|
|
}
|
|
|
|
# 4a. --check must create nothing at all, even under a full sandbox prefix.
|
|
CHECK_ROOT="$WORK/check-root"
|
|
set +e
|
|
PATH="$APPLY_PATH" \
|
|
BF_BIN_DIR="$CHECK_ROOT/bin" \
|
|
BF_INSTALL_DIR="$CHECK_ROOT/checkout" \
|
|
BF_REPO_URL="$REPO_ROOT" \
|
|
bash "$BOOTSTRAP" --check < /dev/null > "$WORK/check.out" 2>&1
|
|
check_rc=$?
|
|
set -e
|
|
expect_rc 'bf-bootstrap --check exits 0' 0 "$check_rc"
|
|
expect_absent 'bf-bootstrap --check creates no checkout' "$CHECK_ROOT/checkout"
|
|
expect_absent 'bf-bootstrap --check creates no bin directory' "$CHECK_ROOT/bin"
|
|
expect_absent 'bf-bootstrap --check creates nothing under its target prefix' "$CHECK_ROOT"
|
|
expect_contains 'bf-bootstrap --check reports that nothing was changed' "$WORK/check.out" 'no changes were made'
|
|
if PATH="$APPLY_PATH" command -v ansible-playbook >/dev/null 2>&1; then
|
|
note 'ansible-playbook resolves even after PATH filtering: the dependency-plan assertion is limited to the static contract'
|
|
else
|
|
expect_contains 'check mode reports the dependency install it would perform' "$WORK/check.out" 'would install with apt-get'
|
|
fi
|
|
|
|
# 4b. --check against the real checkout must install nothing and must discover
|
|
# the admin tools (this is the only non-sandboxed run in the suite).
|
|
local_bin_before="$(ls -1 /usr/local/bin 2>/dev/null | sort | tr '\n' ' ')"
|
|
mode_before="$(stat -c '%a' "$BOOTSTRAP" "$CREDS" 2>/dev/null | tr '\n' ' ')"
|
|
set +e
|
|
bash "$BOOTSTRAP" --check < /dev/null > "$WORK/check-repo.out" 2>&1
|
|
check_repo_rc=$?
|
|
set -e
|
|
expect_rc 'bf-bootstrap --check runs unprivileged against the checkout' 0 "$check_repo_rc"
|
|
expect_contains 'check mode reports admin/bf-bootstrap.sh as a tool' "$WORK/check-repo.out" 'admin/bf-bootstrap.sh'
|
|
expect_contains 'check mode reports admin/bf-creds as a tool' "$WORK/check-repo.out" 'admin/bf-creds'
|
|
expect_contains 'check mode reports a diagnostics tool' "$WORK/check-repo.out" 'scripts/diagnostics/bf-status'
|
|
check
|
|
if [[ "$local_bin_before" == "$(ls -1 /usr/local/bin 2>/dev/null | sort | tr '\n' ' ')" ]]; then
|
|
pass 'bf-bootstrap --check installs nothing into /usr/local/bin'
|
|
else
|
|
fail 'bf-bootstrap --check wrote into /usr/local/bin'
|
|
fi
|
|
check
|
|
if [[ "$mode_before" == "$(stat -c '%a' "$BOOTSTRAP" "$CREDS" 2>/dev/null | tr '\n' ' ')" ]]; then
|
|
pass 'bf-bootstrap --check leaves tool modes unchanged'
|
|
else
|
|
fail 'bf-bootstrap --check changed tool modes'
|
|
fi
|
|
|
|
# 4c. Harness self-test: prove the "nothing was created" assertion can detect a
|
|
# mutation, so 4a is not a vacuous pass. A synthetic probe that only creates
|
|
# its target when check mode is bypassed must trip the very same assertion.
|
|
PROBE="$WORK/probe.sh"
|
|
cat > "$PROBE" <<'PROBEEOF'
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
if [[ "${1:-}" == "--check" ]]; then
|
|
printf 'check only\n'
|
|
exit 0
|
|
fi
|
|
mkdir -p "$2"
|
|
printf 'created\n' > "$2/marker"
|
|
PROBEEOF
|
|
probe_ok="$WORK/probe-ok"
|
|
set +e
|
|
bash "$PROBE" --check "$probe_ok" >/dev/null 2>&1
|
|
set -e
|
|
expect_absent 'harness self-test: the probe creates nothing in check mode' "$probe_ok"
|
|
sed -E 's#--check#--never#g' "$PROBE" > "$WORK/probe-broken.sh"
|
|
probe_broken="$WORK/probe-broken"
|
|
set +e
|
|
bash "$WORK/probe-broken.sh" --check "$probe_broken" >/dev/null 2>&1
|
|
set -e
|
|
expect_present 'harness self-test control: the same assertion detects a mutation' "$probe_broken/marker"
|
|
|
|
# 4d. Apply mode in the sandbox (fresh clone path).
|
|
APPLY_ROOT="$WORK/apply-root"
|
|
mkdir -p "$APPLY_ROOT"
|
|
set +e
|
|
bootstrap_apply "$APPLY_ROOT/checkout" "$APPLY_ROOT/bin" "$WORK/apply1.out" "$WORK/apt1.log"
|
|
apply_rc=$?
|
|
set -e
|
|
expect_rc 'bf-bootstrap apply mode exits 0 in the sandbox' 0 "$apply_rc"
|
|
expect_present 'bf-bootstrap cloned the repository' "$APPLY_ROOT/checkout/.git"
|
|
expect_present 'bf-bootstrap linked a diagnostics tool' "$APPLY_ROOT/bin/bf-status"
|
|
expect_contains 'bf-bootstrap prints the completion contract' "$WORK/apply1.out" 'Kurulum tamam. Başlamak için: sudo bf'
|
|
expect_contains 'bf-bootstrap reports the repository in its summary' "$WORK/apply1.out" "$APPLY_ROOT/checkout"
|
|
# The "console missing from the clone" branch is unreachable in a normal clone
|
|
# because admin/bf and admin/bf-menu are tracked. Exercise it deliberately, or
|
|
# the warning silently rots the day one of them is renamed.
|
|
#
|
|
# Note: deleting the file from a git working tree is NOT enough. Neither is
|
|
# stripping .git entirely -- bootstrap clones with `--branch main` and a plain
|
|
# directory has no such ref. The source has to be a real git repo in which the
|
|
# console is absent from the committed tree, so build one.
|
|
STRIPPED_REPO="$WORK/stripped-repo"
|
|
rm -rf "$STRIPPED_REPO"
|
|
cp -a "$REPO_ROOT" "$STRIPPED_REPO"
|
|
rm -rf "$STRIPPED_REPO/.git"
|
|
rm -f "$STRIPPED_REPO/admin/bf"
|
|
git -C "$STRIPPED_REPO" init -q -b main
|
|
git -C "$STRIPPED_REPO" -c user.name=check -c user.email=check@example.invalid \
|
|
add -A
|
|
git -C "$STRIPPED_REPO" -c user.name=check -c user.email=check@example.invalid \
|
|
commit -q -m 'stripped checkout without the Python console'
|
|
if git -C "$STRIPPED_REPO" cat-file -e 'HEAD:admin/bf' 2>/dev/null; then
|
|
fail 'the stripped source still tracks admin/bf; the scenario is invalid'
|
|
fi
|
|
set +e
|
|
PATH="$APPLY_PATH" \
|
|
BF_SHIM_APT_LOG="$WORK/apt-stripped.log" \
|
|
BF_BIN_DIR="$WORK/stripped-bin" \
|
|
BF_INSTALL_DIR="$WORK/stripped-checkout" \
|
|
BF_REPO_URL="$STRIPPED_REPO" \
|
|
bash "$BOOTSTRAP" --yes < /dev/null > "$WORK/apply-stripped.out" 2>&1
|
|
stripped_rc=$?
|
|
set -e
|
|
expect_rc 'bf-bootstrap succeeds even when the Python console is absent' 0 "$stripped_rc"
|
|
expect_contains 'bf-bootstrap warns when the Python console is not in the clone' "$WORK/apply-stripped.out" 'admin/bf is not present'
|
|
expect_contains 'bf-bootstrap still links the bash fallback' "$WORK/apply-stripped.out" 'bf-menu ready'
|
|
rm -rf "$STRIPPED_REPO" "$WORK/stripped-bin" "$WORK/stripped-checkout"
|
|
linked1="$(find "$APPLY_ROOT/bin" -maxdepth 1 -type l 2>/dev/null | wc -l | tr -d ' ')"
|
|
check
|
|
if [[ "$linked1" -ge 10 ]]; then
|
|
pass "bf-bootstrap linked the tool set ($linked1 links)"
|
|
else
|
|
fail "bf-bootstrap linked too few tools ($linked1 links)"
|
|
fi
|
|
# The dependency branch runs against the apt-get shim, never the real apt.
|
|
if PATH="$APPLY_PATH" command -v ansible-playbook >/dev/null 2>&1; then
|
|
note 'ansible-playbook resolves even after PATH filtering: the dependency-install assertion is skipped'
|
|
else
|
|
expect_contains 'dependency install goes through apt' "$WORK/apt1.log" 'install -y --no-install-recommends'
|
|
expect_contains 'the package list names the missing prerequisite' "$WORK/apt1.log" 'ansible-core'
|
|
fi
|
|
|
|
# 4e. Idempotency: a second apply run must succeed and keep the same links.
|
|
set +e
|
|
bootstrap_apply "$APPLY_ROOT/checkout" "$APPLY_ROOT/bin" "$WORK/apply2.out" "$WORK/apt2.log"
|
|
apply2_rc=$?
|
|
set -e
|
|
expect_rc 'bf-bootstrap apply mode is idempotent on re-run' 0 "$apply2_rc"
|
|
linked2="$(find "$APPLY_ROOT/bin" -maxdepth 1 -type l 2>/dev/null | wc -l | tr -d ' ')"
|
|
check
|
|
if [[ "$linked1" == "$linked2" ]]; then
|
|
pass "re-run keeps the same tool set ($linked2 links)"
|
|
else
|
|
fail "re-run changed the tool set ($linked1 -> $linked2 links)"
|
|
fi
|
|
|
|
# 4f. An existing checkout is refreshed, not reset: local changes are reported
|
|
# and preserved, and admin/bf-* tools are linked from it.
|
|
SEEDED="$WORK/seeded"
|
|
git clone --quiet "$REPO_ROOT" "$SEEDED"
|
|
cp -a "$REPO_ROOT/admin" "$SEEDED/admin"
|
|
set +e
|
|
bootstrap_apply "$SEEDED" "$WORK/seeded-bin" "$WORK/apply3.out" "$WORK/apt3.log"
|
|
apply3_rc=$?
|
|
set -e
|
|
expect_rc 'bf-bootstrap apply mode accepts an existing checkout' 0 "$apply3_rc"
|
|
expect_present 'bf-bootstrap linked admin/bf-bootstrap.sh' "$WORK/seeded-bin/bf-bootstrap.sh"
|
|
expect_present 'bf-bootstrap linked admin/bf-creds' "$WORK/seeded-bin/bf-creds"
|
|
expect_contains 'bf-bootstrap reports local changes instead of resetting them' "$WORK/apply3.out" 'local changes present'
|
|
expect_present 'bf-bootstrap left the existing checkout intact' "$SEEDED/admin/bf-creds"
|
|
|
|
# 4g. Apply mode without root must refuse before touching anything.
|
|
if [[ "$(id -u)" -ne 0 ]]; then
|
|
ROOT_TARGET="$WORK/root-target"
|
|
set +e
|
|
BF_BIN_DIR="$WORK/root-bin" \
|
|
BF_INSTALL_DIR="$ROOT_TARGET" \
|
|
BF_REPO_URL="$REPO_ROOT" \
|
|
bash "$BOOTSTRAP" --yes < /dev/null > "$WORK/root.out" 2>&1
|
|
root_rc=$?
|
|
set -e
|
|
expect_rc 'bf-bootstrap refuses to apply without root' 1 "$root_rc"
|
|
expect_contains 'bf-bootstrap explains the root requirement' "$WORK/root.out" 'must run as root'
|
|
expect_absent 'the refused run created no checkout' "$ROOT_TARGET"
|
|
expect_absent 'the refused run created no bin directory' "$WORK/root-bin"
|
|
else
|
|
note 'running as root: the unprivileged refusal control is skipped'
|
|
fi
|
|
|
|
# 4h. A target directory that is not a git checkout must be refused untouched.
|
|
NOT_A_REPO="$WORK/not-a-repo"
|
|
mkdir -p "$NOT_A_REPO"
|
|
printf 'keep me\n' > "$NOT_A_REPO/precious.txt"
|
|
set +e
|
|
PATH="$SHIM:$PATH" \
|
|
BF_BIN_DIR="$WORK/not-a-repo-bin" \
|
|
BF_INSTALL_DIR="$NOT_A_REPO" \
|
|
BF_REPO_URL="$REPO_ROOT" \
|
|
bash "$BOOTSTRAP" --yes < /dev/null > "$WORK/not-a-repo.out" 2>&1
|
|
not_a_repo_rc=$?
|
|
set -e
|
|
expect_rc 'bf-bootstrap refuses a directory that is not a git checkout' 1 "$not_a_repo_rc"
|
|
expect_contains 'the refusal is explained' "$WORK/not-a-repo.out" 'not a git checkout'
|
|
expect_present 'the foreign directory was left untouched' "$NOT_A_REPO/precious.txt"
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# 5. bf-creds static contracts (+ negative controls).
|
|
# --------------------------------------------------------------------------- #
|
|
contract 'bf-creds is default-OFF by default' "$CREDS" has 'BF_GENERATE_CREDENTIALS:-0'
|
|
contract 'bf-creds has no default-ON regression' "$CREDS" lacks 'BF_GENERATE_CREDENTIALS:-1'
|
|
contract 'bf-creds accepts the explicit --generate opt-in' "$CREDS" has 'GENERATE=1'
|
|
contract 'bf-creds accepts the explicit --fetch opt-in' "$CREDS" has 'FETCH=1'
|
|
contract 'bf-creds marks generation disabled by default' "$CREDS" has 'DISABLED'
|
|
contract 'bf-creds never enables shell tracing' "$CREDS" lacks 'set -x'
|
|
contract 'bf-creds writes the record at mode 0600' "$CREDS_LIB" has 'BF_CREDENTIALS_MODE:=600'
|
|
contract 'bf-creds keeps the delivery path at admin/secrets' "$CREDS_LIB" has 'BF_SECRETS_SUBDIR:=admin/secrets'
|
|
contract 'bf-creds reports the WireGuard private key path only' "$CREDS" has 'private_key_path'
|
|
contract 'bf-creds documents that the SSH decision is unchanged' "$CREDS" has 'PasswordAuthentication no'
|
|
contract 'creds.sh forces 0600 on the written file' "$CREDS_LIB" has 'install -m "\$BF_CREDENTIALS_MODE"'
|
|
contract 'creds.sh forces 0700 on the delivery directory' "$CREDS_LIB" has 'install -d -m "\$BF_SECRETS_DIR_MODE"'
|
|
contract 'creds.sh refuses to write through a symlink' "$CREDS_LIB" has 'refusing to write through a symlink'
|
|
contract 'creds.sh never enables shell tracing' "$CREDS_LIB" lacks 'set -x'
|
|
|
|
# Mutation builder for bf-creds copies: the tool sources lib/creds.sh relative
|
|
# to its own real path, so a mutant needs a lib/ directory beside it.
|
|
creds_mutant() { # <name> <awk-program-file>
|
|
local dir="$WORK/mut-$1"
|
|
mkdir -p "$dir/lib"
|
|
ln -sfn "$CREDS_LIB" "$dir/lib/creds.sh"
|
|
awk -f "$2" "$CREDS" > "$dir/bf-creds"
|
|
chmod 0755 "$dir/bf-creds"
|
|
printf '%s' "$dir/bf-creds"
|
|
}
|
|
|
|
# Negative control: a default-ON variant must trip both default-off checks.
|
|
cat > "$WORK/default-on.awk" <<'AWKEOF'
|
|
{ gsub(/BF_GENERATE_CREDENTIALS:-0/, "BF_GENERATE_CREDENTIALS:-1"); print }
|
|
AWKEOF
|
|
DEFAULT_ON="$(creds_mutant default-on "$WORK/default-on.awk")"
|
|
control 'bf-creds default-off contract is checked' "$DEFAULT_ON" has 'BF_GENERATE_CREDENTIALS:-0'
|
|
control 'bf-creds default-ON ban is checked' "$DEFAULT_ON" lacks 'BF_GENERATE_CREDENTIALS:-1'
|
|
|
|
# Negative control: shell tracing (which would print secrets) must trip the ban.
|
|
mkdir -p "$WORK/mut-xtrace/lib"
|
|
ln -sfn "$CREDS_LIB" "$WORK/mut-xtrace/lib/creds.sh"
|
|
awk '{ print } END { print "set -x" }' "$CREDS" > "$WORK/mut-xtrace/bf-creds"
|
|
chmod 0755 "$WORK/mut-xtrace/bf-creds"
|
|
control 'bf-creds shell-tracing ban is checked' "$WORK/mut-xtrace/bf-creds" lacks 'set -x'
|
|
|
|
# Negative control: a leak of the generated secret must trip the leak detector.
|
|
cat > "$WORK/leak.awk" <<'AWKEOF'
|
|
/cannot generate a random secret/ {
|
|
print
|
|
print " printf \"leaked-credential=%s\\n\" \"$password\""
|
|
next
|
|
}
|
|
{ print }
|
|
AWKEOF
|
|
LEAK_MUTANT="$(creds_mutant leak "$WORK/leak.awk")"
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# 6. bf-creds runtime in a sandbox (identity, default-off, generate, fetch).
|
|
# --------------------------------------------------------------------------- #
|
|
CSB="$WORK/creds-sandbox"
|
|
mkdir -p "$CSB/wg"
|
|
FAKE_WG_PRIVATE='FAKEWGPRIVATE0000000000000000000000000000000='
|
|
FAKE_WG_PUBLIC='FAKEWGPUBLIC1111111111111111111111111111111='
|
|
FAKE_RUSTDESK='987654321'
|
|
printf '%s\n' "$FAKE_WG_PUBLIC" > "$CSB/wg/publickey"
|
|
printf '%s\n' "$FAKE_WG_PRIVATE" > "$CSB/wg/privatekey"
|
|
chmod 0600 "$CSB/wg/privatekey"
|
|
printf '%s\n' "$FAKE_RUSTDESK" > "$CSB/rustdesk-id"
|
|
printf '{ "phase": "PROVISIONED_OFFLINE", "device_id": "BF-12345678" }\n' > "$CSB/state.json"
|
|
|
|
CREDS_FILE="$CSB/credentials.txt"
|
|
CREDS_STATE_FILE="$CSB/state.json"
|
|
CREDS_DEVICE='BF-12345678'
|
|
|
|
# creds_env <script> <out-file> [args...]
|
|
creds_env() {
|
|
local script="$1" out="$2"
|
|
shift 2
|
|
BF_STATE_FILE="${CREDS_STATE_FILE:-$CSB/state.json}" \
|
|
BF_DEVICE="${CREDS_DEVICE-}" \
|
|
BF_WG_DIR="$CSB/wg" \
|
|
BF_RUSTDESK_ID_FILE="$CSB/rustdesk-id" \
|
|
BF_CREDENTIALS_FILE="$CREDS_FILE" \
|
|
bash "$script" "$@" > "$out" 2>&1
|
|
}
|
|
|
|
# 6a. Default behaviour: report only, no generation, and it says so.
|
|
CREDS_FILE="$CSB/default-run.txt"
|
|
set +e
|
|
creds_env "$CREDS" "$WORK/creds-default.out"
|
|
default_rc=$?
|
|
set -e
|
|
expect_rc 'bf-creds default run exits 0' 0 "$default_rc"
|
|
expect_absent 'bf-creds default run generates no credential file' "$CREDS_FILE"
|
|
expect_contains 'bf-creds states that generation is disabled by default' "$WORK/creds-default.out" 'DISABLED'
|
|
expect_contains 'bf-creds states that no password was generated' "$WORK/creds-default.out" 'no password was generated'
|
|
expect_absent_text 'bf-creds default run prints no credential line' "$WORK/creds-default.out" 'local_administrator_password'
|
|
expect_contains 'bf-creds reports the device identity' "$WORK/creds-default.out" 'device_id : BF-12345678'
|
|
expect_contains 'bf-creds reports the WireGuard public key' "$WORK/creds-default.out" "$FAKE_WG_PUBLIC"
|
|
expect_contains 'bf-creds reports the private key path' "$WORK/creds-default.out" "$CSB/wg/privatekey"
|
|
expect_contains 'bf-creds reports the RustDesk ID' "$WORK/creds-default.out" "$FAKE_RUSTDESK"
|
|
|
|
# Negative control for the default-off behaviour: the default-ON mutant must
|
|
# create the credential file without any flag, proving 6a is not vacuous.
|
|
CREDS_FILE="$WORK/default-on-run.txt"
|
|
set +e
|
|
creds_env "$DEFAULT_ON" "$WORK/creds-default-on.out"
|
|
set -e
|
|
expect_present 'control: the default-ON mutant generates without a flag' "$CREDS_FILE"
|
|
|
|
# 6b. --generate writes the record, and the secret never reaches stdout.
|
|
CREDS_FILE="$CSB/credentials.txt"
|
|
set +e
|
|
creds_env "$CREDS" "$WORK/creds-generate.out" --generate
|
|
generate_rc=$?
|
|
set -e
|
|
expect_rc 'bf-creds --generate exits 0' 0 "$generate_rc"
|
|
expect_present 'bf-creds --generate writes the credential record' "$CREDS_FILE"
|
|
expect_mode 'the credential record is mode 600' "$CREDS_FILE" 600
|
|
expect_contains 'the record carries the device id' "$CREDS_FILE" 'device_id: BF-12345678'
|
|
expect_contains 'the record carries the dealer number' "$CREDS_FILE" 'dealer_number: 12345678'
|
|
expect_contains 'the record carries the hostname' "$CREDS_FILE" 'hostname:'
|
|
expect_contains 'the record carries the creation time' "$CREDS_FILE" 'generated_at:'
|
|
expect_contains 'the record carries the WireGuard public key' "$CREDS_FILE" "$FAKE_WG_PUBLIC"
|
|
expect_contains 'the record carries the private key path' "$CREDS_FILE" "$CSB/wg/privatekey"
|
|
expect_contains 'the record states the private key is not recorded' "$CREDS_FILE" 'NOT RECORDED'
|
|
expect_absent_text 'the record never contains the WireGuard private key value' "$CREDS_FILE" "$FAKE_WG_PRIVATE"
|
|
expect_absent_text 'stdout never contains the WireGuard private key value' "$WORK/creds-generate.out" "$FAKE_WG_PRIVATE"
|
|
expect_contains 'stdout names the credential record' "$WORK/creds-generate.out" "$CREDS_FILE"
|
|
expect_contains 'stdout confirms the record was written' "$WORK/creds-generate.out" 'written'
|
|
|
|
GENERATED_PASSWORD="$(sed -n 's/^local_administrator_password:[[:space:]]*//p' "$CREDS_FILE" | head -n 1)"
|
|
check
|
|
if [[ ${#GENERATED_PASSWORD} -ge 16 ]]; then
|
|
pass "the generated password is long enough (${#GENERATED_PASSWORD} characters)"
|
|
else
|
|
fail "the generated password is too short (${#GENERATED_PASSWORD} characters)"
|
|
fi
|
|
check
|
|
if secret_leaked "$WORK/creds-generate.out" "$GENERATED_PASSWORD"; then
|
|
fail 'the generated password leaked to stdout'
|
|
else
|
|
pass 'the generated password never reaches stdout'
|
|
fi
|
|
check
|
|
if secret_leaked "$WORK/creds-generate.out" "$FAKE_WG_PRIVATE"; then
|
|
fail 'the WireGuard private key leaked to stdout'
|
|
else
|
|
pass 'the WireGuard private key never reaches stdout'
|
|
fi
|
|
|
|
# Negative control for the leak detector: the leak mutant must be detected.
|
|
CREDS_FILE="$WORK/leak-run.txt"
|
|
set +e
|
|
creds_env "$LEAK_MUTANT" "$WORK/creds-leak.out" --generate
|
|
set -e
|
|
LEAKED_PASSWORD="$(sed -n 's/^local_administrator_password:[[:space:]]*//p' "$CREDS_FILE" | head -n 1)"
|
|
check
|
|
if [[ -n "$LEAKED_PASSWORD" ]] && secret_leaked "$WORK/creds-leak.out" "$LEAKED_PASSWORD"; then
|
|
pass 'control: the leak detector finds a secret that is printed'
|
|
else
|
|
fail 'control: the leak detector did not find a printed secret (vacuous check)'
|
|
fi
|
|
|
|
# 6c. --fetch delivers the record next to the checkout at 0600/0700.
|
|
FETCH_REPO="$WORK/fetch-repo"
|
|
mkdir -p "$FETCH_REPO"
|
|
CREDS_FILE="$CSB/credentials.txt"
|
|
set +e
|
|
creds_env "$CREDS" "$WORK/creds-fetch.out" --fetch --repo "$FETCH_REPO"
|
|
fetch_rc=$?
|
|
set -e
|
|
FETCH_DEST="$FETCH_REPO/admin/secrets/12345678.txt"
|
|
expect_rc 'bf-creds --fetch exits 0' 0 "$fetch_rc"
|
|
expect_present 'bf-creds --fetch delivers the credential file' "$FETCH_DEST"
|
|
expect_mode 'the delivered file is mode 600' "$FETCH_DEST" 600
|
|
expect_mode 'the delivery directory is mode 700' "$FETCH_REPO/admin/secrets" 700
|
|
check
|
|
if cmp -s "$CSB/credentials.txt" "$FETCH_DEST"; then
|
|
pass 'the delivered file matches the credential record'
|
|
else
|
|
fail 'the delivered file differs from the credential record'
|
|
fi
|
|
expect_contains '--fetch reports only the delivery path' "$WORK/creds-fetch.out" "$FETCH_DEST"
|
|
expect_absent_text '--fetch never prints the generated password' "$WORK/creds-fetch.out" "$GENERATED_PASSWORD"
|
|
expect_absent_text '--fetch never prints the WireGuard private key' "$WORK/creds-fetch.out" "$FAKE_WG_PRIVATE"
|
|
|
|
# 6d. --fetch fails closed when the dealer number cannot be determined.
|
|
CREDS_FILE="$WORK/no-device/credentials.txt"
|
|
mkdir -p "$WORK/no-device"
|
|
cp "$CSB/credentials.txt" "$CREDS_FILE"
|
|
CREDS_STATE_FILE="$WORK/absent-state.json"
|
|
CREDS_DEVICE=''
|
|
set +e
|
|
creds_env "$CREDS" "$WORK/creds-nodevice.out" --fetch --repo "$FETCH_REPO"
|
|
nodevice_rc=$?
|
|
set -e
|
|
expect_rc 'bf-creds --fetch fails closed without a dealer number' 1 "$nodevice_rc"
|
|
expect_contains 'the failure explains the missing dealer number' "$WORK/creds-nodevice.out" 'cannot determine the dealer number'
|
|
CREDS_STATE_FILE="$CSB/state.json"
|
|
CREDS_DEVICE='BF-12345678'
|
|
|
|
# 6e. --dealer-id and --repo argument validation.
|
|
set +e
|
|
creds_env "$CREDS" "$WORK/creds-bad-dealer.out" --dealer-id 123 >/dev/null 2>&1
|
|
bad_dealer_rc=$?
|
|
creds_env "$CREDS" "$WORK/creds-bad-repo.out" --fetch --repo "$WORK/does-not-exist" >/dev/null 2>&1
|
|
bad_repo_rc=$?
|
|
set -e
|
|
expect_rc 'bf-creds rejects a malformed --dealer-id' 2 "$bad_dealer_rc"
|
|
expect_rc 'bf-creds rejects a --repo that does not exist' 2 "$bad_repo_rc"
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# 7. .gitignore: the delivery path is ignored, the tools are not.
|
|
# --------------------------------------------------------------------------- #
|
|
check
|
|
if git -C "$REPO_ROOT" check-ignore -q -- 'admin/secrets/12345678.txt'; then
|
|
pass 'admin/secrets/<dealer-number>.txt is git-ignored'
|
|
else
|
|
fail 'the bf-creds delivery path is NOT git-ignored (add admin/secrets/ to .gitignore)'
|
|
fi
|
|
|
|
# Positive control for `git check-ignore` itself: a pattern that is definitely
|
|
# present must be reported as ignored, so the assertion above cannot pass by
|
|
# accident.
|
|
check
|
|
if git -C "$REPO_ROOT" check-ignore -q -- 'probe/example.key'; then
|
|
pass 'control: git check-ignore evaluates this repository'
|
|
else
|
|
fail 'control: git check-ignore did not report a *.key path as ignored'
|
|
fi
|
|
|
|
# The tools themselves must stay tracked: an over-broad rule would hide them.
|
|
check
|
|
if git -C "$REPO_ROOT" check-ignore -q -- 'admin/bf-bootstrap.sh'; then
|
|
fail 'admin/bf-bootstrap.sh is git-ignored'
|
|
else
|
|
pass 'admin/bf-bootstrap.sh is not git-ignored'
|
|
fi
|
|
check
|
|
if git -C "$REPO_ROOT" check-ignore -q -- 'admin/lib/creds.sh'; then
|
|
fail 'admin/lib/creds.sh is git-ignored'
|
|
else
|
|
pass 'admin/lib/creds.sh is not git-ignored'
|
|
fi
|
|
contract '.gitignore has no over-broad admin or catch-all rule' "$GITIGNORE" lacks '^([*]|admin/?)$'
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# 8. The shipped admin files carry no secret-like material.
|
|
# --------------------------------------------------------------------------- #
|
|
# Heuristic for a *literal* credential assignment:
|
|
# * the separator is `:` or `=`,
|
|
# * the value is NOT a shell reference (`${...}`, `$(...)`),
|
|
# * the value contains a digit or a base64 symbol, which is what separates a
|
|
# credential literal from prose (a log message such as
|
|
# `bf_deliver_secret: destination required` must not be reported).
|
|
# A purely alphabetic secret would slip through: this is a guard against
|
|
# obviously planted material, not a replacement for the repository-wide scan.
|
|
SECRET_ASSIGNMENT='(^|[^A-Za-z])(password|passwd|token|api[_-]?key|secret)[[:space:]]*[:=][[:space:]]*[A-Za-z0-9+/=_.-]*[0-9+/=][A-Za-z0-9+/=_.-]*'
|
|
PRIVATE_KEY_BLOCK='BEGIN (OPENSSH|RSA|EC|DSA|PGP) PRIVATE KEY'
|
|
|
|
for target in "$BOOTSTRAP" "$CREDS" "$CREDS_LIB"; do
|
|
[[ -f "$target" ]] || continue
|
|
contract "no private key block in ${target#"$REPO_ROOT"/}" "$target" lacks_i "$PRIVATE_KEY_BLOCK"
|
|
contract "no secret assignment in ${target#"$REPO_ROOT"/}" "$target" lacks_i "$SECRET_ASSIGNMENT"
|
|
done
|
|
|
|
# Negative controls: a planted secret must be found in each file.
|
|
for target in "$BOOTSTRAP" "$CREDS"; do
|
|
[[ -f "$target" ]] || continue
|
|
planted="$WORK/planted-$(basename "$target")"
|
|
cp "$target" "$planted"
|
|
printf '%s\n' 'DEMO_TOKEN=abcd1234efgh' >> "$planted"
|
|
control "secret assignment detection in $(basename "$target")" "$planted" lacks_i "$SECRET_ASSIGNMENT"
|
|
printf '%s\n' '-----BEGIN OPENSSH PRIVATE KEY-----' >> "$planted"
|
|
control "private key detection in $(basename "$target")" "$planted" lacks_i "$PRIVATE_KEY_BLOCK"
|
|
done
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Summary.
|
|
# --------------------------------------------------------------------------- #
|
|
if [[ "$FAIL" -ne 0 ]]; then
|
|
printf '\ncheck-admin-static: FAILED (%d checks)\n' "$CHECKS" >&2
|
|
exit 1
|
|
fi
|
|
printf '\ncheck-admin-static: all passed (%d checks)\n' "$CHECKS"
|