[Feature] Adopt SkyWalking Horizon UI auth model in Canopy (SSO + local + API tokens)
- Dominant language
- Java
- Stars
- 25k
- Forks
- 6.6k
- Avg merge
- 10h 5m
- Merged PRs (30d)
- 16
Description
## Summary
Bring Canopy's authentication model in line with **SkyWalking Horizon UI** (`apache/skywalking-horizon-ui`): one `auth.backend` selector with `local`, `sso` (OIDC / OAuth2), and an API-tokens file, plus a `break-glass` emergency path. Keep Canopy's existing session shape and role check; reuse Horizon's config schema verbatim so operators get one mental model across both UIs.
## Background
Canopy currently supports only local users (configured via `CANOPY_USERS` as JSON/YAML) with two roles: `admin` and `readonly`. Horizon has a richer and battle-tested model: **local / SSO (OIDC + OAuth2) / API tokens**, optional break-glass, sliding cookie sessions, and 13 RBAC verbs gated per route. Adopting that model — instead of inventing a separate LDAP flow — means Canopy benefits from the same security hardening (dummy-hash timing equalization, scope intersection, role re-resolution) and the same configuration vocabulary.
## Reference implementation
**Repo:** https://github.com/apache/skywalking-horizon-ui (default branch `main`).
Concrete files to mirror (Horizon → Canopy):
| Horizon concept | Horizon file | Reuse strategy |
|---|---|---|
| Backend dispatch (local / sso / break-glass) | `apps/bff/src/http/user.ts` | Same dispatcher pattern in `canopy/server/src/routes/auth.ts` |
| Local argon2id login (with dummy-hash timing) | `apps/bff/src/user/local.ts` | Replace `server/src/routes/auth.ts` bcrypt path with argon2id |
| SSO/OIDC identity mapping | `apps/bff/src/user/oidc/identity.ts` | New `canopy/server/src/auth/oidc.ts` |
| API tokens (SHA-256, `hzn_` prefix, re-resolved roles) | `apps/bff/src/user/tokens.ts` | New `canopy/server/src/auth/tokens.ts` |
| Session middleware (cookie + bearer resolution order) | `apps/bff/src/user/middleware.ts` | Extend `canopy/server/src/plugins/session.ts` |
| In-memory session store | `apps/bff/src/user/sessions.ts` | Replace current session impl |
| Cookie defaults (`horizon_sid`, `cookieSecure`, `ttlMinutes: 60`) | `apps/bff/src/config/schema.ts` (`sessionSchema`) | Use the same defaults |
| Three-variant `Verified` audit taxonomy | `apps/bff/src/user/outcome.ts` | Copy the shape into Canopy |
| Config schema (`auth.local`, `auth.sso`, `auth.breakGlass`, `auth.tokensFile`) | `apps/bff/src/config/schema.ts` | Reuse keys verbatim |
## Goals
- `auth.backend` selector: `local` | `sso` (default).
- `auth.local.users[]` with **argon2id** password hashes, defaulting to empty.
- `auth.sso.providers[]` for **OIDC** (e.g. Google) and **OAuth2** (e.g. GitHub) — same provider shape Horizon uses.
- `auth.sso.roles` with `defaultRoles` + `roleByEmail` (exact-match) + `roleByDomain`.
- `auth.tokensFile` for API tokens with **SHA-256** secrets + bearer header (`Authorization: Bearer hzn_...`).
- Optional `auth.breakGlass` — only honored when SSO backend reports unhealthy.
- Keep Canopy's existing `admin` / `readonly` role names; map Horizon's `viewer`/`maintainer`/`operator`/`admin` → Canopy roles inside the BFF.
## Tasks
1. **Research** — read Horizon's `apps/bff/src/user/`, `apps/bff/src/http/user.ts`, and `apps/bff/src/config/schema.ts`; document Canopy's current `server/src/routes/auth.ts`, `server/src/plugins/session.ts`, `server/src/lib/role.ts`.
2. **Design doc** — `canopy/docs/auth-design.md` covering:
- Config schema (copy Horizon's `authSchema` shape).
- Session cookie + bearer resolution order: **OAuth token → API token → cookie session** (Horizon order).
- Role re-resolution for bearers (cache 30s success, never cache failure).
- Username-as-credential-anchor: tokens name a principal, never a role set.
- Break-glass gating by SSO health probe.
3. **PoC** — implement, mirroring Horizon file-by-file:
- `canopy/server/src/auth/local.ts` — argon2id + dummy-hash timing.
- `canopy/server/src/auth/oidc.ts` — OIDC + OAuth2 providers.
- `canopy/server/src/auth/tokens.ts` — `hzn_` tokens, SHA-256, bearer.
- `canopy/server/src/auth/sessions.ts` — in-memory store.
- `canopy/server/src/auth/middleware.ts` — `requireAuth`, `requireBrowserSession`.
- `canopy/server/src/http/auth.ts` — `/api/auth/login`, `/api/auth/me`, `/api/auth/logout`, OIDC routes.
- `canopy/server/src/config.ts` — extend loader for `auth.*` + `session.*` + `rbac.*`.
4. **Tests** — unit tests for each auth backend using mocks (no live IdP); integration test for the bearer-resolution order; tests covering: timing equalization, scope intersection, role re-resolution, break-glass gating.
5. **Docs** — update `canopy/README.md` with the new env vars (Horizon-style `HORIZON_AUTH_*` names kept as `CANOPY_AUTH_*` for clarity); add `canopy/docs/auth-design.md`; remove any old LDAP-centric notes from prior docs.
## Acceptance Criteria
- `auth.backend: local` with `local.users: []` boots cleanly; no default admin.
- `auth.backend: local` with users set issues a `horizon_sid`-style cookie (`httpOnly`, `sameSite: 'strict'`, `secure: cookieSecure()`).
- `auth.backend: sso` with a single GitHub OAuth2 provider works end-to-end (login → consent screen → role resolution via `roleByEmail`).
- API tokens (`hzn__`, SHA-256 hashed in `tokensFile`) authenticate via `Authorization: Bearer ...`; deleting the local user revokes the token without editing the file.
- Break-glass local admin is honored **only** when SSO is configured and the SSO health probe fails; every break-glass success logs at WARN with username + IP.
- All existing Canopy e2e tests still pass under `auth.backend: local` with one configured admin.
- `canopy/README.md` and `canopy/docs/auth-design.md` updated.
## Out of Scope
- LDAP / SAML (deferred — Horizon does not use it as the primary path either; add later if a user asks).
- Multi-IdP / failover.
- OAuth2.1 server (issuing tokens to MCP clients) — separate issue.
- Password rotation / self-service.
Contributor guide
Research direction
Start by reading Horizon's apps/bff/src/user/, apps/bff/src/http/user.ts, and apps/bff/src/config/schema.ts, then compare them with Canopy's server/src/routes/auth.ts, server/src/plugins/session.ts, and server/src/lib/role.ts. Document the design in canopy/docs/auth-design.md before implementing the listed auth, session, config, test, README, and documentation changes. Done means the stated local, SSO, token, break-glass, role, cookie, and existing-e2e acceptance criteria pass.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- authentication, authorization, backend, documentation, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 28/100