CIS490/bootstrap/__main__.py
max 265f3ad313 Tier-4 sample source: theZoo (no auth, no operator action)
Replaces MalwareBazaar with theZoo (https://github.com/ytisf/theZoo).
theZoo is a public security-research repo with hundreds of malware
samples organized by family, password-protected with the well-known
'infected'. No API key, no signup, nothing for an operator to do —
which is what zero-touch tier-4 actually means.

Changes:

- tools/auto_fetch_samples.py: rewrite. Clones theZoo (shallow, ~500 MB)
  to /var/lib/cis490/theZoo on first run, then for each manifest
  family without a sha256 it locates a matching Binaries/<Name>
  dir, extracts the .zip with password 'infected', picks the largest
  non-text payload as the binary, sha256s it, stages at
  samples/store/<sha256>, and rewrites manifest.toml in place
  (atomic tempfile + os.replace, stat preserved). Mandatory exit
  semantic: non-zero if no real samples landed.

- scripts/install-tier-3-4.sh: dropped the MB-key resolution chain
  (env var → local file → bootstrap.wg fetch). Now just runs
  auto_fetch_samples.py and dies if zero samples land. SKIP_TIER4
  remains as the explicit override but is documented as defeating
  the project.

- bootstrap/app.py + __main__.py + etc/cis490-bootstrap.service:
  removed the /v1/secret/<name> endpoint and the --secrets-root flag.
  Dead code now that no API key needs distributing. Live-rolled
  back on the Pi (404 verified post-restart, stale /etc/cis490/secrets
  dir removed).

- scripts/set-malwarebazaar-key.sh: deleted. No MB key means no
  one-time operator step.

- tests/test_bootstrap_secrets.py: deleted (route removed).

- AGENTS.md: rewrote tier-4 section to reflect zero-operator model.

148/148 tests pass. Bootstrap service rolled back live.
2026-05-01 01:17:50 -05:00

65 lines
1.8 KiB
Python

"""``cis490-bootstrap`` launcher.
Runs as root (needs CA private key access). Listens on 127.0.0.1:8446
behind Caddy's ``bootstrap.wg`` site — Caddy terminates TLS, this
service speaks plain HTTP on loopback only.
"""
from __future__ import annotations
import argparse
import logging
import sys
from pathlib import Path
import uvicorn
from bootstrap.app import make_app
def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(prog="cis490-bootstrap")
p.add_argument("--listen-host", default="127.0.0.1")
p.add_argument("--listen-port", type=int, default=8446)
p.add_argument(
"--issuer-script",
type=Path,
default=Path("/home/max/.env/wg-pki/scripts/issue-cis490-client-cert.sh"),
help="Path to the wg-pki leaf-cert mint script.",
)
p.add_argument(
"--issued-root",
type=Path,
default=Path("/home/max/.env/wg-pki/issued"),
help="Where minted tarballs are cached.",
)
p.add_argument("--log-level", default="info")
args = p.parse_args(argv)
logging.basicConfig(
level=getattr(logging, args.log_level.upper(), logging.INFO),
format="%(asctime)s %(levelname)s %(name)s %(message)s",
)
log = logging.getLogger("cis490.bootstrap.main")
if not args.issuer_script.exists():
log.error("issuer script missing: %s", args.issuer_script)
return 2
app = make_app(
issuer_script=args.issuer_script,
issued_root=args.issued_root,
)
log.info("listening on %s:%d", args.listen_host, args.listen_port)
uvicorn.run(
app,
host=args.listen_host,
port=args.listen_port,
log_level=args.log_level,
access_log=True,
)
return 0
if __name__ == "__main__":
sys.exit(main())