diff --git a/claude.md b/claude.md index ad0ed92..e52bf13 100644 --- a/claude.md +++ b/claude.md @@ -529,6 +529,8 @@ Windows: MSI built by CI (WiX), or `installer/setup.ps1` registering the agent a | `GRPC_HOST` | **yes** | `host:port` agents dial. Boot fails without it — there is no safe default; falling back to the web host would hand agents a port that does not speak gRPC. | | `MONGO_URI` | no | default `mongodb://localhost:27017` | | `MONGO_DB` | no | default `vantage` | +| `REDIS_USERNAME` | no | Redis 6+ ACL user. Leave empty for a legacy `requirepass` instance — go-redis then sends AUTH with one argument instead of two | +| `REDIS_PASSWORD` | no | empty for an unauthenticated Redis | | `REDIS_ADDR` | no | default `localhost:6379` | | `KEY_ENCRYPTION_KEY` | yes in practice | 64-char hex (32 bytes) for AES-256-GCM. Required for private keys, secrets, OIDC secrets, RDP credentials. | | `GUACD_ADDR` | no | default `guacd:4822` | @@ -554,7 +556,7 @@ Windows: MSI built by CI (WiX), or `installer/setup.ps1` registering the agent a `docsite` is the odd one: a **static** build served by `nginx:alpine-slim`, not a Node runtime, and it listens on `80` rather than `3000`. It is reached at **`vantage.hostxtra.co.uk/docs`** — a path on the marketing host, routed by its own Nginx Proxy Manager location, which must sort **above** the catch-all forwarding to `site:3003` or Next answers the 404. A path and not a subdomain because `*.vantage.hostxtra.co.uk` is the per-tenant instance namespace and `APP_ROOT_LABEL` would read a `docs.` label as a tenant slug. NPM forwards the **full** path upstream — it does not strip `/docs` — so `DOCS_BASE_URL`, the proxy location and the directory the image copies the build into (`/usr/share/nginx/html/docs`) must all agree. When they do not, the HTML loads and every asset 404s. -`LICENSE_SIGNING_KEY` appears in **exactly one service in exactly one compose file**: `admin` in `docker-compose.site.yml`. It must never be added to `server`, and the self-hosted `docker-compose.yml` must never mention `admin` or `adminsite` at all. Admin uses an external Redis via `REDIS_ADDR`/`REDIS_USERNAME`/`REDIS_PASSWORD`; the base compose hardcodes `redis:6379` for `server`, so those variables reach admin only. +`LICENSE_SIGNING_KEY` appears in **exactly one service in exactly one compose file**: `admin` in `docker-compose.site.yml`. It must never be added to `server`, and the self-hosted `docker-compose.yml` must never mention `admin` or `adminsite` at all. Admin uses an external Redis via `REDIS_ADDR`/`REDIS_USERNAME`/`REDIS_PASSWORD`. `server` now reads the same three, so a Kubernetes install can point at a managed Redis; the base compose still hardcodes an unauthenticated `redis:6379` for it, so in Docker those credentials remain admin's alone. --- diff --git a/docsite/docs/reference/environment-variables.md b/docsite/docs/reference/environment-variables.md index 76e4187..d9e9e60 100644 --- a/docsite/docs/reference/environment-variables.md +++ b/docsite/docs/reference/environment-variables.md @@ -14,6 +14,8 @@ it is absent. | `GRPC_HOST` | **yes** | | The `host:port` agents dial. Boot fails without it. There is deliberately no fallback to the web host: that would hand every agent a port that does not speak gRPC | | `MONGO_URI` | no | `mongodb://localhost:27017` | The database name is taken from the URI path, falling back to `vantage`. There is no separate `MONGO_DB` | | `REDIS_ADDR` | no | `localhost:6379` | Sessions only | +| `REDIS_USERNAME` | no | | Redis 6+ ACL user. Leave empty against a legacy `requirepass` instance, which authenticates with the password alone | +| `REDIS_PASSWORD` | no | | Leave empty for an unauthenticated Redis. Both of these exist so an install can use a managed Redis rather than the bundled one | | `KEY_ENCRYPTION_KEY` | yes in practice | | 64 hex characters (32 bytes) for AES-256-GCM. Required for private keys, vault secrets, OIDC client secrets and console credentials | | `GITEA_HOST` | yes | `gitea.example.com` | Used to build the install scripts and agent download URLs. The default is a placeholder that will not resolve | | `GUACD_ADDR` | no | `guacd:4822` | The [browser console](../vantage/browser-console.md) daemon | diff --git a/server/cmd/main.go b/server/cmd/main.go index 1b4619f..7426b83 100644 --- a/server/cmd/main.go +++ b/server/cmd/main.go @@ -91,7 +91,9 @@ func main() { services.StartAuditSweeper() redisAddr := getEnv("REDIS_ADDR", "localhost:6379") - if err := auth.InitRedis(redisAddr); err != nil { + redisUser := os.Getenv("REDIS_USERNAME") + redisPass := os.Getenv("REDIS_PASSWORD") + if err := auth.InitRedis(redisAddr, redisUser, redisPass); err != nil { log.Fatalf("failed to connect to Redis: %v", err) } log.Println("connected to Redis") diff --git a/server/internal/auth/session.go b/server/internal/auth/session.go index 2859bcb..7d42924 100644 --- a/server/internal/auth/session.go +++ b/server/internal/auth/session.go @@ -25,8 +25,17 @@ type Session struct { var rdb *redis.Client -func InitRedis(addr string) error { - rdb = redis.NewClient(&redis.Options{Addr: addr}) +// InitRedis connects the session store. +// +// Username and password may both be empty for an unauthenticated instance. For +// a legacy `requirepass` Redis, pass the password with an empty username — +// go-redis then sends AUTH with one argument instead of two. +func InitRedis(addr, username, password string) error { + rdb = redis.NewClient(&redis.Options{ + Addr: addr, + Username: username, + Password: password, + }) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() return rdb.Ping(ctx).Err()