84 lines
2.4 KiB
Python
Executable File
84 lines
2.4 KiB
Python
Executable File
#!/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())
|