diff --git a/README.md b/README.md index 78f5ce3..52ff7de 100644 --- a/README.md +++ b/README.md @@ -311,9 +311,13 @@ The example contains the filesystem and FastCGI settings: BIN_DIRECTORY=/var/cache/uce/work TMP_UPLOAD_PATH=/var/lib/uce/uploads SESSION_PATH=/var/lib/uce/sessions +SESSION_COOKIE_SECURE=1 FCGI_SOCKET_PATH=/run/uce/fastcgi.sock +FCGI_SOCKET_MODE=0666 FCGI_PORT=9993 +CLI_SOCKET_PATH=/run/uce/cli.sock +CLI_SOCKET_MODE=0600 PRECOMPILE_FILES_IN= SITE_DIRECTORY=/var/www/html @@ -328,7 +332,7 @@ For nginx deployments, the most important setting is: - `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. @@ -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 `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` ### 4. Install and enable the systemd service @@ -362,8 +367,8 @@ scripts/systemd/manage-uce-service.sh setup That script: -- installs `scripts/systemd/uce.service` as `/etc/systemd/system/uce.service` -- installs `etc/uce/settings.cfg` to `/etc/uce/settings.cfg` if it does not already exist +- 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, likewise rewriting checkout-root paths - reloads systemd - enables the service at boot - starts the runtime immediately diff --git a/docs/setup.md b/docs/setup.md index 4e349a4..18a6db5 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -11,20 +11,20 @@ A typical deployment has four pieces: 3. `uce.service`, a systemd service that builds/starts/restarts the runtime. 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 -/opt/uce/ UCE repository/runtime root -/var/www/html/ public web root served by nginx/Apache -/etc/uce/settings.cfg runtime configuration -/run/uce/fastcgi.sock FastCGI socket used by nginx/Apache -/run/uce/cli.sock local CLI/admin/test socket -/var/cache/uce/work generated source, wasm modules, caches -/var/lib/uce/uploads multipart upload scratch space -/var/lib/uce/sessions session files +/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 +/etc/uce/settings.cfg runtime configuration +/run/uce/fastcgi.sock FastCGI socket used by nginx/Apache +/run/uce/cli.sock local CLI/admin/test socket +/var/cache/uce/work generated source, wasm modules, caches +/var/lib/uce/uploads multipart upload scratch space +/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 @@ -62,10 +62,11 @@ apt install -y nginx apt install -y apache2 ``` -Build UCE from the repository root: +Build UCE from your repository root: ```bash -cd /opt/uce +repo_root=/path/to/uce-root +cd "$repo_root" bash scripts/build_core_wasm.sh bash scripts/build_linux.sh ``` @@ -104,7 +105,8 @@ The expected directories are: Install the WASI SDK: ```bash -cd /opt/uce +repo_root=/path/to/uce-root +cd "$repo_root" scripts/install_wasi_sdk.sh scripts/install_wasi_sdk.sh --check-only ``` @@ -163,7 +165,7 @@ systemctl restart uce.service ## 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 `/bin/wasm/core.wasm` or leave it relative to `COMPILER_SYS_PATH` as appropriate. Minimum useful settings: @@ -171,9 +173,12 @@ Minimum useful settings: BIN_DIRECTORY=/var/cache/uce/work TMP_UPLOAD_PATH=/var/lib/uce/uploads SESSION_PATH=/var/lib/uce/sessions +SESSION_COOKIE_SECURE=1 FCGI_SOCKET_PATH=/run/uce/fastcgi.sock +FCGI_SOCKET_MODE=0666 CLI_SOCKET_PATH=/run/uce/cli.sock +CLI_SOCKET_MODE=0600 SITE_DIRECTORY=/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_BACKEND_VERBOSE=0 -WASM_CORE_PATH=/opt/uce/bin/wasm/core.wasm +WASM_CORE_PATH=/bin/wasm/core.wasm WASM_MEMORY_LIMIT_BYTES=536870912 WASM_EPOCH_DEADLINE_TICKS=200 WASM_EPOCH_PERIOD_MS=50 @@ -199,11 +204,13 @@ WS_BROKER_OUTBOUND_TIMEOUT_SECONDS=30 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. -- `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. - `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. - `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. - `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. @@ -220,11 +227,12 @@ systemctl restart uce.service For source-checkout deployments, install the provided service helper: ```bash -cd /opt/uce +repo_root=/path/to/uce-root +cd "$repo_root" 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: @@ -234,7 +242,7 @@ scripts/systemd/manage-uce-service.sh restart scripts/systemd/manage-uce-service.sh logs 200 ``` -Equivalent manual systemd service for a source checkout: +Equivalent manual systemd service for a source checkout (`` = checkout root): ```ini [Unit] @@ -244,14 +252,14 @@ Wants=network-online.target [Service] Type=simple -WorkingDirectory=/opt/uce +WorkingDirectory= RuntimeDirectory=uce StateDirectory=uce CacheDirectory=uce 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/bash /opt/uce/scripts/build_linux.sh -ExecStart=/opt/uce/bin/uce_fastcgi.linux.bin +ExecStartPre=/usr/bin/bash /scripts/build_linux.sh +ExecStart=/bin/uce_fastcgi.linux.bin ExecStopPost=/usr/bin/rm -f /run/uce/fastcgi.sock Restart=always 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: ```bash -cd /opt/uce +repo_root=/path/to/uce-root +cd "$repo_root" scripts/uce-cli /tests/cli.uce action=echo message=hello scripts/run_cli_tests.sh ``` @@ -615,7 +624,7 @@ PY ## 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 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. - 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. diff --git a/docs/wasm-runtime-architecture.md b/docs/wasm-runtime-architecture.md index 0d5562a..77ba8e3 100644 --- a/docs/wasm-runtime-architecture.md +++ b/docs/wasm-runtime-architecture.md @@ -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) └─────────────▲──────────────┘ - │ forward render (FastCGI, uce.sock) + │ forward render (FastCGI, FCGI_SOCKET_PATH) │ browser ──raw HTTP / WS──► ┌────────┴─────────┐ (HTTP_PORT 8080) │ WS broker │ owns HTTP_PORT + every @@ -48,14 +48,14 @@ gets invoked*. | Process | Owns | Renders units? | Source | |---|---|---|---| | **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()` | | **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()` | **only workers instantiate Wasmtime and run unit code.** 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 forced by Wasmtime: an `Engine`/`Store` cannot be safely re-created across `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)` (`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 runtime are resolved on demand via the `uce_host_component_resolve` hostcall (`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 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 -still has its own copy-on-write stack and its own copy of the per-request wasm -workspace. This means a delayed task callback can run after the spawning request +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 returns without dereferencing the parent's destroyed workspace. It is still a callback into the inherited child workspace, not a fresh normal request 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: -- **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`. - **serve_http**: the custom-server dispatcher sets `UCE_SERVE_HTTP=1` plus `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** — a non-empty STDIN makes the FastCGI transport flush a premature response 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_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 | |---|---|---| | `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). | -| `CLI_SOCKET_PATH` | `/run/uce/cli.sock` | Worker CLI socket. | +| `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/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. | | `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. | diff --git a/etc/uce/settings.cfg b/etc/uce/settings.cfg index 393c158..752f230 100644 --- a/etc/uce/settings.cfg +++ b/etc/uce/settings.cfg @@ -5,11 +5,15 @@ SESSION_PATH=/var/lib/uce/sessions # LISTEN ON SOCKETS 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 # HTTP-over-Unix command socket for local CLI/control tooling. # Example: curl --unix-socket /run/uce/cli.sock http://localhost/ping 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. # 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_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_EPOCH_DEADLINE_TICKS=200 WASM_EPOCH_PERIOD_MS=50 @@ -85,6 +89,8 @@ ARCHIVE_MAX_ZIP_ENTRIES=4096 # LIFETIME OF SESSION COOKIES IN SECONDS 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 # /doc/index.uce?p=3_blocked_functions). The unit receives the error details in diff --git a/scripts/systemd/manage-uce-service.sh b/scripts/systemd/manage-uce-service.sh index 3ff653c..c7bcf3c 100755 --- a/scripts/systemd/manage-uce-service.sh +++ b/scripts/systemd/manage-uce-service.sh @@ -19,10 +19,23 @@ CONFIG_DEST="/etc/uce/settings.cfg" 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 -D -m 0644 "$UNIT_SOURCE" "$UNIT_DEST" + render_runtime_file "$UNIT_SOURCE" "$UNIT_DEST" 0644 if [[ ! -f "$CONFIG_DEST" ]]; then - install -D -m 0644 "$CONFIG_SOURCE" "$CONFIG_DEST" + render_runtime_file "$CONFIG_SOURCE" "$CONFIG_DEST" 0644 fi systemctl daemon-reload } diff --git a/site/doc/pages/session_destroy.txt b/site/doc/pages/session_destroy.txt index c0853eb..3e896bc 100644 --- a/site/doc/pages/session_destroy.txt +++ b/site/doc/pages/session_destroy.txt @@ -8,9 +8,9 @@ session_name : the name of the session >session :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 diff --git a/site/doc/pages/session_start.txt b/site/doc/pages/session_start.txt index a25e51a..a04953a 100644 --- a/site/doc/pages/session_start.txt +++ b/site/doc/pages/session_start.txt @@ -11,7 +11,7 @@ return value : the current session ID :content 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: diff --git a/site/doc/pages/socket_connect.txt b/site/doc/pages/socket_connect.txt index ee4115f..16d6949 100644 --- a/site/doc/pages/socket_connect.txt +++ b/site/doc/pages/socket_connect.txt @@ -2,7 +2,7 @@ u64 socket_connect(String host, u16 port) :params -host : host name +host : IPv4 address (for example `127.0.0.1`), as a dotted-quad string port : port number return value : the socket handle @@ -12,7 +12,7 @@ return value : the socket handle :content 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 u64 fd = socket_connect("127.0.0.1", 80); diff --git a/site/tests/http.uce b/site/tests/http.uce index d400200..3a696ae 100644 --- a/site/tests/http.uce +++ b/site/tests/http.uce @@ -17,8 +17,9 @@ RENDER(Request& context) String action = first(context.get["action"], "touch"); String session_id = session_start("uce-site-tests"); + String session_path = context.server->config["SESSION_PATH"] + "/" + session_id; if(action == "destroy") - session_destroy(); + session_destroy("uce-site-tests"); else context.session["suite"] = "http"; @@ -56,7 +57,7 @@ RENDER(Request& context) check("session_id_create()", generated_session.length() >= 16, generated_session); 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 check("context.session writes", context.session["suite"] == "http", session_dump); diff --git a/src/lib/fcgi_forward.h b/src/lib/fcgi_forward.h index 71398e1..93f389e 100644 --- a/src/lib/fcgi_forward.h +++ b/src/lib/fcgi_forward.h @@ -2,7 +2,7 @@ // 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 -// 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 // the connection but forwards the actual unit invocation to a clean-engine // worker — the "broker holds connections, units respond like RENDER()" model. diff --git a/src/lib/sys.cpp b/src/lib/sys.cpp index a0c1efc..f835927 100644 --- a/src/lib/sys.cpp +++ b/src/lib/sys.cpp @@ -876,7 +876,6 @@ String shell_exec(String cmd) String shell_escape(String raw) { - // FIXME String result = ""; for(auto c : raw) { @@ -971,8 +970,12 @@ bool path_is_within(String path, String root) bool mkdir(String path) { - shell_exec(String("mkdir -p ")+" "+shell_escape(path)); - return(true); + if(path == "") + 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) @@ -1605,7 +1608,18 @@ void on_child_exit(int sig) 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() @@ -1622,8 +1636,10 @@ StringMap make_server_settings() cfg["SETUP_TEMPLATE"] = "scripts/setup.h.template"; cfg["LIT_ESC"] = "3d5b5_1"; 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_MODE"] = "0600"; // Command socket the WS broker listens on; workers flush ws_* dispatch // command batches here at workspace teardown. cfg["WS_BROKER_SOCKET_PATH"] = "/run/uce/ws-broker.sock"; @@ -1632,6 +1648,7 @@ StringMap make_server_settings() cfg["UCE_HOSTCALL_BLOCKLIST"] = ""; cfg["TMP_UPLOAD_PATH"] = "/tmp/uce/uploads"; cfg["SESSION_PATH"] = "/tmp/uce/sessions"; + cfg["SESSION_COOKIE_SECURE"] = "0"; cfg["COMPILER_SYS_PATH"] = "."; cfg["PRECOMPILE_FILES_IN"] = ""; cfg["SITE_DIRECTORY"] = "site"; diff --git a/src/lib/uri.cpp b/src/lib/uri.cpp index 8e7d4e5..7c79373 100644 --- a/src/lib/uri.cpp +++ b/src/lib/uri.cpp @@ -843,7 +843,8 @@ String session_start(String session_name) if(session_id.length() == 0) { 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_name = session_name; @@ -856,12 +857,23 @@ String session_start(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"])); - context->session.clear(); - save_session_data(context->session_id, context->session); - context->session_id = ""; + bool secure_cookie = context->server->config["SESSION_COOKIE_SECURE"] == "1"; + set_cookie(session_name, "", time() - int_val(context->server->config["SESSION_TIME"]), "/", "", secure_cookie, true); + String session_path = session_file_path(destroy_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 = ""; + } } } diff --git a/src/linux_fastcgi.cpp b/src/linux_fastcgi.cpp index 66dfcc2..5aa1335 100644 --- a/src/linux_fastcgi.cpp +++ b/src/linux_fastcgi.cpp @@ -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() { printf("(P) Starting parent server PID:%i\n", getpid()); @@ -1369,13 +1390,13 @@ void init_base_process() if(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"] != "") { 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