mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 08:10:47 +08:00
Release 0.4.5: installer DB wizard, credential key, lean offline Setup.
Choose bundled or external PostgreSQL in the Setup wizard with connection validation; optional NETX_CREDENTIAL_SECRET_KEY; single desktop shortcut; faster uninstall and tray startup for offline Windows packages. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
86dfd41f44
commit
97393cd407
20 changed files with 1531 additions and 146 deletions
|
|
@ -15,6 +15,7 @@ from .schema_patches import (
|
|||
apply_topology_schema_safety_net,
|
||||
run_alembic_upgrade_to_head,
|
||||
)
|
||||
from .ne_crypto import ensure_credential_secret_key
|
||||
from .security_bootstrap import assert_secure_defaults_or_exit
|
||||
from .ume_runtime import start_api_sideband_threads, start_device_schedulers
|
||||
import netx_api.ume_support as ume_support
|
||||
|
|
@ -42,6 +43,8 @@ def _configure_ume_diag_logging() -> None:
|
|||
def run_api_startup() -> None:
|
||||
"""Full API boot sequence previously inlined in ``main.on_startup``."""
|
||||
assert_secure_defaults_or_exit()
|
||||
# Persist Fernet key for managed-NE passwords (same idea as JWT secret file).
|
||||
ensure_credential_secret_key()
|
||||
_configure_ume_diag_logging()
|
||||
_log.info(
|
||||
"startup: ne_exec_policy_enabled=%s",
|
||||
|
|
|
|||
|
|
@ -80,8 +80,9 @@ class Settings(BaseSettings):
|
|||
ume_topo_nodes_path: str = "/restconf/data/zte-resources-module:TopoNodes"
|
||||
ume_topological_links_path: str = "/restconf/data/zte-resources-module:TopologicalLinks"
|
||||
ume_sync_alarms_history_every_hours: int = 24
|
||||
# Managed NE credentials (Fernet key; generate with cryptography.fernet.Fernet.generate_key())
|
||||
# Managed NE credentials (Fernet). Empty = auto-generate & persist to credential_secret_file.
|
||||
credential_secret_key: str = ""
|
||||
credential_secret_file: str = "data/auth/credential_secret"
|
||||
# Shared-server worker caps (sized for multi-operator use; raise if bastion/DB allow).
|
||||
ne_connect_max_workers: int = 8
|
||||
ne_connect_timeout_sec: int = 30
|
||||
|
|
|
|||
|
|
@ -1,18 +1,73 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from cryptography.fernet import Fernet, InvalidToken
|
||||
|
||||
from .config import settings
|
||||
|
||||
_log = logging.getLogger("netx.crypto")
|
||||
_cached_credential_key: str | None = None
|
||||
|
||||
|
||||
class CredentialCryptoError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def credential_secret_file_path() -> Path:
|
||||
raw = str(getattr(settings, "credential_secret_file", None) or "data/auth/credential_secret").strip()
|
||||
path = Path(raw)
|
||||
if not path.is_absolute():
|
||||
path = Path.cwd() / path
|
||||
return path
|
||||
|
||||
|
||||
def ensure_credential_secret_key() -> str:
|
||||
"""Resolve Fernet key: env ``NETX_CREDENTIAL_SECRET_KEY`` > file > generate once.
|
||||
|
||||
Packaged installs should also write the key into ``.env`` via setup_first_run;
|
||||
this startup/path fallback covers upgrades and missed first-run keys.
|
||||
"""
|
||||
global _cached_credential_key
|
||||
configured = str(settings.credential_secret_key or "").strip()
|
||||
if configured:
|
||||
return configured
|
||||
if _cached_credential_key:
|
||||
return _cached_credential_key
|
||||
|
||||
path = credential_secret_file_path()
|
||||
try:
|
||||
if path.is_file():
|
||||
existing = path.read_text(encoding="utf-8").strip()
|
||||
if existing:
|
||||
_cached_credential_key = existing
|
||||
settings.credential_secret_key = existing
|
||||
return existing
|
||||
except Exception:
|
||||
_log.exception("read credential secret file failed path=%s", path)
|
||||
|
||||
generated = Fernet.generate_key().decode("ascii")
|
||||
try:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(generated + "\n", encoding="utf-8")
|
||||
try:
|
||||
path.chmod(0o600)
|
||||
except Exception:
|
||||
pass
|
||||
_log.info("wrote per-install credential Fernet key to %s", path)
|
||||
except Exception:
|
||||
_log.exception(
|
||||
"write credential secret file failed path=%s; using in-memory key only",
|
||||
path,
|
||||
)
|
||||
_cached_credential_key = generated
|
||||
settings.credential_secret_key = generated
|
||||
return generated
|
||||
|
||||
|
||||
def _fernet() -> Fernet:
|
||||
key = str(settings.credential_secret_key or "").strip()
|
||||
if not key:
|
||||
raise CredentialCryptoError("credential_secret_key_not_configured")
|
||||
key = ensure_credential_secret_key()
|
||||
try:
|
||||
return Fernet(key.encode("ascii"))
|
||||
except Exception as exc:
|
||||
|
|
@ -37,4 +92,7 @@ def decrypt_secret(value: str) -> str:
|
|||
|
||||
|
||||
def credentials_configured() -> bool:
|
||||
return bool(str(settings.credential_secret_key or "").strip())
|
||||
try:
|
||||
return bool(ensure_credential_secret_key())
|
||||
except Exception:
|
||||
return False
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue