myxce/.kiro/steering/security.md
Rongsong Shen 4ec401672b fix(xcecli): restore dangling CA-cert cleanup misses, enable https support
bug-1463: commit 94f50ce6 (bug-1453 CA-cert-plumbing removal) missed
GetJobOutput's dead tls.Config/loadRootCAs block in request.go, plus
peek.go's SetRootCAs call and serverInfo.CACert field access (the
field no longer exists on APIServerInfo) -- broke the xcecli build.

Removed all three dangling references. Also enabled NewAPIClient's
previously-commented-out InsecureSkipVerify TLS config, since no
xcecli method set any TLS config at all -- any https:// apiserver URL
would have failed cert verification, not just GetJobOutput. Matches
bug-1453's documented design (server-side TLS only, no peer
verification, mTLS deferred as P3 hardening).

Documented both the api-server (http+https) and controller
(server-side-cert-only, no client certs) TLS postures in
.kiro/steering/security.md, replacing a stale line claiming TLS is
handled at the network layer, plus a follow-up note for the planned
HAProxy TLS-termination reverse proxy once its cert type and
apiserver's own listener fate are decided.
2026-07-23 07:28:01 +00:00

5.3 KiB

Security Standards

Philosophy

Defense in depth, least privilege, fail closed. Authentication verifies who; authorization verifies what they can do. Centralize both — never duplicate permission checks across handlers.

RBAC Enforcement

All protected routes use RequirePermission middleware attached at registration time:

// In router/routers.go
hostsGroup.GET("", RequirePermission("host:view"), handler)
hostsGroup.POST("/control", RequirePermission("host:control"), handler)

RequirePermission in controllers/rbac_check.go resolves the user from the JWT, looks up their effective permissions (including inherited role permissions), and aborts with 403 if the required permission is absent.

cluster_admin bypasses all permission checks via early return — do not add new bypass paths outside this single mechanism.

Permission String Format

"action:scope" — e.g., "job:submit", "host:view", "queue:manage".

Scopes: all (unrestricted), self (own resources), cluster, hostgroup, self_ug, self_hg.

Permissions are defined in config/rbac.yaml. Roles support inheritance via includes: — resolution is recursive. Add new permissions to the YAML first, then wire them onto routes.

JWT Authentication

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

IP-bound tokens prevent token theft across network boundaries. Do not remove the IP validation.

Token secret is loaded from config/auth.yaml at startup. It must never appear in source code or logs.

Auth Backends

Backend selection is controlled by config/auth.yaml. Supported: configfile, ldap, oauth2, pam. All backends implement the same interface — adding a new backend means implementing that interface, not modifying middleware.

Input Validation

Validate at the API boundary inside preCheck{Resource}Request() helpers — before any service or store access. Return 400 with a clear message on validation failure. Do not pass unvalidated input to downstream layers.

Secrets

  • JWT secret key: config/auth.yaml (file-system protected, chmod 0600)
  • LDAP bind credentials: config/auth.yaml
  • Never log tokens, passwords, secret keys, or full request bodies containing credentials
  • Validate all required config at startup; fail fast with a clear error if missing

Rate Limiting and IP Whitelist

Rate limiting (middleware/rate_limit.go) and IP whitelist (middleware/ip_whitelist.go) are applied before JWT validation in the middleware chain. They use ctx.ClientIP() and abort with 429 / 403 respectively. Both are configured in config/ YAML — do not hardcode thresholds.

Transport

All daemon-to-daemon communication is over TCP. The WAL and replication wire format uses Cap'n Proto — schema files in src/schema/. Do not add unvalidated deserialization paths for external input.

XCE uses server-side TLS only — encryption without peer certificate verification. There is no mTLS and no CA-pinning anywhere in the system; peer verification is deferred as optional P3 hardening (see bug-1453, which deleted all CA-cert/client-cert plumbing from both the C tier and the Go tier as dead code). Do not reintroduce CA-cert loading, client certificates, or cert-pinning without a design doc — this posture is intentional, not an oversight.

Clients talking to the API server (xcecli, or any HTTP client of apiserver) must support both http:// and https:// — the scheme is a per-deployment/user choice (APIServerInfo.URL), not something the client hardcodes. When the scheme is https://, set InsecureSkipVerify: true on the TLS client config (see xcecli/common/request/request.go's NewAPIClient) rather than attempting cert validation — the API server's cert is typically self-signed/auto-generated and the client has no CA to validate it against.

Clients talking to an XCE controller directly (worker, scheduler, and any other libxce-based client via cluster.c's do_connect()) need only server-side certs — no client-side certs are required or supported. xce_cluster_config_t has no ca_cert_path/client-cert fields; do not add any. TLS verification mode is SSL_VERIFY_NONE throughout — this matches the API-server posture above, just at the C/libxce layer instead of Go/HTTP.

Known follow-up (not yet scoped): a HAProxy reverse proxy in front of apiserver is planned to terminate HTTPS, likely with a CA-trusted cert (Let's Encrypt candidate, unconfirmed), and whether apiserver's own RunTLS/httpsConfig.Enabled listener stays in the picture is also undecided. Once both are settled, revisit xcecli's blanket InsecureSkipVerify: true: if the client-facing endpoint ends up CA-trusted, default to real verification (Go's default tls.Config{} validates against the system trust store with no extra plumbing) and make skip-verify an explicit opt-in for self-signed deployments only — matching the automated security review's suggested fix. Don't implement this speculatively; the right shape depends on which of those two decisions lands where.