Ausgabe fuer Sub-Lizenzserver: kein Weg zur Rolle master
This commit is contained in:
1 parent
fd333f60ff
commit
157a244900
9 files changed
+548
-124
No files matched your search
@@ -8,6 +8,7 @@ musste (und ``licensing.py`` byte-identisch gehalten werden *musste*).
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
@@ -51,6 +52,18 @@ ENV_PREFIX = "TESM_LICENSE_"
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def is_master_edition() -> bool:
|
||||
"""Ist das hier die Master-Ausgabe?
|
||||
|
||||
Entscheidet die **Anwesenheit** des Teilpakets ``tesm_license.master``, nicht
|
||||
eine Einstellung: eine Einstellung koennte der lokale Administrator eines
|
||||
Sub-Lizenzservers umstellen, eine fehlende Datei nicht. Das Release fuer
|
||||
Sub-Lizenzserver bringt das Teilpaket nicht mit -- dort gibt es also
|
||||
ueberhaupt keinen Code, der die Rolle ``master`` erzeugen koennte.
|
||||
"""
|
||||
return importlib.util.find_spec("tesm_license.master") is not None
|
||||
|
||||
|
||||
def master_key_path(config: Any) -> Path:
|
||||
return Path(config.data_dir) / "master_signing_key.json"
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ from tesm_core.licensing_client import LICENSED_FEATURES, LicenseError
|
||||
|
||||
import tesm_licensing as lic
|
||||
|
||||
from .. import is_master_edition
|
||||
from ..services import master_key
|
||||
from .common import can_issue_license_server_licenses, log_event, signing_key
|
||||
|
||||
@@ -62,6 +63,7 @@ def overview():
|
||||
),
|
||||
signing_key_health=master_key.health(extension.registry["master_key_path"]),
|
||||
may_issue_license_server=can_issue_license_server_licenses(),
|
||||
master_edition=is_master_edition(),
|
||||
grace_days=lic.GRACE_PERIOD_DAYS,
|
||||
)
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
"""Kommandozeilenwerkzeuge des Lizenzservers.
|
||||
|
||||
Enthaelt insbesondere ``bootstrap`` -- die eigene Lizenz der Wurzelinstanz.
|
||||
``init-key`` erzeugt den Signaturschluessel dieser Installation -- den braucht
|
||||
**jeder** Lizenzserver, um Kundenlizenzen zu signieren, auch ein
|
||||
Sub-Lizenzserver.
|
||||
|
||||
Warum das ein lokales Skript ist und keine Funktion in der Oberflaeche: die
|
||||
Master-Lizenz ist die einzige Lizenz, fuer die es **keine hoehere Instanz gibt**,
|
||||
die sie bestaetigen koennte. Ein Aktivierungs-Handshake gegen sich selbst waere
|
||||
Theater. Das Skript signiert die Datei mit dem eigenen Schluessel und setzt den
|
||||
Aktivierungszustand direkt -- und nur so kann eine Lizenzdatei je die Rolle
|
||||
``master`` tragen. Die Ticket-Oberflaeche bietet diese Rolle an keiner Stelle an.
|
||||
``bootstrap`` stellt die eigene Lizenz der Wurzelinstanz aus und ist damit der
|
||||
einzige Weg, auf dem eine Lizenzdatei die Rolle ``master`` bekommt (ueber
|
||||
Tickets ist sie auf jedem Server abgewiesen). Die Arbeit dazu liegt im
|
||||
Teilpaket ``tesm_license.master``, das nur die Master-Ausgabe mitbringt; fehlt
|
||||
es, sagt der Befehl das und tut nichts. Siehe dort fuer die Begruendung.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -20,7 +21,6 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import tesm_licensing as lic
|
||||
import tesm_licensing_server as licsrv
|
||||
from tesm_core import audit
|
||||
from tesm_core.cli_web import add_web_setup, run_web_setup
|
||||
from tesm_core.db import Database
|
||||
@@ -28,7 +28,7 @@ from tesm_core.extension import core
|
||||
from tesm_core.rbac import service as rbac
|
||||
from tesm_core.security import passwords
|
||||
|
||||
from . import create_app, master_key_path
|
||||
from . import create_app, is_master_edition, master_key_path
|
||||
from .services import legacy_import, master_key
|
||||
|
||||
|
||||
@@ -62,73 +62,29 @@ def cmd_init_key(args: argparse.Namespace) -> int:
|
||||
|
||||
|
||||
def cmd_bootstrap(args: argparse.Namespace) -> int:
|
||||
"""Stellt die eigene Lizenz dieser Wurzelinstanz aus."""
|
||||
"""Stellt die eigene Lizenz dieser Wurzelinstanz aus -- nur in der Master-Ausgabe."""
|
||||
if not is_master_edition():
|
||||
for zeile in (
|
||||
"Diese Ausgabe ist fuer Sub-Lizenzserver und kann keine Master-Lizenz erzeugen.",
|
||||
"",
|
||||
"Ein Sub-Lizenzserver bekommt seine Lizenz vom Master-Lizenzserver und spielt",
|
||||
"sie unter 'Eigene Lizenz' ein. Danach stellt er selbst Kundenlizenzen aus --",
|
||||
"mit seinem eigenen Signaturschluessel, der beim Installieren entstanden ist.",
|
||||
"",
|
||||
"Die Master-Ausgabe liegt im Repository tesm-license-master.",
|
||||
):
|
||||
print(zeile, file=sys.stderr)
|
||||
return 2
|
||||
|
||||
from .master import bootstrap as eigene_lizenz_ausstellen
|
||||
|
||||
app, extension = _app_and_core()
|
||||
key = master_key.ensure(master_key_path(extension.config))
|
||||
|
||||
with app.app_context(), extension.database.session() as conn:
|
||||
vendor = {
|
||||
"name": str(extension.settings.get(conn, "vendor_name") or "Eigenbetrieb"),
|
||||
"email": str(extension.settings.get(conn, "vendor_email") or ""),
|
||||
"phone": str(extension.settings.get(conn, "vendor_phone") or ""),
|
||||
"address": str(extension.settings.get(conn, "vendor_address") or ""),
|
||||
"logo_base64": "",
|
||||
}
|
||||
bundle, _ = licsrv.issue_license(
|
||||
customer={"name": args.name, "contact_email": vendor["email"]},
|
||||
license_type="enterprise",
|
||||
modules=[],
|
||||
valid_days=int(args.days),
|
||||
master_private_key_b64=key.private_key,
|
||||
master_public_key_b64=key.public_key,
|
||||
# Bewusst leer: es gibt keinen uebergeordneten Endpunkt. Damit
|
||||
# laeuft der Handshake fuer diese Lizenz nie ins Netz.
|
||||
master_endpoint="",
|
||||
vendor=vendor,
|
||||
role="master",
|
||||
)
|
||||
document, client_key = lic.split_bundle(bundle)
|
||||
|
||||
config = extension.config
|
||||
config.license_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
config.license_path.write_text(
|
||||
json.dumps(document, indent=2, sort_keys=True, ensure_ascii=False) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
try:
|
||||
config.license_path.chmod(0o640)
|
||||
except OSError:
|
||||
pass
|
||||
from tesm_core.keystore import write_private_file
|
||||
|
||||
write_private_file(
|
||||
config.license_key_path,
|
||||
json.dumps({"license_id": document["license_id"], "client_key": client_key}) + "\n",
|
||||
)
|
||||
|
||||
with extension.database.transaction(conn):
|
||||
conn.execute(
|
||||
"INSERT INTO trusted_issuer_keys (key_id, public_key, label, source, added_at, "
|
||||
"added_by) VALUES (?,?,?,'bootstrap',datetime('now'),'cli') "
|
||||
"ON CONFLICT(key_id) DO NOTHING",
|
||||
(key.key_id, key.public_key, "Eigener Signaturschluessel"),
|
||||
)
|
||||
# Es gibt keine hoehere Instanz, die diese Lizenz aktivieren koennte.
|
||||
extension.settings.set(conn, "license_activation_status", "active", actor="cli")
|
||||
extension.settings.set(conn, "license_pending_request", "", actor="cli")
|
||||
audit.log(
|
||||
conn,
|
||||
actor="cli",
|
||||
action="own_license.bootstrapped",
|
||||
target=document["license_id"],
|
||||
detail=f"Rolle master, {args.days} Tage, Schluessel {key.key_id}",
|
||||
severity="warning",
|
||||
)
|
||||
state = extension.license.reload(conn) if extension.license else None
|
||||
|
||||
document, state = eigene_lizenz_ausstellen(
|
||||
app, extension, days=int(args.days), name=args.name
|
||||
)
|
||||
print(f"Master-Lizenz ausgestellt: {document['license_id']}")
|
||||
print(f"Gueltig bis: {document['expires_at']}")
|
||||
print(f"Datei: {config.license_path}")
|
||||
print(f"Datei: {extension.config.license_path}")
|
||||
if state is not None:
|
||||
print(f"Zustand: gueltig={state.valid}, aktiviert={state.activated}, Rolle={state.role}")
|
||||
return 0
|
||||
@@ -434,7 +390,12 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
init_key.set_defaults(func=cmd_init_key)
|
||||
|
||||
bootstrap = sub.add_parser(
|
||||
"bootstrap", help="Eigene Master-Lizenz dieser Wurzelinstanz ausstellen"
|
||||
"bootstrap",
|
||||
help=(
|
||||
"Eigene Master-Lizenz dieser Wurzelinstanz ausstellen"
|
||||
if is_master_edition()
|
||||
else "nicht in dieser Ausgabe (nur Master-Lizenzserver)"
|
||||
),
|
||||
)
|
||||
bootstrap.add_argument("--days", type=int, default=3650)
|
||||
bootstrap.add_argument("--name", default="Lizenzserver (Eigenbetrieb)")
|
||||
|
||||
@@ -116,9 +116,16 @@
|
||||
{% endif %}
|
||||
</section>
|
||||
{% else %}
|
||||
{{ empty_state('Keine gültige Lizenz',
|
||||
'Spielen Sie eine Lizenzdatei ein oder erzeugen Sie mit "tesm-license-admin bootstrap"
|
||||
die eigene Lizenz der Wurzelinstanz.', 'key') }}
|
||||
{% if master_edition %}
|
||||
{{ empty_state('Keine gültige Lizenz',
|
||||
'Spielen Sie eine Lizenzdatei ein oder erzeugen Sie mit "tesm-license-admin bootstrap"
|
||||
die eigene Lizenz der Wurzelinstanz.', 'key') }}
|
||||
{% else %}
|
||||
{{ empty_state('Keine gültige Lizenz',
|
||||
'Diese Ausgabe ist ein Sub-Lizenzserver: fordern Sie beim Master-Lizenzserver eine
|
||||
Lizenz für diesen Server an und spielen Sie die Datei hier ein. Eine Master-Lizenz
|
||||
kann diese Ausgabe nicht erzeugen.', 'key') }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if pending %}
|
||||
|
||||
Reference in new issue
Block a user