"use strict"; const fs = require("node:fs"); const net = require("node:net"); const path = require("node:path"); const { execFile } = require("node:child_process"); const SOCKET_PATH = process.env.LAKE_LAMP_OWNER_ACCESS_SOCKET || "/run/guanghu-owner-access/owner-access.sock"; const SSH_CONFIG = process.env.LAKE_LAMP_SSH_CONFIG || "/etc/guanghu/action-broker-ssh-config"; const RESTORE_OWNER_PASSWORD_LOGIN = String.raw`set -eu test "$(id -u)" = "0" cfg=/etc/ssh/sshd_config.d/90-guanghu-key-only.conf test -f "$cfg" backup_dir=/root/guanghu-security-backup-$(date -u +%Y%m%dT%H%M%SZ) mkdir -p "$backup_dir" chmod 0700 "$backup_dir" cp -a "$cfg" "$backup_dir/90-guanghu-key-only.conf.before" rollback() { cp -a "$backup_dir/90-guanghu-key-only.conf.before" "$cfg" /usr/sbin/sshd -t systemctl reload ssh 2>/dev/null || systemctl reload sshd } trap 'rc=$?; if [ "$rc" -ne 0 ]; then rollback; fi; exit "$rc"' EXIT /usr/bin/python3 - "$cfg" <<'PY' from pathlib import Path import re, sys path = Path(sys.argv[1]) text = path.read_text() pattern = re.compile(r"(?im)^\s*PasswordAuthentication\s+\S+\s*$") if pattern.search(text): text = pattern.sub("PasswordAuthentication yes", text) else: text = text.rstrip() + "\nPasswordAuthentication yes\n" path.write_text(text) PY /usr/sbin/sshd -t password=$(/usr/sbin/sshd -T | awk '$1 == "passwordauthentication" {print $2; exit}') root_login=$(/usr/sbin/sshd -T | awk '$1 == "permitrootlogin" {print $2; exit}') test "$password" = "yes" test "$root_login" != "yes" systemctl reload ssh 2>/dev/null || systemctl reload sshd printf 'PASSWORD_AUTH=%s\nROOT_PASSWORD_AUTH=%s\nSSH=%s\nBACKUP=%s\n' \ "$password" "$root_login" \ "$(systemctl is-active ssh 2>/dev/null || systemctl is-active sshd)" \ "$backup_dir/90-guanghu-key-only.conf.before" trap - EXIT`; const RESTORE_CODE_CHANNEL_OWNER_LOGIN = String.raw`set -eu test "$(id -u)" = "0" /usr/bin/python3 - <<'PY' import atexit import datetime import json import os import pathlib import sqlite3 import subprocess import time import urllib.request state = pathlib.Path("/var/lib/guanghu/personas/guanghu/hlcc-v16.0.1") channel_db = state / "data/hlcc.db" legacy_db = pathlib.Path("/var/lib/guanghu/forgejo/data/forgejo.db") stamp = datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%dT%H%M%SZ") backup = state / "data/backups" / f"owner-login-recovery-{stamp}.db" service_started = False def ensure_service_started(): if not service_started: subprocess.run(["systemctl", "start", "hlcc-jd-candidate.service"]) atexit.register(ensure_service_started) subprocess.run(["systemctl", "stop", "hlcc-jd-candidate.service"], check=True) channel = sqlite3.connect(channel_db, timeout=30) snapshot = sqlite3.connect(backup, timeout=30) try: channel.execute("pragma wal_checkpoint(truncate)") channel.backup(snapshot) finally: snapshot.close() os.chmod(backup, 0o600) legacy = sqlite3.connect(f"file:{legacy_db}?mode=ro", uri=True, timeout=15) try: credential = legacy.execute( "select passwd,salt,passwd_hash_algo from user where lower_name=?", ("bingshuo",), ).fetchone() finally: legacy.close() if not credential or not all(credential): raise RuntimeError("legacy owner credential unavailable") try: channel.execute("begin immediate") changed = channel.execute( """update user set passwd=?,salt=?,passwd_hash_algo=?,is_active=1,is_admin=1, prohibit_login=0,must_change_password=0,updated_unix=? where lower_name=?""", (*credential, int(time.time()), "bingshuo"), ).rowcount if changed != 1: raise RuntimeError("channel owner identity mismatch") channel.commit() copied = channel.execute( """select passwd,salt,passwd_hash_algo,is_active,is_admin, prohibit_login,must_change_password from user where lower_name=?""", ("bingshuo",), ).fetchone() if copied[:3] != credential or copied[3:] != (1, 1, 0, 0): raise RuntimeError("owner login recovery verification failed") if channel.execute("pragma integrity_check").fetchone()[0] != "ok": raise RuntimeError("channel database integrity check failed") channel.execute("pragma wal_checkpoint(truncate)") finally: channel.close() subprocess.run(["systemctl", "start", "hlcc-jd-candidate.service"], check=True) service_started = True status = {} for _ in range(90): try: with urllib.request.urlopen("http://127.0.0.1:3341/health", timeout=2) as response: status = json.load(response) with urllib.request.urlopen("http://127.0.0.1:3340/api/healthz", timeout=2) as response: app = json.load(response) if status.get("ready") is True and app.get("status") == "pass": break except Exception: pass time.sleep(1) else: subprocess.run(["systemctl", "stop", "hlcc-jd-candidate.service"]) service_started = False restored = sqlite3.connect(channel_db, timeout=30) saved = sqlite3.connect(f"file:{backup}?mode=ro", uri=True, timeout=30) try: saved.backup(restored) finally: saved.close() restored.close() subprocess.run(["systemctl", "start", "hlcc-jd-candidate.service"], check=True) service_started = True raise RuntimeError("channel health failed; database restored from backup") print(json.dumps({ "ACCOUNT": "bingshuo", "CREDENTIAL_SOURCE": "legacy-local-db", "ACTIVE": True, "ADMIN": True, "PROHIBIT_LOGIN": False, "SERVICE": "ready", "BACKUP": str(backup), }, ensure_ascii=False)) PY`; function run(file, args, timeout = 30000) { return new Promise(resolve => execFile(file, args, { timeout, maxBuffer: 100000 }, (error, stdout, stderr) => resolve({ ok: !error, exit_code: error ? (Number.isInteger(error.code) ? error.code : 1) : 0, stdout: String(stdout || "").slice(0, 100000), stderr: String(stderr || "").slice(0, 10000), }))); } async function execute(request) { if (!request || request.cmd || request.command || request.shell || request.args) return { ok: false, error: "arbitrary_command_forbidden" }; if (request.target !== "JD-FD-PRIMARY") return { ok: false, error: "action_not_registered" }; const commands = { "restore-owner-password-login": RESTORE_OWNER_PASSWORD_LOGIN, "restore-code-channel-owner-login": RESTORE_CODE_CHANNEL_OWNER_LOGIN, }; if (!commands[request.action]) return { ok: false, error: "action_not_registered" }; const timeout = request.action === "restore-code-channel-owner-login" ? 150000 : 30000; return run("/usr/bin/ssh", ["-F", SSH_CONFIG, "-o", "BatchMode=yes", "-o", "ConnectTimeout=10", "enterprise-lighthouse", commands[request.action]], timeout); } function reply(socket, value) { socket.end(`${JSON.stringify(value)}\n`); } if (require.main === module) { fs.mkdirSync(path.dirname(SOCKET_PATH), { recursive: true, mode: 0o755 }); try { fs.unlinkSync(SOCKET_PATH); } catch (error) { if (error.code !== "ENOENT") throw error; } const server = net.createServer({ allowHalfOpen: true }, socket => { let input = ""; socket.setTimeout(5000, () => socket.destroy()); socket.on("data", chunk => { input += chunk.toString("utf8"); if (input.length > 4096) socket.destroy(); }); socket.on("end", async () => { let request; try { request = JSON.parse(input); } catch { return reply(socket, { ok: false, error: "invalid_request" }); } reply(socket, await execute(request)); }); }); server.listen(SOCKET_PATH, () => { fs.chownSync(SOCKET_PATH, 0, Number(process.env.LAKE_LAMP_AUTHZ_GID || 0)); fs.chmodSync(SOCKET_PATH, 0o660); }); } module.exports = { execute, RESTORE_OWNER_PASSWORD_LOGIN, RESTORE_CODE_CHANNEL_OWNER_LOGIN, SOCKET_PATH, };