feat: register connected routing nodes without execution
This commit is contained in:
parent
46b63e343c
commit
8be10cc3da
@ -135,6 +135,33 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
))
|
))
|
||||||
audit(db, "intake_created", {"intake_id": intake_id, "domain_id": payload["domain_id"]})
|
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"})
|
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 DOMAINS:
|
||||||
|
return self.respond(400, {"ok": False, "error": "domain_id is not registered"})
|
||||||
|
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/preflight":
|
if self.path == "/v1/preflight":
|
||||||
action, node_id = payload.get("action"), payload.get("target_node_id")
|
action, node_id = payload.get("action"), payload.get("target_node_id")
|
||||||
if action not in FIXED_ACTIONS or not node_id:
|
if action not in FIXED_ACTIONS or not node_id:
|
||||||
|
|||||||
@ -22,6 +22,10 @@ with tempfile.TemporaryDirectory() as temp:
|
|||||||
else: raise AssertionError("server did not start")
|
else: raise AssertionError("server did not start")
|
||||||
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-FIFTH"}).encode(), method="POST", headers={"X-Lighthouse-Admin-Token":"test-token"})
|
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-FIFTH"}).encode(), method="POST", headers={"X-Lighthouse-Admin-Token":"test-token"})
|
||||||
assert json.load(urllib.request.urlopen(request))["state"] == "PENDING_REVIEW"
|
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-FIFTH","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"
|
||||||
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"})
|
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)
|
try: urllib.request.urlopen(bad)
|
||||||
except urllib.error.HTTPError as error: assert error.code == 400
|
except urllib.error.HTTPError as error: assert error.code == 400
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user