163 lines
6.7 KiB
Markdown
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.
|