Project-Template/docs/architecture/scripts/controls.sh

292 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Which operational controls does this project actually have, and how do we know?
#
# bash scripts/controls.sh # the table
# bash scripts/controls.sh --quiet # only the rows that are not fine
#
# Exit codes:
#
# 0 every expected control is present
# 1 at least one expected control is absent or could not be determined
# 2 NOTHING WAS CHECKED — no control was declared expected. Not a pass.
#
# ===========================================================================
# TEMPLATE COPY — configure this before the first run
# ===========================================================================
#
# Set CONTROLS_EXPECTED to the controls this project is supposed to have, then
# the variables for each. Everything not named there reports `n/a`, and the
# defaults are empty on purpose: a script that assumes a library needs a
# healthcheck reports a gap that is not one, and a report full of findings
# nobody can act on is a report nobody reads twice.
#
# Assumes: bash, coreutils, `date`.
#
# ## Why this exists
#
# The question that decides whether a system can be depended on is not "does it
# work" — a demo answers that. It is **which controls are present**, asked
# before something goes wrong rather than after.
#
# Nothing answers it here. `verify.sh` reports which checks ran just now.
# `backup.sh` proves one dump. The Command Center reports on documents and
# tracker labels. None of them says whether this project has backups AND has
# ever restored one AND has somewhere errors go AND has an environment that is
# not production. That list is the actual answer to "is this safe to depend on",
# and until now it lived in whoever remembered.
#
# ## Measured is not the same as declared, and both beat unknown
#
# Every row carries how it is known, because the four states mean genuinely
# different things and flattening them is how a report starts lying:
#
# measured this script observed the fact — a file with a date on it, a
# variable that is set, a command that answered
# declared the operator asserted it in configuration. Checked for shape,
# not for truth: `CONTROLS_AUDIT=activity_events table` is a claim
# this script cannot verify and will not pretend to.
# n/a the project declared the control does not apply. A library has no
# uptime; saying so is an answer, not an omission.
# unknown expected, and could not be determined. **Never rendered as
# absent**, because "I could not tell" and "it is not there" send
# people to different places.
#
# An unknown is not a failure of the project, it is a failure of this script to
# find out, and it exits non-zero for the same reason `verify.sh` exits 2 on an
# empty run: a check that did not happen must not look like one that passed.
#
# ## Wiring it into verify.sh
#
# `verify.sh` runs every executable file in VERIFY_GUARD_DIR, and refuses to
# accept the scripts directory itself as that directory -- because release.sh,
# backup.sh and migrate.sh live there and would all be RUN. So this file is not
# dropped into verify.d. A three-line wrapper is:
#
# # scripts/verify.d/controls
# #!/usr/bin/env bash
# export CONTROLS_EXPECTED="backup restore errors healthcheck"
# exec bash "$(dirname "$0")/../controls.sh" --quiet
#
# The wrapper is where this project's expectations live, which is the right
# place for them: they are a fact about the project, not about the check.
#
# ## It writes nothing
#
# There is no CONTROLS.md and there will not be one. A committed file saying
# "backups: ok" is a description of current state living in a document, which is
# what the batch ledger was and why it was archived: it is right on the day it
# is written and silently wrong afterwards. The live answer comes from running
# this. What belongs in git is the *intent* — `docs/OPERATIONS.md` — which
# changes rarely because it is a decision rather than a measurement.
set -uo pipefail
say() { printf '\033[1mcontrols:\033[0m %s\n' "$*" >&2; }
# ---------------------------------------------------------------------------
# CONFIGURATION
#
# Space-separated, from this set:
# backup restore errors healthcheck ratelimit audit testenv deploy
# ---------------------------------------------------------------------------
EXPECTED="${CONTROLS_EXPECTED:-}"
# Measured controls read the same variables the scripts that own them use, so
# the two cannot disagree about which project this is.
BACKUP_DIR="${BACKUP_DIR:-}"
BACKUP_NAME="${BACKUP_NAME:-}"
BACKUP_MAX_AGE="${CONTROLS_BACKUP_MAX_AGE_DAYS:-2}"
RESTORE_MAX_AGE="${CONTROLS_RESTORE_MAX_AGE_DAYS:-120}"
LAST_RESTORE="${CONTROLS_LAST_RESTORE:-}"
ERROR_VAR="${CONTROLS_ERROR_TRACKING_VAR:-}"
HEALTHCHECK_BASE_URL="${HEALTHCHECK_BASE_URL:-}"
RATELIMIT="${CONTROLS_RATELIMIT:-}"
AUDIT="${CONTROLS_AUDIT:-}"
TEST_ENV="${CONTROLS_TEST_ENV:-}"
STATUS_HOST="${STATUS_HOST:-}"
STATUS_CONTAINER="${STATUS_CONTAINER:-}"
QUIET=""
while [ $# -gt 0 ]; do
case "$1" in
--quiet) QUIET="yes"; shift ;;
-h|--help) say "usage: bash scripts/controls.sh [--quiet]"; say "set CONTROLS_EXPECTED to the controls this project should have."; exit 0 ;;
*) say "unknown argument '$1'."; exit 2 ;;
esac
done
ALL="backup restore errors healthcheck ratelimit audit testenv deploy"
for want in $EXPECTED; do
case " $ALL " in
*" $want "*) ;;
*) say "CONTROLS_EXPECTED names '$want', which is not a control this knows about."
say "Known: $ALL"
exit 2 ;;
esac
done
expects() { case " $EXPECTED " in *" $1 "*) return 0 ;; *) return 1 ;; esac; }
# days_since <YYYY-MM-DD> — prints whole days, or nothing if it cannot be read.
days_since() {
local then now
# The shape is checked before `date` sees it. GNU date parses relative English
# -- `date -d "last tuesday"` succeeds and returns a real timestamp -- so a
# CONTROLS_LAST_RESTORE somebody typed as prose would come back as a plausible
# age instead of an unreadable value. A restore date this script invented is
# worse than one it admits it cannot read.
case "$1" in
[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]) ;;
*) return 1 ;;
esac
then=$(date -u -d "$1" +%s 2>/dev/null) || return 1
[ -n "$then" ] || return 1
now=$(date -u +%s) || return 1
printf '%d' $(( (now - then) / 86400 ))
}
ROWS=""
missing=0
unknowns=0
row() {
# <control> <state> <detail>
ROWS+="$(printf '%-12s %-9s %s' "$1" "$2" "$3")"$'\n'
case "$2" in
absent) missing=$((missing + 1)) ;;
unknown) unknowns=$((unknowns + 1)) ;;
esac
}
check_backup() {
if [ -z "$BACKUP_DIR" ] || [ -z "$BACKUP_NAME" ]; then
row backup unknown "BACKUP_DIR/BACKUP_NAME not set — cannot look"
return
fi
if [ ! -d "$BACKUP_DIR" ]; then
row backup unknown "$BACKUP_DIR is not a directory"
return
fi
local newest="" f
shopt -s nullglob
for f in "$BACKUP_DIR/$BACKUP_NAME"*; do
[ -f "$f" ] || continue
{ [ -z "$newest" ] || [ "$f" -nt "$newest" ]; } && newest="$f"
done
shopt -u nullglob
if [ -z "$newest" ]; then
row backup absent "no dump matching $BACKUP_NAME* in $BACKUP_DIR"
return
fi
local age
age=$(( ( $(date -u +%s) - $(date -u -r "$newest" +%s 2>/dev/null || echo 0) ) / 86400 ))
if [ "$age" -gt "$BACKUP_MAX_AGE" ]; then
row backup absent "newest dump is ${age}d old, limit ${BACKUP_MAX_AGE}d"
else
row backup measured "newest dump ${age}d old"
fi
}
check_restore() {
if [ -z "$LAST_RESTORE" ]; then
row restore absent "CONTROLS_LAST_RESTORE unset — no restore has been recorded"
return
fi
local age
if ! age=$(days_since "$LAST_RESTORE"); then
row restore unknown "CONTROLS_LAST_RESTORE='$LAST_RESTORE' is not a date this can read"
return
fi
if [ "$age" -gt "$RESTORE_MAX_AGE" ]; then
row restore absent "last restore ${age}d ago, limit ${RESTORE_MAX_AGE}d"
else
row restore declared "last restore ${age}d ago (asserted, not observed)"
fi
}
check_errors() {
if [ -z "$ERROR_VAR" ]; then
row errors unknown "CONTROLS_ERROR_TRACKING_VAR unset — name the variable that carries the DSN"
return
fi
if [ -n "${!ERROR_VAR:-}" ]; then
row errors measured "$ERROR_VAR is set"
else
row errors absent "$ERROR_VAR is empty — errors go nowhere"
fi
}
check_healthcheck() {
if [ -n "$HEALTHCHECK_BASE_URL" ]; then
row healthcheck measured "HEALTHCHECK_BASE_URL is set"
else
row healthcheck absent "HEALTHCHECK_BASE_URL unset — nothing ticks"
fi
}
check_deploy() {
if [ -z "$STATUS_HOST" ] || [ -z "$STATUS_CONTAINER" ]; then
row deploy unknown "STATUS_HOST/STATUS_CONTAINER unset — cannot ask what is deployed"
return
fi
local dir version
dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [ ! -x "$dir/status.sh" ] && [ ! -f "$dir/status.sh" ]; then
row deploy unknown "status.sh is not beside this script"
return
fi
if version=$(bash "$dir/status.sh" --deployed-version 2>/dev/null) && [ -n "$version" ]; then
row deploy measured "running $version"
else
# status.sh distinguishes stopped from unreachable and says so on stderr;
# this only needs to know it did not get an answer.
row deploy unknown "status.sh could not read the deployed version — run it directly for the reason"
fi
}
declared_or_absent() { # <control> <value> <hint>
if [ -n "$2" ]; then row "$1" declared "$2"; else row "$1" absent "$3"; fi
}
for c in $ALL; do
if ! expects "$c"; then
row "$c" n/a "not expected for this project"
continue
fi
case "$c" in
backup) check_backup ;;
restore) check_restore ;;
errors) check_errors ;;
healthcheck) check_healthcheck ;;
deploy) check_deploy ;;
ratelimit) declared_or_absent ratelimit "$RATELIMIT" "CONTROLS_RATELIMIT unset — nothing bounds an expensive endpoint" ;;
audit) declared_or_absent audit "$AUDIT" "CONTROLS_AUDIT unset — no record of who changed what" ;;
testenv) declared_or_absent testenv "$TEST_ENV" "CONTROLS_TEST_ENV unset — production is the only environment" ;;
esac
done
if [ -z "$EXPECTED" ]; then
say "CONTROLS_EXPECTED is empty, so no control was checked."
say "That is not a pass. Name what this project is supposed to have:"
say " CONTROLS_EXPECTED='$ALL'"
say "and drop the ones that genuinely do not apply — a library has no uptime."
exit 2
fi
if [ -n "$QUIET" ]; then
printf '%s' "$ROWS" | grep -vE '^\S+ +(measured|declared|n/a) ' >&2 || true
else
printf '%s' "$ROWS" >&2
fi
say ""
if [ "$missing" -gt 0 ] || [ "$unknowns" -gt 0 ]; then
say "$missing absent, $unknowns unknown, of $(printf '%s' "$EXPECTED" | wc -w) expected."
[ "$unknowns" -gt 0 ] && say "An unknown is this script failing to find out, not the control failing to exist. Both are non-zero; only one is the project's fault."
exit 1
fi
say "every expected control present ($(printf '%s' "$EXPECTED" | wc -w) checked)."