Files
2026-07-20 21:41:51 +02:00

163 lines
6.7 KiB
Markdown

# Universal License Platform
Eigenständiger Lizenz-Server mit drei getrennten Portalen, signierten Offline-/Hybrid-/Online-Lizenzen und einer wiederverwendbaren Go-Clientbibliothek.
## Enthalten
- **Admin / Management:** initialisiert die Write-once-Schlüssel, verwaltet Accounts, sieht alle Lizenzen und den Audit-Trail.
- **Reseller / Autor:** stellt Lizenzen aus und verwaltet ausschließlich die eigenen Datensätze, ohne Zugriff auf private Schlüssel.
- **Kunde / Client:** sieht die zugeordneten Lizenzen, Laufzeiten, Status und fertige Client-Konfigurationen.
- **Validierungs-API:** validiert signierte Lizenzen, prüft Sperrstatus und stellt kurzlebige, Ed25519-signierte Leases aus.
- **Hybrid offline:** ein Client verwendet bei temporärer Nichterreichbarkeit eine zuvor verifizierte Lease bis zum signierten Grace-Ende.
- **Client-SDK:** `sdk/go/licenseclient` für Feature Gates, Limits, Hintergrund-Refresh und sicheren Lease-Cache.
## Sicherheitsmodell
- Issuer- und Lease-Private-Keys werden mit **AES-256-GCM** verschlüsselt im Datenspeicher abgelegt.
- Lizenz-Tokens werden ebenfalls verschlüsselt gespeichert; zur Online-Prüfung wird zusätzlich nur ein SHA-256-Hash gebunden.
- Die Schlüsselerzeugung ist **write-once**. Sobald Schlüssel existieren, verschwindet der Button und das Backend lehnt jeden weiteren Generierungsversuch ab.
- Passwörter werden mit PBKDF2-HMAC-SHA256 und individuellem Salt gespeichert.
- Sessions sind HttpOnly, SameSite=Strict, zeitlich begrenzt und CSRF-geschützt.
- Login und Validierungs-API besitzen einfache Rate Limits.
- Sicherheitsheader, restriktive CSP und ein persistenter Audit-Trail sind aktiviert.
- Reseller- und Kundenzugriffe werden serverseitig nach Eigentum bzw. Zuordnung gefiltert.
> Der JSON-Datenspeicher ist atomar und mit Dateimodus `0600` geschrieben, aber für eine einzelne Serverinstanz gedacht. Vor Active/Active-Betrieb sollte `Store` durch PostgreSQL oder eine andere transaktionale Datenbank ersetzt werden.
## Schnellstart
```bash
cd license-platform
cp .env.example .env
openssl rand -base64 32 # als LICENSE_MASTER_KEY eintragen
openssl rand -base64 32 # als LICENSE_ADMIN_API_TOKEN eintragen
# Ein langes Bootstrap-Passwort in LICENSE_BOOTSTRAP_ADMIN_PASSWORD eintragen.
docker compose up -d --build
```
Danach `http://localhost:8091` öffnen und mit dem Bootstrap-Admin anmelden. Der Bootstrap-Account wird nur angelegt, wenn der Datenspeicher noch keinen Administrator enthält.
Im Admin-Portal werden die beiden Schlüsselpaare **einmalig** erzeugt. Private Schlüssel werden nie in der Oberfläche angezeigt.
## Server-URL und automatische Erkennung
Für Hybrid- und Online-Lizenzen schreibt die Plattform `LICENSE_PUBLIC_URL` signiert in `verification.serverUrl`. Das Go-SDK löst die URL in dieser Reihenfolge auf:
1. `licenseclient.Config.ServerURL`
2. Environment `LICENSE_SERVER_URL`
3. signierte `verification.serverUrl` aus der Lizenz
4. `/.well-known/license-server` relativ zur Produkt-Base-URL
Damit kann die URL weiterhin per ENV überschrieben werden, muss bei üblichen Installationen aber nicht doppelt gepflegt werden.
## Clientbibliothek
```go
package main
import (
"context"
"embed"
"os"
"time"
"github.com/b1tsblog/license-platform/pkg/licensekit"
"github.com/b1tsblog/license-platform/sdk/go/licenseclient"
)
//go:embed trusted-keys.json
var trustedKeys []byte
func main() {
trust, err := licensekit.ParseTrustStore(trustedKeys)
if err != nil {
panic(err)
}
client := licenseclient.New(context.Background(), licenseclient.Config{
Product: "my-product", // im Produkt fest verdrahten
ClientVersion: "2.1.0",
Token: os.Getenv("LICENSE_TOKEN"),
TrustStore: trust, // nur Public Keys einbetten
BaseURL: "https://app.example.org",
InstanceID: os.Getenv("LICENSE_INSTANCE_ID"),
Mode: licensekit.ModeHybrid,
CacheFile: "/data/license-lease.json",
RefreshEvery: 15 * time.Minute,
RequestTimeout: 5 * time.Second,
// ServerURL ist optional: ENV oder signierter Token werden erkannt.
})
client.Start(context.Background())
defer client.Close()
if client.Has("advanced_export") {
// Feature freischalten
}
if users, ok := client.Limit("users"); ok {
_ = users
}
}
```
Der Trust Store kann nach der Initialisierung unter `GET /api/v1/trust-store` geladen und in das Clientprodukt eingebettet werden. Ein Trust Store darf niemals kundenseitig frei konfigurierbar sein.
## API
Öffentlich:
- `GET /.well-known/license-server`
- `GET /api/v1/trust-store`
- `POST /api/v1/licenses/validate`
- Kompatibilitätsalias: `POST /v1/introspect`
Management mit `Authorization: Bearer $LICENSE_ADMIN_API_TOKEN`:
- `GET /api/v1/licenses`
- `POST /api/v1/licenses`
- `POST /api/v1/licenses/import` for already signed tokens
- `POST /api/v1/licenses/{id}/revoke`
- `POST /api/v1/licenses/{id}/restore`
Beispiel zur Validierung:
```bash
curl -sS http://localhost:8091/api/v1/licenses/validate \
-H 'Content-Type: application/json' \
-d '{
"token":"LICENSE_TOKEN",
"product":"my-product",
"baseUrl":"https://app.example.org",
"instanceId":"optional-instance"
}'
```
Die vollständige Beschreibung liegt in [`openapi.yaml`](openapi.yaml). Die bisherigen `/v1/admin/licenses`-Routen bleiben als Kompatibilitätsalias verfügbar, sodass das ältere `licenseweb` bestehende Tokens registrieren kann.
Eine schrittweise Übernahme vorhandener Schlüssel und Lizenzen ist in [`docs/MIGRATION.md`](docs/MIGRATION.md) beschrieben.
## ENV-Variablen
| Variable | Zweck |
|---|---|
| `LICENSE_PUBLIC_URL` | Öffentliche Basis-URL; wird in Hybrid-/Online-Lizenzen signiert |
| `LICENSE_MASTER_KEY` / `_FILE` | Base64-kodierter 32-Byte-Schlüssel für AES-256-GCM |
| `LICENSE_BOOTSTRAP_ADMIN_USER` | initialer Admin-Benutzername |
| `LICENSE_BOOTSTRAP_ADMIN_PASSWORD` / `_FILE` | initiales Passwort, mindestens 12 Zeichen |
| `LICENSE_ADMIN_API_TOKEN` / `_FILE` | optionaler Management-API-Bearer |
| `LICENSE_DATA_FILE` | persistenter JSON-Datenspeicher |
| `LICENSE_SESSION_TTL` | Session-Laufzeit, Standard `12h` |
| `LICENSE_DEFAULT_LEASE_TTL` | Standard-Lease, `1h` |
| `LICENSE_MAX_LEASE_TTL` | serverseitiges Maximum, `24h` |
| `LICENSE_SECURE_COOKIES` | bei TLS `true`; HTTPS-URL aktiviert es automatisch |
## Betrieb
```bash
go test ./...
go vet ./...
go test -race ./...
go build ./cmd/server
```
Für produktive Installationen gehören `LICENSE_MASTER_KEY`, Bootstrap-Passwort und API-Token in Docker/Kubernetes Secrets. TLS sollte am Reverse Proxy terminiert werden; `LICENSE_PUBLIC_URL` muss dabei die externe HTTPS-URL enthalten.