6.7 KiB
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/licenseclientfü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
0600geschrieben, aber für eine einzelne Serverinstanz gedacht. Vor Active/Active-Betrieb sollteStoredurch PostgreSQL oder eine andere transaktionale Datenbank ersetzt werden.
Schnellstart
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:
licenseclient.Config.ServerURL- Environment
LICENSE_SERVER_URL - signierte
verification.serverUrlaus der Lizenz /.well-known/license-serverrelativ zur Produkt-Base-URL
Damit kann die URL weiterhin per ENV überschrieben werden, muss bei üblichen Installationen aber nicht doppelt gepflegt werden.
Clientbibliothek
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-serverGET /api/v1/trust-storePOST /api/v1/licenses/validate- Kompatibilitätsalias:
POST /v1/introspect
Management mit Authorization: Bearer $LICENSE_ADMIN_API_TOKEN:
GET /api/v1/licensesPOST /api/v1/licensesPOST /api/v1/licenses/importfor already signed tokensPOST /api/v1/licenses/{id}/revokePOST /api/v1/licenses/{id}/restore
Beispiel zur Validierung:
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. 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 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
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.