209 lines
12 KiB
Python

#!/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 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
);
""")
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)
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/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/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()