新增 oss/core/security/ 模块(852行): - jwt_auth.py: JWT签发/验证(HMAC-SHA256,零外部依赖) - csrf.py: CSRF Token生成与校验 - input_validator.py: JSON Schema校验+类型强制 - tls.py: 自签名证书生成+SSL上下文 新增 oss/core/ops/ 模块: - health.py: 增强版/health端点(CPU/内存/磁盘/运行时间) - metrics.py: Prometheus兼容/metrics端点 对接改造: - engine.py: 导出新模块 - manager.py: 注册/api/login /health /metrics路由 - middleware.py: CSRF+InputValidation中间件 - config.py: JWT_SECRET/CSRF_SECRET等配置项 - security.py→security/__init__.py: 合并插件沙箱与HTTP安全
812 lines
31 KiB
Python
812 lines
31 KiB
Python
"""NebulaShell Core Engine — 核心引擎
|
||
|
||
整合功能:
|
||
- 插件加载(目录结构)
|
||
- 生命周期管理
|
||
- 依赖解析
|
||
- 签名校验(RSA-SHA256)
|
||
- PL 注入(沙箱执行)
|
||
- 能力注册
|
||
- 文件监控与热重载
|
||
- HTTP 服务(子模块)
|
||
- REPL 终端(子模块)
|
||
- 全面防护:完整性检查、内存保护、行为审计、防篡改监控、降级恢复
|
||
- 数据存储接口(为 data-store 插件预留)
|
||
"""
|
||
import sys
|
||
import json
|
||
import re
|
||
import os
|
||
import time
|
||
import types
|
||
import hashlib
|
||
import threading
|
||
import traceback
|
||
import importlib.util
|
||
import functools
|
||
from pathlib import Path
|
||
from typing import Any, Optional, Callable
|
||
|
||
from oss.plugin.types import register_plugin_type
|
||
from oss.plugin.capabilities import scan_capabilities
|
||
from oss.logger.logger import Log
|
||
from oss.config import get_config
|
||
|
||
from oss.core.lifecycle import LifecycleManager, Lifecycle
|
||
from oss.core.security import IntegrityChecker, MemoryGuard, AuditLogger, TamperMonitor, FallbackManager, PluginPermissionError, PluginProxy
|
||
from oss.core.deps import DependencyError, DependencyResolver
|
||
from oss.core.datastore import DataStore
|
||
from oss.core.pl_injector import PLValidationError, PLInjector
|
||
from oss.core.watcher import HotReloadError, FileWatcher
|
||
from oss.core.signature import SignatureError, SignatureVerifier, PluginSigner
|
||
|
||
|
||
class PluginInfo:
|
||
"""插件信息"""
|
||
def __init__(self):
|
||
self.name: str = ""
|
||
self.version: str = ""
|
||
self.author: str = ""
|
||
self.description: str = ""
|
||
self.readme: str = ""
|
||
self.config: dict[str, Any] = {}
|
||
self.extensions: dict[str, Any] = {}
|
||
self.lifecycle: Any = None
|
||
self.capabilities: set[str] = set()
|
||
self.dependencies: list[str] = []
|
||
self.pl_injected: bool = False
|
||
self.file_hash: str = "" # 文件完整性 hash
|
||
|
||
|
||
class CapabilityRegistry:
|
||
"""能力注册表"""
|
||
def __init__(self, permission_check: bool = True):
|
||
self.providers: dict = {}
|
||
self.consumers: dict = {}
|
||
self.permission_check = permission_check
|
||
|
||
def register_provider(self, capability: str, plugin_name: str, instance: Any):
|
||
self.providers[capability] = {"plugin": plugin_name, "instance": instance}
|
||
if capability not in self.consumers:
|
||
self.consumers[capability] = []
|
||
|
||
def register_consumer(self, capability: str, plugin_name: str):
|
||
if capability not in self.consumers:
|
||
self.consumers[capability] = []
|
||
if plugin_name not in self.consumers[capability]:
|
||
self.consumers[capability].append(plugin_name)
|
||
|
||
def get_provider(self, capability: str, requester: str = "", allowed_plugins: list = None) -> Optional[Any]:
|
||
if capability not in self.providers:
|
||
return None
|
||
if self.permission_check and allowed_plugins is not None:
|
||
pn = self.providers[capability]["plugin"]
|
||
if pn != requester and pn not in allowed_plugins and "*" not in allowed_plugins:
|
||
raise PluginPermissionError(f"插件 '{requester}' 无权使用能力 '{capability}'")
|
||
return self.providers[capability]["instance"]
|
||
|
||
def has_capability(self, capability: str) -> bool:
|
||
return capability in self.providers
|
||
|
||
def get_consumers(self, capability: str) -> list:
|
||
return self.consumers.get(capability, [])
|
||
|
||
|
||
class PluginManager:
|
||
"""插件管理器 — Core 的核心"""
|
||
|
||
def __init__(self, permission_check: bool = True):
|
||
self.plugins: dict = {}
|
||
self.capability_registry = CapabilityRegistry(permission_check=permission_check)
|
||
self.permission_check = permission_check
|
||
self.enforce_signature = True
|
||
self.pl_injector = PLInjector(self)
|
||
self.lifecycle_manager = LifecycleManager()
|
||
self.dependency_resolver = DependencyResolver()
|
||
self.signature_verifier = SignatureVerifier()
|
||
self.hot_reload_watcher = None
|
||
|
||
# 全面防护
|
||
self.integrity_checker = IntegrityChecker()
|
||
self.memory_guard = MemoryGuard(self)
|
||
self.audit_logger = AuditLogger()
|
||
self.tamper_monitor = TamperMonitor(self)
|
||
self.fallback_manager = FallbackManager(self)
|
||
|
||
# 数据存储
|
||
self.data_store = DataStore()
|
||
|
||
# HTTP 服务 & REPL
|
||
self.http_server = None
|
||
self.repl_shell = None
|
||
|
||
# NBPF 组件
|
||
self.nbpf_loader = None
|
||
self._nbpf_initialized = False
|
||
|
||
# 插件目录映射
|
||
self._plugin_dirs: dict[str, Path] = {}
|
||
|
||
# ── NBPF 支持 ──
|
||
|
||
def _init_nbpf(self):
|
||
"""初始化 NBPF 加载器"""
|
||
if self._nbpf_initialized:
|
||
return
|
||
try:
|
||
from oss.core.nbpf import NBPFLoader, NBPCrypto, NIRCompiler
|
||
|
||
config = get_config()
|
||
self._trusted_keys_dir = Path(config.get("NBPF_TRUSTED_KEYS_DIR", "./data/nbpf-keys/trusted"))
|
||
rsa_keys_dir = Path(config.get("NBPF_RSA_KEYS_DIR", "./data/nbpf-keys/rsa"))
|
||
|
||
# 加载信任的 Ed25519 公钥
|
||
trusted_ed25519 = {}
|
||
if self._trusted_keys_dir.exists():
|
||
for kf in self._trusted_keys_dir.glob("*.pem"):
|
||
name = kf.stem
|
||
trusted_ed25519[name] = kf.read_bytes()
|
||
|
||
# 加载信任的 RSA 公钥
|
||
trusted_rsa = {}
|
||
if rsa_keys_dir.exists():
|
||
for kf in rsa_keys_dir.glob("*.pem"):
|
||
name = kf.stem
|
||
trusted_rsa[name] = kf.read_bytes()
|
||
|
||
# 加载 RSA 私钥(只匹配名称包含 rsa 的文件,避免误读 Ed25519 私钥)
|
||
rsa_private = None
|
||
private_dir = Path(config.get("NBPF_KEYS_DIR", "./data/nbpf-keys")) / "private"
|
||
if private_dir.exists():
|
||
pk_files = [f for f in private_dir.glob("*.pem") if "rsa" in f.name.lower()]
|
||
if not pk_files:
|
||
# 回退:匹配任意私钥(警告日志)
|
||
pk_files = list(private_dir.glob("*.pem"))
|
||
if pk_files:
|
||
Log.warn("Core", "未找到名称包含 'rsa' 的私钥文件,尝试加载第一个 .pem 文件(可能导致类型错误)")
|
||
if pk_files:
|
||
rsa_private = pk_files[0].read_bytes()
|
||
|
||
self.nbpf_loader = NBPFLoader(
|
||
crypto=NBPCrypto(),
|
||
compiler=NIRCompiler(),
|
||
trusted_ed25519_keys=trusted_ed25519,
|
||
trusted_rsa_keys=trusted_rsa,
|
||
rsa_private_key=rsa_private,
|
||
)
|
||
self._nbpf_initialized = True
|
||
Log.info("Core", "NBPF 加载器已初始化")
|
||
except Exception as e:
|
||
Log.warn("Core", f"NBPF 加载器初始化失败: {e}")
|
||
|
||
def load_nbpf(self, nbpf_path: Path, plugin_name: str = None) -> Optional[Any]:
|
||
"""加载 .nbpf 插件文件
|
||
|
||
如果插件作者不在本地信任列表中,会通过 CLI 交互询问用户是否信任。
|
||
信任后自动将公钥加入信任列表,下次无需再次询问。
|
||
|
||
Args:
|
||
nbpf_path: .nbpf 文件路径
|
||
plugin_name: 可选,插件名称
|
||
|
||
Returns:
|
||
插件实例,失败或用户拒绝信任返回 None
|
||
"""
|
||
if not self._nbpf_initialized:
|
||
self._init_nbpf()
|
||
if self.nbpf_loader is None:
|
||
Log.error("Core", "NBPF 加载器未初始化,无法加载 .nbpf 文件")
|
||
return None
|
||
|
||
# 第一次尝试加载
|
||
result = self._do_load_nbpf(nbpf_path, plugin_name)
|
||
if result is not None:
|
||
return result
|
||
|
||
# 如果第一次失败(未信任且用户首次拒绝),不再重试
|
||
return None
|
||
|
||
def _do_load_nbpf(self, nbpf_path: Path, plugin_name: str = None) -> Optional[Any]:
|
||
"""执行 .nbpf 加载,含信任检查"""
|
||
import base64 as _b64
|
||
import hashlib as _hl
|
||
|
||
try:
|
||
instance, info = self.nbpf_loader.load(nbpf_path, plugin_name)
|
||
name = info["name"]
|
||
is_trusted = info.get("trusted", False)
|
||
|
||
# 如果作者未被信任,询问用户
|
||
if not is_trusted:
|
||
author = info.get("author", "unknown")
|
||
pub_key_b64 = info.get("signer_public_key", "")
|
||
pub_key_bytes = _b64.b64decode(pub_key_b64)
|
||
# 计算公钥指纹(SHA256 前 16 位 hex)
|
||
fingerprint = _hl.sha256(pub_key_bytes).hexdigest()[:16]
|
||
|
||
print("\n" + "=" * 54)
|
||
print(f" [NBPF] 检测到未知作者的插件")
|
||
print(f" {'─' * 50}")
|
||
print(f" 插件名称: {name}")
|
||
print(f" 插件作者: {author}")
|
||
print(f" 插件版本: {info.get('version', '?')}")
|
||
print(f" 作者公钥指纹: {fingerprint}")
|
||
print(f" {'─' * 50}")
|
||
answer = input(" 是否信任此作者? [y/N] > ").strip().lower()
|
||
|
||
if answer in ("y", "yes"):
|
||
# 用户信任 → 保存公钥到信任列表
|
||
self._trust_author(pub_key_bytes, name, author)
|
||
# 重新加载
|
||
return self._do_load_nbpf(nbpf_path, plugin_name)
|
||
else:
|
||
Log.warn("Core", f"用户已拒绝信任作者 '{author}',跳过插件 {name}")
|
||
return None
|
||
|
||
# 构建 PluginInfo
|
||
pinfo = PluginInfo()
|
||
pinfo.name = name
|
||
pinfo.version = info.get("version", "")
|
||
pinfo.author = info.get("author", "")
|
||
pinfo.description = info.get("description", "")
|
||
pinfo.dependencies = info.get("manifest", {}).get("dependencies", [])
|
||
|
||
# 注册到插件列表
|
||
self.plugins[name] = {
|
||
"instance": instance,
|
||
"module": None,
|
||
"info": pinfo,
|
||
"permissions": [],
|
||
"nbpf_path": str(nbpf_path),
|
||
}
|
||
self._plugin_dirs[name] = nbpf_path.parent
|
||
|
||
# 生命周期
|
||
pinfo.lifecycle = self.lifecycle_manager.create(name)
|
||
|
||
# 审计日志
|
||
self.audit_logger.log(name, "loaded", f".nbpf 版本 {pinfo.version}")
|
||
|
||
Log.ok("Core", f"NBPF 插件 '{name}' 加载成功")
|
||
return instance
|
||
except Exception as e:
|
||
Log.error("Core", f"NBPF 插件加载失败: {e}")
|
||
return None
|
||
|
||
def _trust_author(self, pub_key_bytes: bytes, plugin_name: str, author: str):
|
||
"""将作者公钥加入本地信任列表"""
|
||
import hashlib as _hl
|
||
|
||
fingerprint = _hl.sha256(pub_key_bytes).hexdigest()[:16]
|
||
key_name = f"author_{fingerprint}"
|
||
|
||
# 创建信任目录
|
||
if not hasattr(self, '_trusted_keys_dir') or self._trusted_keys_dir is None:
|
||
from oss.config import get_config
|
||
cfg = get_config()
|
||
self._trusted_keys_dir = Path(cfg.get("NBPF_TRUSTED_KEYS_DIR", "./data/nbpf-keys/trusted"))
|
||
self._trusted_keys_dir.mkdir(parents=True, exist_ok=True)
|
||
|
||
# 保存公钥文件
|
||
key_path = self._trusted_keys_dir / f"{key_name}.pem"
|
||
key_path.write_bytes(pub_key_bytes)
|
||
|
||
# 更新加载器的信任列表
|
||
self.nbpf_loader.trusted_ed25519_keys[key_name] = pub_key_bytes
|
||
|
||
Log.ok("NBPF", f"已将作者 '{author}' 加入信任列表 ({key_path})")
|
||
|
||
def _get_plugin_dir(self, plugin_name: str) -> Optional[Path]:
|
||
return self._plugin_dirs.get(plugin_name)
|
||
|
||
def _load_manifest(self, plugin_dir: Path) -> dict:
|
||
mf = plugin_dir / "manifest.json"
|
||
if not mf.exists():
|
||
return {}
|
||
with open(mf, "r", encoding="utf-8") as f:
|
||
return json.load(f)
|
||
|
||
def _load_readme(self, plugin_dir: Path) -> str:
|
||
rf = plugin_dir / "README.md"
|
||
if not rf.exists():
|
||
return ""
|
||
with open(rf, "r", encoding="utf-8") as f:
|
||
return f.read()
|
||
|
||
def _parse_config_file(self, file_path: Path, file_type: str) -> dict:
|
||
"""通用配置文件解析 - 使用 ast.literal_eval 安全解析"""
|
||
import ast
|
||
if not file_path.exists():
|
||
return {}
|
||
try:
|
||
with open(file_path, "r", encoding="utf-8") as f:
|
||
content = f.read()
|
||
except FileNotFoundError:
|
||
Log.warn("Core", f"{file_type}文件不存在:{file_path}")
|
||
return {}
|
||
except PermissionError as e:
|
||
Log.error("Core", f"{file_type}文件无权限读取:{file_path} - {e}")
|
||
return {}
|
||
except UnicodeDecodeError as e:
|
||
Log.error("Core", f"{file_type}文件编码错误:{file_path} - {e}")
|
||
return {}
|
||
|
||
try:
|
||
result = ast.literal_eval(content)
|
||
if isinstance(result, dict):
|
||
return {k: v for k, v in result.items() if not k.startswith("_")}
|
||
except (ValueError, SyntaxError) as e:
|
||
print(f"[Manager] 配置解析错误: {e}")
|
||
|
||
config = {}
|
||
for line in content.split('\n'):
|
||
line = line.strip()
|
||
if not line or line.startswith('#'):
|
||
continue
|
||
match = re.match(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', line)
|
||
if match:
|
||
key, value_str = match.groups()
|
||
if key.startswith('_'):
|
||
continue
|
||
try:
|
||
value = ast.literal_eval(value_str)
|
||
config[key] = value
|
||
except (ValueError, SyntaxError):
|
||
Log.warn("Core", f"{file_path} 跳过无效的值:{line}")
|
||
continue
|
||
return config
|
||
|
||
def _load_config(self, plugin_dir: Path) -> dict:
|
||
return self._parse_config_file(plugin_dir / "config.py", "配置")
|
||
|
||
def _load_extensions(self, plugin_dir: Path) -> dict:
|
||
return self._parse_config_file(plugin_dir / "extensions.py", "扩展")
|
||
|
||
def load(self, plugin_dir: Path, use_sandbox: bool = True) -> Optional[Any]:
|
||
"""加载单个插件
|
||
|
||
支持:
|
||
- 目录结构插件(main.py)
|
||
- .nbpf 文件(直接传入 .nbpf 路径)
|
||
"""
|
||
# 如果是 .nbpf 文件,使用 NBPF 加载器
|
||
if plugin_dir.suffix == ".nbpf":
|
||
return self.load_nbpf(plugin_dir)
|
||
|
||
main_file = plugin_dir / "main.py"
|
||
if not main_file.exists():
|
||
return None
|
||
|
||
manifest = self._load_manifest(plugin_dir)
|
||
readme = self._load_readme(plugin_dir)
|
||
config = self._load_config(plugin_dir)
|
||
extensions = self._load_extensions(plugin_dir)
|
||
capabilities = scan_capabilities(plugin_dir)
|
||
plugin_name = plugin_dir.name.rstrip("}")
|
||
|
||
# 完整性检查:加载前计算 hash
|
||
self.integrity_checker.register(plugin_name, plugin_dir)
|
||
|
||
# PL 注入检查
|
||
pl_injection = manifest.get("config", {}).get("args", {}).get("pl_injection", False)
|
||
if pl_injection:
|
||
Log.tip("Core", f"插件 '{plugin_name}' 声明了 pl_injection,正在检查 PL/ 文件夹...")
|
||
if not self.pl_injector.check_and_load_pl(plugin_dir, plugin_name):
|
||
Log.error("Core", f"插件 '{plugin_name}' 因 PL 注入检查失败被拒绝加载")
|
||
return None
|
||
Log.ok("Core", f"插件 '{plugin_name}' PL 注入检查通过")
|
||
|
||
permissions = manifest.get("permissions", [])
|
||
|
||
spec = importlib.util.spec_from_file_location(f"plugin.{plugin_name}", str(main_file))
|
||
module = importlib.util.module_from_spec(spec)
|
||
module.__package__ = f"plugin.{plugin_name}"
|
||
module.__path__ = [str(plugin_dir)]
|
||
sys.modules[spec.name] = module
|
||
spec.loader.exec_module(module)
|
||
if not hasattr(module, "New"):
|
||
return None
|
||
instance = module.New()
|
||
|
||
if self.permission_check and permissions:
|
||
instance = PluginProxy(plugin_name, instance, permissions, self.plugins)
|
||
|
||
info = PluginInfo()
|
||
meta = manifest.get("metadata", {})
|
||
info.name = meta.get("name", plugin_name)
|
||
info.version = meta.get("version", "")
|
||
info.author = meta.get("author", "")
|
||
info.description = meta.get("description", "")
|
||
info.readme = readme
|
||
info.config = manifest.get("config", {}).get("args", config)
|
||
info.extensions = extensions
|
||
info.capabilities = capabilities
|
||
info.dependencies = manifest.get("dependencies", [])
|
||
info.pl_injected = pl_injection
|
||
info.file_hash = self.integrity_checker.get_hash(plugin_name) or ""
|
||
|
||
for cap in capabilities:
|
||
self.capability_registry.register_provider(cap, plugin_name, instance)
|
||
info.lifecycle = self.lifecycle_manager.create(plugin_name)
|
||
|
||
self.plugins[plugin_name] = {"instance": instance, "module": module, "info": info, "permissions": permissions}
|
||
self._plugin_dirs[plugin_name] = plugin_dir
|
||
|
||
# 审计日志
|
||
self.audit_logger.log(plugin_name, "loaded", f"版本 {info.version}")
|
||
|
||
# 通过 bridge 通知其他插件
|
||
if plugin_name != "plugin-bridge":
|
||
bridge = self._get_bridge()
|
||
if bridge:
|
||
bridge.emit("plugin.loaded", name=plugin_name, version=info.version)
|
||
|
||
return instance
|
||
|
||
def _restart_plugin(self, plugin_name: str):
|
||
"""重启单个插件"""
|
||
if plugin_name not in self.plugins:
|
||
return
|
||
plugin_dir = self._plugin_dirs.get(plugin_name)
|
||
if not plugin_dir:
|
||
return
|
||
# 停止旧实例
|
||
try:
|
||
if hasattr(self.plugins[plugin_name]["instance"], "stop"):
|
||
self.plugins[plugin_name]["instance"].stop()
|
||
except Exception as e:
|
||
print(f"[Manager] 错误: {e}")
|
||
# 从 sys.modules 中移除
|
||
module_name = f"plugin.{plugin_name}"
|
||
if module_name in sys.modules:
|
||
del sys.modules[module_name]
|
||
module_name = f"nbpf.{plugin_name}"
|
||
if module_name in sys.modules:
|
||
del sys.modules[module_name]
|
||
# 重新加载
|
||
del self.plugins[plugin_name]
|
||
self.load(plugin_dir)
|
||
|
||
def load_all(self, mods_dir: str = "mods"):
|
||
if 'plugin' not in sys.modules:
|
||
pkg = types.ModuleType('plugin')
|
||
pkg.__path__ = []
|
||
pkg.__package__ = 'plugin'
|
||
sys.modules['plugin'] = pkg
|
||
Log.tip("Core", "已创建 plugin 命名空间包")
|
||
|
||
from oss.config import get_config
|
||
config = get_config()
|
||
store_dir = str(config.get("STORE_DIR", "./store"))
|
||
|
||
if not self._check_any_plugins(store_dir):
|
||
Log.warn("Core", "未检测到任何插件")
|
||
self._bootstrap_installation()
|
||
|
||
self._load_plugins_from_dir(Path(store_dir))
|
||
self._sort_by_dependencies()
|
||
|
||
def _check_any_plugins(self, store_dir: str) -> bool:
|
||
sp = Path(store_dir)
|
||
if not sp.exists():
|
||
return False
|
||
for vendor_dir in sp.iterdir():
|
||
if vendor_dir.is_dir():
|
||
for plugin_dir in vendor_dir.iterdir():
|
||
if plugin_dir.is_dir() and (plugin_dir / "main.py").exists():
|
||
return True
|
||
return False
|
||
|
||
def _load_plugins_from_dir(self, store_dir: Path):
|
||
if not store_dir.exists():
|
||
Log.warn("Core", f"插件目录不存在: {store_dir}")
|
||
return
|
||
for vendor_dir in sorted(store_dir.iterdir()):
|
||
if not vendor_dir.is_dir():
|
||
continue
|
||
for plugin_dir in sorted(vendor_dir.iterdir()):
|
||
if not plugin_dir.is_dir():
|
||
continue
|
||
try:
|
||
self.load(plugin_dir)
|
||
except Exception as e:
|
||
Log.error("Core", f"加载插件失败 {plugin_dir.name}: {e}")
|
||
self._link_capabilities()
|
||
|
||
def _load_mods_from_dir(self, mods_dir: Path):
|
||
if not mods_dir.exists():
|
||
return
|
||
nbpf_files = []
|
||
for f in mods_dir.iterdir():
|
||
if f.is_file() and f.suffix == ".nbpf":
|
||
nbpf_files.append(f)
|
||
nbpf_files.sort(key=lambda x: x.name)
|
||
for f in nbpf_files:
|
||
Log.info("Core", f"加载模组: {f.name}")
|
||
self.load(f)
|
||
self._link_capabilities()
|
||
|
||
def _check_any_mods(self, mods_dir: str) -> bool:
|
||
sp = Path(mods_dir)
|
||
if sp.exists():
|
||
for f in sp.iterdir():
|
||
if f.is_file() and f.suffix == ".nbpf":
|
||
return True
|
||
return False
|
||
|
||
def _bootstrap_installation(self):
|
||
Log.info("Core", "跳过引导安装(无可用插件)")
|
||
|
||
def _sort_by_dependencies(self):
|
||
for n, i in self.plugins.items():
|
||
self.dependency_resolver.add_dependency(n, i["info"].dependencies)
|
||
try:
|
||
order = self.dependency_resolver.resolve()
|
||
sp = {}
|
||
for n in order:
|
||
if n in self.plugins:
|
||
sp[n] = self.plugins[n]
|
||
for n in set(self.plugins.keys()) - set(sp.keys()):
|
||
sp[n] = self.plugins[n]
|
||
self.plugins = sp
|
||
except Exception as e:
|
||
Log.error("Core", f"依赖解析失败: {e}")
|
||
|
||
def _link_capabilities(self):
|
||
for pn, info in self.plugins.items():
|
||
for cap in info["info"].capabilities:
|
||
if self.capability_registry.has_capability(cap):
|
||
for cn in self.capability_registry.get_consumers(cap):
|
||
if cn in self.plugins:
|
||
ci = self.plugins[cn]["info"]
|
||
ca = self.plugins[cn].get("permissions", [])
|
||
try:
|
||
p = self.capability_registry.get_provider(cap, requester=cn, allowed_plugins=ca)
|
||
if p and hasattr(ci, "extensions"):
|
||
ci.extensions[f"_{cap}_provider"] = p
|
||
except PluginPermissionError as e:
|
||
Log.error("Core", f"权限拒绝: {e}")
|
||
|
||
def start_all(self):
|
||
self._inject_dependencies()
|
||
for n, i in self.plugins.items():
|
||
try:
|
||
wrapped = self.fallback_manager.wrap_plugin_method(n, i["instance"].start)
|
||
wrapped()
|
||
except Exception as e:
|
||
Log.error("Core", f"启动失败 {n}: {e}")
|
||
|
||
def _get_bridge(self):
|
||
"""Get the plugin-bridge instance if loaded."""
|
||
if "plugin-bridge" in self.plugins:
|
||
bridge = self.plugins["plugin-bridge"]["instance"]
|
||
if hasattr(bridge, "emit"):
|
||
return bridge
|
||
return None
|
||
|
||
def init_and_start_all(self):
|
||
Log.info("Core", f"init_and_start_all 被调用,plugins={len(self.plugins)}")
|
||
self._inject_dependencies()
|
||
ordered = self._get_ordered_plugins()
|
||
Log.tip("Core", f"插件启动顺序: {' -> '.join(ordered)}")
|
||
for name in ordered:
|
||
if "Core" in name:
|
||
continue
|
||
try:
|
||
Log.info("Core", f"初始化: {name}")
|
||
wrapped_init = self.fallback_manager.wrap_plugin_method(name, self.plugins[name]["instance"].init)
|
||
wrapped_init()
|
||
except Exception as e:
|
||
Log.error("Core", f"初始化失败 {name}: {e}")
|
||
for name in ordered:
|
||
if "Core" in name:
|
||
continue
|
||
try:
|
||
Log.info("Core", f"启动: {name}")
|
||
wrapped_start = self.fallback_manager.wrap_plugin_method(name, self.plugins[name]["instance"].start)
|
||
wrapped_start()
|
||
bridge = self._get_bridge()
|
||
if bridge and name != "plugin-bridge":
|
||
bridge.emit("plugin.started", name=name)
|
||
except Exception as e:
|
||
Log.error("Core", f"启动失败 {name}: {e}")
|
||
|
||
def _get_ordered_plugins(self) -> list[str]:
|
||
try:
|
||
ordered = [n for n in self.dependency_resolver.resolve() if n in self.plugins]
|
||
if ordered:
|
||
return ordered
|
||
except Exception as e:
|
||
Log.warn("Core", f"依赖解析失败,使用原始顺序: {e}")
|
||
return list(self.plugins.keys())
|
||
|
||
def _inject_dependencies(self):
|
||
Log.info("Core", f"开始注入依赖,共 {len(self.plugins)} 个插件")
|
||
nm = {}
|
||
for n in self.plugins:
|
||
c = n.rstrip("}")
|
||
nm[c] = n
|
||
nm[c + "}"] = n
|
||
for n, i in self.plugins.items():
|
||
inst = i["instance"]
|
||
io = i.get("info")
|
||
if not io or not io.dependencies:
|
||
continue
|
||
for dn in io.dependencies:
|
||
ad = nm.get(dn) or nm.get(dn + "}")
|
||
if ad and ad in self.plugins:
|
||
sn = f"set_{dn.replace('-', '_')}"
|
||
if hasattr(inst, sn):
|
||
try:
|
||
getattr(inst, sn)(self.plugins[ad]["instance"])
|
||
Log.ok("Core", f"注入成功: {n} <- {ad}")
|
||
except Exception as e:
|
||
Log.error("Core", f"注入依赖失败 {n}.{sn}: {e}")
|
||
else:
|
||
Log.warn("Core", f"{n} 没有 {sn} 方法")
|
||
|
||
def stop_all(self):
|
||
for n, i in reversed(list(self.plugins.items())):
|
||
try:
|
||
if hasattr(i["instance"], "stop"):
|
||
i["instance"].stop()
|
||
bridge = self._get_bridge()
|
||
if bridge and n != "plugin-bridge":
|
||
bridge.emit("plugin.stopped", name=n)
|
||
except Exception as e:
|
||
Log.error("Core", f"插件 {n} 停止失败:{type(e).__name__}: {e}")
|
||
self.lifecycle_manager.stop_all()
|
||
|
||
def get_info(self, name: str) -> Optional[PluginInfo]:
|
||
if name in self.plugins:
|
||
return self.plugins[name]["info"]
|
||
return None
|
||
|
||
def has_capability(self, capability: str) -> bool:
|
||
return self.capability_registry.has_capability(capability)
|
||
|
||
def get_capability_provider(self, capability: str) -> Optional[Any]:
|
||
return self.capability_registry.get_provider(capability)
|
||
|
||
# ── HTTP 服务 ──
|
||
|
||
def start_http_server(self):
|
||
"""启动 HTTP 服务(子模块)"""
|
||
try:
|
||
from oss.core.http_api.server import HttpServer, Request, Response
|
||
from oss.core.http_api.router import HttpRouter
|
||
from oss.core.http_api.middleware import MiddlewareChain
|
||
|
||
router = HttpRouter()
|
||
|
||
# ── 登录路由 ──
|
||
def login_handler(req: Request):
|
||
from oss.core.security.jwt_auth import issue_token
|
||
import json
|
||
try:
|
||
data = json.loads(req.body or "{}")
|
||
user = data.get("username", "")
|
||
pwd = data.get("password", "")
|
||
config = get_config()
|
||
admin_user = config.get("ADMIN_USER", "admin")
|
||
admin_pass = config.get("ADMIN_PASS", "admin123")
|
||
if user == admin_user and pwd == admin_pass:
|
||
token = issue_token(user)
|
||
return Response(
|
||
status=200,
|
||
body=json.dumps({"token": token, "user": user}),
|
||
headers={"Content-Type": "application/json"},
|
||
)
|
||
return Response(
|
||
status=401,
|
||
body=json.dumps({"error": "用户名或密码错误"}),
|
||
headers={"Content-Type": "application/json"},
|
||
)
|
||
except Exception as e:
|
||
return Response(
|
||
status=400,
|
||
body=json.dumps({"error": str(e)}),
|
||
headers={"Content-Type": "application/json"},
|
||
)
|
||
|
||
# ── 健康检查路由 ──
|
||
def health_handler(req: Request):
|
||
from oss.core.ops.health import HealthChecker
|
||
import json
|
||
return Response(
|
||
status=200,
|
||
body=json.dumps(HealthChecker.check()),
|
||
headers={"Content-Type": "application/json"},
|
||
)
|
||
|
||
# ── Metrics 路由 ──
|
||
def metrics_handler(req: Request):
|
||
from oss.core.ops.metrics import get_metrics
|
||
return Response(
|
||
status=200,
|
||
body=get_metrics().render(),
|
||
headers={"Content-Type": "text/plain; version=0.0.4"},
|
||
)
|
||
|
||
router.add("POST", "/api/login", login_handler)
|
||
router.add("GET", "/health", health_handler)
|
||
router.add("GET", "/metrics", metrics_handler)
|
||
|
||
middleware = MiddlewareChain()
|
||
self.http_server = HttpServer(router=router, middleware=middleware)
|
||
self.http_server.start()
|
||
Log.ok("Core", "HTTP 服务已启动")
|
||
except Exception as e:
|
||
Log.error("Core", f"HTTP 服务启动失败: {e}")
|
||
|
||
def stop_http_server(self):
|
||
"""停止 HTTP 服务"""
|
||
if self.http_server:
|
||
try:
|
||
self.http_server.stop()
|
||
Log.info("Core", "HTTP 服务已停止")
|
||
except Exception as e:
|
||
Log.error("Core", f"HTTP 服务停止失败: {e}")
|
||
|
||
def get_http_router(self):
|
||
"""获取 HTTP 路由器"""
|
||
if self.http_server:
|
||
return self.http_server.router
|
||
return None
|
||
|
||
# ── REPL ──
|
||
|
||
def start_repl(self):
|
||
"""启动 REPL 终端(子模块)"""
|
||
try:
|
||
from oss.core.repl.main import NebulaShell
|
||
self.repl_shell = NebulaShell(self)
|
||
Log.ok("Core", "REPL 终端已启动")
|
||
self.repl_shell.cmdloop()
|
||
except Exception as e:
|
||
Log.error("Core", f"REPL 启动失败: {e}")
|
||
|
||
# ── 防护管理 ──
|
||
|
||
def start_tamper_monitor(self):
|
||
"""启动防篡改监控"""
|
||
self.tamper_monitor.start()
|
||
|
||
def stop_tamper_monitor(self):
|
||
"""停止防篡改监控"""
|
||
self.tamper_monitor.stop()
|
||
|
||
def get_audit_logs(self, plugin_name: str = None, limit: int = 50) -> list[dict]:
|
||
"""获取审计日志"""
|
||
return self.audit_logger.get_logs(plugin_name, limit)
|
||
|
||
def get_tamper_alerts(self) -> list[dict]:
|
||
"""获取防篡改告警"""
|
||
return self.tamper_monitor.get_alerts()
|
||
|
||
def get_degraded_plugins(self) -> list[str]:
|
||
"""获取降级插件列表"""
|
||
return self.fallback_manager.get_degraded_plugins()
|
||
|
||
def recover_plugin(self, plugin_name: str) -> bool:
|
||
"""手动恢复降级插件"""
|
||
return self.fallback_manager.recover(plugin_name)
|
||
|
||
def get_status(self) -> dict:
|
||
"""获取 Core 状态摘要"""
|
||
nbpf_count = sum(1 for i in self.plugins.values() if i.get("nbpf_path"))
|
||
return {
|
||
"plugins": {
|
||
"total": len(self.plugins),
|
||
"nbpf": nbpf_count,
|
||
"directory": len(self.plugins) - nbpf_count,
|
||
"degraded": self.fallback_manager.get_degraded_plugins(),
|
||
},
|
||
"nbpf_loader": self._nbpf_initialized,
|
||
"http_server": self.http_server is not None,
|
||
"tamper_monitor": self.tamper_monitor._running,
|
||
"audit_logs": len(self.audit_logger._logs),
|
||
"tamper_alerts": len(self.tamper_monitor._alerts),
|
||
"data_store": str(self.data_store._base_dir),
|
||
}
|