harden runtime config and docs

This commit is contained in:
root
2026-06-27 20:58:07 +00:00
parent c148c1b36b
commit 991a0f62b4
13 changed files with 147 additions and 62 deletions
+8 -3
View File
@@ -311,9 +311,13 @@ The example contains the filesystem and FastCGI settings:
BIN_DIRECTORY=/var/cache/uce/work BIN_DIRECTORY=/var/cache/uce/work
TMP_UPLOAD_PATH=/var/lib/uce/uploads TMP_UPLOAD_PATH=/var/lib/uce/uploads
SESSION_PATH=/var/lib/uce/sessions SESSION_PATH=/var/lib/uce/sessions
SESSION_COOKIE_SECURE=1
FCGI_SOCKET_PATH=/run/uce/fastcgi.sock FCGI_SOCKET_PATH=/run/uce/fastcgi.sock
FCGI_SOCKET_MODE=0666
FCGI_PORT=9993 FCGI_PORT=9993
CLI_SOCKET_PATH=/run/uce/cli.sock
CLI_SOCKET_MODE=0600
PRECOMPILE_FILES_IN= PRECOMPILE_FILES_IN=
SITE_DIRECTORY=/var/www/html SITE_DIRECTORY=/var/www/html
@@ -328,7 +332,7 @@ For nginx deployments, the most important setting is:
- `FCGI_SOCKET_PATH=/run/uce/fastcgi.sock` - `FCGI_SOCKET_PATH=/run/uce/fastcgi.sock`
That is the Unix socket nginx should use for normal `.uce` requests. That is the Unix socket nginx should use for normal `.uce` requests. `CLI_SOCKET_PATH` is for local admin/test execution through `scripts/uce-cli`; keep `CLI_SOCKET_MODE=0600` unless a trusted Unix group explicitly needs access.
`FCGI_PORT` is optional if nginx is talking to the Unix socket. Leave it set if you also want a TCP FastCGI listener, or remove it if you want the socket to be the only FastCGI entry point. `FCGI_PORT` is optional if nginx is talking to the Unix socket. Leave it set if you also want a TCP FastCGI listener, or remove it if you want the socket to be the only FastCGI entry point.
@@ -350,6 +354,7 @@ Recommended deployment notes:
- keep `HTTP_PORT` bound to localhost only at the firewall or by network policy; nginx should be the public entry point - keep `HTTP_PORT` bound to localhost only at the firewall or by network policy; nginx should be the public entry point
- keep `BIN_DIRECTORY`, `TMP_UPLOAD_PATH`, and `SESSION_PATH` on writable local storage - keep `BIN_DIRECTORY`, `TMP_UPLOAD_PATH`, and `SESSION_PATH` on writable local storage
- use `SESSION_COOKIE_SECURE=1` for HTTPS-only deployments; leave it `0` only for local/plain-HTTP development
- after editing `/etc/uce/settings.cfg`, restart `uce.service` - after editing `/etc/uce/settings.cfg`, restart `uce.service`
### 4. Install and enable the systemd service ### 4. Install and enable the systemd service
@@ -362,8 +367,8 @@ scripts/systemd/manage-uce-service.sh setup
That script: That script:
- installs `scripts/systemd/uce.service` as `/etc/systemd/system/uce.service` - installs `scripts/systemd/uce.service` as `/etc/systemd/system/uce.service`, rewriting the repository-root path in the unit to the checkout you ran it from
- installs `etc/uce/settings.cfg` to `/etc/uce/settings.cfg` if it does not already exist - installs `etc/uce/settings.cfg` to `/etc/uce/settings.cfg` if it does not already exist, likewise rewriting checkout-root paths
- reloads systemd - reloads systemd
- enables the service at boot - enables the service at boot
- starts the runtime immediately - starts the runtime immediately
+33 -24
View File
@@ -11,20 +11,20 @@ A typical deployment has four pieces:
3. `uce.service`, a systemd service that builds/starts/restarts the runtime. 3. `uce.service`, a systemd service that builds/starts/restarts the runtime.
4. nginx or Apache as the public HTTP server. 4. nginx or Apache as the public HTTP server.
Recommended filesystem layout for a source checkout: Recommended filesystem layout for a source checkout (replace paths as needed):
```text ```text
/opt/uce/ UCE repository/runtime root /path/to/uce-root UCE repository/runtime root (for example `/opt/uce` or `/Code/uce.openfu.com/uce`)
/var/www/html/ public web root served by nginx/Apache /var/www/html/ public web root served by nginx/Apache
/etc/uce/settings.cfg runtime configuration /etc/uce/settings.cfg runtime configuration
/run/uce/fastcgi.sock FastCGI socket used by nginx/Apache /run/uce/fastcgi.sock FastCGI socket used by nginx/Apache
/run/uce/cli.sock local CLI/admin/test socket /run/uce/cli.sock local CLI/admin/test socket
/var/cache/uce/work generated source, wasm modules, caches /var/cache/uce/work generated source, wasm modules, caches
/var/lib/uce/uploads multipart upload scratch space /var/lib/uce/uploads multipart upload scratch space
/var/lib/uce/sessions session files /var/lib/uce/sessions session files
``` ```
For packaged installs, the runtime may live under `/usr/lib/uce` instead of `/opt/uce`. Keep the public web root at `/var/www/html` or another normal web-root path, not under the runtime source tree. For packaged installs, the runtime may live under `/usr/lib/uce` instead of your checkout root. Keep the public web root at `/var/www/html` or another normal web-root path, not under the runtime source tree.
## Build requirements ## Build requirements
@@ -62,10 +62,11 @@ apt install -y nginx
apt install -y apache2 apt install -y apache2
``` ```
Build UCE from the repository root: Build UCE from your repository root:
```bash ```bash
cd /opt/uce repo_root=/path/to/uce-root
cd "$repo_root"
bash scripts/build_core_wasm.sh bash scripts/build_core_wasm.sh
bash scripts/build_linux.sh bash scripts/build_linux.sh
``` ```
@@ -104,7 +105,8 @@ The expected directories are:
Install the WASI SDK: Install the WASI SDK:
```bash ```bash
cd /opt/uce repo_root=/path/to/uce-root
cd "$repo_root"
scripts/install_wasi_sdk.sh scripts/install_wasi_sdk.sh
scripts/install_wasi_sdk.sh --check-only scripts/install_wasi_sdk.sh --check-only
``` ```
@@ -163,7 +165,7 @@ systemctl restart uce.service
## Runtime configuration ## Runtime configuration
Create `/etc/uce/settings.cfg` from `etc/uce/settings.cfg` and adjust paths if your runtime is not under `/opt/uce`. Replace any checkout-specific paths such as `WASM_CORE_PATH` with `/opt/uce/bin/wasm/core.wasm` or your actual runtime path. Create `/etc/uce/settings.cfg` from `etc/uce/settings.cfg` and adjust paths to your checkout root. Replace checkout-specific settings such as `WASM_CORE_PATH` with `<UCE_REPO_ROOT>/bin/wasm/core.wasm` or leave it relative to `COMPILER_SYS_PATH` as appropriate.
Minimum useful settings: Minimum useful settings:
@@ -171,9 +173,12 @@ Minimum useful settings:
BIN_DIRECTORY=/var/cache/uce/work BIN_DIRECTORY=/var/cache/uce/work
TMP_UPLOAD_PATH=/var/lib/uce/uploads TMP_UPLOAD_PATH=/var/lib/uce/uploads
SESSION_PATH=/var/lib/uce/sessions SESSION_PATH=/var/lib/uce/sessions
SESSION_COOKIE_SECURE=1
FCGI_SOCKET_PATH=/run/uce/fastcgi.sock FCGI_SOCKET_PATH=/run/uce/fastcgi.sock
FCGI_SOCKET_MODE=0666
CLI_SOCKET_PATH=/run/uce/cli.sock CLI_SOCKET_PATH=/run/uce/cli.sock
CLI_SOCKET_MODE=0600
SITE_DIRECTORY=/var/www/html SITE_DIRECTORY=/var/www/html
HTTP_DOCUMENT_ROOT=/var/www/html HTTP_DOCUMENT_ROOT=/var/www/html
@@ -183,7 +188,7 @@ PROACTIVE_COMPILE_CHECK_INTERVAL=60
WASM_COMPILE_SCRIPT=scripts/compile_wasm_unit WASM_COMPILE_SCRIPT=scripts/compile_wasm_unit
WASM_BACKEND_VERBOSE=0 WASM_BACKEND_VERBOSE=0
WASM_CORE_PATH=/opt/uce/bin/wasm/core.wasm WASM_CORE_PATH=<UCE_REPO_ROOT>/bin/wasm/core.wasm
WASM_MEMORY_LIMIT_BYTES=536870912 WASM_MEMORY_LIMIT_BYTES=536870912
WASM_EPOCH_DEADLINE_TICKS=200 WASM_EPOCH_DEADLINE_TICKS=200
WASM_EPOCH_PERIOD_MS=50 WASM_EPOCH_PERIOD_MS=50
@@ -199,11 +204,13 @@ WS_BROKER_OUTBOUND_TIMEOUT_SECONDS=30
Important settings: Important settings:
- `FCGI_SOCKET_PATH` is the Unix socket used for normal `.uce` requests. Set it explicitly and keep this value and the web-server `fastcgi_pass` path identical. The reference config uses `/run/uce/fastcgi.sock`; if you choose `/run/uce.sock`, use it in both places. - `FCGI_SOCKET_PATH` is the Unix socket used for normal `.uce` requests. Set it explicitly and keep this value and the web-server `fastcgi_pass` path identical. The reference config uses `/run/uce/fastcgi.sock`; if you choose `/run/uce.sock`, use it in both places.
- `CLI_SOCKET_PATH` is a local HTTP-over-Unix socket used by `scripts/uce-cli` and test/admin units. - `CLI_SOCKET_PATH` is a local HTTP-over-Unix socket used by `scripts/uce-cli` and test/admin units. Keep it private (`CLI_SOCKET_MODE=0600`) unless you intentionally delegate admin/test execution to a trusted Unix group (`0660`).
- `FCGI_SOCKET_MODE` and `CLI_SOCKET_MODE` are octal permission modes applied after socket bind. Prefer tightening `FCGI_SOCKET_MODE` to `0660` when nginx/Apache can share a trusted group with the UCE worker.
- `SITE_DIRECTORY` is the public site tree to scan for `.uce` files. Use `/var/www/html` when the web root is outside the runtime tree; relative paths are resolved from the runtime working directory. - `SITE_DIRECTORY` is the public site tree to scan for `.uce` files. Use `/var/www/html` when the web root is outside the runtime tree; relative paths are resolved from the runtime working directory.
- `HTTP_DOCUMENT_ROOT` is the root used by the built-in HTTP/WebSocket listener when it resolves upgrade requests. Set it to the same web root as nginx/Apache. - `HTTP_DOCUMENT_ROOT` is the root used by the built-in HTTP/WebSocket listener when it resolves upgrade requests. Set it to the same web root as nginx/Apache.
- `BIN_DIRECTORY` stores generated C++, wasm artifacts, compile output, and runtime caches. - `BIN_DIRECTORY` stores generated C++, wasm artifacts, compile output, and runtime caches.
- `TMP_UPLOAD_PATH` and `SESSION_PATH` must be writable by the runtime. - `TMP_UPLOAD_PATH` and `SESSION_PATH` must be writable by the runtime.
- `SESSION_COOKIE_SECURE=1` adds the `Secure` attribute to UCE-managed session cookies and should be used for HTTPS-only deployments. Leave it `0` only for local/plain-HTTP development.
- `HTTP_PORT` is the built-in HTTP/WebSocket listener used for WebSocket upgrade traffic and direct local probes. Bind/firewall it for local access only; nginx/Apache should be the public entry point. - `HTTP_PORT` is the built-in HTTP/WebSocket listener used for WebSocket upgrade traffic and direct local probes. Bind/firewall it for local access only; nginx/Apache should be the public entry point.
- `WS_BROKER_OUTBOUND_TIMEOUT_SECONDS` controls how long a forwarded WS message can remain queued in the broker before being dropped (default `30`). Set to `0` to disable the timeout. - `WS_BROKER_OUTBOUND_TIMEOUT_SECONDS` controls how long a forwarded WS message can remain queued in the broker before being dropped (default `30`). Set to `0` to disable the timeout.
- `WASM_COMPILE_SCRIPT` must point to `scripts/compile_wasm_unit` unless you provide an equivalent compiler. Relative paths are resolved from the runtime root/`COMPILER_SYS_PATH`. That script calls `scripts/check_unit_wasm.py` after linking each unit and uses the pinned WASI SDK on every deployment host. - `WASM_COMPILE_SCRIPT` must point to `scripts/compile_wasm_unit` unless you provide an equivalent compiler. Relative paths are resolved from the runtime root/`COMPILER_SYS_PATH`. That script calls `scripts/check_unit_wasm.py` after linking each unit and uses the pinned WASI SDK on every deployment host.
@@ -220,11 +227,12 @@ systemctl restart uce.service
For source-checkout deployments, install the provided service helper: For source-checkout deployments, install the provided service helper:
```bash ```bash
cd /opt/uce repo_root=/path/to/uce-root
cd "$repo_root"
scripts/systemd/manage-uce-service.sh setup scripts/systemd/manage-uce-service.sh setup
``` ```
That helper installs `scripts/systemd/uce.service`, creates runtime directories, enables the service, and starts it. That helper installs `scripts/systemd/uce.service` from your checked-out tree, rewrites the checked-out repository path into the installed unit and first-time config, creates runtime directories, enables the service, and starts it. Use a custom unit only if you need a nonstandard runtime layout.
Useful commands: Useful commands:
@@ -234,7 +242,7 @@ scripts/systemd/manage-uce-service.sh restart
scripts/systemd/manage-uce-service.sh logs 200 scripts/systemd/manage-uce-service.sh logs 200
``` ```
Equivalent manual systemd service for a source checkout: Equivalent manual systemd service for a source checkout (`<UCE_REPO>` = checkout root):
```ini ```ini
[Unit] [Unit]
@@ -244,14 +252,14 @@ Wants=network-online.target
[Service] [Service]
Type=simple Type=simple
WorkingDirectory=/opt/uce WorkingDirectory=<UCE_REPO>
RuntimeDirectory=uce RuntimeDirectory=uce
StateDirectory=uce StateDirectory=uce
CacheDirectory=uce CacheDirectory=uce
ExecStartPre=/usr/bin/mkdir -p /var/cache/uce/work /var/lib/uce/uploads /var/lib/uce/sessions ExecStartPre=/usr/bin/mkdir -p /var/cache/uce/work /var/lib/uce/uploads /var/lib/uce/sessions
ExecStartPre=/usr/bin/rm -f /run/uce/fastcgi.sock ExecStartPre=/usr/bin/rm -f /run/uce/fastcgi.sock
ExecStartPre=/usr/bin/bash /opt/uce/scripts/build_linux.sh ExecStartPre=/usr/bin/bash <UCE_REPO>/scripts/build_linux.sh
ExecStart=/opt/uce/bin/uce_fastcgi.linux.bin ExecStart=<UCE_REPO>/bin/uce_fastcgi.linux.bin
ExecStopPost=/usr/bin/rm -f /run/uce/fastcgi.sock ExecStopPost=/usr/bin/rm -f /run/uce/fastcgi.sock
Restart=always Restart=always
RestartSec=2 RestartSec=2
@@ -584,7 +592,8 @@ curl -i http://127.0.0.1/examples/uce-starter/ -H 'Host: example.com'
Check the local CLI path: Check the local CLI path:
```bash ```bash
cd /opt/uce repo_root=/path/to/uce-root
cd "$repo_root"
scripts/uce-cli /tests/cli.uce action=echo message=hello scripts/uce-cli /tests/cli.uce action=echo message=hello
scripts/run_cli_tests.sh scripts/run_cli_tests.sh
``` ```
@@ -615,7 +624,7 @@ PY
## Operational footguns ## Operational footguns
- Keep the FastCGI socket path consistent: `FCGI_SOCKET_PATH` and the web-server `fastcgi_pass` must match exactly. The reference config uses `/run/uce/fastcgi.sock`; if you choose `/run/uce.sock`, use it in both places. - Keep the FastCGI socket path consistent: `FCGI_SOCKET_PATH` and the web-server `fastcgi_pass` must match exactly. The reference config uses `/run/uce/fastcgi.sock`; if you choose `/run/uce.sock`, use it in both places.
- Keep the public web root separate from the runtime source tree. The examples use `/opt/uce` for runtime files and `/var/www/html` for public files. - Keep the public web root separate from the runtime source tree. Replace `/opt/uce` with your actual checkout path in local examples (for example `/opt/uce` or `/Code/uce.openfu.com/uce`), and keep public files under `/var/www/html`.
- Set `HTTP_DOCUMENT_ROOT` when the web root is outside the runtime working directory. The built-in HTTP/WebSocket listener resolves upgrade paths from this setting. - Set `HTTP_DOCUMENT_ROOT` when the web root is outside the runtime working directory. The built-in HTTP/WebSocket listener resolves upgrade paths from this setting.
- Do not expose `CLI_SOCKET_PATH` or `HTTP_PORT` as public entry points. The public path should be nginx/Apache. - Do not expose `CLI_SOCKET_PATH` or `HTTP_PORT` as public entry points. The public path should be nginx/Apache.
- Do not trust `Script-Filename` request headers from direct HTTP clients. The built-in HTTP listener resolves from `HTTP_DOCUMENT_ROOT` and rejects `..` path segments. - Do not trust `Script-Filename` request headers from direct HTTP clients. The built-in HTTP listener resolves from `HTTP_DOCUMENT_ROOT` and rejects `..` path segments.
+12 -11
View File
@@ -26,10 +26,10 @@ gets invoked*.
``` ```
┌────────────────────────────┐ ┌────────────────────────────┐
nginx ──FastCGI──► worker pool (N processes) │ /run/uce.sock nginx ──FastCGI──► worker pool (N processes) │ $FCGI_SOCKET_PATH (example `/run/uce/fastcgi.sock`)
(port 80 etc.) │ uniform unit renderers │ (FastCGI + CLI) (port 80 etc.) │ uniform unit renderers │ (FastCGI + CLI)
└─────────────▲──────────────┘ └─────────────▲──────────────┘
│ forward render (FastCGI, uce.sock) │ forward render (FastCGI, FCGI_SOCKET_PATH)
│ │
browser ──raw HTTP / WS──► ┌────────┴─────────┐ browser ──raw HTTP / WS──► ┌────────┴─────────┐
(HTTP_PORT 8080) │ WS broker │ owns HTTP_PORT + every (HTTP_PORT 8080) │ WS broker │ owns HTTP_PORT + every
@@ -48,14 +48,14 @@ gets invoked*.
| Process | Owns | Renders units? | Source | | Process | Owns | Renders units? | Source |
|---|---|---|---| |---|---|---|---|
| **Parent** | nothing; supervises children | no | `main()`, `init_base_process()` | | **Parent** | nothing; supervises children | no | `main()`, `init_base_process()` |
| **Worker** (×`WORKER_COUNT`) | `FCGI_SOCKET_PATH` (`/run/uce.sock`) + `CLI_SOCKET_PATH` | **yes** — the only processes that run wasm | `listen_for_connections()` | | **Worker** (×`WORKER_COUNT`) | `FCGI_SOCKET_PATH` (configured socket path; example `/run/uce/fastcgi.sock`) + `CLI_SOCKET_PATH` | **yes** — the only processes that run wasm | `listen_for_connections()` |
| **WS broker** (×1) | `HTTP_PORT` + every live WS connection + `WS_BROKER_SOCKET_PATH` | no — forwards to the pool | `run_ws_broker()` | | **WS broker** (×1) | `HTTP_PORT` + every live WS connection + `WS_BROKER_SOCKET_PATH` | no — forwards to the pool | `run_ws_broker()` |
| **serve_http dispatcher** (×bind) | one custom-server bind address | no — forwards to the pool | `custom_server_http_dispatcher_loop()` | | **serve_http dispatcher** (×bind) | one custom-server bind address | no — forwards to the pool | `custom_server_http_dispatcher_loop()` |
| **Proactive compiler** | nothing; pre-compiles units | no | `run_proactive_compiler()` | | **Proactive compiler** | nothing; pre-compiles units | no | `run_proactive_compiler()` |
**only workers instantiate Wasmtime and run unit code.** **only workers instantiate Wasmtime and run unit code.**
Every connection-owning process (broker, serve_http dispatcher) forwards the Every connection-owning process (broker, serve_http dispatcher) forwards the
request invocation back to a worker over `/run/uce.sock` using the minimal request invocation back to a worker via `FCGI_SOCKET_PATH` using the minimal
FastCGI client in [`src/lib/fcgi_forward.h`](../src/lib/fcgi_forward.h). This is FastCGI client in [`src/lib/fcgi_forward.h`](../src/lib/fcgi_forward.h). This is
forced by Wasmtime: an `Engine`/`Store` cannot be safely re-created across forced by Wasmtime: an `Engine`/`Store` cannot be safely re-created across
`fork()`, and the brokers fork from the parent that already touched the `fork()`, and the brokers fork from the parent that already touched the
@@ -137,7 +137,7 @@ Each request gets a fresh **workspace** — a per-request wasm instance tree wit
the membrane wired in. `wasm_worker_serve(worker, ctx, entry_unit, handler)` the membrane wired in. `wasm_worker_serve(worker, ctx, entry_unit, handler)`
(`src/wasm/worker.cpp`) is the single entry point for *every* mode: (`src/wasm/worker.cpp`) is the single entry point for *every* mode:
1. Birth a workspace (CoW-snapshot-based where available). 1. Birth a request-scoped workspace with fresh per-request state. Current production workers instantiate a fresh workspace for each request; any future snapshot/CoW optimization must preserve that request-isolation contract.
2. Resolve `entry_unit` + `handler` to an export; components referenced at 2. Resolve `entry_unit` + `handler` to an export; components referenced at
runtime are resolved on demand via the `uce_host_component_resolve` hostcall runtime are resolved on demand via the `uce_host_component_resolve` hostcall
(`component_resolve()` → `__uce_<...>` slot), loading dependency modules (`component_resolve()` → `__uce_<...>` slot), loading dependency modules
@@ -158,8 +158,7 @@ Wasmtime's own trap signals are not escalated into a native fatal signal (see
captures the current `WasmWorkspace*`, but `src/lib/sys.cpp::task()` invokes the captures the current `WasmWorkspace*`, but `src/lib/sys.cpp::task()` invokes the
captured callback only in the forked child, before the hostcall stack unwinds in captured callback only in the forked child, before the hostcall stack unwinds in
that child. The parent request may return and destroy its workspace; the child that child. The parent request may return and destroy its workspace; the child
still has its own copy-on-write stack and its own copy of the per-request wasm is forked from the parent and keeps a private request-context copy of the per-request workspace. This means a delayed task callback can run after the spawning request
workspace. This means a delayed task callback can run after the spawning request
returns without dereferencing the parent's destroyed workspace. It is still a returns without dereferencing the parent's destroyed workspace. It is still a
callback into the inherited child workspace, not a fresh normal request callback into the inherited child workspace, not a fresh normal request
workspace; avoid adding host resources to `WasmWorkspace` that are invalid across workspace; avoid adding host resources to `WasmWorkspace` that are invalid across
@@ -182,7 +181,7 @@ otherwise (page) → serve_via_wasm(entry_unit, "render")
The `UCE_*` params are set by whichever broker forwarded the request: The `UCE_*` params are set by whichever broker forwarded the request:
- **Page render**: FastCGI nginx → `/run/uce.sock` directly; no `UCE_*` flags → `render`. - **Page render**: FastCGI nginx → `FCGI_SOCKET_PATH` directly; no `UCE_*` flags → `render`.
- **CLI**: the CLI socket sets `is_cli`. - **CLI**: the CLI socket sets `is_cli`.
- **serve_http**: the custom-server dispatcher sets `UCE_SERVE_HTTP=1` plus - **serve_http**: the custom-server dispatcher sets `UCE_SERVE_HTTP=1` plus
`UCE_SERVE_HTTP_FUNCTION` and rewrites `SCRIPT_FILENAME` to the configured `UCE_SERVE_HTTP_FUNCTION` and rewrites `SCRIPT_FILENAME` to the configured
@@ -218,7 +217,7 @@ broker loop:
2. The message rides as `UCE_WS_MESSAGE` (base64) with an **empty STDIN body** — 2. The message rides as `UCE_WS_MESSAGE` (base64) with an **empty STDIN body** —
a non-empty STDIN makes the FastCGI transport flush a premature response a non-empty STDIN makes the FastCGI transport flush a premature response
before `on_complete` ever runs. before `on_complete` ever runs.
3. Connect to `/run/uce.sock` (non-blocking) and queue the encoded request in 3. Connect to `FCGI_SOCKET_PATH` (non-blocking) and queue the encoded request in
`ws_broker_outbound[fd]` with an enqueue timestamp. `ws_broker_outbound[fd]` with an enqueue timestamp.
`ws_broker_drain_outbound()` runs after every `process(50)` tick: it finishes `ws_broker_drain_outbound()` runs after every `process(50)` tick: it finishes
@@ -310,8 +309,10 @@ header free-functions are `inline`. The wasm backend exposes only declarations
| Key | Default | Meaning | | Key | Default | Meaning |
|---|---|---| |---|---|---|
| `WASM_BACKEND_VERBOSE` | `0` | Emit `X-UCE-Wasm-*` workspace timing headers (benchmark only). | | `WASM_BACKEND_VERBOSE` | `0` | Emit `X-UCE-Wasm-*` workspace timing headers (benchmark only). |
| `FCGI_SOCKET_PATH` | `/run/uce.sock` | Worker pool FastCGI socket (brokers forward here). | | `FCGI_SOCKET_PATH` | runtime-configured (`/run/uce/fastcgi.sock` in this doc) | Worker pool FastCGI socket (brokers forward here). |
| `CLI_SOCKET_PATH` | `/run/uce/cli.sock` | Worker CLI socket. | | `CLI_SOCKET_PATH` | `/run/uce/cli.sock` | Worker CLI/admin socket. Keep private; reference `CLI_SOCKET_MODE` is `0600`. |
| `FCGI_SOCKET_MODE` | `0666` | Permission mode applied to `FCGI_SOCKET_PATH` after bind; set tighter if nginx/Apache can use a trusted group. |
| `CLI_SOCKET_MODE` | `0600` | Permission mode applied to `CLI_SOCKET_PATH`; set `0660` only for a trusted admin group. |
| `HTTP_PORT` | `8080` | Raw HTTP + WebSocket port — owned by the WS broker. | | `HTTP_PORT` | `8080` | Raw HTTP + WebSocket port — owned by the WS broker. |
| `WS_BROKER_SOCKET_PATH` | `/run/uce/ws-broker.sock` | Broker command socket for `ws_*` flushes. | | `WS_BROKER_SOCKET_PATH` | `/run/uce/ws-broker.sock` | Broker command socket for `ws_*` flushes. |
| `WS_BROKER_OUTBOUND_TIMEOUT_SECONDS` | `30` | Max lifetime in seconds for queued WS broker forwards before drop. | | `WS_BROKER_OUTBOUND_TIMEOUT_SECONDS` | `30` | Max lifetime in seconds for queued WS broker forwards before drop. |
+7 -1
View File
@@ -5,11 +5,15 @@ SESSION_PATH=/var/lib/uce/sessions
# LISTEN ON SOCKETS # LISTEN ON SOCKETS
FCGI_SOCKET_PATH=/run/uce/fastcgi.sock FCGI_SOCKET_PATH=/run/uce/fastcgi.sock
# FastCGI is normally consumed by the local web server user/group.
FCGI_SOCKET_MODE=0666
FCGI_PORT=9993 FCGI_PORT=9993
# HTTP-over-Unix command socket for local CLI/control tooling. # HTTP-over-Unix command socket for local CLI/control tooling.
# Example: curl --unix-socket /run/uce/cli.sock http://localhost/ping # Example: curl --unix-socket /run/uce/cli.sock http://localhost/ping
CLI_SOCKET_PATH=/run/uce/cli.sock CLI_SOCKET_PATH=/run/uce/cli.sock
# Keep the CLI/admin socket private by default; set 0660 only for a trusted group.
CLI_SOCKET_MODE=0600
# Built-in HTTP/WebSocket listener used for WebSocket Upgrade requests. # Built-in HTTP/WebSocket listener used for WebSocket Upgrade requests.
# Keep this behind nginx/Apache on localhost or firewall it from public access. # Keep this behind nginx/Apache on localhost or firewall it from public access.
@@ -34,7 +38,7 @@ WASM_COMPILE_SCRIPT=scripts/compile_wasm_unit
# WASM RUNTIME SETTINGS. Unit execution is always routed through wasm. # WASM RUNTIME SETTINGS. Unit execution is always routed through wasm.
WASM_BACKEND_VERBOSE=0 WASM_BACKEND_VERBOSE=0
WASM_CORE_PATH=/Code/uce.openfu.com/uce/bin/wasm/core.wasm WASM_CORE_PATH=bin/wasm/core.wasm
WASM_MEMORY_LIMIT_BYTES=536870912 WASM_MEMORY_LIMIT_BYTES=536870912
WASM_EPOCH_DEADLINE_TICKS=200 WASM_EPOCH_DEADLINE_TICKS=200
WASM_EPOCH_PERIOD_MS=50 WASM_EPOCH_PERIOD_MS=50
@@ -85,6 +89,8 @@ ARCHIVE_MAX_ZIP_ENTRIES=4096
# LIFETIME OF SESSION COOKIES IN SECONDS # LIFETIME OF SESSION COOKIES IN SECONDS
SESSION_TIME=2592000 SESSION_TIME=2592000
# Set to 1 behind HTTPS-only frontends to add Secure to session cookies.
SESSION_COOKIE_SECURE=0
# DEVELOPER-DEFINED ERROR PAGES (see /doc/index.uce?p=error_pages and # DEVELOPER-DEFINED ERROR PAGES (see /doc/index.uce?p=error_pages and
# /doc/index.uce?p=3_blocked_functions). The unit receives the error details in # /doc/index.uce?p=3_blocked_functions). The unit receives the error details in
+15 -2
View File
@@ -19,10 +19,23 @@ CONFIG_DEST="/etc/uce/settings.cfg"
action="${1:-setup}" action="${1:-setup}"
render_runtime_file() {
local source="$1"
local destination="$2"
local mode="$3"
local tmp
tmp="$(mktemp)"
trap 'rm -f "$tmp"' RETURN
sed "s#/Code/uce.openfu.com/uce#$REPO_ROOT#g" "$source" > "$tmp"
install -D -m "$mode" "$tmp" "$destination"
rm -f "$tmp"
trap - RETURN
}
install_unit() { install_unit() {
install -D -m 0644 "$UNIT_SOURCE" "$UNIT_DEST" render_runtime_file "$UNIT_SOURCE" "$UNIT_DEST" 0644
if [[ ! -f "$CONFIG_DEST" ]]; then if [[ ! -f "$CONFIG_DEST" ]]; then
install -D -m 0644 "$CONFIG_SOURCE" "$CONFIG_DEST" render_runtime_file "$CONFIG_SOURCE" "$CONFIG_DEST" 0644
fi fi
systemctl daemon-reload systemctl daemon-reload
} }
+2 -2
View File
@@ -8,9 +8,9 @@ session_name : the name of the session
>session >session
:content :content
Deletes the cookie specified by `session_name` and clears the data stored under the current session ID. Deletes the cookie specified by `session_name`, removes the matching server-side session file, and clears the data stored under the current session ID.
This also empties `context.session_id` and `context.session`. This also empties `context.session_id`, `context.session_name`, and `context.session`.
:example :example
+1 -1
View File
@@ -11,7 +11,7 @@ return value : the current session ID
:content :content
Starts a session or reconnects to an existing one. Starts a session or reconnects to an existing one.
If the cookie named by `session_name` does not exist, UCE creates it and fills it with a new unique session ID. The function then loads the session data for that ID. If the cookie named by `session_name` does not exist, UCE creates it and fills it with a new unique session ID. The function then loads the session data for that ID. Session cookies are emitted with `Path=/`, `HttpOnly`, `SameSite=Lax`, and `Secure` when `SESSION_COOKIE_SECURE=1` is set in the runtime config.
After `session_start()` completes, the following `context` fields are populated: After `session_start()` completes, the following `context` fields are populated:
+2 -2
View File
@@ -2,7 +2,7 @@
u64 socket_connect(String host, u16 port) u64 socket_connect(String host, u16 port)
:params :params
host : host name host : IPv4 address (for example `127.0.0.1`), as a dotted-quad string
port : port number port : port number
return value : the socket handle return value : the socket handle
@@ -12,7 +12,7 @@ return value : the socket handle
:content :content
Opens a socket connection to the given `host` and `port`. Opens a socket connection to the given `host` and `port`.
The returned socket handle is then used with `socket_read()`, `socket_write()`, and `socket_close()`. The returned socket handle is then used with `socket_read()`, `socket_write()`, and `socket_close()`. Currently this helper accepts IPv4 dotted-quad addresses in socket calls and does not perform hostname resolution.
:example :example
u64 fd = socket_connect("127.0.0.1", 80); u64 fd = socket_connect("127.0.0.1", 80);
+3 -2
View File
@@ -17,8 +17,9 @@ RENDER(Request& context)
String action = first(context.get["action"], "touch"); String action = first(context.get["action"], "touch");
String session_id = session_start("uce-site-tests"); String session_id = session_start("uce-site-tests");
String session_path = context.server->config["SESSION_PATH"] + "/" + session_id;
if(action == "destroy") if(action == "destroy")
session_destroy(); session_destroy("uce-site-tests");
else else
context.session["suite"] = "http"; context.session["suite"] = "http";
@@ -56,7 +57,7 @@ RENDER(Request& context)
check("session_id_create()", generated_session.length() >= 16, generated_session); check("session_id_create()", generated_session.length() >= 16, generated_session);
if(action == "destroy") if(action == "destroy")
check("session_destroy()", session_dump.find("suite") == String::npos, session_dump); check("session_destroy()", session_dump.find("suite") == String::npos && context.session_id == "" && context.session_name == "" && !file_exists(session_path), session_dump + " path=" + session_path);
else else
check("context.session writes", context.session["suite"] == "http", session_dump); check("context.session writes", context.session["suite"] == "http", session_dump);
+1 -1
View File
@@ -2,7 +2,7 @@
// Minimal FastCGI client used by the connection brokers (the custom HTTP server // Minimal FastCGI client used by the connection brokers (the custom HTTP server
// dispatcher and the websocket exec child) to render a request through a normal // dispatcher and the websocket exec child) to render a request through a normal
// worker on /run/uce.sock instead of rendering wasm in the broker's own forked // worker on FCGI_SOCKET_PATH instead of rendering wasm in the broker's own forked
// process. Wasmtime cannot be safely re-created across fork, so the broker owns // process. Wasmtime cannot be safely re-created across fork, so the broker owns
// the connection but forwards the actual unit invocation to a clean-engine // the connection but forwards the actual unit invocation to a clean-engine
// worker — the "broker holds connections, units respond like RENDER()" model. // worker — the "broker holds connections, units respond like RENDER()" model.
+22 -5
View File
@@ -876,7 +876,6 @@ String shell_exec(String cmd)
String shell_escape(String raw) String shell_escape(String raw)
{ {
// FIXME
String result = ""; String result = "";
for(auto c : raw) for(auto c : raw)
{ {
@@ -971,8 +970,12 @@ bool path_is_within(String path, String root)
bool mkdir(String path) bool mkdir(String path)
{ {
shell_exec(String("mkdir -p ")+" "+shell_escape(path)); if(path == "")
return(true); return(false);
std::error_code ec;
if(std::filesystem::exists(path, ec))
return(std::filesystem::is_directory(path, ec));
return(std::filesystem::create_directories(path, ec) || std::filesystem::is_directory(path, ec));
} }
bool file_exists(String path) bool file_exists(String path)
@@ -1605,7 +1608,18 @@ void on_child_exit(int sig)
StringList ls(String dir) StringList ls(String dir)
{ {
return(split(trim(shell_exec("ls -1 "+shell_escape(dir))), "\n")); StringList entries;
std::error_code ec;
if(!std::filesystem::is_directory(dir, ec))
return(entries);
for(auto const& entry : std::filesystem::directory_iterator(dir, ec))
{
if(ec)
break;
entries.push_back(entry.path().filename().string());
}
std::sort(entries.begin(), entries.end());
return(entries);
} }
StringMap make_server_settings() StringMap make_server_settings()
@@ -1622,8 +1636,10 @@ StringMap make_server_settings()
cfg["SETUP_TEMPLATE"] = "scripts/setup.h.template"; cfg["SETUP_TEMPLATE"] = "scripts/setup.h.template";
cfg["LIT_ESC"] = "3d5b5_1"; cfg["LIT_ESC"] = "3d5b5_1";
cfg["CONTENT_TYPE"] = "text/html; charset=utf-8"; cfg["CONTENT_TYPE"] = "text/html; charset=utf-8";
cfg["FCGI_SOCKET_PATH"] = "/run/uce.sock"; cfg["FCGI_SOCKET_PATH"] = "/run/uce/fastcgi.sock";
cfg["FCGI_SOCKET_MODE"] = "0666";
cfg["CLI_SOCKET_PATH"] = "/run/uce/cli.sock"; cfg["CLI_SOCKET_PATH"] = "/run/uce/cli.sock";
cfg["CLI_SOCKET_MODE"] = "0600";
// Command socket the WS broker listens on; workers flush ws_* dispatch // Command socket the WS broker listens on; workers flush ws_* dispatch
// command batches here at workspace teardown. // command batches here at workspace teardown.
cfg["WS_BROKER_SOCKET_PATH"] = "/run/uce/ws-broker.sock"; cfg["WS_BROKER_SOCKET_PATH"] = "/run/uce/ws-broker.sock";
@@ -1632,6 +1648,7 @@ StringMap make_server_settings()
cfg["UCE_HOSTCALL_BLOCKLIST"] = ""; cfg["UCE_HOSTCALL_BLOCKLIST"] = "";
cfg["TMP_UPLOAD_PATH"] = "/tmp/uce/uploads"; cfg["TMP_UPLOAD_PATH"] = "/tmp/uce/uploads";
cfg["SESSION_PATH"] = "/tmp/uce/sessions"; cfg["SESSION_PATH"] = "/tmp/uce/sessions";
cfg["SESSION_COOKIE_SECURE"] = "0";
cfg["COMPILER_SYS_PATH"] = "."; cfg["COMPILER_SYS_PATH"] = ".";
cfg["PRECOMPILE_FILES_IN"] = ""; cfg["PRECOMPILE_FILES_IN"] = "";
cfg["SITE_DIRECTORY"] = "site"; cfg["SITE_DIRECTORY"] = "site";
+18 -6
View File
@@ -843,7 +843,8 @@ String session_start(String session_name)
if(session_id.length() == 0) if(session_id.length() == 0)
{ {
session_id = session_id_create(); session_id = session_id_create();
set_cookie(session_name, session_id, time() + int_val(context->server->config["SESSION_TIME"])); bool secure_cookie = context->server->config["SESSION_COOKIE_SECURE"] == "1";
set_cookie(session_name, session_id, time() + int_val(context->server->config["SESSION_TIME"]), "/", "", secure_cookie, true);
} }
context->session_id = session_id; context->session_id = session_id;
context->session_name = session_name; context->session_name = session_name;
@@ -856,12 +857,23 @@ String session_start(String session_name)
void session_destroy(String session_name) void session_destroy(String session_name)
{ {
if(context->cookies[session_name].length() > 0) String cookie_session_id = context->cookies[session_name];
String active_session_id = context->session_name == session_name ? context->session_id : "";
String destroy_session_id = active_session_id != "" ? active_session_id : cookie_session_id;
if(cookie_session_id.length() > 0 || active_session_id.length() > 0)
{ {
set_cookie(session_name, "", time() - int_val(context->server->config["SESSION_TIME"])); bool secure_cookie = context->server->config["SESSION_COOKIE_SECURE"] == "1";
context->session.clear(); set_cookie(session_name, "", time() - int_val(context->server->config["SESSION_TIME"]), "/", "", secure_cookie, true);
save_session_data(context->session_id, context->session); String session_path = session_file_path(destroy_session_id);
context->session_id = ""; if(session_path != "")
file_unlink(session_path);
if(active_session_id != "")
{
context->session.clear();
context->session_loaded_hash = session_hash_serialized(session_serialize(context->session));
context->session_id = "";
context->session_name = "";
}
} }
} }
+23 -2
View File
@@ -1353,6 +1353,27 @@ void listen_for_connections()
} }
} }
mode_t configured_socket_mode(String value, mode_t fallback)
{
value = trim(value);
if(value == "")
return(fallback);
char* end = 0;
long parsed = strtol(value.c_str(), &end, 8);
if(end == value.c_str() || *end != '\0' || parsed < 0 || parsed > 0777)
return(fallback);
return((mode_t)parsed);
}
void chmod_configured_socket(String path, String mode_value, mode_t fallback)
{
if(path == "")
return;
mode_t mode = configured_socket_mode(mode_value, fallback);
if(chmod(path.c_str(), mode) != 0)
fprintf(stderr, "(!) Could not chmod socket %s to %04o: %s\n", path.c_str(), (unsigned int)mode, strerror(errno));
}
void init_base_process() void init_base_process()
{ {
printf("(P) Starting parent server PID:%i\n", getpid()); printf("(P) Starting parent server PID:%i\n", getpid());
@@ -1369,13 +1390,13 @@ void init_base_process()
if(server_state.config["FCGI_SOCKET_PATH"] != "") if(server_state.config["FCGI_SOCKET_PATH"] != "")
{ {
server.listen(server_state.config["FCGI_SOCKET_PATH"]); server.listen(server_state.config["FCGI_SOCKET_PATH"]);
chmod(server_state.config["FCGI_SOCKET_PATH"].c_str(), S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); chmod_configured_socket(server_state.config["FCGI_SOCKET_PATH"], server_state.config["FCGI_SOCKET_MODE"], 0666);
} }
if(server_state.config["CLI_SOCKET_PATH"] != "") if(server_state.config["CLI_SOCKET_PATH"] != "")
{ {
server.listen_cli(server_state.config["CLI_SOCKET_PATH"]); server.listen_cli(server_state.config["CLI_SOCKET_PATH"]);
chmod(server_state.config["CLI_SOCKET_PATH"].c_str(), S_IRWXU | S_IRGRP | S_IWGRP); chmod_configured_socket(server_state.config["CLI_SOCKET_PATH"], server_state.config["CLI_SOCKET_MODE"], 0600);
} }
// HTTP_PORT (WebSocket + raw HTTP) is owned by the dedicated WS broker, not // HTTP_PORT (WebSocket + raw HTTP) is owned by the dedicated WS broker, not