Add enterprise-auth -create-tenant/-grant-membership-* operator flags
Replaces the manual psql INSERT dance phase-4-runbook.md's §3a/§3b documented for bootstrapping the very first tenant_memberships row (create the tenant, log in once so UpsertUserBySSO creates a users row, hand-write an INSERT with that user's UUID). Two new offline operator flags, same "gated by access to enterprise-auth's own environment, not a network-reachable endpoint" shape as -mint-service-token and enterprise-api's -provision-tenant: - -create-tenant=<id> [-display-name=<name>]: creates a tenant row in rbacstore (control-plane only -- pair with enterprise-api -provision-tenant separately for ClickHouse/Tantivy data-plane provisioning, still two operator actions today, a named gap this doesn't unify). Refuses to run twice for the same id. - -grant-membership-tenant/-grant-membership-user-email/ -grant-membership-role: grants a tenant_memberships row by email instead of requiring the operator to hand-look-up a UUID. The user must already exist (attempted an SSO login at least once -- this flag deliberately never creates a user itself, since that identity has to come from a real IdP round trip). role=owner also calls SetOwner, since Owner is a dedicated tenants.owner_user_id column, not just the highest tenant_memberships role. Deliberately kept as offline flags rather than an authenticated HTTP admin API: an HTTP endpoint would have to solve "who's allowed to create the very first tenant/membership" itself, a real bootstrap problem the offline-flag pattern already used elsewhere in this binary sidesteps entirely. New rbacstore.GetUserByEmail supports the email-based lookup (email is already the natural key UpsertUserBySSO upserts on). Covered by two new skip-gated integration tests in rbacstore_test.go, same RBACSTORE_TEST_POSTGRES_ADDR convention as the rest of this package -- not run against a live database in this environment, consistent with everything else in this phase's Postgres-backed work. No dedicated test for the main.go flag handlers themselves, matching the existing precedent for -mint-service-token/-provision-tenant (neither has one either). Docs updated: phase-4-runbook.md's §3a/§3b bootstrap steps and its "known gaps" list, enterprise/README.md gets a new "Bootstrapping a tenant and its first human user" section and a stale "there's no login flow to issue a human session yet" line (obsolete since OIDC/SAML login shipped) is fixed.
This commit is contained in:
+32
-26
@@ -132,32 +132,32 @@ curl -s http://localhost:8082/auth/features
|
||||
```
|
||||
|
||||
Before a login can succeed, the logging-in identity needs a
|
||||
`tenant_memberships` row -- there's no admin UI for this yet, so insert
|
||||
one directly:
|
||||
`tenant_memberships` row. There's still no admin UI for this, but as of
|
||||
this runbook revision there's no manual SQL either --
|
||||
`enterprise-auth`'s `-create-tenant`/`-grant-membership-*` operator
|
||||
flags replace the old psql dance:
|
||||
|
||||
```sh
|
||||
docker run --rm --network sentry_default postgres:16-alpine psql \
|
||||
"postgres://sentry:sentry-dev-only@metadata-postgres:5432/sentry_metadata" -c \
|
||||
"INSERT INTO tenants (id, display_name, status) VALUES ('acme', 'Acme Corp', 'active') ON CONFLICT DO NOTHING;"
|
||||
# The users row is created automatically on first login (UpsertUserBySSO)
|
||||
# -- but tenant_memberships needs the user's ID, which doesn't exist
|
||||
# until after a first login attempt fails with 403. Log in once (it'll
|
||||
# fail with "no tenant membership"), then:
|
||||
docker run --rm --network sentry_default postgres:16-alpine psql \
|
||||
"postgres://sentry:sentry-dev-only@metadata-postgres:5432/sentry_metadata" -c \
|
||||
"SELECT id, email FROM users;"
|
||||
docker run --rm --network sentry_default postgres:16-alpine psql \
|
||||
"postgres://sentry:sentry-dev-only@metadata-postgres:5432/sentry_metadata" -c \
|
||||
"INSERT INTO tenant_memberships (id, tenant_id, user_id, role) VALUES (gen_random_uuid(), 'acme', '<user id from above>', 'viewer');"
|
||||
docker compose run --rm enterprise-auth -create-tenant=acme -display-name="Acme Corp"
|
||||
# Log in once at http://localhost:8082/auth/oidc/login -- it'll fail
|
||||
# with "no tenant membership" (403), but UpsertUserBySSO already created
|
||||
# the users row by that point, which -grant-membership-user-email needs.
|
||||
docker compose run --rm enterprise-auth \
|
||||
-grant-membership-tenant=acme -grant-membership-user-email=<the email you logged in with> -grant-membership-role=viewer
|
||||
```
|
||||
|
||||
Then visit `http://localhost:8082/auth/oidc/login` in a real browser,
|
||||
complete the IdP's login, and confirm you land on
|
||||
Then visit `http://localhost:8082/auth/oidc/login` again in a real
|
||||
browser, complete the IdP's login, and confirm you land on
|
||||
`POST_LOGIN_REDIRECT_URL` (`http://localhost:3000` by default) with a
|
||||
`sentry_session` cookie set. This whole bootstrap sequence (manual SQL
|
||||
to create the first tenant membership) is exactly the kind of rough
|
||||
edge an admin UI would smooth over -- named as real future work, not
|
||||
hidden.
|
||||
`sentry_session` cookie set. `-create-tenant` only touches `rbacstore`
|
||||
(control-plane/RBAC) -- it's independent of `enterprise-api
|
||||
-provision-tenant`'s ClickHouse/Tantivy data-plane provisioning (§8),
|
||||
so a tenant created this way can log users in immediately but can't yet
|
||||
serve their queries until that's run too, the same "two separate
|
||||
operator actions" gap named in "Known gaps" below. Not yet built: an
|
||||
equivalent for revoking/listing memberships, or anything for
|
||||
`dashboard_permissions` grants (§5a) beyond calling the HTTP endpoints
|
||||
directly.
|
||||
|
||||
## 3b. `enterprise-auth`: human login via SAML (new -- same "verified
|
||||
live in this session, not against a real running container or a real
|
||||
@@ -185,9 +185,10 @@ curl -s http://localhost:8082/auth/features
|
||||
# expect: {"sso_configured":true,"oidc_enabled":false,"saml_enabled":true}
|
||||
```
|
||||
|
||||
Bootstrapping the first `tenant_memberships` row is the same manual-SQL
|
||||
dance as §3a (log in once, it fails with 403, insert the membership
|
||||
using the `users` row that got created, log in again). Then visit
|
||||
Bootstrapping the first `tenant_memberships` row uses the same
|
||||
`-create-tenant`/`-grant-membership-*` flags as §3a (log in once, it
|
||||
fails with 403, grant the membership using the email you logged in
|
||||
with, log in again). Then visit
|
||||
`http://localhost:8082/auth/saml/login` in a real browser, complete the
|
||||
IdP's login, and confirm a `sentry_session` cookie lands after redirect
|
||||
to `POST_LOGIN_REDIRECT_URL`. Note SAML's `sentry_saml_request` cookie
|
||||
@@ -448,8 +449,13 @@ Full accounting: `/docs/security/threat-model.md`. Headline items:
|
||||
verification), not yet a real external IdP or a running
|
||||
`enterprise-auth` container. No tenant-picker UI for a multi-membership
|
||||
identity either (refused outright) for either protocol.
|
||||
- No admin UI to create a `tenant_memberships` row -- §3a's manual SQL
|
||||
bootstrap is the only way to grant a logged-in identity access today.
|
||||
- No admin UI to create a `tenant_memberships` row, but §3a/§3b's manual
|
||||
SQL bootstrap is gone -- `enterprise-auth -create-tenant`/
|
||||
`-grant-membership-*` (offline operator flags, same shape as
|
||||
`-mint-service-token`) replace it. Nothing yet for revoking a
|
||||
membership, listing a tenant's members, or changing a role after the
|
||||
fact (SetMembership's upsert supports it at the storage layer; there's
|
||||
just no flag exposing it).
|
||||
- **Per-resource dashboard grants are now enforced** (`api/dashboards`'
|
||||
handler reads `dashboard_permissions` via
|
||||
`enterprise/internal/rbacstore.DashboardPermissions`, only when
|
||||
|
||||
Reference in New Issue
Block a user