docs(apiserver): document security defaults changed by bug-1945..1952

deployment.md: auth.yaml now holds bcrypt hashes of the (still public)
dev credentials with startup warnings; JWT secret from XCE_JWT_SECRET or a
random per-process secret; logout revocation is per process;
security.trusted_proxies for reverse proxies.
security.md steering: JWT parsing, revocation, secret resolution,
trusted proxies, middleware abort rule, bcrypt and role-change rule.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-10-10 08:43:31 +00:00
parent a80768f31d
commit 27ef506058
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
2 changed files with 13 additions and 4 deletions

View file

@ -33,11 +33,17 @@ Permissions are defined in `config/rbac.yaml`. Roles support inheritance via `in
Token flow:
1. `POST /auth/logon` → validates credentials via configured auth backend → issues JWT with `{username, IP, expiry}` claims
2. Every subsequent request: `Authorization: Bearer <token>` header
3. `middleware/validatehandler.go` strips the prefix, parses with golang-jwt, **validates the IP claim against the request IP**
3. `middleware/validatehandler.go` strips the prefix, parses with golang-jwt via `controllers.ParseTokenClaims` (HS256 only, revocation checked), **validates the IP claim against the request IP**
IP-bound tokens prevent token theft across network boundaries. Do not remove the IP validation.
IP-bound tokens prevent token theft across network boundaries. Do not remove the IP validation. The request IP is `ctx.ClientIP()`, which honours `X-Forwarded-For`/`X-Real-IP` only from `app.yaml` `security.trusted_proxies` (default none, bug-1948).
Token secret is loaded from `config/auth.yaml` at startup. It must never appear in source code or logs.
Every parse path uses `controllers.JWTKeyFunc` + `JWTValidMethods()` (pinned to `JWTSigningMethod`, HS256, bug-1950). Logout revokes the token's `jti` until its expiry in an in-memory set (`controllers/token_revocation.go`, bug-1946): per process, not persisted, not shared between apiservers.
Token secret: `XCE_JWT_SECRET`, else `app.yaml` `authentication.token.secretKey` (shipped empty). Empty or the old public default `xce-api-key` means unset: a random per-process secret is generated with a startup warning (bug-1945). It must never appear in source code or logs.
Middleware that rejects a request must call `ctx.Abort()` after `ReturnError` (which only writes the response, bug-1949).
Config-file auth (`backend/auth/config_file.go`) accepts bcrypt hashes (recommended) or plaintext (compared in constant time); startup warns for plaintext entries and for shipped default credentials (bug-1947). Only `rbac:control` with scope `all` may grant or revoke user roles (bug-1952).
## Auth Backends

View file

@ -285,7 +285,10 @@ xceadmin config push cluster.bin --controller localhost:9991
Be aware of these before treating anything above as a production runbook — they are honest gaps in this repo today, not documented-but-hidden features:
1. **No process supervision is shipped.** No systemd unit, no Dockerfile, no docker-compose, no supervisord config exists anywhere in this repo for any of the four daemons. A systemd-unit *sketch* exists in `src/docs/config-replication-design.md` but has a wrong binary name (`xcecontroller` vs the real `xce_controller`) and specifies `Type=notify`, which the controller does not support (it never calls `sd_notify`) — using it as written will hang at startup and time out. If you adapt it, switch to `Type=simple` and fix the binary name.
2. **`src/frontend/apiserver/config/auth.yaml` ships plaintext dev credentials checked into git**: four accounts total — `admin`/`admin` and `op7x2k9`/`CHANGE_ME_dev_only` (both role `cluster_admin`), plus `user1`/`user1` and `user2`/`user2` (role `basic_user`). Treat all of them as exposed by default — don't ship the checked-in file as-is; copy `auth.yaml.example` and set real credentials/an external auth provider instead.
2. **`src/frontend/apiserver/config/auth.yaml` ships known dev credentials checked into git** (stored as bcrypt hashes since bug-1947, but the passwords are public): four accounts total — `admin`/`admin` and `op7x2k9`/`CHANGE_ME_dev_only` (both role `cluster_admin`), plus `user1`/`user1` and `user2`/`user2` (role `basic_user`). The apiserver logs a warning at startup for each default credential still active and for each plaintext `password:` entry. Don't ship the checked-in file as-is; set real credentials (`go run ./tools/encrypt -action=hash` in `src/frontend/apiserver` prints a bcrypt hash) or an external auth provider instead.
- **JWT secret**: set `XCE_JWT_SECRET` (or `authentication.token.secretKey` in `app.yaml`, which ships empty). Without one, the apiserver signs tokens with a random per-process secret and logs a warning: tokens do not survive a restart, and several apiservers behind one endpoint (the nginx sample's upstream) must share the same `XCE_JWT_SECRET`. The old public default `xce-api-key` is treated as unset (bug-1945).
- **Logout** revokes the token in memory until its expiry (bug-1946); the revocation is per apiserver process, not persisted and not shared between apiservers.
- **Reverse proxy**: `X-Forwarded-For`/`X-Real-IP` are honoured only from addresses in `app.yaml` `security.trusted_proxies` (default none, bug-1948). Behind nginx/HAProxy, list the proxy's address there, or every client is seen with the proxy's IP (IP whitelist, JWT IP binding).
3. **All controller-bound traffic is encrypted but not authenticated.** Controller↔controller HA replication and every `libxce` client (worker→controller, scheduler→controller, apiserver→controller, xceadmin→controller) run over TLS, but `SSL_VERIFY_NONE` is used throughout — no client certificates, no hostname/identity verification. This buys confidentiality and tamper-resistance in transit, not authentication of the peer; anyone who can reach the port and complete a TLS handshake is treated as trusted the moment the transport-level join succeeds.
4. **apiserver's own TLS uses a checked-in self-signed cert with no CA trust story**; `xcecli` hardcodes `InsecureSkipVerify: true` against it. A reverse-proxy-terminated-TLS model is sketched (nginx sample config) but not automated. (This is apiserver's REST-facing HTTPS, separate from its TLS-encrypted `libxce` connection to the controller in item 3.)
5. **`xceadmin config push` against a real cluster** — the decode-crash bug is fixed (2026-07-30, `decode_reply()` unwraps the `Request` envelope), but `--dry-run` still has no real controller-side handler (see Known Limitations item 2).