Dev mode — no Cloudflare Access header present. Authenticated as dev@local.
EPL
dev@local

Engagement Platform Labs — Quick Reference

Hardware Kit (Per Student)

RoleItemNotes
Engagement platformLaptop running VS Code devcontainerOpenWrt 23.05.3 rootfs; full stack
Drop deviceGL.iNet Mango (GL-MT300N-V2) — ~$2916MB NOR, ExtRoot required for Tailscale
StorageUSB flash drive (16GB+) — ~$8ExtRoot overlay for the Mango
Operator clientSame laptopDiscord, wrangler, ssh
Total~$40-50 per student

Architecture

Operator laptop
├── Discord client
├── wrangler / cloudflared CLI
└── VS Code devcontainer  ("engagement platform")
        ├── tailscale  (ep-<slot>)
        ├── cloudflared  (tunnel origin → api.<slot>.eplabs.cloud)
        ├── python3 + tooling
        └── OpenWrt 23.05.3 ramips/mt76x8 rootfs baseline

GL.iNet Mango  ("drop device")
├── custom drop firmware (16MB NOR — minimal)
└── /overlay → USB ExtRoot
        ├── tailscale  (drop-<slot>)
        └── /etc/uci-defaults/99-enroll.sh  (one-shot, self-deletes)

Cloudflare edge
├── Worker  (api.<slot>.eplabs.cloud)
├── D1      fleet-database  (devices, audit_log, sessions)
├── KV      (rate_limits, job_queue)
├── R2      artifacts/<job_id>.pcap  (signed URLs)
├── Access  (service tokens for devices, JWT for operator)
└── Tunnel  (cloudflared origin → devcontainer)

Tailscale tailnet
├── ep-<slot>    (devcontainer)
└── drop-<slot>  (Mango)

Course Structure (14 Labs, 2 Days)

Day 1: Foundation (7 hours)

#TopicTime
01Hardware familiarization & recovery45m
02Engagement platform build (devcontainer + Mango)75m
03Overlay deployment, ExtRoot, persistence60m
04Domain & Cloudflare verification15m
05Tailscale mesh60m
06Cloudflare Tunnel from devcontainer45m
07First Worker deployment60m

Day 2: Control Plane + Capstone (7 hours)

#TopicTime
08Cloudflare Access — SSO + service tokens45m
09D1 device registry + audit log60m
10KV + R2 storage75m
11ChatOps with EmojiChef60m
12Drop device deployment (Mango)45m
13Redirector / edge relay60m
14Capstone end-to-end75m

Quick Start Commands

# Build both artifacts (from labs/ directory)
make engagement-platform   # devcontainer rootfs
make drop-mango            # Mango sysupgrade image

# Validate a specific lab
make validate-05           # example: Lab 05

# Open devcontainer in VS Code
# VS Code → "Reopen in Container" (reads .devcontainer/devcontainer.json)

Worker Endpoints

From labs/lab07-first-worker/worker/src/index.js:

EndpointMethodIntroducedDescription
/v1/healthGETLab 07Health check
/v1/devices/enrollPOSTLab 09Register device (D1 INSERT)
/v1/devicesGETLab 09List devices (D1 SELECT)
/v1/commands/<device>POSTLab 10Enqueue command (KV)
/v1/jobs/<id>GETLab 10Read job status (KV)
/v1/jobs/<id>/completePATCHLab 14Report Mango exec result
/v1/artifacts/uploadPOSTLab 10Mint R2 signed PUT URL
/v1/artifacts/<id>GETLab 10Mint R2 signed GET URL
/v1/chatops/discordPOSTLab 11EmojiChef decode → job queue
/relay/*ANYLab 13Malleable C2 relay (Oblique-Relay)

Required Worker bindings: FLEET_DB (D1), RATE_LIMITS (KV), ARTIFACTS (R2), DISCORD_PUBLIC_KEY (secret), DISCORD_WEBHOOK_URL (secret), RELAY_BACKEND (var).

D1 Schema

From labs/lab09-d1-database/schema.sql:

CREATE TABLE IF NOT EXISTS devices (
    device_id          TEXT    NOT NULL PRIMARY KEY,
    device_type        TEXT    NOT NULL,          -- 'mango' | 'engagement-platform'
    tag                TEXT    NOT NULL,          -- e.g. device-mango-<ts>
    tailscale_hostname TEXT    NOT NULL,
    engagement_id      TEXT    NOT NULL DEFAULT 'workshop',
    metadata           TEXT    NOT NULL DEFAULT '{}',
    enrolled_at        TEXT    NOT NULL DEFAULT (datetime('now')),
    last_seen          TEXT    NOT NULL DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS audit_log (
    id          INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    operator_id TEXT,
    device_id   TEXT,
    action      TEXT    NOT NULL,
    details     TEXT    NOT NULL DEFAULT '{}',
    source_ip   TEXT,
    user_agent  TEXT,
    created_at  TEXT    NOT NULL DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS sessions (
    session_id  TEXT    NOT NULL PRIMARY KEY,
    operator_id TEXT    NOT NULL,
    expires     INTEGER NOT NULL,                 -- Unix epoch seconds
    metadata    TEXT    NOT NULL DEFAULT '{}',
    created_at  TEXT    NOT NULL DEFAULT (datetime('now'))
);

Apply with:

wrangler d1 execute fleet-database --file=labs/lab09-d1-database/schema.sql --remote

EmojiChef Quick Recipe

Base codepoint: 0x1F345 (🍅). Bits per emoji: 6. Range: 0x1F345 (🍅) to 0x1F37F (🍿).

Encode: convert each ASCII char to 8-bit binary → concatenate → split into 6-bit chunks → each chunk N maps to String.fromCodePoint(0x1F345 + N).

Decode: reverse of above; trailing partial byte is dropped.

Verified test vector (hand-checked, derivation in labs/lab11-chatops-emojichef/test-vectors.txt):

PlaintextEmojiVerified
HSC🍗🍊🍒🍈Yes — bit derivation shown in test-vectors.txt

Note: The status and reboot vectors in docs/technical_specifications.md do not match the algorithm as implemented. Run the Node snippet in test-vectors.txt to generate correct vectors before using them in tests. Only HSC🍗🍊🍒🍈 has been verified by hand.

Capstone Flow (Lab 14)

Discord emoji → Worker decode → CF Access auth → D1 audit insert
             → KV job enqueue
             → operator reads job → tailscale ssh root@drop-<slot>
             → Mango: tcpdump-mini → pcap to R2 (signed PUT URL)
             → Mango: PATCH /v1/jobs/<id>/complete
             → Worker mints signed GET URL → Discord reply

OPSEC Notes

RiskLab DefaultProduction Hardening
Pre-shared keysSingle shared Tailscale keyPer-device ephemeral + ACL tags
Tokens in firmwarePlaintext in squashfsNo perfect answer; minimize scope
Device identityGL default hostnameRandomized in 99-enroll.sh
Tailnet accessBroad ACLRestrict to callback-only per acl-policy.example.hujson

Key Firmware Facts

ParameterValue
OpenWrt version23.05.3
Targetramips / mt76x8
Mango profileglinet_gl-mt300n-v2
Devcontainer baseopenwrt/rootfs:ramips-mt76x8-23.05.3
ImageBuilder imageopenwrt/imagebuilder:ramips-mt76x8-23.05.3
Mango NOR flash16MB (squashfs target <= 13MB)
Mango RAM128MB
Serial console115200 8N1 3.3V