Engagement Platform Labs — Quick Reference
Hardware Kit (Per Student)
| Role | Item | Notes |
|---|---|---|
| Engagement platform | Laptop running VS Code devcontainer | OpenWrt 23.05.3 rootfs; full stack |
| Drop device | GL.iNet Mango (GL-MT300N-V2) — ~$29 | 16MB NOR, ExtRoot required for Tailscale |
| Storage | USB flash drive (16GB+) — ~$8 | ExtRoot overlay for the Mango |
| Operator client | Same laptop | Discord, 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)
| # | Topic | Time |
|---|---|---|
| 01 | Hardware familiarization & recovery | 45m |
| 02 | Engagement platform build (devcontainer + Mango) | 75m |
| 03 | Overlay deployment, ExtRoot, persistence | 60m |
| 04 | Domain & Cloudflare verification | 15m |
| 05 | Tailscale mesh | 60m |
| 06 | Cloudflare Tunnel from devcontainer | 45m |
| 07 | First Worker deployment | 60m |
Day 2: Control Plane + Capstone (7 hours)
| # | Topic | Time |
|---|---|---|
| 08 | Cloudflare Access — SSO + service tokens | 45m |
| 09 | D1 device registry + audit log | 60m |
| 10 | KV + R2 storage | 75m |
| 11 | ChatOps with EmojiChef | 60m |
| 12 | Drop device deployment (Mango) | 45m |
| 13 | Redirector / edge relay | 60m |
| 14 | Capstone end-to-end | 75m |
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:
| Endpoint | Method | Introduced | Description |
|---|---|---|---|
/v1/health | GET | Lab 07 | Health check |
/v1/devices/enroll | POST | Lab 09 | Register device (D1 INSERT) |
/v1/devices | GET | Lab 09 | List devices (D1 SELECT) |
/v1/commands/<device> | POST | Lab 10 | Enqueue command (KV) |
/v1/jobs/<id> | GET | Lab 10 | Read job status (KV) |
/v1/jobs/<id>/complete | PATCH | Lab 14 | Report Mango exec result |
/v1/artifacts/upload | POST | Lab 10 | Mint R2 signed PUT URL |
/v1/artifacts/<id> | GET | Lab 10 | Mint R2 signed GET URL |
/v1/chatops/discord | POST | Lab 11 | EmojiChef decode → job queue |
/relay/* | ANY | Lab 13 | Malleable 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):
| Plaintext | Emoji | Verified |
|---|---|---|
HSC | 🍗🍊🍒🍈 | Yes — bit derivation shown in test-vectors.txt |
Note: The
statusandrebootvectors indocs/technical_specifications.mddo not match the algorithm as implemented. Run the Node snippet intest-vectors.txtto generate correct vectors before using them in tests. OnlyHSC→🍗🍊🍒🍈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
| Risk | Lab Default | Production Hardening |
|---|---|---|
| Pre-shared keys | Single shared Tailscale key | Per-device ephemeral + ACL tags |
| Tokens in firmware | Plaintext in squashfs | No perfect answer; minimize scope |
| Device identity | GL default hostname | Randomized in 99-enroll.sh |
| Tailnet access | Broad ACL | Restrict to callback-only per acl-policy.example.hujson |
Key Firmware Facts
| Parameter | Value |
|---|---|
| OpenWrt version | 23.05.3 |
| Target | ramips / mt76x8 |
| Mango profile | glinet_gl-mt300n-v2 |
| Devcontainer base | openwrt/rootfs:ramips-mt76x8-23.05.3 |
| ImageBuilder image | openwrt/imagebuilder:ramips-mt76x8-23.05.3 |
| Mango NOR flash | 16MB (squashfs target <= 13MB) |
| Mango RAM | 128MB |
| Serial console | 115200 8N1 3.3V |