#!/usr/bin/env bash # Tier-3 + Tier-4 deploy orchestrator. Idempotent. ZERO operator # interaction — including no API key, no signup, no manual upload. # # Steps (each idempotent on its own): # 1. install-msfrpcd.sh — auto-install metasploit-framework via # Rapid7 omnibus + drop systemd unit # 2. fetch-metasploitable2.sh — pull the disk image from the # SourceForge public mirror (TOFU) # 3. setup_bridge.sh — bring up br-malware host-only bridge # for callback-payload modules # 4. Tier-3 verify — fire vsftpd_234_backdoor against the # freshly-fetched VM, confirm session # lands and an episode is recorded # 5. Tier-4 deploy — clone theZoo (public security-research # repo, no auth), extract one real # binary per manifest family, stage at # samples/store/, rewrite # manifest.toml in place. MANDATORY: # the deploy fails if zero samples land. # # Inputs (env, all optional): # SKIP_VERIFY — set to skip the live Tier-3 fire test # SKIP_BRIDGE — set to skip bridge setup (limits to non-callback modules) # SKIP_TIER4 — set to skip Tier-4 deploy entirely (DEPRECATED; # leaves you with mimic-only data, defeats the project) # # Run as root from anywhere on the lab host. Sub-scripts handle their # own root checks. set -euo pipefail REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" INSTALL_ROOT="${INSTALL_ROOT:-/opt/cis490}" DATA_ROOT="${DATA_ROOT:-/var/lib/cis490}" ETC_ROOT="${ETC_ROOT:-/etc/cis490}" log() { printf '[install-tier-3-4] %s\n' "$*" >&2; } die() { log "FATAL: $*"; exit 1; } [[ $EUID -eq 0 ]] || die "must run as root" # Resolve script paths — prefer $INSTALL_ROOT (production) over # $REPO_ROOT (dev clone) so a re-run under systemd uses the same # scripts the orchestrator does. script_path() { local name="$1" if [[ -x "$INSTALL_ROOT/scripts/$name" ]]; then echo "$INSTALL_ROOT/scripts/$name"; return elif [[ -x "$REPO_ROOT/scripts/$name" ]]; then echo "$REPO_ROOT/scripts/$name"; return elif [[ -x "$INSTALL_ROOT/vm/$name" ]]; then echo "$INSTALL_ROOT/vm/$name"; return elif [[ -x "$REPO_ROOT/vm/$name" ]]; then echo "$REPO_ROOT/vm/$name"; return else die "$name not found in $INSTALL_ROOT or $REPO_ROOT" fi } # --- 1. msfrpcd -------------------------------------------------------- log "[1/5] install metasploit-framework + msfrpcd unit" "$(script_path install-msfrpcd.sh)" if ! systemctl is-active --quiet cis490-msfrpcd; then log "starting cis490-msfrpcd" systemctl enable --now cis490-msfrpcd fi sleep 3 if ! ss -ltn 2>/dev/null | grep -q ':55553'; then log "cis490-msfrpcd not listening on 127.0.0.1:55553 yet — waiting up to 30s" for _ in $(seq 1 30); do ss -ltn 2>/dev/null | grep -q ':55553' && break sleep 1 done fi ss -ltn 2>/dev/null | grep -q ':55553' || \ die "msfrpcd never bound to :55553 — check 'journalctl -u cis490-msfrpcd'" log "msfrpcd ✓" # --- 2. metasploitable2 image ------------------------------------------ log "[2/5] fetch Metasploitable2 disk image" OUT_DIR="$DATA_ROOT/vm/images" install -d -m 0755 -o cis490 -g cis490 "$OUT_DIR" OUT_DIR="$OUT_DIR" "$(script_path fetch-metasploitable2.sh)" chown cis490:cis490 "$OUT_DIR/metasploitable2.qcow2" 2>/dev/null || true log "metasploitable2.qcow2 ✓" # --- 3. bridge --------------------------------------------------------- if [[ -z "${SKIP_BRIDGE:-}" ]]; then log "[3/5] bring up br-malware host-only bridge" "$(script_path setup_bridge.sh)" || log "bridge setup failed (non-fatal); only non-callback modules will fire" log "br-malware ✓" else log "[3/5] SKIP_BRIDGE set — limiting to non-callback modules" fi # --- 4. Tier-3 verify -------------------------------------------------- # Uses distccd_command_exec (SLIRP-safe bind_perl, no bridge required). # vsftpd_234_backdoor hardcodes port 6200 which collides across SLIRP # slots and requires the host-only bridge — not usable as a SLIRP verify. # distccd: service on guest:3632 → host:5632; bind shell on guest:4444. if [[ -z "${SKIP_VERIFY:-}" ]]; then log "[4/5] verify Tier-3 fire (distccd_command_exec)" set -a # shellcheck disable=SC1091 . "$ETC_ROOT/msfrpc.env" set +a PY="$INSTALL_ROOT/.venv/bin/python" [[ -x "$PY" ]] || PY="$(command -v python3)" if ! TARGET_PORTS="5632:3632,4444:4444" PORT_BASE=5632 \ sudo -E -u cis490 "$PY" "$INSTALL_ROOT/tools/run_tier3_demo.py" \ --module distccd_command_exec \ --target-port 5632 \ --data-root "$DATA_ROOT/data" \ --target-boot-timeout 240 \ > /tmp/cis490-tier3-verify.log 2>&1; then log "verify run failed — log at /tmp/cis490-tier3-verify.log; dumping last 30 lines:" tail -30 /tmp/cis490-tier3-verify.log >&2 || true die "Tier-3 fire failed" fi if grep -q '^episode_id = ' /tmp/cis490-tier3-verify.log; then log "Tier-3 verified ✓ ($(grep '^episode_id = ' /tmp/cis490-tier3-verify.log))" else log "verify run finished but no episode_id seen — log at /tmp/cis490-tier3-verify.log" fi else log "[4/5] SKIP_VERIFY set" fi # --- 5. Tier-4 deploy (MANDATORY, no auth required) -------------------- if [[ -n "${SKIP_TIER4:-}" ]]; then log "[5/5] SKIP_TIER4 set — leaving this host on Tier 2/3 mimic-only." log " This is NOT the recommended configuration; the project's" log " training target is real-binary episodes." else log "[5/5] Tier-4 deploy (real malware fetch from theZoo — mandatory)" command -v git >/dev/null || die "git not installed; need it to clone theZoo" PY="$INSTALL_ROOT/.venv/bin/python" [[ -x "$PY" ]] || PY="$(command -v python3)" # theZoo clone lives on shared persistent storage so re-runs don't # re-download. cis490 user owns it for periodic git pull. THEZOO_DIR="${THEZOO_DIR:-/var/lib/cis490/theZoo}" install -d -o cis490 -g cis490 -m 0755 "$(dirname "$THEZOO_DIR")" if ! sudo -E -u cis490 "$PY" \ "$INSTALL_ROOT/tools/auto_fetch_samples.py" \ --thezoo-clone-dir "$THEZOO_DIR" \ > /tmp/cis490-tier4-deploy.log 2>&1; then log "Tier-4 fetch failed — last 30 lines of /tmp/cis490-tier4-deploy.log:" tail -30 /tmp/cis490-tier4-deploy.log >&2 || true die "Tier-4 deploy failed; without real binaries this host produces only mimics" fi REAL_COUNT="$(ls "$INSTALL_ROOT/samples/store/" 2>/dev/null | wc -l)" if [[ "$REAL_COUNT" -lt 1 ]]; then log "auto_fetch_samples.py exited 0 but samples/store/ is empty — see /tmp/cis490-tier4-deploy.log" tail -30 /tmp/cis490-tier4-deploy.log >&2 || true die "Tier-4 deploy failed: no real binaries staged" fi log "Tier-4 ✓ ($REAL_COUNT real binaries staged in $INSTALL_ROOT/samples/store/)" fi # Restart the orchestrator now so the next wave actually runs Tier-3 # against the freshly-staged msfrpcd + samples. Skipped only if the # unit isn't enabled yet (first install hasn't run `systemctl enable`). if systemctl is-enabled --quiet cis490-orchestrator 2>/dev/null; then log "restarting cis490-orchestrator to pick up new modules + samples" systemctl restart cis490-orchestrator || \ log "WARN: orchestrator restart failed — check 'journalctl -u cis490-orchestrator'" fi log "" log "=================================================================" log " Tier-3 deploy complete on $(hostname)" log "=================================================================" log " - metasploit-framework + cis490-msfrpcd.service active" log " - $OUT_DIR/metasploitable2.qcow2 staged" log " - bridge: $(ip link show br-malware >/dev/null 2>&1 && echo up || echo skipped)" log " - Tier-4: $(ls "$INSTALL_ROOT/samples/store/" 2>/dev/null | wc -l) real binaries staged" log "================================================================="