Make local login reachable, and write down how it works
Local login is implemented, wired through api, alerting and web, and undiscoverable. No compose file turns it on, the Helm chart sets none of its variables, and no markdown in the repository mentions -seed-admin, LOCAL_AUTH_ENABLED or local login at all. The only way to find it is to read cmd/api/main.go's authorizer switch. Enabling it in docker-compose.yml is not the answer: a plain `docker compose up` has no authentication, and every Phase 0-3 runbook verifies the pipeline with bare curl against /query. Turning login on by default would break the project's own documented verification. So it's an opt-in overlay instead. Four settings have to agree, and only one of them is obviously about login. Each fails differently and none of the failures name the cause: the route 404s, or the browser refuses the request before sending it, or login returns 200 and every later request is anonymous because the cookie was never stored, or the same symptom again from the opposite end because the bundle never attaches it. That is what the new document is mostly for. The Helm chart still has no local-login support. Recorded in the document as a gap rather than papered over. Signed-off-by: John Coffey <[email protected]>
This commit is contained in:
@@ -111,6 +111,25 @@ cluster. Override per invocation:
|
|||||||
COMPOSE_PROFILES=enterprise docker compose up
|
COMPOSE_PROFILES=enterprise docker compose up
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Signing in
|
||||||
|
|
||||||
|
A plain `docker compose up` has **no authentication** — every runbook in
|
||||||
|
`docs/` verifies the pipeline with bare `curl` against `/query`, and those
|
||||||
|
steps depend on that. For a login screen without an identity provider behind
|
||||||
|
it, add the local-login overlay:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.local-auth.yml up -d --build
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.local-auth.yml run --rm api -seed-admin
|
||||||
|
```
|
||||||
|
|
||||||
|
The second command prints a generated password once. Full detail, including
|
||||||
|
what each setting does and how each one fails on its own, is in
|
||||||
|
[`docs/local-login.md`](docs/local-login.md).
|
||||||
|
|
||||||
|
SSO (OIDC/SAML) is the other option and takes precedence over local login
|
||||||
|
where both are configured — see [`docs/phase-4-runbook.md`](docs/phase-4-runbook.md).
|
||||||
|
|
||||||
Kubernetes deployment via the Helm chart in [`deploy/`](deploy/README.md).
|
Kubernetes deployment via the Helm chart in [`deploy/`](deploy/README.md).
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# Turns on local username/password login, which docker-compose.yml
|
||||||
|
# deliberately leaves off: a plain `docker compose up` has no
|
||||||
|
# authentication at all, and the Phase 0-3 runbooks' bare curls against
|
||||||
|
# /query depend on that staying true.
|
||||||
|
#
|
||||||
|
# Usage -- both commands need both -f flags:
|
||||||
|
#
|
||||||
|
# docker compose -f docker-compose.yml -f docker-compose.local-auth.yml up -d --build
|
||||||
|
# docker compose -f docker-compose.yml -f docker-compose.local-auth.yml run --rm api -seed-admin
|
||||||
|
#
|
||||||
|
# The second prints a generated password once. See /docs/local-login.md.
|
||||||
|
#
|
||||||
|
# The four settings below have to agree with each other, and three of
|
||||||
|
# the four are not obviously about login at all:
|
||||||
|
#
|
||||||
|
# LOCAL_AUTH_ENABLED registers /auth/* on api, and makes both
|
||||||
|
# api and alerting swap WithCORS for
|
||||||
|
# WithCredentialedCORS
|
||||||
|
# CORS_ALLOWED_ORIGIN must be a literal origin. The default is
|
||||||
|
# "*", and a browser categorically refuses
|
||||||
|
# to combine a credentialed fetch with a
|
||||||
|
# wildcard origin -- so leaving the default
|
||||||
|
# in place fails every request the moment
|
||||||
|
# the cookie starts being sent
|
||||||
|
# LOCAL_AUTH_COOKIE_SECURE the session cookie is Secure by default
|
||||||
|
# and would not be stored over
|
||||||
|
# http://localhost. Set it back to true for
|
||||||
|
# anything served over HTTPS
|
||||||
|
# VITE_LOCAL_AUTH_ENABLED a BUILD arg, so this needs --build, not a
|
||||||
|
# restart. Without it the bundle never
|
||||||
|
# sends credentials: 'include' and no
|
||||||
|
# request ever carries the session
|
||||||
|
services:
|
||||||
|
api:
|
||||||
|
environment:
|
||||||
|
LOCAL_AUTH_ENABLED: "true"
|
||||||
|
LOCAL_AUTH_COOKIE_SECURE: "false"
|
||||||
|
CORS_ALLOWED_ORIGIN: "http://localhost:3000"
|
||||||
|
|
||||||
|
alerting:
|
||||||
|
environment:
|
||||||
|
LOCAL_AUTH_ENABLED: "true"
|
||||||
|
CORS_ALLOWED_ORIGIN: "http://localhost:3000"
|
||||||
|
|
||||||
|
web:
|
||||||
|
build:
|
||||||
|
args:
|
||||||
|
VITE_LOCAL_AUTH_ENABLED: "true"
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
# Local login
|
||||||
|
|
||||||
|
Username and password authentication, served by `api` itself against the
|
||||||
|
control-plane Postgres. It is the alternative to Phase 4's SSO
|
||||||
|
(`enterprise-auth`, OIDC/SAML) for a deployment that wants a login screen
|
||||||
|
without an identity provider behind it.
|
||||||
|
|
||||||
|
The two are mutually exclusive by construction. `cmd/api/main.go` picks
|
||||||
|
one authorizer: `ENTERPRISE_AUTH_URL` wins if it is set, and only
|
||||||
|
otherwise does `LOCAL_AUTH_ENABLED` take effect. With SSO configured,
|
||||||
|
`/auth/*` is never registered and local login does not exist.
|
||||||
|
|
||||||
|
## Off by default, and why
|
||||||
|
|
||||||
|
A plain `docker compose up` has no authentication at all. That is
|
||||||
|
deliberate rather than an oversight: every Phase 0-3 runbook verifies the
|
||||||
|
pipeline with bare `curl` against `/query`, and those steps only work
|
||||||
|
against an unauthenticated API. Turning login on globally would break the
|
||||||
|
project's own documented verification procedure.
|
||||||
|
|
||||||
|
So it is an opt-in overlay:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.local-auth.yml up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
`--build` is not optional. One of the four settings is a Vite build arg
|
||||||
|
baked into the static bundle, so a restart alone leaves the frontend
|
||||||
|
disagreeing with the backend.
|
||||||
|
|
||||||
|
## Creating the first account
|
||||||
|
|
||||||
|
There is no sign-up. The first account is an operator action:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.local-auth.yml \
|
||||||
|
run --rm api -seed-admin
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
created default admin user:
|
||||||
|
username: admin
|
||||||
|
password: <generated>
|
||||||
|
this password will not be shown again -- save it now.
|
||||||
|
```
|
||||||
|
|
||||||
|
It creates `admin` as an **owner** with a random password, prints it once,
|
||||||
|
and stores only a bcrypt hash. Losing it means resetting it, not
|
||||||
|
recovering it. The command is idempotent: if the `admin` account already
|
||||||
|
exists it says so and does nothing.
|
||||||
|
|
||||||
|
Then sign in at <http://localhost:3000/login>. Change the password from
|
||||||
|
the account page.
|
||||||
|
|
||||||
|
## The four settings, and why they must agree
|
||||||
|
|
||||||
|
| Setting | Service | What it does |
|
||||||
|
|---|---|---|
|
||||||
|
| `LOCAL_AUTH_ENABLED` | `api`, `alerting` | Registers `/auth/*`, and swaps `WithCORS` for `WithCredentialedCORS` |
|
||||||
|
| `CORS_ALLOWED_ORIGIN` | `api`, `alerting` | Must be a literal origin — the default `*` is refused by browsers for credentialed requests |
|
||||||
|
| `LOCAL_AUTH_COOKIE_SECURE` | `api` | Defaults to `true`; the cookie is not stored over plain `http://localhost` unless this is `false` |
|
||||||
|
| `VITE_LOCAL_AUTH_ENABLED` | `web` (build arg) | Without it the bundle never sends `credentials: 'include'` |
|
||||||
|
|
||||||
|
Only the first is obviously about login, and every one of them fails
|
||||||
|
differently:
|
||||||
|
|
||||||
|
- `LOCAL_AUTH_ENABLED` unset — the login page posts to `/auth/login` and
|
||||||
|
gets a **404**. The route is not registered at all, deliberately, so a
|
||||||
|
deployment with the feature off answers as though it does not exist
|
||||||
|
rather than advertising a disabled feature.
|
||||||
|
- `CORS_ALLOWED_ORIGIN` left at `*` — the browser refuses the request
|
||||||
|
before it is sent, and the page reports a network failure rather than
|
||||||
|
an HTTP status.
|
||||||
|
- `LOCAL_AUTH_COOKIE_SECURE` left at `true` over HTTP — login returns
|
||||||
|
`200` and appears to work, then every subsequent request is
|
||||||
|
unauthenticated, because the cookie was never stored.
|
||||||
|
- `VITE_LOCAL_AUTH_ENABLED` unset — same symptom as the previous one, and
|
||||||
|
from a different cause: the cookie exists but is never attached.
|
||||||
|
|
||||||
|
Set `LOCAL_AUTH_COOKIE_SECURE` back to `true` for anything served over
|
||||||
|
HTTPS, and set `CORS_ALLOWED_ORIGIN` to the real web origin. The values in
|
||||||
|
the overlay assume `http://localhost:3000`.
|
||||||
|
|
||||||
|
## Roles
|
||||||
|
|
||||||
|
`owner`, `admin`, `editor`, `viewer`, from `api/authz`. Owners manage
|
||||||
|
everyone; admins may create and delete `viewer`/`editor` accounts only,
|
||||||
|
and cannot reset an owner's password. Two guards prevent a deployment
|
||||||
|
from becoming unadministrable: the last owner can be neither deleted nor
|
||||||
|
demoted, and no account can delete itself while signed in.
|
||||||
|
|
||||||
|
## Not covered by the Helm chart
|
||||||
|
|
||||||
|
`deploy/helm/cairnobs` has no local-login support — it sets none of these
|
||||||
|
variables. A Kubernetes deployment authenticates via `enterprise-auth`
|
||||||
|
SSO or not at all. This is a gap, not a decision.
|
||||||
Reference in New Issue
Block a user