Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a193b9d6e9 | ||
|
|
56974a4308 |
+1
-1
@@ -1 +1 @@
|
|||||||
1.2.6
|
1.2.8
|
||||||
|
|||||||
+62
-3
@@ -39,6 +39,31 @@ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey,
|
|||||||
LICENSE_TYPES = ("trial", "standard", "custom", "enterprise")
|
LICENSE_TYPES = ("trial", "standard", "custom", "enterprise")
|
||||||
ALL_MODULES = ("dhcp", "fileshare", "maintenance")
|
ALL_MODULES = ("dhcp", "fileshare", "maintenance")
|
||||||
|
|
||||||
|
# Rolle der lizenzierten Instanz, eingebettet in JEDE ausgestellte
|
||||||
|
# Lizenzdatei (signiert, siehe issue_license) -- NICHT nur DB-seitige
|
||||||
|
# Buchfuehrung wie das gleichnamige, bislang ungenutzte licenses.role-Feld
|
||||||
|
# in der Master-DB vorher. Ermoeglicht einer lizenzierten Instanz, anhand
|
||||||
|
# ihrer EIGENEN, geladenen Lizenz strukturell zu erkennen, was sie darf:
|
||||||
|
# "master" -- die Wurzel-Instanz. Wird AUSSCHLIESSLICH lokal per
|
||||||
|
# create_master_license.py erzeugt, NIE ueber die
|
||||||
|
# normale Ticket-/Ausstellungs-UI -- das waere sonst
|
||||||
|
# eine Moeglichkeit, sich selbst zur Wurzel zu
|
||||||
|
# erklaeren. Einzige Rolle, die weitere
|
||||||
|
# "license_server"-Lizenzen ausstellen darf.
|
||||||
|
# "license_server" -- ein vom Master (oder einer anderen "master"-Instanz)
|
||||||
|
# ausgestellter Lizenzserver ("Sub-Lizenzserver").
|
||||||
|
# Darf alles, was ein Lizenzserver koennen soll
|
||||||
|
# (Kunden/Tickets/Lizenzen fuer eigene TESM-Kunden
|
||||||
|
# ausstellen, Benutzer/Gruppen/Einstellungen
|
||||||
|
# verwalten) -- AUSSER selbst weitere
|
||||||
|
# "license_server"-Lizenzen ausstellen. Diese Sperre
|
||||||
|
# ist strukturell (haengt an der eigenen Rolle, nicht
|
||||||
|
# an einem vergebbaren Recht) und daher auch von einem
|
||||||
|
# lokalen Admin dieser Instanz nicht aufhebbar.
|
||||||
|
# "customer" -- eine gewoehnliche TESM-Kundeninstanz (Default,
|
||||||
|
# unveraendertes Verhalten).
|
||||||
|
LICENSE_ROLES = ("master", "license_server", "customer")
|
||||||
|
|
||||||
GRACE_PERIOD_DAYS = 30 # nach Ablauf, bevor lizenzpflichtige Funktionen tatsächlich abgeschaltet werden
|
GRACE_PERIOD_DAYS = 30 # nach Ablauf, bevor lizenzpflichtige Funktionen tatsächlich abgeschaltet werden
|
||||||
EXPIRY_WARNING_DAYS = 30 # Vorlauf für die orange "läuft bald ab"-Anzeige
|
EXPIRY_WARNING_DAYS = 30 # Vorlauf für die orange "läuft bald ab"-Anzeige
|
||||||
HEARTBEAT_WARNING_DAYS = 14 # Nichterreichbarkeits-Hinweis (rein informativ, schaltet nie etwas ab)
|
HEARTBEAT_WARNING_DAYS = 14 # Nichterreichbarkeits-Hinweis (rein informativ, schaltet nie etwas ab)
|
||||||
@@ -120,13 +145,23 @@ LIFETIME_EXPIRES_AT = "2099-12-31T00:00:00Z"
|
|||||||
|
|
||||||
def issue_license(*, customer, license_type, modules, valid_days,
|
def issue_license(*, customer, license_type, modules, valid_days,
|
||||||
master_private_key_b64, master_public_key_b64,
|
master_private_key_b64, master_public_key_b64,
|
||||||
master_endpoint, vendor, license_id=None, now=None, lifetime=False):
|
master_endpoint, vendor, license_id=None, now=None, lifetime=False,
|
||||||
|
role="customer"):
|
||||||
"""Vom MASTER aufgerufen: erzeugt eine neue, signierte Lizenz samt
|
"""Vom MASTER aufgerufen: erzeugt eine neue, signierte Lizenz samt
|
||||||
frischem Pro-Lizenz-Schlüsselpaar.
|
frischem Pro-Lizenz-Schlüsselpaar.
|
||||||
|
|
||||||
lifetime=True ignoriert valid_days komplett und setzt stattdessen das
|
lifetime=True ignoriert valid_days komplett und setzt stattdessen das
|
||||||
feste Sentinel-Datum LIFETIME_EXPIRES_AT -- siehe dortigen Kommentar.
|
feste Sentinel-Datum LIFETIME_EXPIRES_AT -- siehe dortigen Kommentar.
|
||||||
|
|
||||||
|
role (siehe LICENSE_ROLES): "customer" (Default, unverändertes
|
||||||
|
Verhalten) für gewöhnliche TESM-Kundeninstanzen, "license_server" für
|
||||||
|
einen vom Aufrufer ausgestellten Sub-Lizenzserver. role="master" darf
|
||||||
|
NUR von create_master_license.py verwendet werden (lokal, ohne diese
|
||||||
|
Funktion über die normale Ticket-UI aufzurufen) -- diese Funktion prüft
|
||||||
|
das nicht selbst, das Verbot ist stattdessen dadurch durchgesetzt, dass
|
||||||
|
die App-seitige Ausstellungsfunktion role="master" gar nicht erst als
|
||||||
|
Option anbietet (siehe app.py can_issue_license_server_licenses()).
|
||||||
|
|
||||||
Rückgabe: (license_file, license_pubkey) -- license_file ist die
|
Rückgabe: (license_file, license_pubkey) -- license_file ist die
|
||||||
komplette, an den Kunden auszuhändigende Datei (inkl. dem PRIVATEN
|
komplette, an den Kunden auszuhändigende Datei (inkl. dem PRIVATEN
|
||||||
Lizenzschlüssel); license_pubkey ist NUR für die Master-Datenbank
|
Lizenzschlüssel); license_pubkey ist NUR für die Master-Datenbank
|
||||||
@@ -134,6 +169,8 @@ def issue_license(*, customer, license_type, modules, valid_days,
|
|||||||
dieser Lizenz) und wird nicht an den Kunden weitergegeben."""
|
dieser Lizenz) und wird nicht an den Kunden weitergegeben."""
|
||||||
if license_type not in LICENSE_TYPES:
|
if license_type not in LICENSE_TYPES:
|
||||||
raise ValueError(f"Unbekannter Lizenztyp: {license_type!r} (erlaubt: {LICENSE_TYPES})")
|
raise ValueError(f"Unbekannter Lizenztyp: {license_type!r} (erlaubt: {LICENSE_TYPES})")
|
||||||
|
if role not in LICENSE_ROLES:
|
||||||
|
raise ValueError(f"Unbekannte Rolle: {role!r} (erlaubt: {LICENSE_ROLES})")
|
||||||
unknown = set(modules) - set(ALL_MODULES)
|
unknown = set(modules) - set(ALL_MODULES)
|
||||||
if unknown:
|
if unknown:
|
||||||
raise ValueError(f"Unbekannte Module: {sorted(unknown)} (erlaubt: {ALL_MODULES})")
|
raise ValueError(f"Unbekannte Module: {sorted(unknown)} (erlaubt: {ALL_MODULES})")
|
||||||
@@ -151,6 +188,7 @@ def issue_license(*, customer, license_type, modules, valid_days,
|
|||||||
"master_pubkey": master_public_key_b64,
|
"master_pubkey": master_public_key_b64,
|
||||||
"master_endpoint": master_endpoint,
|
"master_endpoint": master_endpoint,
|
||||||
"vendor": vendor,
|
"vendor": vendor,
|
||||||
|
"role": role,
|
||||||
}
|
}
|
||||||
signature = sign_payload(payload, master_private_key_b64)
|
signature = sign_payload(payload, master_private_key_b64)
|
||||||
license_file = {**payload, "license_privkey": license_priv, "signature": signature}
|
license_file = {**payload, "license_privkey": license_priv, "signature": signature}
|
||||||
@@ -190,6 +228,12 @@ def license_status(license_file: dict, now=None) -> dict:
|
|||||||
"type": license_file.get("type"),
|
"type": license_file.get("type"),
|
||||||
"customer": license_file.get("customer"),
|
"customer": license_file.get("customer"),
|
||||||
"expires_at": license_file.get("expires_at"),
|
"expires_at": license_file.get("expires_at"),
|
||||||
|
# .get(..., "customer"): aeltere, vor Einfuehrung von role signierte
|
||||||
|
# Lizenzdateien haben dieses Feld noch nicht -- fehlend bedeutet
|
||||||
|
# unveraendert "gewoehnliche Kundeninstanz", nie "master"/
|
||||||
|
# "license_server" (sonst koennte eine alte Datei rueckwirkend
|
||||||
|
# unbeabsichtigt privilegiert erscheinen).
|
||||||
|
"role": license_file.get("role", "customer"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -231,11 +275,24 @@ def system_fingerprint():
|
|||||||
# gebunden ist, siehe Plan-Abschnitt "Warum das die Doppelnutzung wirksam
|
# gebunden ist, siehe Plan-Abschnitt "Warum das die Doppelnutzung wirksam
|
||||||
# verhindert".
|
# verhindert".
|
||||||
|
|
||||||
def build_client_request(action, license_file, nonce=None) -> dict:
|
def build_client_request(action, license_file, nonce=None, report=None) -> dict:
|
||||||
"""Vom CLIENT aufgerufen: erzeugt eine signierte Anfrage (action:
|
"""Vom CLIENT aufgerufen: erzeugt eine signierte Anfrage (action:
|
||||||
"activate"/"deactivate"/"heartbeat"). Das Ergebnis ist unverändert für
|
"activate"/"deactivate"/"heartbeat"). Das Ergebnis ist unverändert für
|
||||||
einen Online-API-Aufruf nutzbar; für den Offline-Fall wird es
|
einen Online-API-Aufruf nutzbar; für den Offline-Fall wird es
|
||||||
zusätzlich mit encode_code() in einen kopierbaren Code umgewandelt."""
|
zusätzlich mit encode_code() in einen kopierbaren Code umgewandelt.
|
||||||
|
|
||||||
|
report: NUR für Heartbeats einer role="license_server"-Lizenz gedacht
|
||||||
|
(siehe Lizenzserver-app.py _build_own_issuance_report()) -- ein
|
||||||
|
optionaler Schnappschuss der eigenen Kunden/Tickets/Lizenzen dieses
|
||||||
|
Sub-Lizenzservers, den der Master beim Empfang in seine
|
||||||
|
reported_sub_*-Tabellen übernimmt, um einen Gesamtüberblick zu haben.
|
||||||
|
Bewusst Teil DIESER generischen, gemeinsam genutzten Funktion (nicht
|
||||||
|
ein eigenes Protokoll-Feature) -- ein normaler TESM-Client/eine
|
||||||
|
"customer"-Lizenz übergibt hier einfach nie einen Wert, das Feld fehlt
|
||||||
|
dann in der Payload wie bisher (keine Verhaltensänderung für alle
|
||||||
|
bestehenden Aufrufer). Wie jedes andere Feld wird auch report
|
||||||
|
mitsigniert -- ein Angreifer ohne den privaten Lizenzschlüssel kann
|
||||||
|
keine gefälschten Berichte unterschieben."""
|
||||||
if action not in ("activate", "deactivate", "heartbeat"):
|
if action not in ("activate", "deactivate", "heartbeat"):
|
||||||
raise ValueError(f"Unbekannte Aktion: {action!r}")
|
raise ValueError(f"Unbekannte Aktion: {action!r}")
|
||||||
payload = {
|
payload = {
|
||||||
@@ -252,6 +309,8 @@ def build_client_request(action, license_file, nonce=None) -> dict:
|
|||||||
"nonce": nonce or uuid.uuid4().hex,
|
"nonce": nonce or uuid.uuid4().hex,
|
||||||
"timestamp": _iso(time.time()),
|
"timestamp": _iso(time.time()),
|
||||||
}
|
}
|
||||||
|
if report is not None:
|
||||||
|
payload["report"] = report
|
||||||
signature = sign_payload(payload, license_file["license_privkey"])
|
signature = sign_payload(payload, license_file["license_privkey"])
|
||||||
return {**payload, "signature": signature}
|
return {**payload, "signature": signature}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user