feat(device-edge): add canonical core channel deployment

This commit is contained in:
Codex
2026-08-11 20:00:58 +03:00
parent 1124c15216
commit 6461e7fca8
8 changed files with 877 additions and 14 deletions
+315 -9
View File
@@ -44,6 +44,8 @@ BACKHAUL_USER = "nodedc-backhaul"
BACKHAUL_GROUP = "nodedc-backhaul"
RELAY_USER = "nodedc-relay"
RELAY_GROUP = "nodedc-relay"
CHANNEL_USER = "nodedc-channel"
CHANNEL_GROUP = "nodedc-channel"
TAILSCALE_REQUIRED_TAG = "tag:device-edge-vps"
MANAGEMENT_KEY_FINGERPRINT = (
"SHA256:DYYy1E3DaxIQGC0jnsW6SP7gXdBHUy3A1zn4pvgVUEw"
@@ -89,6 +91,14 @@ NFTABLES_CONFIG = Path("/etc/nftables.conf")
TAILSCALE_UNIT = Path("/etc/systemd/system/nodedc-b2-tailscaled.service")
BACKHAUL_UNIT = Path("/etc/systemd/system/nodedc-b2-backhaul.service")
RELAY_UNIT = Path("/etc/systemd/system/nodedc-b2-relay.service")
CHANNEL_UNIT = Path("/etc/systemd/system/nodedc-device-edge-channel.service")
CHANNEL_TRUST_ROOT = Path("/var/lib/nodedc-b2-vps/channel-trust")
CHANNEL_PRIVATE_KEY = CHANNEL_TRUST_ROOT / "edge-private-key.pem"
CHANNEL_CERTIFICATE = CHANNEL_TRUST_ROOT / "edge-certificate.pem"
CHANNEL_CORE_CERTIFICATE = CHANNEL_TRUST_ROOT / "core-certificate.pem"
CHANNEL_RUNTIME_CONFIG = CHANNEL_TRUST_ROOT / "runtime.json"
CHANNEL_HEALTH_PORT = 18222
CHANNEL_PUBLIC_PORT = 8443
FOUNDATION_ENTRIES = (
"vps/config/00-nodedc-b2-vps.conf",
@@ -109,11 +119,23 @@ RELAY_ENTRIES = (
"services/device-edge-relay/src",
"deployment/device-edge-vps-relay-v1.json",
)
CORE_CHANNEL_ENTRIES = (
"packages/device-protocol-contract/package.json",
"packages/device-protocol-contract/src",
"packages/device-edge-channel-contract/package.json",
"packages/device-edge-channel-contract/src",
"services/device-edge-channel/package.json",
"services/device-edge-channel/src",
"vps/config/nftables-core-channel.conf",
"vps/systemd/nodedc-device-edge-channel.service",
"deployment/device-edge-vps-core-channel-v1.json",
)
PHASE_ENTRIES = {
"foundation": FOUNDATION_ENTRIES,
"backhaul": BACKHAUL_ENTRIES,
"relay": RELAY_ENTRIES,
"core-channel": CORE_CHANNEL_ENTRIES,
}
SUPERSEDED_TRANSPORT_PHASES = frozenset({"backhaul", "relay"})
@@ -121,19 +143,19 @@ SUPERSEDED_TRANSPORT_PHASES = frozenset({"backhaul", "relay"})
PHASE_FILE_SHA256 = {
"foundation": {
"vps/config/00-nodedc-b2-vps.conf":
"cc94d0579f85d0af9746b9ce760bc72980f4a22fb59027e1f5f9c7bf3aaebd64",
"2079748f48b2297ecb067e16ae46248e3a981b26331aa0998dc14fdf7454cd4a",
"vps/config/nftables-foundation.conf":
"4d44f902d8d98d1aa8506fca9d9582e700f6424def2b1d667ab2cd5a5ee84934",
"bce5b8c6e2226d47d8d322f8ee9e7158f2d7a553c5e2c4bc2a722d6067f46e28",
"vps/systemd/nodedc-b2-tailscaled.service":
"de147d29bc1759f56533d31058993df55e1200973f2899903bdbf3d13ff579da",
"deployment/device-edge-vps-foundation-v1.json":
"317c98b42520fff3238275908482de7aa611b4ee41c6b1f8062abd2730ab072a",
"5ae007195b17d6cfaeb564abbed6dc9234eb36bf62db23bf2b9ace539c398898",
f"vendor/{NODE_ARCHIVE}": NODE_ARCHIVE_SHA256,
f"vendor/{TAILSCALE_ARCHIVE}": TAILSCALE_ARCHIVE_SHA256,
},
"backhaul": {
"vps/config/backhaul_ssh_config":
"ff1a3575b5a55a56b08a8642aa820d1f8e5f07e0e7698fdd0e01a142452674d4",
"d0df8b70dda025b7c1c3fecd2dafffe60a5bb753650d3bc37db65db626cfc1af",
"vps/systemd/nodedc-b2-backhaul.service":
"64c26cad21cc17675c67ae4a57fc43b129065a8d22fda894648340b310d3aa8c",
"deployment/device-edge-vps-backhaul-v1.json":
@@ -141,7 +163,7 @@ PHASE_FILE_SHA256 = {
},
"relay": {
"vps/config/nftables-relay.conf":
"d99290bf825a3aff2ad8f9dbc6502ba6c32295f0cabd3f71c3d9b94c06373259",
"497485a2fb1b79faa95eb67ca30a89a3b65c3d4d8ddcb14cd1dc9acfeb2bb6f2",
"vps/systemd/nodedc-b2-relay.service":
"12a927a4cb42016229ac438f6e75969e1bf015037f2b5b0a60ab0591bdf424d2",
"services/device-edge-relay/src/runtime.mjs":
@@ -151,6 +173,28 @@ PHASE_FILE_SHA256 = {
"deployment/device-edge-vps-relay-v1.json":
"cb3c2fff4878021783efef4f4a4d1ec31c8e3d8e6325fe33657fec22bec20165",
},
"core-channel": {
"packages/device-protocol-contract/package.json":
"19d0d07da0341e8c2e8d3485400767566b18f6270245f0e9a9681c95013ba3b7",
"packages/device-protocol-contract/src/index.mjs":
"21a8b2b85a807899f946387c7976eaffcdc438b7db5e3d41fc3930f4437f0ec7",
"packages/device-edge-channel-contract/package.json":
"57d5349b5dcef2cacd4f3e4fad010359a65d59f5f903eff07d89f67c497f97c0",
"packages/device-edge-channel-contract/src/index.mjs":
"09d45e6104779212605fed650544794aafe1c2b1ab66f33f07bef4d92f430c80",
"services/device-edge-channel/package.json":
"bdf502be43b62bdd6db05b022a532d93ba954277ac5143d6058d2f27f6a2e9d2",
"services/device-edge-channel/src/runtime.mjs":
"cbb07f7e644a68e9c8c36c1c2ecf0224c06c46c08c49339d62637b10a1495501",
"services/device-edge-channel/src/server.mjs":
"ea891634a18efb9eb44f17b56c95ba97527215c4d0a6147cc2b7bad1d7356e36",
"vps/config/nftables-core-channel.conf":
"1a04a5450042e80b8a20da3c6634dd6bc68693f191a463ba9a62984279d81d0c",
"vps/systemd/nodedc-device-edge-channel.service":
"57c6c0c5eb952e1e196f50c410a8537468af145941b21dd4aad6ff0e8ca56249",
"deployment/device-edge-vps-core-channel-v1.json":
"938f6f7959b78e6be3a6e91a54dca2922fbd813f1a33dca5cebe0dc256a83a14",
},
}
@@ -452,7 +496,7 @@ def current_phase_preflight(phase: str):
die("VPS foundation live/runtime root already exists")
if any(path.exists() for path in (SSHD_DROPIN, TAILSCALE_UNIT, BACKHAUL_UNIT, RELAY_UNIT)):
die("VPS foundation system path already exists")
for port in (1055, 18221, 19921, 9921):
for port in (1055, 18221, CHANNEL_HEALTH_PORT, 19921, 9921, CHANNEL_PUBLIC_PORT):
assert_port_closed(port)
return {"predecessor": "clean-ubuntu-24.04.4"}
@@ -462,6 +506,19 @@ def current_phase_preflight(phase: str):
require_running_tailnet=phase in {"backhaul", "relay"},
expected_key_user=BACKHAUL_USER if phase == "relay" else SERVICE_USER,
)
if phase == "core-channel":
if service_active("nodedc-b2-backhaul.service"):
die("frozen VPS backhaul service must remain inactive")
if service_active("nodedc-b2-relay.service"):
die("frozen VPS relay service must remain inactive")
if CHANNEL_UNIT.exists() or (LIVE_ROOT / CORE_CHANNEL_ENTRIES[-1]).exists():
die("VPS Core channel target path already exists")
if user_exists(CHANNEL_USER):
die("VPS Core channel runtime user already exists")
assert_channel_trust(require_runtime_owner=False)
for port in (CHANNEL_HEALTH_PORT, CHANNEL_PUBLIC_PORT, 9921):
assert_port_closed(port)
return {"predecessor": "accepted-foundation-closed-channel"}
if phase == "backhaul":
for tool in (Path("/usr/bin/ssh"), Path("/usr/bin/nc")):
assert_executable_command_path(
@@ -529,6 +586,8 @@ def backup_targets_for_phase(phase: str):
return common + [SSHD_DROPIN, NFTABLES_CONFIG, TAILSCALE_UNIT]
if phase == "backhaul":
return common + [BACKHAUL_UNIT, BACKHAUL_KNOWN_HOSTS]
if phase == "core-channel":
return common + [CHANNEL_UNIT, NFTABLES_CONFIG, CHANNEL_TRUST_ROOT]
return common + [RELAY_UNIT, NFTABLES_CONFIG]
@@ -568,7 +627,7 @@ def create_backup(patch_id: str, phase: str):
"serviceUserExisted": user_exists(),
"serviceUsersExisted": {
name: user_exists(name)
for name in (SERVICE_USER, BACKHAUL_USER, RELAY_USER)
for name in (SERVICE_USER, BACKHAUL_USER, RELAY_USER, CHANNEL_USER)
},
"services": {
name: {
@@ -582,6 +641,7 @@ def create_backup(patch_id: str, phase: str):
"nodedc-b2-tailscaled.service",
"nodedc-b2-backhaul.service",
"nodedc-b2-relay.service",
"nodedc-device-edge-channel.service",
)
},
}
@@ -740,6 +800,146 @@ def assign_backhaul_trust(account):
os.chmod(BACKHAUL_KNOWN_HOSTS, 0o444)
def certificate_fingerprint(path: Path):
output = run([
"/usr/bin/openssl",
"x509",
"-in",
str(path),
"-noout",
"-fingerprint",
"-sha256",
]).stdout.strip()
prefix = "sha256 Fingerprint="
if not output.lower().startswith(prefix.lower()):
die("VPS certificate fingerprint output is invalid")
fingerprint = output.split("=", 1)[1].upper()
if not re.fullmatch(r"(?:[A-F0-9]{2}:){31}[A-F0-9]{2}", fingerprint):
die("VPS certificate fingerprint is invalid")
return fingerprint
def assert_channel_trust(*, require_runtime_owner: bool):
directory = assert_directory_nonsymlink(
CHANNEL_TRUST_ROOT,
"Core channel trust root",
)
expected_uid = 0
expected_gid = 0
if require_runtime_owner:
account = pwd.getpwnam(CHANNEL_USER)
expected_uid = account.pw_uid
expected_gid = account.pw_gid
if (
directory.st_uid != expected_uid
or directory.st_gid != expected_gid
or (directory.st_mode & 0o777) != 0o700
):
die("Core channel trust root ownership/mode mismatch")
for path, maximum, mode in (
(CHANNEL_PRIVATE_KEY, 32 * 1024, 0o400),
(CHANNEL_CERTIFICATE, 32 * 1024, 0o444),
(CHANNEL_CORE_CERTIFICATE, 32 * 1024, 0o444),
(CHANNEL_RUNTIME_CONFIG, 32 * 1024, 0o444),
):
state = assert_regular_nonsymlink(path, f"Core channel trust {path.name}")
if state.st_size < 1 or state.st_size > maximum:
die(f"Core channel trust file size mismatch: {path.name}")
if (
state.st_uid != expected_uid
or state.st_gid != expected_gid
or (state.st_mode & 0o777) != mode
):
die(f"Core channel trust file ownership/mode mismatch: {path.name}")
private_text = CHANNEL_PRIVATE_KEY.read_text(encoding="ascii")
public_text = (
CHANNEL_CERTIFICATE.read_text(encoding="ascii")
+ CHANNEL_CORE_CERTIFICATE.read_text(encoding="ascii")
)
if "PRIVATE KEY" not in private_text or "PRIVATE KEY" in public_text:
die("Core channel private/public trust boundary mismatch")
for path in (
CHANNEL_CERTIFICATE,
CHANNEL_CORE_CERTIFICATE,
):
if path.read_text(encoding="ascii").count("-----BEGIN CERTIFICATE-----") != 1:
die(f"Core channel certificate cardinality mismatch: {path.name}")
run(["/usr/bin/openssl", "pkey", "-in", str(CHANNEL_PRIVATE_KEY), "-check", "-noout"])
run(["/usr/bin/openssl", "x509", "-in", str(CHANNEL_CERTIFICATE), "-noout"])
run(["/usr/bin/openssl", "x509", "-in", str(CHANNEL_CORE_CERTIFICATE), "-noout"])
run([
"/usr/bin/openssl", "verify", "-purpose", "sslserver",
"-CAfile", str(CHANNEL_CERTIFICATE), str(CHANNEL_CERTIFICATE),
])
run([
"/usr/bin/openssl", "verify", "-purpose", "sslclient",
"-CAfile", str(CHANNEL_CORE_CERTIFICATE), str(CHANNEL_CORE_CERTIFICATE),
])
run([
"/usr/bin/openssl", "x509", "-in", str(CHANNEL_CERTIFICATE),
"-noout", "-checkip", PUBLIC_IPV4,
])
certificate_key = run([
"/usr/bin/openssl", "x509", "-in", str(CHANNEL_CERTIFICATE), "-pubkey", "-noout",
]).stdout.strip()
private_key = run([
"/usr/bin/openssl", "pkey", "-in", str(CHANNEL_PRIVATE_KEY), "-pubout",
]).stdout.strip()
if certificate_key != private_key:
die("Core channel Edge certificate/private key mismatch")
try:
document = json.loads(CHANNEL_RUNTIME_CONFIG.read_text(encoding="utf-8"))
except json.JSONDecodeError:
die("Core channel runtime configuration is invalid JSON")
if set(document) != {
"schemaVersion",
"edgeRegistrationId",
"channelGeneration",
"trustGeneration",
"allowedCoreFingerprints",
}:
die("Core channel runtime configuration key set mismatch")
if document.get("schemaVersion") != "nodedc.device-edge.channel-runtime.v1":
die("Core channel runtime configuration schema mismatch")
for key in ("edgeRegistrationId", "channelGeneration", "trustGeneration"):
if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._:-]{0,127}", str(document.get(key, ""))):
die(f"Core channel runtime configuration ref mismatch: {key}")
fingerprints = document.get("allowedCoreFingerprints")
if (
not isinstance(fingerprints, list)
or not 1 <= len(fingerprints) <= 2
or len(fingerprints) != len(set(fingerprints))
or any(
not isinstance(value, str)
or not re.fullmatch(r"(?:[A-F0-9]{2}:){31}[A-F0-9]{2}", value)
for value in fingerprints
)
):
die("Core channel Core identity allowlist mismatch")
if certificate_fingerprint(CHANNEL_CORE_CERTIFICATE) not in fingerprints:
die("Core channel Core trust fingerprint mismatch")
return document
def assign_channel_trust(account):
assert_channel_trust(require_runtime_owner=False)
os.chown(CHANNEL_TRUST_ROOT, account.pw_uid, account.pw_gid)
os.chmod(CHANNEL_TRUST_ROOT, 0o700)
for path, mode in (
(CHANNEL_PRIVATE_KEY, 0o400),
(CHANNEL_CERTIFICATE, 0o444),
(CHANNEL_CORE_CERTIFICATE, 0o444),
(CHANNEL_RUNTIME_CONFIG, 0o444),
):
os.chown(path, account.pw_uid, account.pw_gid)
os.chmod(path, mode)
return assert_channel_trust(require_runtime_owner=True)
def apply_nftables(source: Path):
install_file(source, NFTABLES_CONFIG, 0o644)
run(["/usr/sbin/nft", "-c", "-f", str(NFTABLES_CONFIG)])
@@ -813,6 +1013,19 @@ def apply_relay(payload: Path):
validate_relay_runtime()
def apply_core_channel(payload: Path):
account = ensure_service_user(
CHANNEL_USER,
"/var/lib/nodedc-b2-vps/channel-runtime",
)
assign_channel_trust(account)
install_file(LIVE_ROOT / CORE_CHANNEL_ENTRIES[7], CHANNEL_UNIT, 0o644)
apply_nftables(LIVE_ROOT / CORE_CHANNEL_ENTRIES[6])
systemctl("daemon-reload")
systemctl("enable", "--now", "nodedc-device-edge-channel.service")
validate_core_channel_runtime()
def sshd_effective():
return run(["/usr/sbin/sshd", "-T"]).stdout.lower()
@@ -919,6 +1132,85 @@ def relay_health():
die(f"VPS relay health timeout: {last_error}")
def core_channel_health(*, require_accepted: bool):
last_error = None
for _attempt in range(60):
try:
with urllib.request.urlopen(
f"http://127.0.0.1:{CHANNEL_HEALTH_PORT}/healthz",
timeout=3,
) as response:
payload = json.loads(response.read(65537).decode("utf-8"))
if (
response.status == 200
and payload.get("ok") is True
and (
not require_accepted
or payload.get("channel") == "accepted"
)
):
return payload
last_error = f"channel={payload.get('channel')}"
except Exception as error:
last_error = str(error)
time.sleep(2)
die(f"VPS Core channel health timeout: {last_error}")
def validate_core_channel_runtime():
validate_foundation_runtime(
require_running_tailnet=False,
expected_key_user=SERVICE_USER,
)
source_file_state("core-channel")
assert_channel_trust(require_runtime_owner=True)
if service_active("nodedc-b2-backhaul.service"):
die("frozen VPS backhaul service became active")
if service_active("nodedc-b2-relay.service"):
die("frozen VPS relay service became active")
if not service_active("nodedc-device-edge-channel.service"):
die("VPS Core channel service is not active")
health = core_channel_health(require_accepted=True)
expected = {
"ok": True,
"service": "nodedc-device-edge-channel",
"channel": "accepted",
"trackerIngress": "disabled",
"commandTransport": "disabled",
}
for key, value in expected.items():
if health.get(key) != value:
die(f"VPS Core channel health contract mismatch: {key}")
if not port_is_open(PUBLIC_IPV4, CHANNEL_PUBLIC_PORT, timeout=5):
die("VPS public Core channel listener is unavailable")
assert_port_closed(9921)
nft = run(["/usr/sbin/nft", "list", "table", "inet", "nodedc_b2_vps"]).stdout
if (
"policy drop" not in nft
or "tcp dport 8443" not in nft
or "tcp dport 9921" in nft
):
die("VPS Core channel firewall contract mismatch")
unit = run([
"/usr/bin/systemctl",
"show",
"nodedc-device-edge-channel.service",
"--property=User,Group,NoNewPrivileges,MemoryMax,MemorySwapMax,CPUQuotaPerSecUSec,TasksMax,LimitNOFILE",
]).stdout
for required in (
"User=nodedc-channel",
"Group=nodedc-channel",
"NoNewPrivileges=yes",
"MemoryMax=134217728",
"MemorySwapMax=0",
"TasksMax=64",
"LimitNOFILE=1024",
):
if required not in unit:
die(f"VPS Core channel resource boundary mismatch: {required}")
return health
def validate_relay_runtime():
validate_backhaul_runtime()
source_file_state("relay")
@@ -979,6 +1271,7 @@ def rollback(backup: Path, phase: str):
for service in (
"nodedc-b2-relay.service",
"nodedc-b2-backhaul.service",
"nodedc-device-edge-channel.service",
"nodedc-b2-tailscaled.service",
):
if phase == "foundation" or service != "nodedc-b2-tailscaled.service":
@@ -1004,6 +1297,9 @@ def rollback(backup: Path, phase: str):
if phase == "relay":
if not users_before.get(RELAY_USER, False) and user_exists(RELAY_USER):
run(["/usr/sbin/userdel", RELAY_USER], check=False)
if phase == "core-channel":
if not users_before.get(CHANNEL_USER, False) and user_exists(CHANNEL_USER):
run(["/usr/sbin/userdel", CHANNEL_USER], check=False)
if phase == "foundation" and not metadata.get("serviceUserExisted"):
runtime_state_root = Path("/var/lib/nodedc-b2-vps")
if LIVE_ROOT.exists() and not LIVE_ROOT.is_symlink():
@@ -1052,13 +1348,21 @@ def plan_artifact(artifact_argument: str):
print("public_b2_ingress=disabled")
print("services=nodedc-b2-backhaul")
print(f"backhaul_runtime_identity={BACKHAUL_USER}:private-key-owner")
else:
elif phase == "relay":
print("public_b2_ingress=155.212.211.15:9921/tcp")
print("health=127.0.0.1:18221")
print("private_upstream=127.0.0.1:19921")
print("source_admission=public-ipv4-only")
print("services=nodedc-b2-relay")
print(f"relay_runtime_identity={RELAY_USER}:no-credentials")
else:
print("public_core_channel=155.212.211.15:8443/tcp:tls13-mtls-h2")
print(f"health=127.0.0.1:{CHANNEL_HEALTH_PORT}")
print("public_b2_ingress=disabled")
print("tracker_tcp_9921=closed")
print("services=nodedc-device-edge-channel")
print(f"channel_runtime_identity={CHANNEL_USER}:host-local-private-key")
print("peer_trust=preprovisioned-pinned-self-signed-core-certificate+fingerprint")
print("command_transport=disabled")
print("gelios=untouched")
print("dns=unchanged")
@@ -1088,8 +1392,10 @@ def apply_artifact(artifact_argument: str):
apply_foundation(loaded["payload"])
elif loaded["phase"] == "backhaul":
apply_backhaul(loaded["payload"])
else:
elif loaded["phase"] == "relay":
apply_relay(loaded["payload"])
else:
apply_core_channel(loaded["payload"])
archived = archive_artifact(loaded["artifact"], APPLIED_ROOT)
record = {