123 lines
4.5 KiB
Markdown
Executable File
123 lines
4.5 KiB
Markdown
Executable File
---
|
||
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* |