Runtime & Internals¶
Architecture¶
Every command that touches Docker runs as user sre (uid 1100): sre-wrapper exports USER_USERNAME and calls sudo sre --user; the CLI then drops from root to sre_uid. How the drop happens depends on the project:
Non-privileged projects (no machine has
privileged=True): privileges are dropped permanently at start viasetuid(sre_uid)/setgid(docker_gid)— the process can never regain root for the rest of its life.Privileged projects (at least one machine has
privileged=True): privileges are dropped temporarily viaseteuid/setegidonly. Kathara requires the deploy/connect/exec paths to run as genuine root (to mount cgroups, attach a TTY to/sbin/init, etc.), so the CLI raises the effective uid back to 0 around those operations and lowers it again afterwards. Host-side subprocesses still drop tosre_uidin the child viapreexec_drop_to_sre(), so user code on the host never runs as root.
Filesystem layout¶
Path |
Description |
|---|---|
|
Installation root |
|
CLI binary |
|
C binary for student use (handles sudo) |
|
GUI launcher script |
|
Lab files |
|
Shared libraries for project files ( |
|
SVG icons and logos |
|
Compiled gettext translations for the CLI |
|
Runtime state root ( |
|
Exam configuration (absent = no exam) |
|
One directory per running lab instance |
|
Default evaluation archive directory |
|
Self-grade cooldown timestamps |
|
Shared directories for projects with |
Running lab directory¶
/var/lib/sre/projects/{timestamp}@@@{lab_name}@@@{username}/
├── .private/ mode 0o700
│ ├── data.json serialized Data instance, mode 0o600
│ ├── srelab symlink to the lab's srelab.py
│ ├── eval_in_progress lock file (PID) during active eval
│ └── auto_eval.log one ISO timestamp per student self-evaluation
├── info.json public machine/question metadata (InfoLab)
├── scheme.svg graphviz network diagram
├── answers/
│ ├── answers.json student answers (updated by GUI)
│ └── cheat.json instructor-provided answers (if any)
└── shared/ mode 0o777 (only if shared_path=True)
Configuration¶
Constants (src/SRE/params.py)¶
Constant |
Value |
Description |
|---|---|---|
|
|
UID of the |
|
|
GID of the |
|
|
Max parallel Docker API calls during eval |
|
|
Default auto-eval period (s) |
|
|
Default exam duration (min), used if |
|
|
|
|
|
Window before |
|
|
Extract last GECOS sub-field as student email |
|
|
External terminal emulator |
|
|
Default terminal font size (pt) |
|
|
Default terminal colors |
|
|
Name substrings that hide labs from |
|
|
Allowed lab source directories |
|
|
Keys used in |
Environment variables¶
GUI / wrapper → sre CLI¶
Variable |
Used by |
Description |
|---|---|---|
|
sre CLI (with |
Student login name (set by |
|
sre CLI, GUI, tests |
Override |
|
params.py |
Override the sre-wrapper path |
|
sre CLI |
Loads French help strings when set to a French locale |
sre CLI → containers¶
Injected into machines at deploy / exec / state-transition time.
Variable |
Description |
|---|---|
|
Running lab name ( |
|
X authority cookie forwarded into privileged containers so GUI apps can reach the host X server — defined by |
|
Host IP reachable from containers (default |
Locale¶
The CLI uses gettext (locale/fr/LC_MESSAGES/sre.mo); the GUI uses Qt .qm catalogs (translations/sysreseval_fr.qm). Both are regenerated by make translations.
Dynamic module loading¶
Each lab is loaded at runtime via importlib.util.spec_from_file_location('srelab', ...). The module path must sit under params.authorized_src_dir (/opt/sre/lab/ or /home/etudiant/), and /opt/sre/lib/ is prepended to sys.path so labs can from ips import …, from net_config import …, etc.
Data serialization¶
Data0 encodes to JSON/msgpack with a polymorphic envelope:
{
"__type__": "srelab.Data",
"data": {
"secret": "changeme",
"ips": {"router": "10.0.0.1/24"},
"nets": {"lan1": "10.0.0.0/24"}
}
}
Value |
Encoding |
|---|---|
|
|
|
|
|
|
|
|
Nested |
|
data.json is saved mode 0o600 inside .private/ (mode 0o700).
Test execution¶
Grade.run_tests() per step:
Call
grade()to register tests (no execution).Group commands per machine and encode them into one env var
EXETESTS@@@{timeout}:{cmd}@@@{timeout}:{cmd}@@@….Run
exetests.pyinside each container; outputs are separated by a per-run UUID.Parse outputs into
self._tests[(machine, step)][(command, timeout)]; repeat untilstep > max_step; callgrade()a final time to compute grades.
Up to 16 machines tested concurrently (ThreadPoolExecutor). Exit codes: 0–255 actual, -1 = timeout.
Progress reporting¶
sre start emits JSON lines to stderr so the GUI can render progress:
{"phase": "pull", "status": "start", "image": "sre/base:1.10"}
{"phase": "pull", "status": "progress", "image": "sre/base:1.10", "percent": 42}
{"phase": "pull", "status": "end", "image": "sre/base:1.10"}
{"phase": "deploy", "status": "start", "total": 3}
{"phase": "deploy", "status": "progress", "current": 1, "total": 3}
{"phase": "deploy", "status": "end", "total": 3}
Security model¶
Concern |
Mechanism |
|---|---|
Allowed UIDs |
|
Privilege drop |
Root calls |
Lab path whitelist |
|
Eval lock |
Lock file opened with |
Host commands drop privileges |
|
Shell injection |
|
|
Mode |
Unknown |
|
Single GUI instance |
PID file at |
Student command restrictions |
|
State access control |
|
Archive format¶
Every evaluation writes a zstandard-compressed msgpack file with a .zst extension to params.archive_dirs (default ['/var/lib/sre/archives']; labs may extend the list via a module-level archive_dirs attribute).
Filename: {eval_date}_{running_lab_name}.zst — eval_date is YYYYmmddHHMMSS, running_lab_name is {timestamp}@@@{lab_name}@@@{username}. The leading timestamp sorts chronologically with ls.
archive = {
"running_lab_name": "{timestamp}@@@{lab_name}@@@{username}",
"eval_date": "20260615110042",
"data_json": "<JSON string>", # serialized Data
"tests": {
("router", 1): {("ping -c1 8.8.8.8", 5): ("64 bytes ...\n", 0), ...},
...
},
"errors": ["error message", ...],
"answers": {
"<question_hash>": "<student answer>",
"hostname": "pc-101",
"login": "alice",
"fullname": "Alice Martin", # from GECOS, always present
"email": "alice@example.com", # when email_in_gecos_last_field=True
"language": "fr",
"answers_updated_at": "20260615110000",
"auto_eval_count": 2, # self-evals before this one
"exam_mode": true, # exam-mode only
"exam_started_at": "2026-06-15T09:00", # exam-mode only
"exam_duration": 120, # exam-mode only
"exam_time_remaining": 3600, # exam-mode only
},
"grade_list": [
{"title": "Connectivity", "max_grade": 4, "grade": 4,
"grade_letter": null, "description": ""},
...
],
"total_grade": 14.0,
"total_max": 20.0,
}
Archives are written by sre eval, eval-all, eval-exam, end-exam, re-eval, and consumed by sre cat, sheet, outline, check-eval, watch — see CLI Reference.