init
This commit is contained in:
@@ -1,2 +1,162 @@
|
||||
# license-managent-system
|
||||
# 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.
|
||||
|
||||
Reference in New Issue
Block a user