ac0ff8e2af
ci / build-test (push) Has been cancelled
- untrack CLAUDE.md (tailnet leak) + stale HANDOFF.md; keep local via .gitignore - add source-available LICENSE (attribution on fork, royalty on commercial use) - add lab/ reproduction kit (fake-cred LIVE-VM/mock rotation POCs) + lab/README - rewrite README to current status (22 drivers, 8 LIVE-VM/14 MOCK-ONLY/0 LIVE-REAL, 247 tests) and carry the credential-handling safety rules Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
209 lines
9.5 KiB
Bash
Executable File
209 lines
9.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# incredigo controlled-environment test harness. Self-contained + repeatable:
|
|
# re-provisions a FRESH isolated gopass store and fake inputs for all 8 scanners,
|
|
# then runs every command with PASS/FAIL assertions + a global secret-leak canary.
|
|
# FAKE creds only. Never touches a real store.
|
|
set -uo pipefail
|
|
export PATH=/usr/local/bin:$PATH
|
|
export GOPASS_HOMEDIR="$HOME/.lab-gopass"
|
|
export GNUPGHOME="$HOME/.lab-gnupg"
|
|
export GOPASS_NO_NOTIFY=true
|
|
export INCREDIGO_PASSPHRASE='lab-seal-passphrase-001'
|
|
|
|
LAB="$HOME/lab"
|
|
MASTER="$HOME/lab-master.log"
|
|
: > "$MASTER"
|
|
PASS=0; FAIL=0; FAILED=()
|
|
|
|
note(){ echo; echo "==== $* ===="; }
|
|
# check NAME : asserts that the last captured $OUT matches a grep -E pattern
|
|
check(){ local name="$1" pat="$2"; if grep -Eq "$pat" <<<"$OUT"; then echo " PASS $name"; PASS=$((PASS+1)); else echo " FAIL $name (want /$pat/)"; FAIL=$((FAIL+1)); FAILED+=("$name"); fi; }
|
|
checkno(){ local name="$1" pat="$2"; if grep -Eq "$pat" <<<"$OUT"; then echo " FAIL $name (unwanted /$pat/)"; FAIL=$((FAIL+1)); FAILED+=("$name"); else echo " PASS $name"; PASS=$((PASS+1)); fi; }
|
|
# run CMD... -> captures combined output into $OUT and appends to MASTER
|
|
run(){ OUT="$("$@" 2>&1)"; printf '\n### %s\n%s\n' "$*" "$OUT" >> "$MASTER"; }
|
|
|
|
############################ provision (fresh) ############################
|
|
note "PROVISION fresh isolated store + fake inputs"
|
|
rm -rf "$GOPASS_HOMEDIR" "$GNUPGHOME" "$LAB" "$HOME/.aws" "$HOME/.docker" "$HOME/.kube" \
|
|
"$HOME/.ssh" "$HOME/.netrc" "$HOME/.git-credentials" "$HOME/.incredigo"
|
|
mkdir -p "$GNUPGHOME"; chmod 700 "$GNUPGHOME"; mkdir -p "$GOPASS_HOMEDIR"
|
|
cat > /tmp/key.batch <<'EOF'
|
|
%no-protection
|
|
Key-Type: eddsa
|
|
Key-Curve: ed25519
|
|
Subkey-Type: ecdh
|
|
Subkey-Curve: cv25519
|
|
Name-Real: Incredigo Lab
|
|
Name-Email: lab@incredigo.local
|
|
Expire-Date: 0
|
|
%commit
|
|
EOF
|
|
gpg --batch --generate-key /tmp/key.batch 2>/dev/null
|
|
FPR=$(gpg --list-keys --with-colons lab@incredigo.local | awk -F: '/^fpr:/{print $10; exit}')
|
|
gopass init --crypto gpg --storage fs "$FPR" </dev/null >/dev/null 2>&1
|
|
|
|
# --- fake inputs (every secret carries a unique CANARY token for leak-checks) ---
|
|
mkdir -p "$LAB" "$LAB/k8s" "$LAB/extra" "$HOME/.aws" "$HOME/.docker" "$HOME/.kube" "$HOME/.ssh"
|
|
cd "$LAB"
|
|
cat > .env <<'EOF'
|
|
API_TOKEN=tok_CANARYenv01_4f9c2a7e8b1d6033
|
|
DATABASE_URL=postgres://app:CANARYdb01pw@db.lab.local:5432/app
|
|
SENDGRID_API_KEY=SG.CANARYsg01.aaaaaaaaaaaaaaaaaaaa
|
|
PORT=8080
|
|
EOF
|
|
cat > "$HOME/.aws/credentials" <<'EOF'
|
|
[default]
|
|
aws_access_key_id = AKIALAB000000FAKE0001
|
|
aws_secret_access_key = wJalrCANARYaws01secretKEYwJalr0001
|
|
EOF
|
|
cat > "$HOME/.netrc" <<'EOF'
|
|
machine api.lab.local login labuser password CANARYnetrc01pw
|
|
EOF
|
|
cat > "$HOME/.git-credentials" <<'EOF'
|
|
https://gituser:CANARYgit01tok@github.com
|
|
EOF
|
|
DOCKER_AUTH=$(printf 'dockeruser:CANARYdocker01pw' | base64)
|
|
cat > "$HOME/.docker/config.json" <<EOF
|
|
{ "auths": { "registry.lab.local": { "auth": "$DOCKER_AUTH" } } }
|
|
EOF
|
|
cat > "$HOME/.kube/config" <<'EOF'
|
|
apiVersion: v1
|
|
kind: Config
|
|
users:
|
|
- name: lab-admin
|
|
user:
|
|
token: CANARYkube01token
|
|
EOF
|
|
cat > "$HOME/.ssh/id_ed25519" <<'EOF'
|
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
|
CANARYssh01keybodyb3BlbnNzaC1rZXktdjEAAAAABG5vbmU
|
|
-----END OPENSSH PRIVATE KEY-----
|
|
EOF
|
|
chmod 600 "$HOME/.ssh/id_ed25519"
|
|
cat > "$LAB/extra/service-account.json" <<'EOF'
|
|
{"type":"service_account","private_key":"CANARYfile01blob"}
|
|
EOF
|
|
# consumer files for the blast-radius map (reference markers, not secrets)
|
|
cat > docker-compose.yml <<'EOF'
|
|
services:
|
|
app:
|
|
environment:
|
|
- API_TOKEN=${API_TOKEN}
|
|
- DATABASE_URL=${DATABASE_URL}
|
|
EOF
|
|
cat > k8s/deploy.yaml <<'EOF'
|
|
env:
|
|
- name: API_TOKEN
|
|
valueFrom: { secretKeyRef: { name: app, key: API_TOKEN } }
|
|
- name: SENDGRID_API_KEY
|
|
valueFrom: { secretKeyRef: { name: app, key: SENDGRID_API_KEY } }
|
|
EOF
|
|
|
|
# the canary strings that must NEVER appear in any command output
|
|
SECRETS=( CANARYenv01 CANARYdb01pw CANARYsg01 wJalrCANARYaws01 CANARYnetrc01pw
|
|
CANARYgit01tok "$DOCKER_AUTH" CANARYkube01token CANARYssh01keybody CANARYfile01blob )
|
|
|
|
############################ T1 scan (all 8 sources) ############################
|
|
note "T1 scan --path extra (all sources)"
|
|
run incredigo scan --path "$LAB/extra"
|
|
echo "$OUT"
|
|
for s in aws env netrc ssh docker kube git file; do check "scan lists '$s'" "(^|[[:space:]])$s([[:space:]])"; done
|
|
|
|
############################ T2 status ############################
|
|
note "T2 status"
|
|
run incredigo status; check "status header" "STATE"
|
|
|
|
############################ T3 migrate (fresh store) ############################
|
|
note "T3 migrate --path extra --dedupe"
|
|
run incredigo migrate --path "$LAB/extra" --dedupe
|
|
echo "$OUT"
|
|
check "migrate reports count" "migrated [1-9]"
|
|
OUT="$(gopass ls --flat 2>&1)"; check "store has imported/ entries" "imported/"
|
|
|
|
############################ T4/T5 age export+import roundtrip ############################
|
|
note "T4 export (age)"
|
|
run incredigo export --prefix imported/ --out "$LAB/backup.age"; check "age sealed" "sealed [0-9]+ entr"
|
|
note "T5 age roundtrip into a clean prefix"
|
|
printf 'roundtrip-CANARYrt01\n' | gopass insert --multiline=false -f rt/alpha >/dev/null 2>&1
|
|
run incredigo export --prefix rt/ --out "$LAB/rt.age"; check "rt export" "sealed 1 entr"
|
|
gopass rm -f rt/alpha >/dev/null 2>&1
|
|
run incredigo import --in "$LAB/rt.age" --force; check "rt import restored" "restored 1 entr"
|
|
OUT="$(gopass show -o rt/alpha 2>&1)"; check "rt value matches" "^roundtrip-CANARYrt01$"
|
|
|
|
############################ T6 hmac sealer roundtrip ############################
|
|
note "T6 hmac sealer roundtrip"
|
|
run incredigo export --prefix rt/ --sealer hmac --out "$LAB/rt.hmac"; check "hmac sealed" "sealed 1 entr"
|
|
gopass rm -f rt/alpha >/dev/null 2>&1
|
|
run incredigo import --in "$LAB/rt.hmac" --sealer hmac --force; check "hmac import" "restored 1 entr"
|
|
OUT="$(gopass show -o rt/alpha 2>&1)"; check "hmac value matches" "^roundtrip-CANARYrt01$"
|
|
|
|
############################ T7 openssl sealer roundtrip ############################
|
|
note "T7 openssl sealer roundtrip"
|
|
if command -v openssl >/dev/null; then
|
|
run incredigo export --prefix rt/ --sealer openssl --out "$LAB/rt.ossl"; check "openssl sealed" "sealed 1 entr"
|
|
gopass rm -f rt/alpha >/dev/null 2>&1
|
|
run incredigo import --in "$LAB/rt.ossl" --sealer openssl --force; check "openssl import" "restored 1 entr"
|
|
OUT="$(gopass show -o rt/alpha 2>&1)"; check "openssl value matches" "^roundtrip-CANARYrt01$"
|
|
else echo " SKIP openssl not installed"; fi
|
|
|
|
############################ T8 rotate --dry-run ############################
|
|
note "T8 rotate --dry-run"
|
|
run incredigo rotate --prefix imported/ --dry-run
|
|
echo "$OUT"
|
|
check "backup gate ran" "backup gate: [0-9]+ entr"
|
|
check "spine completed" "DRY RUN complete"
|
|
check "noop rotated" "noop"
|
|
|
|
############################ T8b backup-gate COVERAGE (independent ground truth) ############################
|
|
# Regression guard for the 2026-06-16 lapse: the gate's sealed==verified check is
|
|
# self-referential (both counts come from `gopass ls --flat`), so a path hidden from
|
|
# `ls --flat` (e.g. a ".env" dot-segment) is dropped from BOTH sides and the gate
|
|
# passes while under-covering the store. Here we count the on-disk *.gpg files
|
|
# directly (independent of `ls --flat`) and assert the gate sealed exactly that many.
|
|
note "T8b backup gate covers EVERY store entry (independent .gpg count)"
|
|
GATE_N=$(grep -Eo 'backup gate: [0-9]+ entr' <<<"$OUT" | grep -Eo '[0-9]+' | head -1)
|
|
DISK_N=$(find "$GOPASS_HOMEDIR" -name '*.gpg' -path '*/imported/*' 2>/dev/null | wc -l | tr -d ' ')
|
|
printf '\n### T8b coverage gate=%s disk=%s\n' "$GATE_N" "$DISK_N" >> "$MASTER"
|
|
OUT="gate=$GATE_N disk=$DISK_N"; echo " $OUT"
|
|
check "gate count parsed" "gate=[0-9]+ "
|
|
check "backup covers ALL entries" "gate=${DISK_N} disk=${DISK_N}"
|
|
|
|
############################ T9 rotate --blast ############################
|
|
note "T9 rotate --blast"
|
|
run incredigo rotate --prefix imported/ --dry-run --blast --blast-root "$LAB"
|
|
echo "$OUT"
|
|
check "blast CONSUMERS col" "CONSUMERS"
|
|
check "API_TOKEN -> 2 files" "API_TOKEN.*2 file"
|
|
check "blast radius listed" "blast radius"
|
|
|
|
############################ T10 worklist ############################
|
|
note "T10 worklist"
|
|
run incredigo worklist; check "worklist table" "change-password link"
|
|
|
|
############################ T11 negative: --execute refused ############################
|
|
note "T11 rotate --execute must be refused"
|
|
run incredigo rotate --execute; check "execute refused" "refused"
|
|
|
|
############################ T12 negative: empty passphrase ############################
|
|
note "T12 empty INCREDIGO_PASSPHRASE rejected"
|
|
OUT="$(INCREDIGO_PASSPHRASE='' incredigo export --prefix imported/ --out /tmp/should-not-exist.age 2>&1)"
|
|
printf '\n### empty-pass\n%s\n' "$OUT" >> "$MASTER"
|
|
check "empty passphrase error" "empty"
|
|
|
|
############################ T13 GLOBAL leak canary ############################
|
|
note "T13 GLOBAL leak-check across ALL command output"
|
|
LEAKED=0
|
|
for s in "${SECRETS[@]}"; do
|
|
if grep -Fq "$s" "$MASTER"; then echo " FAIL secret leaked: $s"; LEAKED=$((LEAKED+1)); fi
|
|
done
|
|
if grep -Fq "PRIVATE KEY" "$MASTER"; then echo " FAIL 'PRIVATE KEY' material in output"; LEAKED=$((LEAKED+1)); fi
|
|
if [ "$LEAKED" -eq 0 ]; then echo " PASS no secret canary in any output ($MASTER)"; PASS=$((PASS+1));
|
|
else echo " FAIL $LEAKED leak(s) detected"; FAIL=$((FAIL+1)); FAILED+=("leak-check"); fi
|
|
|
|
############################ summary ############################
|
|
echo; echo "================= SUMMARY ================="
|
|
echo " PASS=$PASS FAIL=$FAIL"
|
|
[ "$FAIL" -gt 0 ] && printf ' failed: %s\n' "${FAILED[*]}"
|
|
echo "HARNESS_DONE rc=$FAIL"
|
|
exit 0
|