Add red blue security loop
This commit is contained in:
parent
0af1257d86
commit
7f537c4025
12
README.md
12
README.md
|
|
@ -244,6 +244,18 @@ is stale:
|
|||
|
||||
This keeps local AI answers grounded in repo files and runbooks.
|
||||
|
||||
## Red/Blue Loop
|
||||
|
||||
Safe red-team/blue-team checks are grouped under one command:
|
||||
|
||||
```bash
|
||||
./jeannie red-blue plan
|
||||
./jeannie red-blue run --local
|
||||
```
|
||||
|
||||
Local mode runs deterministic prompt-injection and eval checks. Live scans are
|
||||
listed but gated so they can run from the Debian host when intended.
|
||||
|
||||
## Deploying
|
||||
|
||||
From the Debian server:
|
||||
|
|
|
|||
|
|
@ -244,6 +244,18 @@ is stale:
|
|||
|
||||
This keeps local AI answers grounded in repo files and runbooks.
|
||||
|
||||
## Red/Blue Loop
|
||||
|
||||
Safe red-team/blue-team checks are grouped under one command:
|
||||
|
||||
```bash
|
||||
./{{ main_script }} red-blue plan
|
||||
./{{ main_script }} red-blue run --local
|
||||
```
|
||||
|
||||
Local mode runs deterministic prompt-injection and eval checks. Live scans are
|
||||
listed but gated so they can run from the Debian host when intended.
|
||||
|
||||
## Deploying
|
||||
|
||||
From the Debian server:
|
||||
|
|
|
|||
|
|
@ -387,6 +387,10 @@ the public website deployment.
|
|||
`security/prompt-injection-lab`. Cases cover hostile runbooks, tool output,
|
||||
fake dashboard mitigations, and unsafe command arguments.
|
||||
|
||||
`red-blue {plan|run|ledger}`
|
||||
: Plan or run the homelab red-team/blue-team loop. `run --local` executes only
|
||||
deterministic local checks; live security scans remain explicit.
|
||||
|
||||
Kyverno hardening policies
|
||||
: `apps/supply-chain-policy` includes audit-mode checks for privileged pods,
|
||||
privilege escalation, hostPath use, resource requests/limits, and mutable image
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
# Red-Team / Blue-Team Loop
|
||||
|
||||
This directory defines the safe recurring security loop for the homelab. The
|
||||
first implementation runs local deterministic checks and prints the gated live
|
||||
checks that should run from the Debian host.
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
./jeannie red-blue plan
|
||||
./jeannie red-blue run --local
|
||||
```
|
||||
|
||||
The loop is intentionally top-risk oriented: fix, accept, or defer findings in
|
||||
`infra/red-blue-loop/ledger.tsv`.
|
||||
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
id mode command risk
|
||||
prompt_injection local ./jeannie prompt-injection-lab run prompt injection regressions
|
||||
ai_evals local ./jeannie ai-evals run Jeannie/RAG behavior regressions
|
||||
secret_scan live ./jeannie security-secrets secret leakage
|
||||
web_scan live ./jeannie security-web public web exposure
|
||||
k8s_policy live ./jeannie security-k8s Kubernetes hardening drift
|
||||
nuclei live ./jeannie security-nuclei public known-CVE exposure
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
date finding status owner notes
|
||||
2026-06-30 initial-loop accepted jv Loop created; add real findings as fixed, accepted, or deferred.
|
||||
|
||||
|
8
jeannie
8
jeannie
|
|
@ -6346,6 +6346,10 @@ ai_memory() {
|
|||
"${REPO_ROOT}/scripts/ai-memory" "${@:2}"
|
||||
}
|
||||
|
||||
red_blue_loop() {
|
||||
"${REPO_ROOT}/scripts/red-blue-loop" "${@:2}"
|
||||
}
|
||||
|
||||
validate_homelab() {
|
||||
"${REPO_ROOT}/scripts/validate-homelab"
|
||||
}
|
||||
|
|
@ -6606,6 +6610,7 @@ Security Learning
|
|||
security-runtime Check runtime security sensors.
|
||||
security-attack-path Print prioritized attack-path findings.
|
||||
prompt-injection-lab {list|run|show} Run local prompt-injection defense drills.
|
||||
red-blue {plan|run|ledger} Run or plan safe red-team/blue-team loop.
|
||||
|
||||
AI And Indexing
|
||||
ai-index Build the local homelab RAG index.
|
||||
|
|
@ -6890,6 +6895,9 @@ case "${1:-}" in
|
|||
prompt-injection-lab)
|
||||
prompt_injection_lab "$@"
|
||||
;;
|
||||
red-blue)
|
||||
red_blue_loop "$@"
|
||||
;;
|
||||
openwrt)
|
||||
openwrt
|
||||
;;
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ EOF
|
|||
|
||||
is_read_only_command() {
|
||||
case "$1" in
|
||||
status|capacity|capacity-advisor|capacity-limits|recover-plan|incident|safety-case|agent-sandbox|scorecard|gitops-status|cert-check|backup-status|synthetic-checks|resource-budget|route-inventory|golden-ledger|workers|change-journal|map|validate|access-audit|doctor-versions|doctor-edge|doctor-gitea|doctor-rpi|doctor-cluster|preflight|doctor-preapply|inventory-check|secrets-check|tailnet-policy-check|ai-check|ask|ai-evals|ai-scheduler|ai-memory|impact|review-last-change|security-scan|security-host|security-trivy|security-secrets|security-nuclei|security-web|security-logs|security-attack-path|prompt-injection-lab|control-plane)
|
||||
status|capacity|capacity-advisor|capacity-limits|recover-plan|incident|safety-case|agent-sandbox|scorecard|gitops-status|cert-check|backup-status|synthetic-checks|resource-budget|route-inventory|golden-ledger|workers|change-journal|map|validate|access-audit|doctor-versions|doctor-edge|doctor-gitea|doctor-rpi|doctor-cluster|preflight|doctor-preapply|inventory-check|secrets-check|tailnet-policy-check|ai-check|ask|ai-evals|ai-scheduler|ai-memory|impact|review-last-change|security-scan|security-host|security-trivy|security-secrets|security-nuclei|security-web|security-logs|security-attack-path|prompt-injection-lab|red-blue|control-plane)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Run or plan the homelab red-team/blue-team loop."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
CHECKS_FILE = REPO_ROOT / "infra" / "red-blue-loop" / "checks.tsv"
|
||||
LEDGER_FILE = REPO_ROOT / "infra" / "red-blue-loop" / "ledger.tsv"
|
||||
|
||||
|
||||
def checks() -> list[dict[str, str]]:
|
||||
with CHECKS_FILE.open(encoding="utf-8", newline="") as handle:
|
||||
return list(csv.DictReader(handle, delimiter="\t"))
|
||||
|
||||
|
||||
def plan() -> int:
|
||||
print("Red/Blue Loop")
|
||||
print("=============")
|
||||
for row in checks():
|
||||
print(f"{row['mode']:5} {row['id']:18} {row['command']}")
|
||||
print(f" risk: {row['risk']}")
|
||||
print()
|
||||
print(f"Ledger: {LEDGER_FILE.relative_to(REPO_ROOT)}")
|
||||
return 0
|
||||
|
||||
|
||||
def run(local_only: bool) -> int:
|
||||
failures = 0
|
||||
selected = [row for row in checks() if row["mode"] == "local" or not local_only]
|
||||
print("Red/Blue Loop Run")
|
||||
print("=================")
|
||||
for row in selected:
|
||||
print(f"\n== {row['id']} ==")
|
||||
if row["mode"] != "local" and local_only:
|
||||
print("skip: live check gated")
|
||||
continue
|
||||
process = subprocess.run(
|
||||
row["command"].split(),
|
||||
cwd=REPO_ROOT,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
check=False,
|
||||
)
|
||||
print(process.stdout.rstrip())
|
||||
if process.returncode != 0:
|
||||
failures += 1
|
||||
print()
|
||||
print(f"failures={failures}")
|
||||
print(f"ledger={LEDGER_FILE.relative_to(REPO_ROOT)}")
|
||||
return 1 if failures else 0
|
||||
|
||||
|
||||
def ledger() -> int:
|
||||
print(LEDGER_FILE.read_text(encoding="utf-8").rstrip())
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
subparsers.add_parser("plan")
|
||||
run_parser = subparsers.add_parser("run")
|
||||
run_parser.add_argument("--local", action="store_true", help="Run only deterministic local checks.")
|
||||
subparsers.add_parser("ledger")
|
||||
args = parser.parse_args()
|
||||
if args.command == "plan":
|
||||
return plan()
|
||||
if args.command == "run":
|
||||
return run(local_only=args.local)
|
||||
if args.command == "ledger":
|
||||
return ledger()
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Loading…
Reference in New Issue