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
@@ -0,0 +1,54 @@
{
"schemaVersion": "nodedc.device-edge-vps.core-channel.v1",
"mode": "provider-neutral-core-initiated-mtls-http2",
"status": "closed-tracker-ingress",
"authority": "DCPLATFORM-21/DCPLATFORM-76/ADR-0001",
"component": "device-edge-vps",
"phase": "core-channel",
"runtimeHost": "koffyvngij",
"runtimeUser": "nodedc-channel",
"runtimeService": "nodedc-device-edge-channel.service",
"runtime": "accepted-node-v22.23.2-no-docker",
"publicIngress": "tcp/8443-mtls-only",
"health": "127.0.0.1:18222",
"trackerIngress": "disabled",
"rawDeviceTcp9921": "closed",
"commandTransport": "disabled",
"gelios": "untouched",
"privateKeyBoundary": "runner-managed-host-local-only",
"peerTrustPrerequisite": "exact-pinned-self-signed-core-certificate-and-fingerprint",
"tls": "TLSv1.3+h2+mutual-authentication",
"resourceCeilings": {
"memory": "128M",
"swap": "0",
"cpu": "50%",
"tasks": 64,
"openFiles": 1024
},
"preserved": [
"management-ssh-key",
"accepted-node-runtime",
"foundation-source",
"gelios-production-path"
],
"forbidden": [
"vps-initiated-synology-connection",
"generic-tcp-forwarding",
"tailscale-ssh-backhaul",
"docker",
"public-health",
"tracker-tcp/9921"
],
"acceptance": [
"exact-non-root-runtime-identity",
"tls13-h2-mutual-authentication",
"edge-server-and-core-client-self-signed-identities-mutually-pinned",
"core-initiated-channel-accepted",
"unknown-core-certificate-rejected",
"public-8443-only-beside-management-ssh",
"tracker-tcp-9921-closed",
"loopback-health-contract",
"resource-ceilings-present"
],
"rollback": "close-8443-stop-channel-restore-exact-accepted-foundation-without-backhaul-or-relay"
}
@@ -0,0 +1,203 @@
import { lstat, readFile } from "node:fs/promises";
import { createServer } from "node:http";
import { pathToFileURL } from "node:url";
import { createDeviceEdgeChannelServer } from "./runtime.mjs";
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
await main();
}
async function main() {
const runtime = await readRuntimeConfiguration(process.env);
const channel = createDeviceEdgeChannelServer(runtime.channel);
const health = createHealthServer(channel, runtime.health);
let stopping = false;
await channel.start();
await listen(health, runtime.health.port, runtime.health.host);
console.log(JSON.stringify({
event: "device_edge_channel_started",
host: runtime.channel.host,
port: runtime.channel.port,
healthHost: runtime.health.host,
healthPort: runtime.health.port,
edgeRegistrationId: runtime.channel.edgeRegistrationId,
channelGeneration: runtime.channel.channelGeneration,
trustGeneration: runtime.channel.trustGeneration,
trackerIngress: "disabled",
commandTransport: "disabled",
}));
process.on("SIGTERM", shutdown);
process.on("SIGINT", shutdown);
async function shutdown() {
if (stopping) return;
stopping = true;
await Promise.allSettled([
channel.stop(),
closeServer(health),
]);
process.exit(0);
}
}
export async function readRuntimeConfiguration(environment = {}) {
const configPath = requiredPath(
environment.DEVICE_EDGE_CHANNEL_CONFIG_FILE,
"device_edge_channel_config_file_required",
);
const keyPath = requiredPath(
environment.DEVICE_EDGE_CHANNEL_KEY_FILE,
"device_edge_channel_key_file_required",
);
const certificatePath = requiredPath(
environment.DEVICE_EDGE_CHANNEL_CERTIFICATE_FILE,
"device_edge_channel_certificate_file_required",
);
const coreTrustPath = requiredPath(
environment.DEVICE_EDGE_CHANNEL_CORE_TRUST_FILE,
"device_edge_channel_core_trust_file_required",
);
const config = normalizeRuntimeDocument(JSON.parse(
await readBoundedRegularFile(configPath, 32 * 1024, "utf8"),
));
const [key, cert, ca] = await Promise.all([
readBoundedRegularFile(keyPath, 32 * 1024),
readBoundedRegularFile(certificatePath, 32 * 1024),
readBoundedRegularFile(coreTrustPath, 64 * 1024),
]);
return Object.freeze({
channel: Object.freeze({
edgeRegistrationId: config.edgeRegistrationId,
channelGeneration: config.channelGeneration,
trustGeneration: config.trustGeneration,
host: normalizeHost(environment.DEVICE_EDGE_CHANNEL_HOST ?? "0.0.0.0"),
port: normalizePort(environment.DEVICE_EDGE_CHANNEL_PORT, 8443),
tls: Object.freeze({
key,
cert,
ca,
allowedCoreFingerprints: config.allowedCoreFingerprints,
}),
}),
health: Object.freeze({
host: normalizeHost(
environment.DEVICE_EDGE_CHANNEL_HEALTH_HOST ?? "127.0.0.1",
),
port: normalizePort(environment.DEVICE_EDGE_CHANNEL_HEALTH_PORT, 18222),
}),
});
}
export function normalizeRuntimeDocument(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new TypeError("device_edge_channel_runtime_config_invalid");
}
const allowedKeys = new Set([
"schemaVersion",
"edgeRegistrationId",
"channelGeneration",
"trustGeneration",
"allowedCoreFingerprints",
]);
if (Object.keys(value).some((key) => !allowedKeys.has(key))) {
throw new TypeError("device_edge_channel_runtime_config_key_invalid");
}
if (value.schemaVersion !== "nodedc.device-edge.channel-runtime.v1") {
throw new TypeError("device_edge_channel_runtime_schema_invalid");
}
const fingerprints = value.allowedCoreFingerprints;
if (!Array.isArray(fingerprints) || fingerprints.length < 1 || fingerprints.length > 2) {
throw new TypeError("device_edge_channel_runtime_core_identity_invalid");
}
if (new Set(fingerprints).size !== fingerprints.length) {
throw new TypeError("device_edge_channel_runtime_core_identity_invalid");
}
return Object.freeze({
schemaVersion: value.schemaVersion,
edgeRegistrationId: normalizeRef(value.edgeRegistrationId, "edge_registration"),
channelGeneration: normalizeRef(value.channelGeneration, "channel_generation"),
trustGeneration: normalizeRef(value.trustGeneration, "trust_generation"),
allowedCoreFingerprints: Object.freeze(fingerprints.map((fingerprint) => {
if (typeof fingerprint !== "string" || !/^([A-F0-9]{2}:){31}[A-F0-9]{2}$/.test(fingerprint)) {
throw new TypeError("device_edge_channel_runtime_core_identity_invalid");
}
return fingerprint;
})),
});
}
function createHealthServer(channel, healthConfig) {
return createServer((request, response) => {
response.setHeader("Content-Type", "application/json; charset=utf-8");
response.setHeader("Cache-Control", "no-store");
response.setHeader("X-Content-Type-Options", "nosniff");
if (request.method !== "GET" || request.url !== "/healthz") {
response.statusCode = 404;
response.end(JSON.stringify({ ok: false, error: "not_found" }));
return;
}
const status = channel.status();
response.statusCode = 200;
response.end(JSON.stringify({
ok: true,
service: "nodedc-device-edge-channel",
health: `${healthConfig.host}:${healthConfig.port}`,
...status,
}));
});
}
async function readBoundedRegularFile(path, maximumBytes, encoding = null) {
const state = await lstat(path);
if (!state.isFile() || state.isSymbolicLink() || state.size < 1 || state.size > maximumBytes) {
throw new Error("device_edge_channel_runtime_file_invalid");
}
return readFile(path, encoding ?? undefined);
}
function requiredPath(value, code) {
if (typeof value !== "string" || value.trim() === "" || !value.startsWith("/")) {
throw new Error(code);
}
return value;
}
function normalizeRef(value, field) {
if (typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(value)) {
throw new TypeError(`device_edge_channel_runtime_${field}_invalid`);
}
return value;
}
function normalizeHost(value) {
if (typeof value !== "string" || value.length < 1 || value.length > 253) {
throw new TypeError("device_edge_channel_runtime_host_invalid");
}
return value;
}
function normalizePort(value, fallback) {
const number = Number(value ?? fallback);
if (!Number.isSafeInteger(number) || number < 1 || number > 65_535) {
throw new TypeError("device_edge_channel_runtime_port_invalid");
}
return number;
}
function listen(server, port, host) {
return new Promise((resolve, reject) => {
server.once("error", reject);
server.listen(port, host, () => {
server.off("error", reject);
resolve();
});
});
}
function closeServer(server) {
return new Promise((resolve) => server.close(() => resolve()));
}
@@ -0,0 +1,46 @@
import assert from "node:assert/strict";
import { test } from "node:test";
import { normalizeRuntimeDocument } from "../src/server.mjs";
const fingerprint = Array.from({ length: 32 }, () => "AB").join(":");
test("runtime document accepts one generation-bound Core identity", () => {
const result = normalizeRuntimeDocument({
schemaVersion: "nodedc.device-edge.channel-runtime.v1",
edgeRegistrationId: "edge:moscow-vps-1",
channelGeneration: "channel:1",
trustGeneration: "trust:1",
allowedCoreFingerprints: [fingerprint],
});
assert.equal(result.edgeRegistrationId, "edge:moscow-vps-1");
assert.deepEqual(result.allowedCoreFingerprints, [fingerprint]);
});
test("runtime document rejects hidden authority and missing identity", () => {
assert.throws(() => normalizeRuntimeDocument({
schemaVersion: "nodedc.device-edge.channel-runtime.v1",
edgeRegistrationId: "edge:moscow-vps-1",
channelGeneration: "channel:1",
trustGeneration: "trust:1",
allowedCoreFingerprints: [fingerprint],
endpoint: "https://attacker.invalid",
}), /runtime_config_key_invalid/);
assert.throws(() => normalizeRuntimeDocument({
schemaVersion: "nodedc.device-edge.channel-runtime.v1",
edgeRegistrationId: "edge:moscow-vps-1",
channelGeneration: "channel:1",
trustGeneration: "trust:1",
allowedCoreFingerprints: [],
}), /runtime_core_identity_invalid/);
assert.throws(() => normalizeRuntimeDocument({
schemaVersion: "nodedc.device-edge.channel-runtime.v1",
edgeRegistrationId: "edge:moscow-vps-1",
channelGeneration: "channel:1",
trustGeneration: "trust:1",
allowedCoreFingerprints: [fingerprint, fingerprint],
}), /runtime_core_identity_invalid/);
});
@@ -0,0 +1,26 @@
#!/usr/sbin/nft -f
flush ruleset
table inet nodedc_b2_vps {
chain input {
type filter hook input priority -10; policy drop;
iifname "lo" accept
ct state invalid drop
ct state established,related accept
ip protocol icmp accept
ip6 nexthdr ipv6-icmp accept
tcp dport 22 ct state new limit rate 30/minute burst 60 packets accept
tcp dport 8443 ct state new limit rate 120/minute burst 120 packets accept
}
chain forward {
type filter hook forward priority -10; policy drop;
}
chain output {
type filter hook output priority -10; policy accept;
}
}
@@ -0,0 +1,50 @@
[Unit]
Description=NODE.DC provider-neutral Device Edge Core channel
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=nodedc-channel
Group=nodedc-channel
ExecStart=/opt/nodedc-b2-vps/runtime/node/bin/node --jitless /opt/nodedc-b2-vps/services/device-edge-channel/src/server.mjs
Environment=DEVICE_EDGE_CHANNEL_HOST=0.0.0.0
Environment=DEVICE_EDGE_CHANNEL_PORT=8443
Environment=DEVICE_EDGE_CHANNEL_HEALTH_HOST=127.0.0.1
Environment=DEVICE_EDGE_CHANNEL_HEALTH_PORT=18222
Environment=DEVICE_EDGE_CHANNEL_CONFIG_FILE=/var/lib/nodedc-b2-vps/channel-trust/runtime.json
Environment=DEVICE_EDGE_CHANNEL_KEY_FILE=/var/lib/nodedc-b2-vps/channel-trust/edge-private-key.pem
Environment=DEVICE_EDGE_CHANNEL_CERTIFICATE_FILE=/var/lib/nodedc-b2-vps/channel-trust/edge-certificate.pem
Environment=DEVICE_EDGE_CHANNEL_CORE_TRUST_FILE=/var/lib/nodedc-b2-vps/channel-trust/core-certificate.pem
Restart=always
RestartSec=2
TimeoutStartSec=20
TimeoutStopSec=15
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes
RestrictSUIDSGID=yes
RestrictRealtime=yes
LockPersonality=yes
MemoryDenyWriteExecute=yes
SystemCallArchitectures=native
RestrictAddressFamilies=AF_INET AF_INET6
CapabilityBoundingSet=
AmbientCapabilities=
UMask=0077
MemoryMax=128M
MemorySwapMax=0
CPUQuota=50%
TasksMax=64
LimitNOFILE=1024
[Install]
WantedBy=multi-user.target