feat: add collectors and rules
This commit is contained in:
91
40-rules/backup_restore_drill_recent.sh
Executable file
91
40-rules/backup_restore_drill_recent.sh
Executable file
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/../scripts/lib/common.sh"
|
||||
require_cmd jq
|
||||
|
||||
EVID_DIR="${1:?usage: backup_restore_drill_recent.sh <evidence_dir>}"
|
||||
TS="$(iso_utc_now)"
|
||||
FILE="$EVID_DIR/backup_restore_drill.json"
|
||||
|
||||
MAX_DAYS="${VMCC_MAX_RESTORE_DRILL_AGE_DAYS:-7}"
|
||||
|
||||
file_mtime_epoch() {
|
||||
local file="$1"
|
||||
if stat -c %Y "$file" >/dev/null 2>&1; then
|
||||
stat -c %Y "$file"
|
||||
else
|
||||
stat -f %m "$file"
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ ! -f "$FILE" ]]; then
|
||||
json_emit "$(jq -n --arg ts "$TS" '{
|
||||
version:"1.0.0",
|
||||
rule_id:"backup.restore_drill_recent",
|
||||
control_ids:["BC-01"],
|
||||
passed:false,
|
||||
severity:"MEDIUM",
|
||||
timestamp:$ts,
|
||||
evidence:[{path:"backup_restore_drill.json"}],
|
||||
details:{error:"missing evidence file"}
|
||||
}')"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
COLLECTED="$(jq -r '.collected // false' "$FILE")"
|
||||
if [[ "$COLLECTED" != "true" ]]; then
|
||||
json_emit "$(jq -n --arg ts "$TS" '{
|
||||
version:"1.0.0",
|
||||
rule_id:"backup.restore_drill_recent",
|
||||
control_ids:["BC-01"],
|
||||
passed:false,
|
||||
severity:"MEDIUM",
|
||||
timestamp:$ts,
|
||||
evidence:[{path:"backup_restore_drill.json"}],
|
||||
details:{error:"no restore drill evidence found"}
|
||||
}')"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
PATH_FOUND="$(jq -r '.path // empty' "$FILE")"
|
||||
if [[ -z "$PATH_FOUND" || ! -f "$PATH_FOUND" ]]; then
|
||||
json_emit "$(jq -n --arg ts "$TS" --arg p "$PATH_FOUND" '{
|
||||
version:"1.0.0",
|
||||
rule_id:"backup.restore_drill_recent",
|
||||
control_ids:["BC-01"],
|
||||
passed:false,
|
||||
severity:"MEDIUM",
|
||||
timestamp:$ts,
|
||||
evidence:[{path:"backup_restore_drill.json"}],
|
||||
details:{error:"referenced drill file missing", referenced:$p}
|
||||
}')"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
NOW_EPOCH="$(date -u +%s)"
|
||||
MTIME_EPOCH="$(file_mtime_epoch "$PATH_FOUND")"
|
||||
AGE_DAYS="$(( (NOW_EPOCH - MTIME_EPOCH) / 86400 ))"
|
||||
|
||||
if [[ "$AGE_DAYS" -le "$MAX_DAYS" ]]; then
|
||||
json_emit "$(jq -n --arg ts "$TS" --argjson age "$AGE_DAYS" '{
|
||||
version:"1.0.0",
|
||||
rule_id:"backup.restore_drill_recent",
|
||||
control_ids:["BC-01"],
|
||||
passed:true,
|
||||
severity:"MEDIUM",
|
||||
timestamp:$ts,
|
||||
evidence:[{path:"backup_restore_drill.json"}],
|
||||
details:{age_days:$age}
|
||||
}')"
|
||||
else
|
||||
json_emit "$(jq -n --arg ts "$TS" --argjson age "$AGE_DAYS" --argjson max "$MAX_DAYS" '{
|
||||
version:"1.0.0",
|
||||
rule_id:"backup.restore_drill_recent",
|
||||
control_ids:["BC-01"],
|
||||
passed:false,
|
||||
severity:"MEDIUM",
|
||||
timestamp:$ts,
|
||||
evidence:[{path:"backup_restore_drill.json"}],
|
||||
details:{error:"restore drill too old", age_days:$age, max_days:$max}
|
||||
}')"
|
||||
fi
|
||||
78
40-rules/governance_constitution_pinned.sh
Executable file
78
40-rules/governance_constitution_pinned.sh
Executable file
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/../scripts/lib/common.sh"
|
||||
require_cmd jq
|
||||
|
||||
EVID_DIR="${1:?usage: governance_constitution_pinned.sh <evidence_dir>}"
|
||||
TS="$(iso_utc_now)"
|
||||
FILE="$EVID_DIR/constitution_hash.json"
|
||||
|
||||
PINNED_SHA256="${VMCC_PINNED_CONSTITUTION_SHA256:-}"
|
||||
|
||||
if [[ ! -f "$FILE" ]]; then
|
||||
json_emit "$(jq -n --arg ts "$TS" '{
|
||||
version:"1.0.0",
|
||||
rule_id:"governance.constitution_pinned",
|
||||
control_ids:["GV-01"],
|
||||
passed:false,
|
||||
severity:"HIGH",
|
||||
timestamp:$ts,
|
||||
evidence:[{path:"constitution_hash.json"}],
|
||||
details:{error:"missing evidence file"}
|
||||
}')"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
COLLECTED="$(jq -r '.collected // false' "$FILE")"
|
||||
if [[ "$COLLECTED" != "true" ]]; then
|
||||
json_emit "$(jq -n --arg ts "$TS" '{
|
||||
version:"1.0.0",
|
||||
rule_id:"governance.constitution_pinned",
|
||||
control_ids:["GV-01"],
|
||||
passed:false,
|
||||
severity:"HIGH",
|
||||
timestamp:$ts,
|
||||
evidence:[{path:"constitution_hash.json"}],
|
||||
details:{error:"constitution hash not collected"}
|
||||
}')"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
OBSERVED="$(jq -r '.sha256 // empty' "$FILE")"
|
||||
if [[ -z "$PINNED_SHA256" ]]; then
|
||||
json_emit "$(jq -n --arg ts "$TS" --arg observed "$OBSERVED" '{
|
||||
version:"1.0.0",
|
||||
rule_id:"governance.constitution_pinned",
|
||||
control_ids:["GV-01"],
|
||||
passed:false,
|
||||
severity:"HIGH",
|
||||
timestamp:$ts,
|
||||
evidence:[{path:"constitution_hash.json"}],
|
||||
details:{error:"no pinned hash configured", observed_sha256:$observed}
|
||||
}')"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$OBSERVED" == "$PINNED_SHA256" ]]; then
|
||||
json_emit "$(jq -n --arg ts "$TS" '{
|
||||
version:"1.0.0",
|
||||
rule_id:"governance.constitution_pinned",
|
||||
control_ids:["GV-01"],
|
||||
passed:true,
|
||||
severity:"HIGH",
|
||||
timestamp:$ts,
|
||||
evidence:[{path:"constitution_hash.json"}],
|
||||
details:{}
|
||||
}')"
|
||||
else
|
||||
json_emit "$(jq -n --arg ts "$TS" --arg observed "$OBSERVED" --arg pinned "$PINNED_SHA256" '{
|
||||
version:"1.0.0",
|
||||
rule_id:"governance.constitution_pinned",
|
||||
control_ids:["GV-01"],
|
||||
passed:false,
|
||||
severity:"HIGH",
|
||||
timestamp:$ts,
|
||||
evidence:[{path:"constitution_hash.json"}],
|
||||
details:{error:"hash mismatch", observed_sha256:$observed, pinned_sha256:$pinned}
|
||||
}')"
|
||||
fi
|
||||
65
40-rules/ledger_hash_chain_intact.sh
Executable file
65
40-rules/ledger_hash_chain_intact.sh
Executable file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/../scripts/lib/common.sh"
|
||||
require_cmd jq
|
||||
|
||||
EVID_DIR="${1:?usage: ledger_hash_chain_intact.sh <evidence_dir>}"
|
||||
TS="$(iso_utc_now)"
|
||||
|
||||
FILE="$EVID_DIR/ledger_verify.json"
|
||||
if [[ ! -f "$FILE" ]]; then
|
||||
json_emit "$(jq -n --arg ts "$TS" '{
|
||||
version:"1.0.0",
|
||||
rule_id:"ledger.hash_chain_intact",
|
||||
control_ids:["AU-01","AU-02"],
|
||||
passed:false,
|
||||
severity:"CRITICAL",
|
||||
timestamp:$ts,
|
||||
evidence:[{path:"ledger_verify.json"}],
|
||||
details:{error:"missing evidence file"}
|
||||
}')"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
COLLECTED="$(jq -r '.collected // true' "$FILE")"
|
||||
if [[ "$COLLECTED" != "true" ]]; then
|
||||
json_emit "$(jq -n --arg ts "$TS" '{
|
||||
version:"1.0.0",
|
||||
rule_id:"ledger.hash_chain_intact",
|
||||
control_ids:["AU-01","AU-02"],
|
||||
passed:false,
|
||||
severity:"CRITICAL",
|
||||
timestamp:$ts,
|
||||
evidence:[{path:"ledger_verify.json"}],
|
||||
details:{error:"ledger verify not collected"}
|
||||
}')"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
OK="$(jq -r '.ok // false' "$FILE")"
|
||||
ENTRY_COUNT="$(jq -r '.entry_count // 0' "$FILE")"
|
||||
FAILURES_JSON="$(jq -c '.failures // []' "$FILE")"
|
||||
|
||||
if [[ "$OK" == "true" ]]; then
|
||||
json_emit "$(jq -n --arg ts "$TS" --argjson count "$ENTRY_COUNT" '{
|
||||
version:"1.0.0",
|
||||
rule_id:"ledger.hash_chain_intact",
|
||||
control_ids:["AU-01","AU-02"],
|
||||
passed:true,
|
||||
severity:"CRITICAL",
|
||||
timestamp:$ts,
|
||||
evidence:[{path:"ledger_verify.json"}],
|
||||
details:{entries_checked:$count}
|
||||
}')"
|
||||
else
|
||||
json_emit "$(jq -n --arg ts "$TS" --argjson count "$ENTRY_COUNT" --argjson failures "$FAILURES_JSON" '{
|
||||
version:"1.0.0",
|
||||
rule_id:"ledger.hash_chain_intact",
|
||||
control_ids:["AU-01","AU-02"],
|
||||
passed:false,
|
||||
severity:"CRITICAL",
|
||||
timestamp:$ts,
|
||||
evidence:[{path:"ledger_verify.json"}],
|
||||
details:{entries_checked:$count, failures:$failures}
|
||||
}')"
|
||||
fi
|
||||
Reference in New Issue
Block a user