[HLCC-ICE-000001][ZY-CONTRIB-20260723-001] feat: 以来光者贡献链启用冰朔第五域个人子频道
This commit is contained in:
commit
5615453e4e
660 changed files with 122355 additions and 0 deletions
9
server-tools/enterprise-lighthouse/README.md
Normal file
9
server-tools/enterprise-lighthouse/README.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# 企业灯塔基础服务
|
||||
|
||||
这是总灯塔的第一阶段:五域登记、节点接入申请、固定动作预检和审计。它**不是**远程 Shell,拒绝 `cmd`、`shell`、`command` 字段,也不执行操作。
|
||||
|
||||
部署后只监听本机 `127.0.0.1:8031`。公众网站与后续 GLSV 页面通过 Nginx 以单独的受控路由接入;在邮件确认与节点连接器完成前,不开放节点激活或执行。
|
||||
|
||||
人类管理员与人格体遵守同一条地图门禁:先 `GET /v1/navigation-map` 完整读取地图,再向 `/v1/navigation-map/ack` 签收当前哈希。任何域、动作或地图版本变化都会让旧签收失效;未签收时所有登记、预检和变更请求返回 `423`。管理员 token 不能绕过地图。
|
||||
|
||||
固定动作只有:`health_check`、`backup`、`deploy_release`、`restart_service`、`rollback`。部署、重启和回滚必须在预检中具备备份引用与回滚计划;预检通过也只是“可申请人类授权”,不会执行。
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
[Unit]
|
||||
Description=Guanghu Enterprise Lighthouse Registry and Preflight
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=lighthouse
|
||||
Group=lighthouse
|
||||
WorkingDirectory=/opt/guanghu-enterprise-lighthouse
|
||||
EnvironmentFile=/etc/guanghu-enterprise-lighthouse.env
|
||||
ExecStart=/usr/bin/python3 /opt/guanghu-enterprise-lighthouse/lighthouse.py
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
NoNewPrivileges=yes
|
||||
PrivateTmp=yes
|
||||
ProtectSystem=strict
|
||||
ProtectHome=yes
|
||||
ReadWritePaths=/var/lib/guanghu-enterprise-lighthouse
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
248
server-tools/enterprise-lighthouse/lighthouse.py
Normal file
248
server-tools/enterprise-lighthouse/lighthouse.py
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Enterprise Lighthouse foundation: registry, preflight, and audit only.
|
||||
|
||||
This service is deliberately not a remote shell. It accepts node admission
|
||||
requests and validates fixed operation plans; execution remains with a future
|
||||
GLSV node connector after human authorization.
|
||||
"""
|
||||
import hmac
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import sqlite3
|
||||
import time
|
||||
import uuid
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from ipaddress import ip_address
|
||||
|
||||
DB = os.environ.get("LIGHTHOUSE_DB", "/var/lib/guanghu-enterprise-lighthouse/lighthouse.db")
|
||||
TOKEN = os.environ.get("LIGHTHOUSE_ADMIN_TOKEN", "")
|
||||
HOST = os.environ.get("LIGHTHOUSE_BIND", "127.0.0.1")
|
||||
PORT = int(os.environ.get("LIGHTHOUSE_PORT", "8031"))
|
||||
FIXED_ACTIONS = {"health_check", "backup", "deploy_release", "restart_service", "rollback"}
|
||||
DOMAINS = {
|
||||
"DOMAIN-ZS": "零感域", "DOMAIN-MAIN": "光湖主域", "DOMAIN-SUB": "光湖分域",
|
||||
"DOMAIN-ZERO": "光湖零域", "DOMAIN-FIFTH": "第五域",
|
||||
}
|
||||
ENTERPRISE_MANAGED_DOMAINS = {"DOMAIN-ZS", "DOMAIN-MAIN", "DOMAIN-SUB", "DOMAIN-ZERO"}
|
||||
EXTERNAL_FOUNDATION_DOMAINS = {"DOMAIN-FIFTH"}
|
||||
|
||||
|
||||
def now():
|
||||
return int(time.time())
|
||||
|
||||
|
||||
def connection():
|
||||
os.makedirs(os.path.dirname(DB), exist_ok=True)
|
||||
db = sqlite3.connect(DB)
|
||||
db.row_factory = sqlite3.Row
|
||||
db.executescript("""
|
||||
CREATE TABLE IF NOT EXISTS domains (
|
||||
id TEXT PRIMARY KEY, name TEXT NOT NULL, state TEXT NOT NULL, created_at INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS intakes (
|
||||
id TEXT PRIMARY KEY, created_at INTEGER NOT NULL, state TEXT NOT NULL,
|
||||
human_name TEXT NOT NULL, email TEXT NOT NULL, server_ip TEXT NOT NULL,
|
||||
domain_id TEXT NOT NULL, persona_ids TEXT NOT NULL, repository_urls TEXT NOT NULL,
|
||||
hosting_mode TEXT NOT NULL, notes TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS nodes (
|
||||
id TEXT PRIMARY KEY, domain_id TEXT NOT NULL, intake_id TEXT, state TEXT NOT NULL,
|
||||
display_name TEXT NOT NULL, server_ip TEXT NOT NULL, allowed_actions TEXT NOT NULL,
|
||||
public_key_fingerprint TEXT, last_heartbeat INTEGER, created_at INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS audit (
|
||||
id TEXT PRIMARY KEY, created_at INTEGER NOT NULL, kind TEXT NOT NULL, payload TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS map_acks (
|
||||
principal TEXT PRIMARY KEY, map_hash TEXT NOT NULL, acknowledged_at INTEGER NOT NULL
|
||||
);
|
||||
""")
|
||||
for domain_id, name in DOMAINS.items():
|
||||
state = "EXTERNAL_PRIVATE_FOUNDATION" if domain_id in EXTERNAL_FOUNDATION_DOMAINS else "PENDING_ENTRY_NODE"
|
||||
db.execute("INSERT OR IGNORE INTO domains VALUES (?, ?, ?, ?)", (domain_id, name, state, now()))
|
||||
db.commit()
|
||||
return db
|
||||
|
||||
|
||||
def audit(db, kind, payload):
|
||||
db.execute("INSERT INTO audit VALUES (?, ?, ?, ?)", (str(uuid.uuid4()), now(), kind, json.dumps(payload, ensure_ascii=False)))
|
||||
db.commit()
|
||||
|
||||
|
||||
def parse_json(handler):
|
||||
length = int(handler.headers.get("Content-Length", "0"))
|
||||
if not 0 < length <= 50_000:
|
||||
raise ValueError("request body must be between 1 and 50000 bytes")
|
||||
return json.loads(handler.rfile.read(length).decode("utf-8"))
|
||||
|
||||
|
||||
def valid_email(value):
|
||||
return isinstance(value, str) and len(value) <= 254 and value.count("@") == 1
|
||||
|
||||
|
||||
def require_admin(handler):
|
||||
received = handler.headers.get("X-Lighthouse-Admin-Token", "")
|
||||
return bool(TOKEN) and hmac.compare_digest(received, TOKEN)
|
||||
|
||||
|
||||
def navigation_map():
|
||||
body = {
|
||||
"schema": "guanghu.enterprise-navigation-map/v1",
|
||||
"node_id": "AW-GZ-001",
|
||||
"domains": [{"id": key, "name": DOMAINS[key]} for key in sorted(DOMAINS)],
|
||||
"fixed_actions": sorted(FIXED_ACTIONS),
|
||||
"mandatory_order": ["read-navigation-map", "ack-current-map", "execute-registered-action"],
|
||||
"forbidden": ["raw-shell", "unregistered-action", "secret-in-repository", "unmapped-human-operation"],
|
||||
}
|
||||
encoded = json.dumps(body, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode()
|
||||
return body, hashlib.sha256(encoded).hexdigest()
|
||||
|
||||
|
||||
def admin_principal():
|
||||
return hashlib.sha256(TOKEN.encode()).hexdigest()
|
||||
|
||||
|
||||
def has_current_map_ack(db):
|
||||
_, current_hash = navigation_map()
|
||||
row = db.execute("SELECT map_hash FROM map_acks WHERE principal=?", (admin_principal(),)).fetchone()
|
||||
return bool(row and hmac.compare_digest(row["map_hash"], current_hash))
|
||||
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
server_version = "GuanghuEnterpriseLighthouse/1.0"
|
||||
|
||||
def log_message(self, fmt, *args):
|
||||
print("[lighthouse] " + fmt % args)
|
||||
|
||||
def respond(self, status, body):
|
||||
encoded = json.dumps(body, ensure_ascii=False).encode("utf-8")
|
||||
self.send_response(status)
|
||||
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||
self.send_header("Content-Length", str(len(encoded)))
|
||||
self.send_header("Cache-Control", "no-store")
|
||||
self.end_headers()
|
||||
self.wfile.write(encoded)
|
||||
|
||||
def do_GET(self):
|
||||
db = connection()
|
||||
try:
|
||||
if self.path == "/health":
|
||||
return self.respond(200, {"ok": True, "service": "guanghu-enterprise-lighthouse", "mode": "registry-and-preflight-only", "execution": "disabled"})
|
||||
if self.path == "/v1/status":
|
||||
counts = {row["state"]: row["count"] for row in db.execute("SELECT state, COUNT(*) AS count FROM nodes GROUP BY state")}
|
||||
return self.respond(200, {"ok": True, "domains": [dict(row) for row in db.execute("SELECT id,name,state FROM domains ORDER BY id")], "node_counts": counts, "fixed_actions": sorted(FIXED_ACTIONS), "raw_shell": "rejected"})
|
||||
if self.path == "/v1/navigation-map":
|
||||
body, map_hash = navigation_map()
|
||||
return self.respond(200, {"ok": True, "map_hash": map_hash, "navigation_map": body})
|
||||
if self.path == "/v1/nodes":
|
||||
return self.respond(200, {"ok": True, "nodes": [dict(row) for row in db.execute("SELECT id,domain_id,state,display_name,allowed_actions,last_heartbeat,created_at FROM nodes ORDER BY created_at DESC")]})
|
||||
return self.respond(404, {"ok": False, "error": "not found"})
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def do_POST(self):
|
||||
if not require_admin(self):
|
||||
return self.respond(401, {"ok": False, "error": "admin authorization required"})
|
||||
try:
|
||||
payload = parse_json(self)
|
||||
except (ValueError, json.JSONDecodeError) as error:
|
||||
return self.respond(400, {"ok": False, "error": str(error)})
|
||||
if "cmd" in payload or "shell" in payload or "command" in payload:
|
||||
return self.respond(400, {"ok": False, "error": "raw commands are never accepted by the lighthouse"})
|
||||
db = connection()
|
||||
try:
|
||||
if self.path == "/v1/navigation-map/ack":
|
||||
_, current_hash = navigation_map()
|
||||
supplied = str(payload.get("map_hash", ""))
|
||||
if not hmac.compare_digest(supplied, current_hash):
|
||||
return self.respond(409, {"ok": False, "error": "navigation map changed; read the current map again"})
|
||||
db.execute("INSERT OR REPLACE INTO map_acks VALUES (?, ?, ?)", (admin_principal(), current_hash, now()))
|
||||
audit(db, "navigation_map_acknowledged", {"map_hash": current_hash})
|
||||
return self.respond(200, {"ok": True, "map_hash": current_hash})
|
||||
if not has_current_map_ack(db):
|
||||
return self.respond(423, {"ok": False, "error": "current navigation map must be read and acknowledged before any operation", "required": ["GET /v1/navigation-map", "POST /v1/navigation-map/ack"]})
|
||||
if self.path == "/v1/intakes":
|
||||
required = ("human_name", "email", "server_ip", "domain_id")
|
||||
if any(not payload.get(field) for field in required) or payload["domain_id"] not in DOMAINS or not valid_email(payload["email"]):
|
||||
return self.respond(400, {"ok": False, "error": "human_name, valid email, server_ip, and known domain_id are required"})
|
||||
if payload["domain_id"] not in ENTERPRISE_MANAGED_DOMAINS:
|
||||
return self.respond(403, {"ok": False, "error": "this domain is not managed by the enterprise lighthouse; a separate explicit sovereign authorization is required"})
|
||||
try:
|
||||
ip_address(payload["server_ip"])
|
||||
except ValueError:
|
||||
return self.respond(400, {"ok": False, "error": "server_ip must be a valid IP address"})
|
||||
intake_id = "INTAKE-" + uuid.uuid4().hex[:12].upper()
|
||||
db.execute("INSERT INTO intakes VALUES (?, ?, 'PENDING_REVIEW', ?, ?, ?, ?, ?, ?, ?, ?)", (
|
||||
intake_id, now(), payload["human_name"].strip(), payload["email"].strip(), payload["server_ip"], payload["domain_id"],
|
||||
json.dumps(payload.get("persona_ids", [])), json.dumps(payload.get("repository_urls", [])),
|
||||
payload.get("hosting_mode", "own"), payload.get("notes", ""),
|
||||
))
|
||||
audit(db, "intake_created", {"intake_id": intake_id, "domain_id": payload["domain_id"]})
|
||||
return self.respond(201, {"ok": True, "intake_id": intake_id, "state": "PENDING_REVIEW", "next": "sovereign approval, human email confirmation, then node connector enrollment"})
|
||||
if self.path == "/v1/nodes/bootstrap":
|
||||
"""Register a manually verified routing node without enabling execution.
|
||||
|
||||
This exists for the controlled migration of an already verified
|
||||
domain entry. It deliberately cannot make a node ACTIVE: the
|
||||
GLSV connector, server-local key enrollment, and human email
|
||||
confirmation remain required before any operation can proceed.
|
||||
"""
|
||||
required = ("id", "domain_id", "display_name", "server_ip")
|
||||
if any(not isinstance(payload.get(field), str) or not payload[field].strip() for field in required):
|
||||
return self.respond(400, {"ok": False, "error": "id, domain_id, display_name, and server_ip are required"})
|
||||
if payload["domain_id"] not in ENTERPRISE_MANAGED_DOMAINS:
|
||||
return self.respond(403, {"ok": False, "error": "this domain cannot be registered by the enterprise lighthouse"})
|
||||
try:
|
||||
ip_address(payload["server_ip"])
|
||||
except ValueError:
|
||||
return self.respond(400, {"ok": False, "error": "server_ip must be a valid IP address"})
|
||||
node_id = payload["id"].strip()
|
||||
if db.execute("SELECT 1 FROM nodes WHERE id=?", (node_id,)).fetchone():
|
||||
return self.respond(409, {"ok": False, "error": "node id is already registered"})
|
||||
db.execute("INSERT INTO nodes VALUES (?, ?, NULL, 'CONNECTED_PENDING_CONNECTOR', ?, ?, ?, NULL, ?, ?)", (
|
||||
node_id, payload["domain_id"], payload["display_name"].strip(), payload["server_ip"],
|
||||
json.dumps(["health_check"]), now(), now(),
|
||||
))
|
||||
db.execute("UPDATE domains SET state='ENTRY_NODE_CONNECTED' WHERE id=?", (payload["domain_id"],))
|
||||
audit(db, "node_bootstrap_connected", {"node_id": node_id, "domain_id": payload["domain_id"], "mode": "manual_verified_routing_only"})
|
||||
return self.respond(201, {"ok": True, "node_id": node_id, "state": "CONNECTED_PENDING_CONNECTOR", "execution": "disabled until GLSV connector enrollment and human authorization"})
|
||||
if self.path == "/v1/nodes/revoke":
|
||||
node_id = payload.get("node_id")
|
||||
if not isinstance(node_id, str) or not node_id.strip():
|
||||
return self.respond(400, {"ok": False, "error": "node_id is required"})
|
||||
node = db.execute("SELECT id, domain_id, state FROM nodes WHERE id=?", (node_id.strip(),)).fetchone()
|
||||
if not node:
|
||||
return self.respond(404, {"ok": False, "error": "node is not registered"})
|
||||
if node["state"] == "ACTIVE":
|
||||
return self.respond(409, {"ok": False, "error": "ACTIVE nodes require a separate migration or retirement procedure"})
|
||||
db.execute("DELETE FROM nodes WHERE id=?", (node["id"],))
|
||||
if node["domain_id"] in EXTERNAL_FOUNDATION_DOMAINS:
|
||||
db.execute("UPDATE domains SET state='EXTERNAL_PRIVATE_FOUNDATION' WHERE id=?", (node["domain_id"],))
|
||||
audit(db, "node_revoked", {"node_id": node["id"], "domain_id": node["domain_id"], "reason": "administrative revocation"})
|
||||
return self.respond(200, {"ok": True, "node_id": node["id"], "state": "REVOKED", "execution": "disabled"})
|
||||
if self.path == "/v1/preflight":
|
||||
action, node_id = payload.get("action"), payload.get("target_node_id")
|
||||
if action not in FIXED_ACTIONS or not node_id:
|
||||
return self.respond(400, {"ok": False, "error": "fixed action and target_node_id are required", "allowed_actions": sorted(FIXED_ACTIONS)})
|
||||
node = db.execute("SELECT * FROM nodes WHERE id=?", (node_id,)).fetchone()
|
||||
if not node:
|
||||
return self.respond(404, {"ok": False, "decision": "REJECT", "reason": "target node is not registered"})
|
||||
allowed = json.loads(node["allowed_actions"])
|
||||
reasons = []
|
||||
if node["state"] != "ACTIVE": reasons.append("target node is not ACTIVE")
|
||||
if action not in allowed: reasons.append("action is not in the node allowlist")
|
||||
if action in {"deploy_release", "restart_service", "rollback"} and not payload.get("rollback_plan"): reasons.append("rollback_plan is required")
|
||||
if action in {"deploy_release", "restart_service"} and not payload.get("backup_reference"): reasons.append("backup_reference is required")
|
||||
decision = "ALLOW_FOR_AUTHORIZATION" if not reasons else "REJECT"
|
||||
result = {"ok": not reasons, "decision": decision, "reasons": reasons, "execution": "not performed; GLSV human authorization still required"}
|
||||
audit(db, "preflight", {"target_node_id": node_id, "action": action, "decision": decision, "reasons": reasons})
|
||||
return self.respond(200, result)
|
||||
return self.respond(404, {"ok": False, "error": "not found"})
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(f"Enterprise Lighthouse listening on {HOST}:{PORT}")
|
||||
ThreadingHTTPServer((HOST, PORT), Handler).serve_forever()
|
||||
46
server-tools/enterprise-lighthouse/test_lighthouse.py
Normal file
46
server-tools/enterprise-lighthouse/test_lighthouse.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#!/usr/bin/env python3
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).parent
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
env = {**os.environ, "LIGHTHOUSE_DB": f"{temp}/lighthouse.db", "LIGHTHOUSE_ADMIN_TOKEN": "test-token", "LIGHTHOUSE_PORT": "48031"}
|
||||
process = subprocess.Popen([sys.executable, "lighthouse.py"], cwd=ROOT, env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
try:
|
||||
for _ in range(30):
|
||||
try:
|
||||
assert json.load(urllib.request.urlopen("http://127.0.0.1:48031/health", timeout=1))["execution"] == "disabled"
|
||||
break
|
||||
except OSError:
|
||||
time.sleep(.1)
|
||||
else: raise AssertionError("server did not start")
|
||||
locked = urllib.request.Request("http://127.0.0.1:48031/v1/intakes", data=json.dumps({"human_name":"Test","email":"test@example.invalid","server_ip":"203.0.113.8","domain_id":"DOMAIN-ZS"}).encode(), method="POST", headers={"X-Lighthouse-Admin-Token":"test-token"})
|
||||
try: urllib.request.urlopen(locked)
|
||||
except urllib.error.HTTPError as error: assert error.code == 423
|
||||
else: raise AssertionError("mutation was allowed before navigation map acknowledgement")
|
||||
nav = json.load(urllib.request.urlopen("http://127.0.0.1:48031/v1/navigation-map"))
|
||||
ack = urllib.request.Request("http://127.0.0.1:48031/v1/navigation-map/ack", data=json.dumps({"map_hash":nav["map_hash"]}).encode(), method="POST", headers={"X-Lighthouse-Admin-Token":"test-token"})
|
||||
assert json.load(urllib.request.urlopen(ack))["ok"] is True
|
||||
request = urllib.request.Request("http://127.0.0.1:48031/v1/intakes", data=json.dumps({"human_name":"Test","email":"test@example.invalid","server_ip":"203.0.113.8","domain_id":"DOMAIN-ZS"}).encode(), method="POST", headers={"X-Lighthouse-Admin-Token":"test-token"})
|
||||
assert json.load(urllib.request.urlopen(request))["state"] == "PENDING_REVIEW"
|
||||
bootstrap = urllib.request.Request("http://127.0.0.1:48031/v1/nodes/bootstrap", data=json.dumps({"id":"NODE-TEST-001","domain_id":"DOMAIN-ZS","display_name":"Test entry","server_ip":"203.0.113.8"}).encode(), method="POST", headers={"X-Lighthouse-Admin-Token":"test-token"})
|
||||
assert json.load(urllib.request.urlopen(bootstrap))["state"] == "CONNECTED_PENDING_CONNECTOR"
|
||||
preflight = urllib.request.Request("http://127.0.0.1:48031/v1/preflight", data=json.dumps({"action":"health_check","target_node_id":"NODE-TEST-001"}).encode(), method="POST", headers={"X-Lighthouse-Admin-Token":"test-token"})
|
||||
assert json.load(urllib.request.urlopen(preflight))["decision"] == "REJECT"
|
||||
fifth = urllib.request.Request("http://127.0.0.1:48031/v1/nodes/bootstrap", data=json.dumps({"id":"NODE-FIFTH-001","domain_id":"DOMAIN-FIFTH","display_name":"Not allowed","server_ip":"203.0.113.8"}).encode(), method="POST", headers={"X-Lighthouse-Admin-Token":"test-token"})
|
||||
try: urllib.request.urlopen(fifth)
|
||||
except urllib.error.HTTPError as error: assert error.code == 403
|
||||
else: raise AssertionError("private fifth domain was accepted by enterprise lighthouse")
|
||||
bad = urllib.request.Request("http://127.0.0.1:48031/v1/intakes", data=b'{"cmd":"rm -rf /"}', method="POST", headers={"X-Lighthouse-Admin-Token":"test-token"})
|
||||
try: urllib.request.urlopen(bad)
|
||||
except urllib.error.HTTPError as error: assert error.code == 400
|
||||
else: raise AssertionError("raw command was not rejected")
|
||||
finally:
|
||||
process.terminate(); process.wait(timeout=5)
|
||||
print("enterprise lighthouse tests passed")
|
||||
Loading…
Reference in a new issue