This repository is the standalone home for the SCL Network Topology Creator plugin.
This branch (
federated_module_testing_framework) extends the plugin to build SLIPS federation test networks. A node's image + config are composed from its profile (slips_variant →federation_network-slips; services →federation_network-service; else the base image). The UI/API bind to127.0.0.1:9002only. Build the images with./build-images.sh.
It provides a local control plane for designing and running generated StratoCyberLab network topologies. Each topology can define routed networks, router hierarchies, Ubuntu hosts, host roles, local users, optional generated data, internet access per network, router firewall rules, and SSH access on selected hosts.
It also lets you place the hackerlab container onto one selected network so you can start the lab from that segment.
Routers can be chained in a parent-child tree, and each network can be attached to several routers while choosing one of them as the default gateway.
Firewall rules are edited in a clickable graph instead of a long checkbox list.
When a topology with SLIPS sensors is started, each peer's runtime logs
(/var/log/slips, /var/log/slips_output) are bind-mounted to the host in
real time — nothing else from the container is mounted. The host path is
configurable per device (no hardcoded server path):
<EXPERIMENTS_ROOT>/<experiment>/<peer>/slips
<EXPERIMENTS_ROOT>/<experiment>/<peer>/slips_output
EXPERIMENTS_ROOT— host base dir (default/var/lib/scl-experiments). Set it on the control plane env when deploying.<experiment>— thelog_namepassed by the Runner plugin (<id>/startbody) or, when started without one, the topology name.<peer>— the SLIPS peer hostname (slips-1,slips-2, …).
Start the control plane with a custom base, e.g.:
EXPERIMENTS_ROOT=/data/scl-experiments docker compose up -d --buildThe Runner plugin reads these live logs for result collection (and also mounts
EXPERIMENTS_ROOT, so no end-of-run docker cp is needed).
A node with Type = Static attacker (or profile.role = "attacker") is a
static attacker device. Its image federation_network-attacker is a very
basic Ubuntu + nmap defined inside this plugin (attacker/), and runs
nmap -sS -A + brute-forcing over its network subnet every 5 minutes — the
deterministic part of the attack. The Aracne pivot (attacker_pivot) does the
LLM-driven part. Select one and place it on a network to scan it. The image is
built by the topology plugin's "Build images" (it lives in this repo, not in a
mounted build source).
Hermetic unit suites (no Docker): python3 -m pytest tests/test_backend.py tests/test_server.py tests/test_ui.py.
Validate the baked images build + run:
docker build -f attacker/Dockerfile -t federation_network-attacker:latest attacker/ (the static attacker, in this repo),
and the federation images from the mounted build source. Full runbook: the Experiment Runner plugin's docs/TESTING.md.
metadata.jsondescribes the plugin for SCL plugin discovery.docker-compose.ymlstarts the plugin control plane.Dockerfilebuilds the control-plane container.app.pyserves the UI and implements topology storage/start/stop.
Generated topologies use one or more router containers. Root routers connect to playground-net, child routers connect to their parent through transit networks, and assigned networks hang off the router you choose. Network segments are configured as Docker bridge networks with deterministic 10.77.<n>.0/24 subnets. Hosts are Ubuntu containers with role labels and startup scripts.
The first version intentionally starts with Ubuntu-only hosts. Service roles prepare directories, users, and role-specific files; lightweight package-backed services are attempted when a segment has internet access.
If SSH is enabled for a host, the generated container creates the specified SSH user and starts sshd.
If you select a hackerlab network, the plugin adds the scl-hackerlab container to that network with a deterministic .2 address.
The first topology start builds a shared Ubuntu runtime image. The build uses host networking first so package installation also works on Docker hosts whose bridge network has no outbound route, then retries with Docker's default build network for compatibility. Later topology starts reuse the local image.
Clone this repository into the plugins directory of an existing StratoCyberLab checkout:
cd /path/to/stratocyberlab
git clone https://github.com/<github-owner>/SCL-Network-Topology-Creator.git plugins/network-topology
docker compose up -d --build control-planeOpen the plugin UI from the SCL Plugins section, select Network Topology Builder, and press Start.
Replace <github-owner> with the GitHub account or organization where this repository is published.
If you add or remove plugins under ./plugins, restart the SCL dashboard so it rescans plugin metadata. For ordinary changes inside this repository, restart only the plugin container and refresh the plugin page.
Legacy saved topologies named SSH Lab are removed automatically the next time the plugin UI loads, because they belonged to the old standalone lab example.
The plugin container bundles the Docker CLI (with docker compose) and mounts the Docker socket, so it both serves the UI/API and deploys the generated topologies — no separate deploy step. The UI is a pure topology authoring tool: you can start it and create networks right away with nothing pre-built.
docker compose up -d --build
docker compose ps # control-plane "Up"- On the same machine (localhost): open
http://127.0.0.1:9002 - From a remote computer, tunnel over SSH and open the URL on your machine:
ssh -N -L 9002:127.0.0.1:9002 user@server # then open http://127.0.0.1:9002 on your laptop - The port is bound to
127.0.0.1only — see Remote access via SSH tunnel below for details.
The federation_network-slips / federation_network-service images are required only when you Start a topology that has slips/service nodes. Build them:
- From the UI: press Build images (mount the SLIPS/runner project into the container first):
FEDERATION_BUILD_SOURCES=/path/to/thesis_project docker compose up -d --build # then click "Build images" in the UI (or POST /api/images/build) - Or manually:
./build-images.sh /path/to/thesis_project
The topologies (networks, nodes, profiles, firewall) need no images — you can design + save them before/without any build.
In the UI: New starts with a single Federation network containing 3 SLIPS peers (weak/middle/strong) plus the FTP / Web / SNMP service devices. Set each node's profile (slips variant, services, connections, internal, attacker pivot), set Repeats to scale a node into N identical devices, Save, then deploy via the API (Start/Stop are API-only — runner-driven). The same work is available headlessly through the HTTP API section next.
Node behavior notes:
- Services
snmpandwebare mutually exclusive on a node (both would fight over port 8000). - Connections are authored per node as a list of
{ type, target, interval }: pick a connection type from the dropdown, optionally a target device (hostname — stable, unlike IPs), and how often (cron). Each added connection appears as an editable row. - Set Repeats on a node (instead of duplicating) and the plugin replicates it
Ntimes internally with fresh ids/names/IPs.
The two distinct web pages are baked into the runtime images from the runner project (they are not re-generated by the plugin):
- Service web (
ubuntu-2, port 8000):build-images.shbuilds the service image, whoseservice/DockerfilerunsCOPY snmp/www /www; the service entrypoint serves/wwwwithpython3 -m http.server 8000. Source:snmp/www/index.html(Acme Network — Device Management). - Slips web (
slips-1withRUN_WEB, port 8000): the slips imageCOPY slips/slips_site /var/www/slips_site; the slips entrypoint starts Apache on :8000 whenRUN_WEB=1. Source:slips/slips_site/index.html(Acme Devices — Internal Portal).
The test_served_webpages_identical_to_og test in tests/test_deploy_long.py diffs both against the runner sources byte-for-byte.
Author a node once and set Repeats to clone it in the plugin (no UI duplication). generate_compose/expand_repeats create one Docker service per replica with unique id/name/IP, include every replica in SLIPS_PEERS, bake each replica's cron, and never mark replicas as the attacker pivot. Covered by the test_repeats_* tests.
Connection types live in connections.json next to app.py — add a new external/internal connection there without touching code:
{
"my_conn": {
"label": "My connection (external)",
"scope": "external",
"interval": "*/7 * * * *",
"command": "myscript.sh"
},
"my_internal": {
"label": "My internal check",
"scope": "internal",
"interval": "* * * * *",
"command": "check-thing.sh {target}",
"target_role": "ftp"
}
}scopeisexternal(fixed command) orinternal(resolves{target}to the chosen device; falls back to the first host runningtarget_role).intervalis the default cron schedule; a per-node connection can override it.- The bundled registry ships
wikipedia(opens a random Wikipedia page) /imagesexternal connections andftp_check/web_internalinternal ones.
The web UI is a thin client over a REST API. You can drive the whole workflow headlessly (no browser) with curl or any HTTP client, which is what experiment runners and automation use.
Base URL (local machine): http://127.0.0.1:9002
| Method | Path | Description |
|---|---|---|
GET |
/api/topologies |
List saved topologies (id, name, #networks, #hosts, running flag) |
POST |
/api/topologies |
Create / update a topology from a JSON body. Validates and regenerates docker-compose.yml. |
GET |
/api/topologies/<id> |
Fetch one topology's full JSON + running flag |
POST |
/api/topologies/<id>/start |
Deploy the topology (docker-compose up -d). Returns a job_id. |
POST |
/api/topologies/<id>/stop |
Tear it down (docker-compose down). Returns a job_id. |
GET |
/api/images |
Which federation_network-* images exist locally |
POST |
/api/images/build |
Build + tag the federation_network-* images (background job_id) |
GET |
/api/jobs/<job_id> |
Poll status of a background start/stop/build/data job |
POST |
/api/generate-data |
AI-generate host seed data (requires the SCL LLM endpoint) |
start, stop and generate-data run in the background and return a job_id; poll /api/jobs/<job_id> until status == "completed" (built into the UI — automation should do the same).
Minimal headless example — create a topology, deploy it, poll, tear down:
# Create / save (server validates and writes docker-compose.yml)
curl -s -X POST http://127.0.0.1:9002/api/topologies \
-H 'Content-Type: application/json' \
-d '{
"name": "my-lab",
"networks": [
{"id":"net-a","name":"Office","cidr":"10.77.1.0/24",
"hosts":[{"id":"h1","name":"web","type":"web-server"}]}
],
"routers":[{"id":"r1","name":"core"}]
}'
# → {"topology": {"id": "my-lab-ab12cd", ...}}
# List, then deploy
ID=my-lab-ab12cd
curl -s http://127.0.0.1:9002/api/topologies # find the id
JOB=$(curl -s -X POST http://127.0.0.1:9002/api/topologies/$ID/start -d '{}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["job_id"])')
curl -s http://127.0.0.1:9002/api/jobs/$JOB # poll until completed
# Tear down
curl -s -X POST http://127.0.0.1:9002/api/topologies/$ID/stop -d '{}'The plugin binds its UI and API to 127.0.0.1:9002 only (see docker-compose.yml → ports: "127.0.0.1:9002:9002"). It is never exposed to the LAN or the internet — only to processes on the server itself.
To reach it from a remote computer, open an SSH tunnel to the server, then open your local browser:
# On your laptop
ssh -L 9002:127.0.0.1:9002 user@server
# Then open this on the LAPTOP (all traffic flows over the encrypted SSH session):
open http://127.0.0.1:9002 # or just browse to http://127.0.0.1:9002If you do not need an interactive shell, add -N:
ssh -N -L 9002:127.0.0.1:9002 user@serverDetails / notes:
- The plugin is reachable only by SSH accounts that can log into the server; anything else on the network cannot connect.
curlexamples in the API section work unchanged over a tunnel once the local port is forwarded (use127.0.0.1:9002).- To forward multiple ports (e.g. the UI plus another service), pass several
-Lflags:-L 9002:127.0.0.1:9002 -L 9003:127.0.0.1:9003. - If you already keep an SSH session open (ControlMaster), add
-f -Nto background the tunnel without blocking your shell.
The UI can request AI-generated sample data for selected hosts. The plugin calls the SCL dashboard LLM endpoint through http://dashboard/api/llm/chat, so it uses the model configured in the main SCL assistant.