# 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.