Lizenzierung
Überblick
GateControl ist Open-Source-fähig: Ohne Lizenzschlüssel läuft die Anwendung in einem abgespeckten Community-Modus. Kommerzielle Funktionen werden durch ein zentrales Feature-Flag-System freigeschaltet. Das System kennt drei Zustände — unlicensed, community-mit-Key und pro/lifetime — und fällt bei jedem Ausfall sauber auf den letzten sicheren Zustand zurück.
Der gesamte Feature-Gate läuft über eine einzige Query-API: license.hasFeature(key). Diese eine Funktion wird von Middleware, Template-Engine und Services gleichermassen konsultiert. Limits (Peer-Anzahl, Route-Anzahl) kommen ebenfalls aus demselben Feature-Objekt, werden aber über eine zweite Variante (getFeatureLimit / requireLimit) abgefragt.
Licensing ist bewusst defensiv gebaut: Der Server darf nicht blockieren, wenn der Lizenzserver unerreichbar ist. Daten dürfen nie zerstört werden, wenn ein Downgrade erkannt wird — bestehende Peers/Routes werden nur deaktiviert, nicht gelöscht. Ein neuer Lizenzschlüssel reaktiviert sie.
Architektur
┌──────────────────┐
│ config/license │ GC_LICENSE_KEY, GC_LICENSE_SIGNING_KEY,
│ env oder DB │ tokenPath (JWT-Cache)
└────────┬─────────┘
│
▼
┌──────────────────┐ ┌────────────────────┐
│ license.js │◀──────▶│ callmetechie.de │
│ cachedFeatures │ HTTPS │ Lizenzserver │
│ cachedPlan │ └────────────────────┘
└─┬──────┬────────┬┘
│ │ │
hasFeature getFeatureLimit isWithinLimit
│ │ │
┌──────┴──┐ │ ┌────┴──────┐
│Middleware│ │ │Templates │
│requireX │ │ │license.features.*│
└─────────┘ │ └──────────┘
▼
┌──────────────────┐
│ Services │
│ (opportunistisch) │
└──────────────────┘
Komponenten
| Komponente | Pfad | Rolle |
|---|---|---|
| License-Service | src/services/license.js |
Validierung, Caching, Refresh, Enforcement |
| Middleware | src/middleware/license.js |
requireFeature, requireLimit, injectLicense |
| JWT-Cache | /data/.license-token (default) |
Offline-Validierung via HMAC-Signatur |
| Hardware-Fingerprint | intern | SHA-256 über DMI-UUID + CPU + RAM |
Lizenz-Modi
| Modus | Bedingung | Features |
|---|---|---|
| unlicensed | kein Lizenzschlüssel | COMMUNITY_FALLBACK (hardcodiert) |
| community | Lizenzschlüssel mit Plan community |
Server-Response |
| pro / lifetime | Lizenzschlüssel mit entsprechendem Plan | Server-Response |
Das Flag unlicensed ist separat vom Plan. Ein Nutzer mit registriertem Community-Key und plan='community' ist nicht unlicensed — er bekommt einen aktuellen Feature-Satz vom Lizenzserver statt des eingefrorenen Fallbacks. Diese Unterscheidung ermöglicht es, Community-Features serverseitig zu erweitern ohne neuen Client-Release.
Feature-Katalog
Der COMMUNITY_FALLBACK (src/services/license.js Zeile 15) ist die Source-of-Truth für den unlizenzierten Modus. Numerische Werte sind Limits (-1 = unbegrenzt), Booleans sind Feature-Toggles.
Limits
| Key | Community | Bedeutung |
|---|---|---|
vpn_peers |
3 | Aktive WG-Peers |
http_routes |
1 | Aktive HTTP-Routen |
l4_routes |
0 | Aktive L4-Routen |
gateway_peers |
1 | Home-Gateway-Peers |
gateway_http_targets |
3 | HTTP-Routen pro Gateway |
Routing-Features
route_auth, ip_access_control, peer_acl, rate_limiting, compression, custom_headers, load_balancing, retry_on_error, circuit_breaker, request_mirroring, bot_blocking, request_debugging, acme_custom_ca, sticky_sessions.
Monitoring und Operations
uptime_monitoring, traffic_history (true), prometheus_metrics, log_export, backup_restore (true), scheduled_backups, email_alerts, webhooks.
Integration
api_tokens, machine_binding, custom_branding, custom_dns, internal_dns.
Remote Desktop
remote_desktop (alle RDP-Endpoints), rdp_via_gateway (access_mode=gateway), split_tunnel_preset.
Gateway-spezifisch
gateway_tcp_routing, gateway_wol.
Validierungsfluss
validateLicense() läuft einmal beim Start und dann alle 7 Tage (refreshLicenseInBackground):
- Key laden — Env-Variable hat Vorrang, sonst aus DB (verschlüsselt in
settings.license_signing_key_encrypted). - Kein Key →
setCommunityMode(),unlicensed=true, Enforcement, return. - Kein Signing-Key →
setCommunityMode(), Warnung loggen. - Cached Token probieren — JWT aus
/data/.license-tokenmit HS256 verifizieren. Fingerprint muss passen. Bei Erfolg anwenden, Background-Refresh starten. - Online validieren — POST an
config.license.servermit{license_key, hardware_fingerprint, device_name, product_slug}. Token speichern. - Fallback auf expired Token — wenn Lizenzserver unerreichbar und
ignoreExpiration-Token existiert. - Letzter Fallback —
setCommunityMode()und weiterlaufen.
Die Schritte 5 und 6 sind wichtig: Ein Netzwerkausfall darf den Server nicht zur Blockade führen. Ein abgelaufenes Token ist besser als gar kein Feature.
Hardware-Fingerprint
getHardwareFingerprint() baut einen SHA-256 über:
/sys/class/dmi/id/product_uuid(BIOS/Mainboard)os.cpus()[].model(CPU-Modell)/proc/meminfo(MemTotal)- Fallback:
/etc/machine-id, dannos.hostname()
Wichtig: device_name im Request-Payload nutzt den Hostname aus config.app.baseUrl (new URL(baseUrl).hostname), nicht os.hostname(). Das verhindert, dass Container-Instanzen bei jedem Neustart als neues Gerät gezählt werden. Siehe auch feedback_license_domain.md.
Enforcement bei Downgrade
Wenn ein Lizenzrefresh einen Plan-Wechsel erkennt (previousPlan !== cachedPlan), läuft enforceLimitsInternal():
- Für jedes Limit-Feature (
vpn_peers,http_routes,l4_routes) wird die Ist-Menge gezählt. - Ist
count > limit, werdencount - limitälteste Einträge (ORDER BYcreated_atASC) aufenabled=0gesetzt. - WireGuard-Config bzw. Caddy-Config werden synchronisiert — deaktivierte Peers/Routes verschwinden vom Wire.
- Activity-Log-Eintrag
peer_license_disabled/route_license_disabled. - Optional Email-Alert wenn
email_alerts_enabled=true.
Datensätze werden nie gelöscht. Ein späterer Upgrade reaktiviert sie nicht automatisch, aber der Admin sieht sie im UI als deaktiviert und kann manuell enable.
Pattern für neue Features
Bei jedem neuen kostenpflichtigen Feature müssen vier Dinge passieren (siehe feedback_new_feature_license.md):
- Flag in COMMUNITY_FALLBACK — meist
false, als Limit meist0. - API-Guard —
router.use(requireFeature('X'))oder pro-Route. - UI-Guard — Template-Wrap mit
{% if license.features.X %}und Lock-Fallback. - Service-Fallback — bei Downgrade darf kein Service crashen, sondern nur den Feature-Code-Pfad überspringen.
Key-Rotation und Removal
removeLicense() löscht das JWT, stoppt den Refresh-Timer, setzt Community-Mode, entfernt den Key aus der DB und startet Enforcement. _overrideForTest(features) patcht cachedFeatures direkt — nur für Test-Setup, nicht für Production.
Testen des Licensing-Systems
Der Service exportiert _overrideForTest und _getHardwareFingerprint für Testharnesse. In Test-Setup wird der echte Refresh-Timer nicht gestartet (keine startLicenseRefresh()-Call), damit Tests determiniert bleiben.
Siehe auch
- routing.md — HTTP/L4-Routes werden über
route_auth,rate_limitingetc. gated. - home-gateway.md —
gateway_peers,gateway_tcp_routing,gateway_wol. - rdp-routes.md —
remote_desktop,rdp_via_gateway. - internal-dns.md —
internal_dns. - ../API.md — HTTP-Antwortformate für 403-Feature-Errors.
Quelldateien
src/services/license.js— Kern-Service,COMMUNITY_FALLBACK,validateLicense,enforceLimitsInternal.src/middleware/license.js—injectLicense,requireFeature,requireLimit,requireFeatureField.src/services/settings.js— DB-gestützte Key-Speicherung für UI-Aktivierung.config/default.js—license.server,license.tokenPath,license.key,license.signingKey.