Accept managed collector service tokens on the collector API
A token scoped to the new collector.ingest permission is a collector service token: the collector endpoints accept it via X-API-Key or Bearer alongside the env fleet keys (which remain the fallback), giving the fleet credential rotation, revocation, and last-used visibility from the API Tokens page. Containment holds both ways: a collector token authorizes nothing else, and no other credential gains collector access. Shared token validation refactored out of the auth shim; a Collector service token quick-preset in the create modal; integration guide documents minting, rotation via site-config.json, and the service-identity pattern. 765 tests pass; live acceptance matrix verified. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,80 @@ a real caller (the GE-Enforce fleet agent) to it.
|
||||
HTTP 500 `Collector API key not configured` and rejects every request. An
|
||||
unconfigured server never silently accepts unauthenticated data.
|
||||
- A caller that sends the wrong key (or no key) gets HTTP 401 `Invalid API key`.
|
||||
- In addition to the env keys, a managed API token scoped to `collector.ingest`
|
||||
is accepted as a collector credential on every collector endpoint. See
|
||||
"Managed collector tokens" below; env keys remain the fallback.
|
||||
|
||||
### Managed collector tokens (recommended)
|
||||
|
||||
Alongside the env keys, every collector endpoint (`/api/collector/<plugin>`,
|
||||
`/pc`, `/apps`, `/heartbeat`, `/bulk`, `/status`) also accepts a **managed API
|
||||
token** (PAT) scoped to the `collector.ingest` permission. The env keys stay
|
||||
supported as a bootstrap/legacy fallback - nothing breaks - but a managed token
|
||||
is the preferred credential because it can be minted, rotated, and revoked from
|
||||
the UI (Settings > API Tokens) and its use shows up in `lastusedat` and the
|
||||
audit log.
|
||||
|
||||
What makes a token a collector service token: it is scoped to ONLY
|
||||
`collector.ingest`. That scope authorizes the collector ingest API and NOTHING
|
||||
else. The existing scoped-token machinery contains it automatically - a scoped
|
||||
token passes `require_permission` only for its listed permissions and is denied
|
||||
on every role-gated (`require_role`) endpoint and on import mode, and
|
||||
`collector.ingest` gates no normal route. So a collector token that leaks cannot
|
||||
be used to read or write anything through the regular API; it can only submit
|
||||
collector payloads.
|
||||
|
||||
Both wire transports are accepted (send whichever is convenient; GE-Enforce
|
||||
sends `X-API-Key` today, so that stays ergonomic):
|
||||
|
||||
```
|
||||
POST /api/collector/computers
|
||||
X-API-Key: shopdb_pat_<40 hex>
|
||||
```
|
||||
or
|
||||
```
|
||||
POST /api/collector/computers
|
||||
Authorization: Bearer shopdb_pat_<40 hex>
|
||||
```
|
||||
|
||||
An unscoped PAT, or a PAT scoped to some other permission, is NOT a collector
|
||||
token and is rejected (401) - only `collector.ingest` in the scope list counts.
|
||||
A revoked or expired token is rejected (401) on both transports.
|
||||
|
||||
#### How to mint one (admin flow)
|
||||
|
||||
The simplest contained flow: an **admin** mints the token, scoped to
|
||||
`collector.ingest`. Because the token is scoped, the admin-role bypass is
|
||||
suspended for it, so the token is contained to the collector API even though its
|
||||
owner is an admin - it cannot act with admin authority anywhere.
|
||||
|
||||
1. Settings > API Tokens > New Token.
|
||||
2. Click the **Collector service token** preset (pre-selects only
|
||||
`collector.ingest`), name it (e.g. `wj-fleet-collector`), optionally set an
|
||||
expiry, Create.
|
||||
3. Copy the `shopdb_pat_...` secret (shown once) and deploy it to the fleet the
|
||||
same way as the env key: the `collectorApiKey` field in per-site
|
||||
`site-config.json` (see "Delivering the API key to clients" below). The
|
||||
client sends it in `X-API-Key` exactly as it sends an env key today - no
|
||||
client code change.
|
||||
|
||||
Service identity (documented, not built): if you prefer a non-admin owner,
|
||||
create a dedicated low-privilege user (e.g. `svc-collector`) whose role holds
|
||||
only `collector.ingest`, plus `apitokens.create` if that user is to mint its own
|
||||
token. The scope ceiling then caps any token it mints at `collector.ingest`.
|
||||
The admin-minted route above is simpler and equally contained, so it is the
|
||||
recommended default.
|
||||
|
||||
#### Rotation
|
||||
|
||||
Managed tokens rotate without a fleet re-image:
|
||||
|
||||
1. Mint a new collector token (steps above).
|
||||
2. Deploy it via `site-config.json` (`collectorApiKey`) - update the one per-site
|
||||
value.
|
||||
3. Confirm the new token is in use: watch its `lastusedat` climb in Settings >
|
||||
API Tokens (and the old token's `lastusedat` go stale).
|
||||
4. Revoke the old token once traffic has moved. Revocation is immediate.
|
||||
|
||||
### Generic endpoint contract: `POST /api/collector/<plugin>`
|
||||
|
||||
@@ -256,6 +330,13 @@ collector schema, and POSTs with the `X-API-Key` header over TLS 1.2. Every
|
||||
field name below was checked against `get_collector_schema` in
|
||||
`plugins/computers/plugin.py`.
|
||||
|
||||
The `X-API-Key` value can be EITHER a `COLLECTOR_API_KEY[_COMPUTERS]` env key OR
|
||||
a managed token scoped to `collector.ingest` (a `shopdb_pat_...` secret; see
|
||||
"Managed collector tokens"). The script is identical for both - it just carries
|
||||
whatever `collectorApiKey` the site-config supplies - so switching a site from an
|
||||
env key to a managed token (and rotating it) is a config change, not a script
|
||||
change.
|
||||
|
||||
```powershell
|
||||
# Send-ShopdbCollectorReport.ps1
|
||||
# Reports this PC's identity to shopdb-flask via POST /api/collector/computers.
|
||||
|
||||
Reference in New Issue
Block a user