From 27ef506058a2c314a534308edb910004fa87aa7d Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Sat, 10 Oct 2026 08:43:31 +0000 Subject: [PATCH] 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) --- .kiro/steering/security.md | 12 +++++++++--- src/docs/deployment.md | 5 ++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.kiro/steering/security.md b/.kiro/steering/security.md index 55d2125e..3d464c16 100644 --- a/.kiro/steering/security.md +++ b/.kiro/steering/security.md @@ -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 ` 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 diff --git a/src/docs/deployment.md b/src/docs/deployment.md index be118bfa..897e634b 100644 --- a/src/docs/deployment.md +++ b/src/docs/deployment.md @@ -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).