Add HTTPS management console and response planning

This commit is contained in:
Luna 2026-06-12 02:39:53 -07:00
parent a56d72edd6
commit a7129e5666
16 changed files with 927 additions and 160 deletions

View file

@ -89,12 +89,20 @@ def main(argv: list[str] | None = None) -> int:
po.add_argument("action", nargs="?", default="check", choices=["check"],
help="posture action (default: check)")
po.add_argument("--json", action="store_true", help="emit findings as JSON")
rsp = sub.add_parser("respond",
help="build dry-run response plans for incidents")
rsp.add_argument("action", nargs="?", default="plan", choices=["plan"],
help="response action (default: plan)")
rsp.add_argument("id", nargs="?", help="incident id")
rsp.add_argument("--json", action="store_true", help="emit plan as JSON")
wd = sub.add_parser("watchdog",
help="poll a remote dashboard and push if Sentinel is silent")
wd.add_argument("--url", required=True, help="dashboard base URL")
wd.add_argument("--token", default="", help="dashboard bearer token")
wd.add_argument("--max-age", type=int, default=120,
help="heartbeat staleness threshold (seconds)")
wd.add_argument("--insecure-tls", action="store_true",
help="allow self-signed/untrusted dashboard certificates")
args = parser.parse_args(argv)
cfg = Config.load(args.config)
@ -138,15 +146,20 @@ def main(argv: list[str] | None = None) -> int:
return _cmd_incident(cfg, args.action, args.id, args.json)
if args.cmd == "posture":
return _cmd_posture(cfg, args.json)
if args.cmd == "respond":
return _cmd_respond(cfg, args.action, args.id, args.json)
if args.cmd == "watchdog":
return _cmd_watchdog(cfg, args.url, args.token, args.max_age)
return _cmd_watchdog(cfg, args.url, args.token, args.max_age,
not args.insecure_tls)
return _cmd_run(cfg)
def _cmd_watchdog(cfg: Config, url: str, token: str, max_age: int) -> int:
def _cmd_watchdog(cfg: Config, url: str, token: str, max_age: int,
verify_tls: bool = True) -> int:
from . import notify
from .selfprotect import poll_status, watchdog_verdict
ok, msg = watchdog_verdict(poll_status(url, token), max_age)
ok, msg = watchdog_verdict(poll_status(url, token, verify_tls=verify_tls),
max_age)
print(("OK: " if ok else "ALERT: ") + msg)
if not ok:
n = notify.Notification(
@ -328,6 +341,42 @@ def _cmd_posture(cfg: Config, as_json: bool) -> int:
return 1
def _cmd_respond(cfg: Config, action: str, iid: str | None, as_json: bool) -> int:
import json
from . import respond
if action != "plan":
print(f"error: unsupported respond action: {action}", file=sys.stderr)
return 2
if not iid:
print("error: 'respond plan' needs an incident id "
"(see 'incident list')", file=sys.stderr)
return 2
bundle = respond.load_bundle(cfg, iid)
if bundle is None:
print(f"error: no such incident: {iid}", file=sys.stderr)
return 1
plan = respond.build_plan(bundle, cfg)
if as_json:
print(json.dumps(plan, indent=2))
return 0
summary = plan["summary"]
print(f"Response plan {plan['plan_id']} [{summary['severity']}]")
print(f" incident: {plan['incident_id']}")
print(f" mode: {plan['mode']} (no commands executed)")
print(f" signatures: {', '.join(summary['signatures']) or ''}")
print(f" snapshots: {summary['snapshot_count']}")
print(" actions:")
for a in plan["actions"]:
cmd = " ".join(a["command"])
print(f" {a['id']} [{a['risk']}] {a['title']}")
print(f" {cmd}")
print(f" reason: {a['reason']}")
return 0
def _cmd_fim_check(cfg: Config, packages: bool) -> int:
from . import fim
sentinel = Sentinel(cfg)