241 lines
16 KiB
Markdown
241 lines
16 KiB
Markdown
# Administration API access - 0.29.0
|
||
|
||
## 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.28.2 keeps the deliberately narrow Certificate API exception for automation and makes it least-privilege. An operator can create one or more **Certificate API Key ID + Bearer token** credentials in Settings -> Certificate API. Each new credential requires an explicit allowlist of 1-32 certificate IDs and is reload-only by default. Deploy access must be enabled separately.
|
||
|
||
A reload-only credential authorizes only:
|
||
|
||
- `POST /api/certificates/{certificate_id}/reload` when that ID is allowlisted.
|
||
|
||
`POST /api/certificates/reload` is administrator-session only and never accepts a Certificate API token. `POST /api/certificates/deploy` accepts a Certificate API token only when the credential has explicit deploy permission and the request's `certificate_id` is also allowlisted.
|
||
|
||
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. Credentials created before 0.28.2 migrate to reload-only compatibility access for all certificate IDs (`*`); rotate them to narrow the allowlist. 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/build/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 tools/audit/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
|