Initial commit
This commit is contained in:
123
docs/license-jwt-quota.md
Executable file
123
docs/license-jwt-quota.md
Executable file
@@ -0,0 +1,123 @@
|
||||
---
|
||||
title: Offline JWT licence & daily‑run quota
|
||||
description: How Stella‑Ops enforces a **runs‑per‑day** limit in fully air‑gapped deployments.
|
||||
nav:
|
||||
order: 36
|
||||
---
|
||||
|
||||
# JWT‑based daily‑run licence (offline‑capable)
|
||||
|
||||
When *Stella‑Ops* scanners operate entirely **offline**, they cannot phone home
|
||||
for metering.
|
||||
Instead, the backend accepts a **signed JSON Web Token (JWT)** that states the
|
||||
**maximum number of scans per UTC day**.
|
||||
If no token is supplied, a _grace quota_ of **33 runs/24 h** applies.
|
||||
|
||||
---
|
||||
|
||||
## 1 Token contents
|
||||
|
||||
| Claim | Purpose | Example |
|
||||
|-------|---------|---------|
|
||||
| `sub` | Customer / licensee identifier | `"f47ac10b…"` |
|
||||
| `iat` | Issued‑at timestamp | `1722566400` |
|
||||
| `exp` | Absolute licence expiry | `2025‑12‑31T23:59:59Z` |
|
||||
| `tier` | **Max scans per UTC day** | `{{ quota_token }}` |
|
||||
| `tid` | Token identifier (32‑byte) | `"7d2285..."` |
|
||||
| `pkg` | Product SKU / edition | `"stella‑core"` |
|
||||
|
||||
Tokens are signed with **RS256** and verified locally using the bundled public key.
|
||||
Only the public key ships inside the container; the private key never leaves
|
||||
the build pipeline.
|
||||
|
||||
---
|
||||
|
||||
## 2 Obtaining a token
|
||||
|
||||
1. **Request** → `POST /register { email:"alice@example.org" }`
|
||||
2. Service hashes the e‑mail (SHA‑256), stores it, and issues a JWT (60 days by default).
|
||||
3. Token is e‑mailed to you.
|
||||
|
||||
A new request for the same e‑mail returns the **same** token until it nears
|
||||
expiry, avoiding quota “top‑ups” by re‑registration.
|
||||
|
||||
---
|
||||
|
||||
## 3 Supplying the token to an air‑gapped stack
|
||||
|
||||
```bash
|
||||
# recommended
|
||||
docker run \
|
||||
-v /opt/stella/license/alice.jwt:/run/secrets/stella_license.jwt:ro \
|
||||
stella‑ops
|
||||
````
|
||||
|
||||
Other supported paths:
|
||||
|
||||
| Method | Mount point | Hot‑reload |
|
||||
| ------------- | ------------------------ | ----------- |
|
||||
| Docker secret | `/run/secrets/…` | ✓ (inotify) |
|
||||
| Bind‑mounted | user‑chosen path (above) | ✓ |
|
||||
| Env variable | `STELLA_LICENSE_JWT` | ✗ restart |
|
||||
|
||||
---
|
||||
|
||||
## 4 Quota‑enforcement algorithm
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start --> Verify[Verify JWT signature]
|
||||
Verify -->|Invalid| Deny1[Run in non licensed mode]
|
||||
Verify --> Load[load today's counter UTC]
|
||||
Load -->|SUM of last 24h scans < daily_quota| Permit[allow scan, add scan]
|
||||
Permit --> End
|
||||
Load -->|SUM of last 24h scans ≥ daily_quota| Deny1
|
||||
```
|
||||
|
||||
|
||||
## 5 Renewal procedure
|
||||
|
||||
| Scenario | Action |
|
||||
| -------------- | --------------------------------------------------------------------------------- |
|
||||
| More capacity | Request new token with higher `daily_quota`; replace file – **no restart needed** |
|
||||
| Licence expiry | Same as above; new `exp` date |
|
||||
| Key rotation | Container image ships new public key(s); older tokens still verify |
|
||||
|
||||
---
|
||||
|
||||
## 6 Fallback limits
|
||||
|
||||
| Situation | Daily quota |
|
||||
| ----------------------- | ----------------------------------- |
|
||||
| Valid JWT present | value of `daily_quota` claim ({{ quota_token }}) |
|
||||
| No JWT | **33** |
|
||||
| JWT expired (if used) | treated as **anonymous** unless policy enforces hard‑fail |
|
||||
| Token signature invalid | **0** (reject) |
|
||||
|
||||
---
|
||||
|
||||
## 7 Threat‑model highlights (future work / optional hardening)
|
||||
|
||||
| Threat | Mitigation |
|
||||
| --------------------------- | ---------------------------------------------------------------------- |
|
||||
| Copy token & DB to 2nd node | Bind `sub`/`tid` to host fingerprint (TPM EK) – optional enterprise control |
|
||||
| Counter DB rollback | Hash‑chain + monotonic clock – optional enterprise control |
|
||||
| Flooding single node | Redis‑backed cluster rate‑limit (30 hits / 60 s) + edge Nginx (20 r/s) |
|
||||
| Key compromise | Rotate RS256 key‑pair, ship new pubkey, re‑sign tokens |
|
||||
|
||||
---
|
||||
|
||||
## 8 Anonymous (33 runs) mode
|
||||
|
||||
Offline PoCs without registration still work:
|
||||
|
||||
```bash
|
||||
docker compose exec stella-ops stella-jwt reload # reloads, discovers no token
|
||||
```
|
||||
|
||||
…but **production deployments *must* register** to unlock real‑world quotas and
|
||||
receive security advisories via e‑mail.
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025‑08‑02*
|
Reference in New Issue
Block a user