241 lines
16 KiB
Markdown
241 lines
16 KiB
Markdown
# Administration API access - 0.26.6
|
||
|
||
## Sign-in boundary
|
||
|
||
The administration listener remains separate from reverse-proxy listeners and uses
|
||
loopback HTTP by default. Settings -> Administration can enable HTTPS, select either
|
||
the application-managed default certificate or an inventory certificate, persist a
|
||
new bind address and allow explicit DNS hostnames. A persisted non-loopback bind is
|
||
accepted when HTTPS is enabled. The legacy startup flags remain only for deliberately
|
||
plain remote HTTP. Public vhost `/docs` paths are not captured by the admin listener.
|
||
|
||
When the dashboard is published through a trusted reverse proxy, an explicit
|
||
`administration.allowed_hosts` DNS entry is matched against the external authority rather
|
||
than the private admin socket port. `X-Forwarded-Host` is trusted only from loopback or a
|
||
configured trusted proxy; the external `X-Forwarded-Proto` additionally requires the
|
||
forwarded-proto trust setting except for loopback. Origin/CSRF checks and Secure cookies use
|
||
that external origin. Direct IP/localhost access still requires the real administration port.
|
||
|
||
### Publishing the panel through RustProxy itself
|
||
|
||
0.16.0 supports the explicitly opted-in same-process topology where a public HTTPS vhost proxies to the local administration listener. The reserved `rustproxy_session` cookie is passed through only when all of these conditions are true: the browser-facing request is TLS, its DNS hostname is explicitly present in `administration.allowed_hosts`, the selected backend directly addresses the configured administration socket by IP, and that backend has `accept_local_listener_risk=true`. The control-plane route is forced out of shared cache.
|
||
|
||
This is a narrow exception to the normal isolation rule. For every ordinary backend RustProxy still removes `rustproxy_session` before forwarding requests and strips an upstream attempt to set that cookie in responses. DNS backend names are not accepted for the special self-admin cookie path. This prevents a normal application backend from reading or replacing the administration session.
|
||
|
||
The anonymous page contains only the sign-in/setup form, logo, shared stylesheet,
|
||
a dedicated login script, and filtered language strings. It does not fetch the
|
||
configuration, status, metrics, logs, certificate inventory, dashboard modules or
|
||
the full translation bundle. Adding a new language JSON still discovers that
|
||
language at build time; login strings use the same fallback rules.
|
||
|
||
Exact anonymous methods/paths:
|
||
|
||
| Method | Paths | Content |
|
||
|---|---|---|
|
||
| GET/HEAD | `/`, `/overview`, `/vhosts`, `/redirects`, `/streams`, `/acl-profiles`, `/configuration`, `/tls`, `/cache`, `/logging`, `/settings` | Minimal login when no valid cookie is present; the same URL returns the authenticated application shell when the supplied session is valid. `/configuration` is a compatibility alias for Settings -> Manual config. |
|
||
| GET/HEAD | `/assets/login.js`, `/assets/login-langs.json`, `/assets/style.css`, `/favicon.svg` | Login assets; no runtime state |
|
||
| GET/HEAD | `/healthz` | `{"status":"ok"}` only |
|
||
| GET/HEAD | `/api/auth/status` | `{"setup_required":false}` or `true` only |
|
||
| POST | `/api/auth/login`, `/api/auth/setup` | Credential/capability validation, exact Origin and request marker required |
|
||
|
||
The document allowlist above is intentionally session-aware: a browser refresh on a protected
|
||
panel path first validates the cookie server-side instead of rendering a false logged-out state.
|
||
The login document also rechecks `/api/auth/session` when restored from browser history, preventing
|
||
a stale back/forward-cache copy from contradicting a still-valid session. The authenticated shell
|
||
also treats `auth.invalid_origin` during startup/session revalidation as a stale restored-page signal
|
||
and performs at most one reload of the current same-origin URL. A repeated failure is shown normally;
|
||
server Host/Origin, request-marker and CSRF validation is unchanged. Every other admin
|
||
path/method, including full assets, `/api/metrics`, `/metrics`, unknown endpoints and the OpenAPI
|
||
schema, requires an active database session.
|
||
Missing/invalid/expired cookies return `401 {"error":"auth.required"}` before
|
||
query/body extraction. Invalid Host/Origin may return 403 first. Only GET/HEAD
|
||
`/docs` and `/docs/` navigate to login through a fixed `303 /?next=docs` redirect;
|
||
API/spec/assets never return login HTML instead of a status error.
|
||
|
||
|
||
Administrator browser sessions have a configurable absolute lifetime of **7–30 days**
|
||
(default 30) through `administration.session_days`. The server stores only a hash of the
|
||
session secret, keeps the cookie HttpOnly/SameSite=Strict (and Secure on HTTPS), and
|
||
never extends a session past its creation-time absolute expiry. The cookie is refreshed
|
||
with only the remaining lifetime so an upgraded still-valid short session can adopt the
|
||
new configured bound. Logout, password change and revoke-all still invalidate server-side
|
||
sessions immediately.
|
||
|
||
Responses are no-store. Headers include CSP, nosniff, frame denial, no-referrer and
|
||
restricted camera/microphone/geolocation permissions. Error responses do not expose
|
||
request-body excerpts for malformed anonymous login/setup input. Anonymous responses contain no application version; fingerprint/revision
|
||
response headers are stripped on the admin surface. Authenticated status and the
|
||
protected schema intentionally retain the current version. The application name/logo and the single setup-needed bit are intentional
|
||
public information, not claims of anonymity or software fingerprint prevention.
|
||
|
||
Authenticated `GET /api/status` also returns a small `runtime` object with the platform,
|
||
statistics adapter name, resolved data directory and SQLite database path. This is used by
|
||
the Settings UI so Linux, BSD/OPNsense and custom installations show their actual runtime
|
||
paths instead of browser-side guesses. The object is not available anonymously and does
|
||
not expose credentials, environment values, certificate contents or arbitrary filesystem
|
||
listings.
|
||
|
||
## Docs Reader and API view
|
||
|
||
`/docs` opens an authenticated documentation workspace. The default **Reader** tab loads
|
||
`/docs/content.json`, a build-time bundle of the project Markdown documentation listed in
|
||
`docs/README.md`. The bundle contains documentation source text only; it does not contain
|
||
live configuration, credentials or runtime state. Reader links between bundled Markdown
|
||
documents stay inside the same authenticated documentation surface.
|
||
|
||
The separate **API** tab uses Swagger UI. `/docs/openapi.json` describes the local endpoints and common request schemas; it
|
||
contains no live configuration, actual credentials, generated keys, user accounts,
|
||
cache entries or listener addresses. Extensible schemas summarize configuration;
|
||
the Rust validator remains authoritative for the full configuration model.
|
||
|
||
Documentation uses the same HttpOnly `rustproxy_session` cookie as the panel.
|
||
Do not paste session cookies into the schema or store them in browser storage.
|
||
GET execution is enabled by default; write execution requires a checkbox and an
|
||
explicit confirmation for each request. The script validates the current session,
|
||
allows only documented same-origin operations, and sends the current session's
|
||
`X-CSRF-Token` plus `X-RustProxy-Request: 1`. The browser supplies Origin. The server
|
||
performs the real authorization checks regardless of any browser options.
|
||
|
||
Login/setup operations and indefinite SSE streams are documented but not executed
|
||
by Swagger UI. Native logout/password writes can invalidate the browser session; reload or
|
||
sign in again afterward. Ordinary API mutations still require the administrator session,
|
||
Origin/request marker and CSRF token.
|
||
|
||
0.23.0 adds one deliberately narrow exception for certificate automation. An operator can
|
||
create one or more **Certificate API Key ID + Bearer token** credentials in Settings -> Certificate API. Each credential may carry an operator comment and authorizes only:
|
||
|
||
- `POST /api/certificates/deploy`
|
||
- `POST /api/certificates/reload`
|
||
- `POST /api/certificates/{certificate_id}/reload`
|
||
|
||
Both `Authorization: Bearer <token>` and `X-RustProxy-Certificate-Key: <key-id>` are required.
|
||
The clear token is returned only on creation/rotation; the database stores its SHA-256 hash. Up to 32 Key IDs can coexist, and each can be revoked independently or all at once. The credential does not authorize configuration, logs, metrics, users or any other endpoint. Non-loopback use is accepted only on the configured HTTPS administration surface. Revocation from Settings invalidates that credential immediately.
|
||
|
||
Local host automation does not need this token. The installed binary can run
|
||
`rust-proxy tls deploy ...` or `rust-proxy tls reload` directly; it obtains short-lived local
|
||
CLI authority from the local database and calls the guarded loopback administration API.
|
||
`dev.sh` is a development helper and is not required in production.
|
||
|
||
The external validator, query-supplied schema/server configuration and persisted
|
||
Swagger authorization are disabled. Runtime JS/CSS are served from the same admin
|
||
origin behind its guard. Only the two necessary Swagger vendor asset filenames, the documentation bundle and a plain-text license notice
|
||
(`/docs/licenses.txt`) are routed; all require an administrator session.
|
||
Swagger's inline layout styles have a document-scoped CSP exception; scripts stay
|
||
self-only with no unsafe-eval allowance. The ordinary dashboard CSP is unchanged.
|
||
|
||
Logout broadcasts an in-memory notification between tabs. The panel drops its
|
||
operational document on logout/revocation; documentation clears its schema,
|
||
responses and write opt-in. Docs recheck sessions on actions, visibility and every
|
||
30 seconds while visible. Browser history restoration revalidates; this is not a
|
||
promise to erase browser/OS screenshots or data intentionally downloaded by an admin.
|
||
|
||
## First build / offline assets (fixed in 0.7.2)
|
||
|
||
No `SWAGGER_UI_DOWNLOAD_URL` environment variable or `.cargo/config.toml` is required.
|
||
The default is Swagger UI **5.32.11**, selected by `scripts/swagger_assets.py` and
|
||
called from this application's `build.rs`. The former `utoipa-swagger-ui` adapter
|
||
is removed: there is no separate dependency build silently choosing its older
|
||
historical browser default. Direct Cargo builds using `--manifest-path` use the
|
||
project's absolute manifest directory, not the caller's working directory.
|
||
|
||
Python **3.10+ is now also required for direct Cargo builds**, as it already was
|
||
for `dev.sh`. `build.rs` tries `python3`, then `python` if that executable is absent.
|
||
Use `RUST_PROXY_PYTHON=/absolute/path/to/python3` to select an interpreter explicitly.
|
||
The helper uses Python's verified HTTPS transport and standard-library ZIP reader;
|
||
`curl` is no longer a Swagger build requirement. Missing tools/network/assets give
|
||
an actionable build error rather than an absent-environment-variable panic.
|
||
|
||
Normal build (no Swagger environment setup):
|
||
|
||
```bash
|
||
cd RustProxy
|
||
bash dev.sh check
|
||
bash dev.sh test
|
||
bash dev.sh release
|
||
```
|
||
|
||
The first build still needs the normal Cargo registry and the pinned upstream
|
||
archive. Swagger JS/CSS and license notices are embedded in **both debug and
|
||
release binaries**, served behind the existing admin guard. No runtime download,
|
||
CDN, public asset endpoint or alternate authentication mechanism was introduced.
|
||
|
||
An offline or GitHub-restricted build can use a reviewed local copy of the same
|
||
GitHub **tag archive**, with its original `swagger-ui-5.32.11/` directory structure:
|
||
|
||
```bash
|
||
export SWAGGER_UI_DOWNLOAD_URL=file:///srv/build-assets/swagger-ui-5.32.11.zip
|
||
# Optional: require the archive digest independently verified by your build team.
|
||
# export SWAGGER_UI_ARCHIVE_SHA256=<64-hex-digit-verified-digest>
|
||
bash dev.sh check
|
||
bash dev.sh test
|
||
bash dev.sh release
|
||
```
|
||
|
||
Unset/empty source values use the default pin. Different remote URLs and old tag
|
||
archives are rejected. Local file URLs support escaped spaces and must be absolute.
|
||
`SWAGGER_UI_ARCHIVE_SHA256`, when set, checks the entire archive before publication.
|
||
No upstream checksum is fabricated: HTTPS provenance, the archive's declared tag
|
||
and its layout are not themselves a cryptographic authenticity guarantee for an
|
||
operator-supplied ZIP. Independently review/pin local inputs.
|
||
|
||
The helper only reads exact, bounded members; it does not extract arbitrary ZIP
|
||
paths. It retains licenses/notices, validates package name/version, rejects duplicate
|
||
required members, symlinks and oversized assets, caches only validated downloads in
|
||
Cargo's output directory, and writes outputs atomically. Failed preparation blocks
|
||
compilation instead of embedding stale assets. A `source.json` receipt records
|
||
archive and output SHA-256 values in `OUT_DIR/swagger-ui/`.
|
||
|
||
Cargo tracks changes to the helper, source/digest/interpreter environment variables,
|
||
local or cached ZIP and generated outputs. Repeated identical preparation preserves
|
||
output timestamps. `CARGO_NET_OFFLINE=true` forbids a new helper download; ordinary
|
||
Cargo dependencies must also be cached for a fully offline build. A local archive
|
||
is the recommended explicit setup for `cargo --offline`; Cargo CLI offline flags
|
||
are not assumed to propagate automatically into subprocess environments.
|
||
|
||
0.24.1 adds cross-platform statistics dependencies. This packaging workspace has no Cargo toolchain, so it does not fabricate or hand-edit a resolver result. Generate and preserve `Cargo.lock` with Cargo on the compiler-enabled release host, then run `cargo check --locked` / the normal `dev.sh` gates before deployment.
|
||
|
||
Build-helper tests use synthetic ZIPs; they do not claim to render Swagger. This
|
||
environment lacks Rust/Cargo and cannot fetch the upstream assets. Compilation,
|
||
real embedding/rendering and live access-control acceptance remain to be run on a
|
||
build server. Browser tests still label their simulated API/Swagger renderer.
|
||
|
||
## Focused live regression probe
|
||
|
||
After running the built server:
|
||
|
||
```bash
|
||
python3 scripts/admin_audit.py --url http://127.0.0.1:9090
|
||
```
|
||
|
||
The script sends anonymous read-only loopback probes, follows no redirects, ignores
|
||
proxy environment variables, prints no response bodies, and fails on unexpected
|
||
public content/statuses. It does not send credentials or mutate configuration. It
|
||
is also called by the real-binary security integration suite. Rust router tests
|
||
add write/method/duplicate-cookie/CSRF checks. A passing probe is a regression gate,
|
||
not a complete pentest, dependency scan or guarantee against all vulnerabilities.
|
||
|
||
## Custom location headers
|
||
|
||
Vhosts -> edit -> Custom locations -> Location headers. Add rules independently
|
||
for proxy requests (to backend) and responses (to client). `set` replaces, `append`
|
||
adds another field value, `remove` has no value. Maximum 32 rules per direction.
|
||
The existing JSON editor is synchronized and remains optional. Invalid JSON,
|
||
newlines, unknown operations and protected names cannot silently save an old draft.
|
||
|
||
Rules apply global -> vhost -> matching location. Proxy transport, forwarding,
|
||
verified authentication identity, configured Server/security/diagnostic policies
|
||
remain system-managed and can override or forbid a conflicting custom rule.
|
||
Values are literal, not Nginx variables; changing Host/forwarded identity still uses
|
||
the designated backend/forwarding controls. Headers for sibling locations remain
|
||
unchanged. No configuration schema migration is needed for existing header rules.
|
||
|
||
## References
|
||
|
||
- OWASP REST Security Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html
|
||
- OWASP Session Management Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html
|
||
- Swagger cookie authentication: https://swagger.io/docs/specification/v3_0/authentication/cookie-authentication/
|
||
- Swagger UI configuration: https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/
|
||
- Cargo configuration discovery: https://doc.rust-lang.org/cargo/reference/config.html
|
||
- Cargo build scripts: https://doc.rust-lang.org/cargo/reference/build-scripts.html
|
||
- Pinned browser release: https://github.com/swagger-api/swagger-ui/tree/v5.32.11
|