Tenancy and access control¶
One server holds one fleet. Reads can be scoped to teams: a read token
minted for some teams, or an authenticating proxy's team header, reads
only the clusters those teams own a namespace in and, of those, only their
findings and team scores, a finding that spans teams cut to their
namespaces and objects (Read access). Teams are the
attribution findings already carry (a namespace label, or the server's
--team-map). Team scoping limits what a read shows; it is not
isolation. The server has no users or roles, one database holds every
team's data, and whoever operates the server or the database sees all of
it. Tenants that must not share an operator, a server process or a
database therefore need separate servers, each with its own database and
tokens. In the cluster, the agent's footprint is described in
Security model and RBAC.
Credentials¶
The server has five credentials, each optional except where noted:
| Credential | Authorizes | Configured with |
|---|---|---|
| read token | every GET /api/v1/* endpoint (clusters, reports, findings, history, team rollups, exports, registry), POST /api/v1/gate, /metrics, for the whole fleet |
--read-token; with none of the read credentials the read API is open, which serve refuses unless the address it binds is loopback or --allow-anonymous-read is set; --require-read-credential keeps it closed even when the database holds no read token |
| team-scoped read tokens | the same reads, for some teams (or *, the fleet); /metrics takes a fleet-wide one only |
upgradescope tokens create --read --teams payments --teams checkout (one team per flag); see Read access |
| per-cluster ingest tokens | POST /api/v1/snapshots as one cluster name |
upgradescope tokens create <cluster> |
| shared ingest token | POST /api/v1/snapshots as any cluster |
--ingest-token |
| admin token | deleting and renaming clusters, plus reads | --admin-token; empty = both refused |
Per-cluster tokens are 64 random hex characters, printed once by
tokens create. The server stores each one's sha256 hash and its first 8
characters, which tokens list prints to tell them apart; never the
token. tokens revoke <cluster> --id <id> revokes one and --all every
active one of a cluster. To rotate without a gap: tokens create, put
the new token in the agent's Secret (the chart mounts it as a file the
agent re-reads, so no restart: the agent's next push after the kubelet has
synced the Secret, up to about 60 to 90 seconds at its default settings
plus a 5-second check, uses it, so wait for one successful push with the
new token before you revoke the old id: revoking sooner answers the agent's
pushes with 401 until the kubelet has synced; a token the agent got from
--server-token or $UPGRADESCOPE_SERVER_TOKEN is read once at start and needs
kubectl rollout restart deploy/<fullname>-agent, where <fullname> is the
release name plus -upgradescope, cut to 63 characters, or the release name
alone when it contains upgradescope), and then revoke the old id.
Tokens are bearer secrets: anyone who sees one in transit can replay it.
Serve HTTPS (--tls-cert-file, the chart's server.tls, or an Ingress
that terminates TLS) wherever pushes or reads cross a network you do not
trust. The agent logs a warning at startup when it would send its token
over plain http:// to a host that is not loopback.
The server's SQLite database, and its -wal and -shm files, are
created readable by their owner only (0600), and a directory serve
creates for them 0700; an existing database is tightened on open.
What read tokens do not do:
- They are not identities. Reads are not attributed to anyone. A scoped token reads as its teams; who holds it is up to you.
- They do not protect the dashboard's static files (HTML, JS, CSS),
which hold no data, nor
/healthzand/readyz. - They are not encryption. Without TLS (
--tls-cert-file, the chart'sserver.tls, or an Ingress that terminates it) they cross the network in the clear. - They live in the browser. The dashboard keeps one in
sessionStorage(orlocalStoragewhen remembered); the server's Content-Security-Policy forbids inline and third-party script, which is what would read it. - They cannot push snapshots or delete clusters: those need the ingest and admin tokens.
Rotating --read-token means handing the new value to every consumer at
once. With the chart, change the value
(helm upgrade --set server.readToken=<new>, or the contents of your
server.existingSecret): the server mounts it as a file and re-reads it, so
there is no restart, and the new token works once the kubelet has synced the
Secret (up to about 60 to 90 seconds at its default settings) plus a
5-second check; until then the old token still works
(Upgrade). A --read-token given as a flag or
$UPGRADESCOPE_READ_TOKEN is read once at start and needs a restart
(kubectl rollout restart deploy/<fullname>-server, <fullname> as above).
Stored read tokens rotate one consumer at
a time (tokens create --read, hand it out, tokens revoke --read --id <id>).
For people, put an authenticating proxy in front of the server, which can
also set each person's team scope:
Putting the dashboard behind SSO.