CallMeTechie
EN Anmelden
Home Produkte Blog Über mich Kontakt

Lizenzierung

v1.x · Updated vor 1 Monat

Ü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):

  1. Key laden — Env-Variable hat Vorrang, sonst aus DB (verschlüsselt in settings.license_signing_key_encrypted).
  2. Kein KeysetCommunityMode(), unlicensed=true, Enforcement, return.
  3. Kein Signing-KeysetCommunityMode(), Warnung loggen.
  4. Cached Token probieren — JWT aus /data/.license-token mit HS256 verifizieren. Fingerprint muss passen. Bei Erfolg anwenden, Background-Refresh starten.
  5. Online validieren — POST an config.license.server mit {license_key, hardware_fingerprint, device_name, product_slug}. Token speichern.
  6. Fallback auf expired Token — wenn Lizenzserver unerreichbar und ignoreExpiration-Token existiert.
  7. Letzter FallbacksetCommunityMode() 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:

  1. /sys/class/dmi/id/product_uuid (BIOS/Mainboard)
  2. os.cpus()[].model (CPU-Modell)
  3. /proc/meminfo (MemTotal)
  4. Fallback: /etc/machine-id, dann os.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():

  1. Für jedes Limit-Feature (vpn_peers, http_routes, l4_routes) wird die Ist-Menge gezählt.
  2. Ist count > limit, werden count - limit älteste Einträge (ORDER BY created_at ASC) auf enabled=0 gesetzt.
  3. WireGuard-Config bzw. Caddy-Config werden synchronisiert — deaktivierte Peers/Routes verschwinden vom Wire.
  4. Activity-Log-Eintrag peer_license_disabled / route_license_disabled.
  5. 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):

  1. Flag in COMMUNITY_FALLBACK — meist false, als Limit meist 0.
  2. API-Guardrouter.use(requireFeature('X')) oder pro-Route.
  3. UI-Guard — Template-Wrap mit {% if license.features.X %} und Lock-Fallback.
  4. 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

Quelldateien

  • src/services/license.js — Kern-Service, COMMUNITY_FALLBACK, validateLicense, enforceLimitsInternal.
  • src/middleware/license.jsinjectLicense, requireFeature, requireLimit, requireFeatureField.
  • src/services/settings.js — DB-gestützte Key-Speicherung für UI-Aktivierung.
  • config/default.jslicense.server, license.tokenPath, license.key, license.signingKey.

Cookie Settings

Wir verwenden Cookies, um Ihre Erfahrung zu verbessern. Essentielle Cookies sind immer aktiv.

Datenschutzerklärung
ESC
↑↓ navigate open esc close