3.3 KiB
Auth model
How sherlock obtains and distributes credentials.
Mental model: wallet of OAuth sessions
Sherlock is a wallet. Each entry is one OAuth session against one upstream service's Authentik provider (gitea, grafana, miniflux, invidious, …). There is no master Authentik session; every entry is service-keyed.
This matches the homelab topology: caddy reverse-proxies the public
endpoints and enforces edge OIDC for browsers; each downstream service
also has its own Authentik OAuth client used to bind a service-side
account to the operator's Authentik identity. An MCP that wants to
call gitea.x needs a token issued by gitea's specific Authentik
provider — caddy + gitea both verify it. The same goes for every
other service. One master token cannot satisfy all of them.
Authentication is lazy and per-MCP
sherlock login does not exist. The operator does not preemptively
authenticate. Each MCP at startup calls
ts, err := authn.Ensure(ctx, store, "gitea", cfg, opts)
Ensure (in internal/authn) handles every case:
- Wallet has fresh tokens for
gitea→ return them. - Wallet has stale tokens with a usable refresh token → refresh under a cross-process flock, persist, return.
- Wallet is empty (or refresh failed) → bind the loopback listener on
127.0.0.1:6990, run a fresh OAuth PKCE flow against the service's Authentik provider (opens a browser viaxdg-open), persist, return.
The first time an operator runs sherlock copilot in a fresh
environment, the agent spawns its MCPs; each MCP triggers its own
browser flow as it needs one. Subsequent runs are silent until the
refresh window opens.
What lives in services.d/<name>.toml (Phase 2)
Each MCP needs an OIDC Config (issuer + client ID + scopes) to pass
to Ensure. The operator-editable source of truth is
[service]
name = "gitea"
issuer = "https://id.alexandru.macocian.me/application/o/gitea/"
client_id = "Ig8...abc"
scopes = ["openid", "profile", "email"]
audience = "gitea.alexandru.macocian.me" # informational; aud check is on the IdP side
[mcp]
env_var = "GITEA_TOKEN" # what sherlock injects into the agent env (Phase 2+)
Sherlock-the-CLI does not read these in Phase 1 (the CLI has no authentication subcommands). Phase 2 MCPs load their own service's TOML at startup. See service-registry.md for the full schema.
Audience binding
Per the MCP 2025-06-18 spec, downstream tokens MUST be audience-bound.
Each Authentik provider issues tokens with aud set to its service's
canonical name. Caddy and the upstream both verify it. Sherlock never
tries to reuse one service's token for another.
Scope minimisation
Default service registrations request read-only scopes. Write scopes require an explicit override in the service's TOML (Phase 2).
Out of scope for sherlock
- Issuing SSH certs (gssh handles this — gssh-integration.md).
- Multi-user / shared tenancy. Sherlock is per-operator, full stop.
- Acting as a Resource Server itself.
- RFC 8693 token exchange. Tried during planning; rejected because each service's Authentik provider has its own consent screen and scope set and the exchange story across them is more brittle than just running N separate PKCE flows lazily.