4.5 KiB
		
	
	
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	title, description, nav
| title | description | nav | ||
|---|---|---|---|---|
| Offline JWT licence & daily‑run quota | How Stella‑Ops enforces a **runs‑per‑day** limit in fully air‑gapped deployments. | 
 | 
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
- Request → POST /register { email:"alice@example.org" }
- Service hashes the e‑mail (SHA‑256), stores it, and issues a JWT (60 days by default).
- 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
# 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
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 expdate | 
| 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_quotaclaim ({{ 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/tidto 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:
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