geenforce: resource-scope binding for fetch tokens (0.15.0)
A geenforce.fetch token can now be pinned to specific manifest scopes so a fleet-wide key (a display's, delivered by DSC or baked into the image) is not a skeleton key for the whole content store. NULL binding = unrestricted, so every existing service token keeps working. Core: - ApiToken.resourcescopes column + resourcescopelist property (migration 7d30_apitoken_resourcescopes; NULL = unrestricted). - apitokens API create/update accept + persist an optional resourcescopes list (a resource-name allowlist; not permission-catalog names). - New contract helper authorized_service_token(scope): same check as service_token_authorized but returns the ApiToken so a plugin can read its binding. Contract 0.14.0 -> 0.15.0; also export SupportTeam. GE-Enforce enforcement: - get_manifest: a bound token requesting a scope outside its allowlist -> 403. - get_payload: a bound token may only pull a blob its own scope(s) reference (service.blob_referenced_by_scopes); anything else -> 404 (no hash probing). - Decorator stashes the authorized token on g for the route to read. Also fixes a pre-existing contract-surface violation: the printers/printedparts alert helpers imported shopdb.core.models / shopdb.extensions directly; now via shopdb.api (SupportTeam newly exported). Docs: GE-ENFORCE-DISPLAY.md provisioning note, PLUGIN-HOOKS.md, CLAUDE.md. 9 new resource-binding tests; full suite 1131 passing.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# GE-Enforce: the gea-shopfloor-display scope
|
||||
|
||||
Displays are the share-less corner of the fleet. They are Intune/Entra-joined,
|
||||
Displays are the share-less corner of the fleet. They are Entra-joined,
|
||||
credential-less kiosk PCs that pull their manifest over HTTPS on port 443 and
|
||||
authenticate with a read-only service PAT scoped `geenforce.fetch`, sent as
|
||||
`X-API-Key`. They have no SMB share mount. The kiosk engine and the kiosk
|
||||
@@ -8,6 +8,25 @@ browser are baked into the display image, not shipped over HTTPS, so the display
|
||||
manifest heals POLICY / CONFIG drift only, never EXEs. It is self-sufficient and
|
||||
does not inherit the fleet-wide `common` scope (see below).
|
||||
|
||||
## The display fetch token MUST be resource-bound
|
||||
|
||||
The same read-only key ships to every display (delivered by DSC, or baked into
|
||||
the image), so it must not be a skeleton key for the whole content store. Mint
|
||||
the display token bound to just this scope, so a leak cannot pull any other
|
||||
scope's manifest or any blob by hash:
|
||||
|
||||
```
|
||||
POST /api/apitokens
|
||||
{ "name": "display fetch", "scopes": ["geenforce.fetch"],
|
||||
"resourcescopes": ["gea-shopfloor-display"] }
|
||||
```
|
||||
|
||||
With `resourcescopes` set, `GET /manifest?pctype=<other>` returns 403 and
|
||||
`GET /payload/<sha>` returns 404 for any blob the display scope does not
|
||||
reference. `resourcescopes` NULL (unset) = unrestricted, for back-compat with
|
||||
existing service tokens. Rotate by minting a new bound token and revoking the
|
||||
old one (deactivate it server-side); DSC re-delivers, or re-image.
|
||||
|
||||
There are three display subtypes, selected by `C:\Enrollment\display-type.txt`:
|
||||
`Dashboard`, `Lobby`, and `3DPrintRoom`.
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ The contract is locked in [ADR-001](../docs/adr/ADR-001-asset-as-platform-contra
|
||||
The framework declares its contract version in `shopdb/__init__.py`:
|
||||
|
||||
```python
|
||||
__contract_version__ = '0.14.0'
|
||||
__contract_version__ = '0.15.0'
|
||||
```
|
||||
|
||||
Each plugin's `manifest.json` declares the range of contract versions it supports:
|
||||
@@ -497,6 +497,12 @@ What `shopdb.api` exposes:
|
||||
(`service_token_authorized(scope)` returns True when the request carries a
|
||||
managed service token scoped for `scope` whose owner holds that permission -
|
||||
for unattended plugin endpoints like the GE-Enforce fetch API)
|
||||
- `authorized_service_token(scope)` (0.15.0) - same check as
|
||||
`service_token_authorized` but returns the `ApiToken` itself (or None), so a
|
||||
plugin can honor the token's optional resource binding
|
||||
(`token.resourcescopelist`: an allowlist of resource names the token may
|
||||
reach, NULL = unrestricted). GE-Enforce uses it to pin a display's fetch
|
||||
token to its own manifest scope + that scope's blobs.
|
||||
- Helpers: `audit_log`, `resolve_asset_position`, `resolve_dualpath_pairs`,
|
||||
`dualpath_single_machine_enabled`
|
||||
- Import mode: `apply_import_timestamps`, `import_mode_active`,
|
||||
@@ -506,6 +512,8 @@ What `shopdb.api` exposes:
|
||||
`cmmc_usb_connection`
|
||||
- `User` / `Role` (0.13.0) - the account and role models, e.g. resolving
|
||||
alert recipients' emails from selected user ids or role membership
|
||||
- `SupportTeam` (0.15.0) - the support-team model (carries a `webhookurl`), so
|
||||
an alerting plugin can route a notification to a chosen team's Teams webhook
|
||||
- Mailer (0.12.0): `send_email(to, subject, html, text=None)` and
|
||||
`send_alert(subject, html, text=None)` - settings-first, no-op safe when
|
||||
email is unconfigured; send_alert targets the site's alert_recipients
|
||||
|
||||
Reference in New Issue
Block a user