You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The frontend-proxy (Caddy) now supports three TLS modes controlled by
tls.mode in settings.yaml:
- off: plain HTTP on a single port (previous behavior)
- internal: self-signed certificate with HTTPS + plain HTTP side-by-side
- acme: Let's Encrypt certificate via ACME with automatic renewal
The Caddyfile is rendered at container start from a Jinja2 template
(Caddyfile.prod.j2) by render_caddyfile.py based on environment
variables. Shared routing directives live in Caddyfile.routes, imported
by all prod configurations.
New settings: tls.mode, tls.domain (required when TLS is enabled),
tls.redirect_http_to_https, and ports.https_host_port. New installs
default to internal mode on ports 8080/8443. Existing configs are
backfilled to off mode preserving current port assignments.
A caddy-data Docker volume persists certificate state across restarts.
Copy file name to clipboardExpand all lines: README.md
+62-50Lines changed: 62 additions & 50 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,6 +4,59 @@ The framework consists of multiple components that have to be built the first ti
4
4
5
5
The following describes how to build REF, how to run it, and how to upgrade it. To learn more about creating new exercises, head to [exercises.md](./EXERCISES.md).
6
6
7
+
### Configuration
8
+
9
+
All configuration lives in `settings.yaml`, the single source of truth. On first use, `./ctrl.sh build` auto-runs `./prepare.py` which generates `settings.yaml` with cryptographically random secrets and renders two downstream artifacts from it:
10
+
11
+
***`settings.env`** — environment variables consumed by docker-compose.
12
+
***`docker-compose.yml`** — rendered from `docker-compose.template.yml`.
13
+
14
+
To inspect or edit `settings.yaml` before the first build, run `./prepare.py` manually. After editing an existing `settings.yaml`, re-run `./prepare.py` to propagate changes to the downstream files. The script backfills new fields and prunes obsolete ones automatically, so it is safe to re-run on upgrades. Use `./prepare.py --fresh` to regenerate everything from scratch (the old `settings.yaml` is backed up first).
15
+
16
+
#### Settings reference
17
+
18
+
| Section | Key | Default | Description |
19
+
|---------|-----|---------|-------------|
20
+
|`ports`|`ssh_host_port`| 2222 | SSH reverse-proxy listen port |
21
+
||`http_host_port`| 8080 | HTTP port (plain or redirect) |
22
+
||`https_host_port`| 8443 | HTTPS port |
23
+
|`tls`|`mode`|`internal`|`off` (plain HTTP), `internal` (self-signed), or `acme` (Let's Encrypt) |
24
+
||`domain`| — | Required for `internal` and `acme` modes |
25
+
||`redirect_http_to_https`|`false`| Redirect HTTP port to HTTPS (`internal` and `acme` modes only) |
26
+
|`paths`|`data`|`./data`| Persistent data directory on the host |
|`runtime`|`binfmt_support`|`false`| Enable multi-architecture container support |
29
+
|`admin`|`password`|*(random)*| Admin password (username is `0`); printed on first run |
30
+
||`ssh_key`| — | Optional SSH public key for the admin account |
31
+
32
+
Secrets (`secrets` section) are auto-generated and should not normally be edited. They include the Flask session key, the HMAC key shared between the SSH proxy and the web API, and the PostgreSQL password.
33
+
34
+
#### TLS modes
35
+
36
+
The `tls.mode` setting controls how the frontend-proxy serves traffic:
37
+
38
+
| Mode | Ports | Behavior |
39
+
|------|-------|----------|
40
+
|`off`|`http_host_port` only | Plain HTTP on a single port. No TLS. |
41
+
|`internal`|`http_host_port` + `https_host_port`| Self-signed TLS certificate (generated by Caddy). HTTPS on `https_host_port`, plain HTTP on `http_host_port`. Both serve the full site independently by default. Set `redirect_http_to_https: true` to redirect HTTP to HTTPS instead. Accessible by domain and by IP. |
42
+
|`acme`|`http_host_port` + `https_host_port`| Let's Encrypt certificate via ACME. HTTP automatically redirects to HTTPS. Requires `domain` to resolve to the server and host ports 80 + 443 to be publicly reachable. |
43
+
44
+
After changing `tls.mode` or `tls.domain`, run `./prepare.py && ./ctrl.sh build && ./ctrl.sh restart`.
45
+
46
+
#### Web entry points
47
+
48
+
All HTTP traffic is served through a single Caddy reverse proxy
49
+
(`frontend-proxy/`). Students reach `/spa/register`; admins reach
50
+
`/admin/` (redirects to the exercise view). SSH connections go through
51
+
the SSH reverse proxy on the configured SSH port.
52
+
53
+
In production the Vue SPA is baked into the `frontend-proxy` image as a
54
+
static bundle — rebuild with `./ctrl.sh build` after any SPA change.
55
+
56
+
#### Hot reloading (development only)
57
+
58
+
`./ctrl.sh up --hot-reloading` starts an extra Vite dev server for the SPA and enables Flask auto-reload. Do **not** use this on a publicly reachable host — Vite's dev server is not hardened (see `docs/ARCHITECTURE.md` for details).
59
+
7
60
### Building REF
8
61
The build process is split into two parts. While the first part is mandatory and entails building the framework itself, the second part is only required if you plan to host exercises where ASLR is disabled for setuid binaries.
# Generate configuration (settings.yaml + settings.env), docker-compose.yml, and
20
-
# container SSH keys. This MUST be run once before the first ./ctrl.sh build.
21
-
# All secrets are generated with a cryptographically secure RNG. The generated
22
-
# settings.yaml file is the canonical configuration; settings.env is rendered
23
-
# from it for docker-compose. The admin password is printed at the end of the
24
-
# run — note it down, it can also be found in settings.yaml.
25
-
#
26
-
# prepare.py refuses to run if settings.yaml already exists, so existing
27
-
# secrets are never silently overwritten.
28
-
./prepare.py
29
-
30
-
# Build all images. This command will check if your system meets the requirements
31
-
# and will print error messages in case something is not working as expected.
72
+
# Optional: run prepare.py manually to inspect or edit settings.yaml
73
+
# before building. If skipped, ctrl.sh build auto-runs it on first use
74
+
# with secure random defaults.
75
+
# ./prepare.py
76
+
32
77
./ctrl.sh build
33
78
```
34
79
@@ -44,39 +89,6 @@ After successfully building REF, the database has to be initialized:
44
89
./ctrl.sh down
45
90
```
46
91
47
-
### Single-port web interface
48
-
49
-
The Flask web app (`/`, `/admin/*`, `/api/*`), the Vue student SPA
50
-
(`/spa/*`), and Flask's static assets (`/static/*`) are all fronted by
51
-
a single Caddy reverse proxy (`frontend-proxy/`) on host port 8000.
52
-
Students go to `http://<host>:8000/spa/register` (or whichever landing
53
-
page is configured); admins go to `http://<host>:8000/admin/` (which
54
-
redirects to the exercise view). Student SSH connections go through the
55
-
SSH reverse proxy on port 2222.
56
-
57
-
In production, `frontend-proxy` serves the Vue SPA as a static bundle
58
-
that is baked into the image at `docker build` time via a multi-stage
59
-
Dockerfile — rebuild with `./ctrl.sh build` after any change under
60
-
`spa-frontend/`.
61
-
62
-
### Hot reloading (local development only)
63
-
64
-
Use `./ctrl.sh up --hot-reloading` during local development to get
65
-
Flask auto-reload **and** Vite HMR for the SPA. This flag starts an
66
-
extra `spa-frontend` container (running `vite dev`) which is gated
67
-
behind the `dev` compose profile; Caddy then reverse-proxies `/spa/*`
68
-
(including the HMR websocket) to that container.
69
-
70
-
> ⚠️ **Do not run `--hot-reloading` on a publicly reachable host.**
71
-
> Vite's dev server is not designed for hostile clients — it serves
72
-
> raw source, exposes `/@fs/`, and has had several path-traversal CVEs
73
-
> in recent releases. While `--hot-reloading` is active, the SPA
74
-
> renders a yellow/black hazard-striped warning strip at the top of
75
-
> every page as a visible reminder; the whole warning is tree-shaken
76
-
> out of the production `vite build`, so nothing about it leaks into
77
-
> the prod bundle. See `docs/ARCHITECTURE.md` for the full model.
78
-
79
-
80
92
#### Build the custom Linux kernel
81
93
Building the custom Linux kernel is only required if you need the `no-randomize` attribute for some exercises. This attribute allows you to disable ASLR for a specific binary, even if it is a setuid binary. This is not allowed for unmodified kernels. The following assumes that your system is based on Debian and uses GRUB as a bootloader. For other systems or bootloaders, the instructions have to be adapted accordingly.
82
94
@@ -183,18 +195,18 @@ In case the update fails, remove the `data` directory and move the backup to `da
183
195
After starting the application, the following services are running on the host:
184
196
185
197
#### SSH Entry-Server
186
-
This services is the entry server for all SSH connections to the exercises. Based on the clients user name and the public key, incoming SSH connection are forwarded to a container of the respective exercise.
198
+
The entry server for all SSH connections to the exercises. Based on the client's username and public key, incoming SSH connections are forwarded to a container of the respective exercise.
187
199
188
200
```
189
201
Hostname: ssh-reverse-proxy
190
-
Port: 2222
202
+
Port: See settings.yaml (ports.ssh_host_port, default 2222)
191
203
```
192
204
193
205
#### Web Interface
194
-
The web interface for managing exercises and users. Students also use it to register. All HTTP traffic is served through the `frontend-proxy` (Caddy) on port 8000, which reverse-proxies to the `web` (Flask) service internally.
206
+
The web interface for managing exercises and users. Students also use it to register. All HTTP traffic is served through the `frontend-proxy` (Caddy), which reverse-proxies to the `web` (Flask) service internally.
195
207
```
196
208
Hostname: frontend-proxy (host-facing), web (internal Flask app)
197
-
Port: 8000
209
+
Port: See settings.yaml (ports.http_host_port / ports.https_host_port)
to the Flask `web` container on the internal `web-host` network.
30
30
31
-
The `ssh-reverse-proxy`still calls `http://web:8000` over the internal
31
+
The `ssh-reverse-proxy` calls `http://web:8000` over the internal
32
32
`web-and-ssh` network and does **not** go through Caddy.
33
33
34
34
## Components
@@ -116,22 +116,39 @@ Isolated Docker container per student/exercise based on Ubuntu 24.04.
116
116
117
117
### 2b. Frontend Proxy (`frontend-proxy/`)
118
118
119
-
Caddy-based reverse proxy container that terminates host port 8000 and fans
120
-
out to the Flask webapp and the Vue SPA. Built from a multi-stage
121
-
Dockerfile that compiles the SPA bundle (stage 1: `node:22-alpine`,
122
-
`npm run build`) and copies it into a `caddy:2-alpine` runtime image
123
-
(stage 2). At container start, `entrypoint.sh` picks `Caddyfile.dev`
124
-
(reverse-proxies `/spa/*` to `spa-frontend:5173` for HMR) or
125
-
`Caddyfile.prod` (serves the baked `/srv/spa-dist` with SPA history-mode
126
-
fallback) based on `$HOT_RELOADING`.
119
+
Caddy-based reverse proxy container that serves the web interface over
120
+
HTTP and/or HTTPS depending on `tls.mode` in `settings.yaml`. Built from
121
+
a multi-stage Dockerfile that compiles the SPA bundle (stage 1:
122
+
`node:22-alpine`, `npm run build`) and copies it into a
123
+
`caddy:2.8-alpine` runtime image (stage 2) along with Python 3 and
124
+
Jinja2.
127
125
128
-
**Stack:** Caddy 2 + multi-stage Node builder
126
+
At container start, `entrypoint.sh` either uses `Caddyfile.dev` (for
127
+
`--hot-reloading`) or calls `render_caddyfile.py` which renders
128
+
`Caddyfile.prod.j2` into a Caddyfile based on `TLS_MODE`, `DOMAIN`, and
129
+
`HTTPS_HOST_PORT` environment variables passed from docker-compose.
130
+
131
+
**TLS modes** (set via `tls.mode` in `settings.yaml`):
132
+
133
+
| Mode | Container ports | Description |
134
+
|------|-----------------|-------------|
135
+
|`off`|`:8000` (HTTP) | Plain HTTP, no TLS. |
136
+
|`internal`|`:8443` (HTTPS) + `:8080` (HTTP) | Self-signed certificate generated by Caddy. Both ports serve the full site independently; the HTTP port does not redirect to HTTPS by default. Accessible by domain name and by IP address. |
137
+
|`acme`|`:443` (HTTPS) + `:80` (HTTP) | Let's Encrypt certificate via ACME. Caddy handles provisioning and renewal automatically. HTTP redirects to HTTPS. |
138
+
139
+
A `caddy-data` Docker volume persists Caddy's certificate storage across
140
+
container restarts (essential for `acme` mode to avoid hitting Let's
0 commit comments