fix(device-plane): constrain B2 discovery to loopback

This commit is contained in:
Codex 2026-07-26 01:05:14 +03:00
parent 2b1795509b
commit a3308c9b3d
6 changed files with 42 additions and 39 deletions

View File

@ -1,9 +1,9 @@
{
"schemaVersion": "nodedc.device-plane.b2-discovery-ingress.v1",
"mode": "verified-b2-discovery-only",
"mode": "verified-b2-loopback-discovery-only",
"predecessorPatchId": "device-plane-foundation-network-publication-20260725-003",
"predecessorArtifactSha256": "6fdd5a12c310786db1753882fc1378184fe378d2cc533633a8c73c951521b7bf",
"sourceAction": "publish-verified-b2-discovery-ingress-source",
"sourceAction": "publish-verified-b2-loopback-discovery-source",
"runtimeAction": "build-and-recreate-stateless-services",
"selectedServices": [
"device-control-core",
@ -17,7 +17,7 @@
"publishedPorts": [
"127.0.0.1:18120:18120",
"127.0.0.1:18121:18121",
"0.0.0.0:9921:9921/tcp"
"127.0.0.1:9921:9921/tcp"
],
"protocolProfile": "arusnavi.b2.internal.v1",
"framingSpecification": "arusnavi.internal.protocol-sheet.gid-12.v1",

View File

@ -100,8 +100,8 @@ services:
DEVICE_GATEWAY_HEALTH_HOST: 0.0.0.0
DEVICE_GATEWAY_HEALTH_PORT: "18121"
DEVICE_GATEWAY_LISTEN_ENABLED: "true"
DEVICE_GATEWAY_PUBLIC_INGRESS_ENABLED: "true"
DEVICE_GATEWAY_TCP_HOST: 0.0.0.0
DEVICE_GATEWAY_PUBLIC_INGRESS_ENABLED: "false"
DEVICE_GATEWAY_TCP_HOST: 127.0.0.1
DEVICE_GATEWAY_TCP_PORT: "9921"
DEVICE_GATEWAY_CORE_URL: http://device-control-core:18120
DEVICE_GATEWAY_CORE_TOKEN_FILE: /run/nodedc-secrets/gateway-core-token
@ -120,7 +120,7 @@ services:
create_host_path: false
ports:
- "127.0.0.1:18121:18121"
- "0.0.0.0:9921:9921"
- "127.0.0.1:9921:9921"
networks:
- device-plane-private
- device-plane-control

View File

@ -193,10 +193,12 @@ raw TCP remain forbidden until separate reviewed acceptance slices.
through the standalone root administrative gate.
3. Plan/apply the exact discovery-ingress artifact and independently prove:
Core/Gateway/PostgreSQL health, PostgreSQL generation preservation,
`0.0.0.0:9921` publication, verified framing, quarantine-only Core ingest and
disabled command transport.
4. Only after runtime acceptance, allow TCP 9921 in DSM firewall and configure
WAN-to-NAS TCP forwarding. Keep the HTTP(S) Reverse Proxy out of this path.
5. Add the NDC route to one approved B2 free server slot while preserving
Gelios, then prove HEADER/discovery/PACKAGE acknowledgement. Claim and
tenant assignment remain a later explicit platform-admin operation.
`127.0.0.1:9921` loopback-only publication, verified framing,
quarantine-only Core ingest and disabled command transport.
4. Do not publish raw device TCP from the multi-service Synology. Design and
independently review an isolated edge/DMZ/VPS ingress with no lateral NAS
access, explicit firewall/NAT evidence, acceptance and rollback.
5. Only after that separate edge gate, add the NDC route to one approved B2
free server slot while preserving Gelios, then prove
HEADER/discovery/PACKAGE acknowledgement. Claim and tenant assignment
remain a later explicit platform-admin operation.

View File

@ -23,7 +23,7 @@ const artifactDir = resolve(
|| resolve(scriptDir, "../deploy-artifacts"),
);
const [
patchId = "device-plane-b2-discovery-ingress-20260726-001",
patchId = "device-plane-b2-discovery-loopback-20260726-002",
...extra
] = process.argv.slice(2);
@ -99,7 +99,7 @@ try {
artifact: target,
sha256: digest,
component: "device-plane",
transition: "verified-b2-discovery-only",
transition: "verified-b2-loopback-discovery-only",
entries: files,
services: ["device-control-core", "device-gateway"],
preservedRuntime: [
@ -109,8 +109,8 @@ try {
],
ingress: {
transport: "tcp",
published: "0.0.0.0:9921:9921",
mode: "discovery-only",
published: "127.0.0.1:9921:9921",
mode: "loopback-discovery-only",
framing: "verified-read-only",
lifecycle: "quarantine",
commandTransport: "disabled",
@ -137,12 +137,12 @@ async function assertBoundary() {
for (const fragment of [
'DEVICE_DISCOVERY_INGEST_ENABLED: "true"',
'DEVICE_GATEWAY_LISTEN_ENABLED: "true"',
'DEVICE_GATEWAY_PUBLIC_INGRESS_ENABLED: "true"',
'DEVICE_GATEWAY_PUBLIC_INGRESS_ENABLED: "false"',
"DEVICE_GATEWAY_CORE_URL: http://device-control-core:18120",
"DEVICE_GATEWAY_CORE_TOKEN_FILE: /run/nodedc-secrets/gateway-core-token",
'"127.0.0.1:18120:18120"',
'"127.0.0.1:18121:18121"',
'"0.0.0.0:9921:9921"',
'"127.0.0.1:9921:9921"',
"name: nodedc-device-plane-private",
"internal: true",
"name: nodedc-device-plane-control",
@ -180,12 +180,12 @@ async function assertBoundary() {
));
const expected = {
schemaVersion: "nodedc.device-plane.b2-discovery-ingress.v1",
mode: "verified-b2-discovery-only",
mode: "verified-b2-loopback-discovery-only",
predecessorPatchId:
"device-plane-foundation-network-publication-20260725-003",
predecessorArtifactSha256:
"6fdd5a12c310786db1753882fc1378184fe378d2cc533633a8c73c951521b7bf",
sourceAction: "publish-verified-b2-discovery-ingress-source",
sourceAction: "publish-verified-b2-loopback-discovery-source",
runtimeAction: "build-and-recreate-stateless-services",
selectedServices: ["device-control-core", "device-gateway"],
preservedServices: ["device-postgres"],
@ -194,7 +194,7 @@ async function assertBoundary() {
publishedPorts: [
"127.0.0.1:18120:18120",
"127.0.0.1:18121:18121",
"0.0.0.0:9921:9921/tcp",
"127.0.0.1:9921:9921/tcp",
],
protocolProfile: "arusnavi.b2.internal.v1",
framingSpecification:

View File

@ -91,7 +91,7 @@ DEVICE_PLANE_B2_DISCOVERY_INGRESS_PREDECESSOR_ARTIFACT_SHA256 = (
"6fdd5a12c310786db1753882fc1378184fe378d2cc533633a8c73c951521b7bf"
)
DEVICE_PLANE_B2_DISCOVERY_INGRESS_COMPOSE_SHA256 = (
"50bc7842481ea73b89891a65bf243c2ecb7bbb4c093e95e3c20ddbfaa8ad6726"
"eb1018cc0ffeaa0c019944d8810e2daa01eeca4c06289efff175785fb16457e7"
)
DEVICE_PLANE_FOUNDATION_FAILED_PATCH_ID = (
"device-plane-foundation-20260725-001"
@ -8362,14 +8362,14 @@ def validate_device_plane_foundation_network_publication_payload(payload_dir):
def expected_device_plane_b2_discovery_ingress_descriptor():
return {
"schemaVersion": "nodedc.device-plane.b2-discovery-ingress.v1",
"mode": "verified-b2-discovery-only",
"mode": "verified-b2-loopback-discovery-only",
"predecessorPatchId": (
DEVICE_PLANE_B2_DISCOVERY_INGRESS_PREDECESSOR_PATCH_ID
),
"predecessorArtifactSha256": (
DEVICE_PLANE_B2_DISCOVERY_INGRESS_PREDECESSOR_ARTIFACT_SHA256
),
"sourceAction": "publish-verified-b2-discovery-ingress-source",
"sourceAction": "publish-verified-b2-loopback-discovery-source",
"runtimeAction": "build-and-recreate-stateless-services",
"selectedServices": [
"device-control-core",
@ -8381,7 +8381,7 @@ def expected_device_plane_b2_discovery_ingress_descriptor():
"publishedPorts": [
"127.0.0.1:18120:18120",
"127.0.0.1:18121:18121",
"0.0.0.0:9921:9921/tcp",
"127.0.0.1:9921:9921/tcp",
],
"protocolProfile": "arusnavi.b2.internal.v1",
"framingSpecification": (
@ -9381,7 +9381,7 @@ def validate_device_plane_b2_discovery_ingress_runtime(runtime_before):
"HostPort": "18121",
}],
"9921/tcp": [{
"HostIp": "0.0.0.0",
"HostIp": "127.0.0.1",
"HostPort": "9921",
}],
},
@ -9492,8 +9492,8 @@ def validate_device_plane_b2_discovery_ingress_runtime(runtime_before):
)
gateway_required = {
"DEVICE_GATEWAY_LISTEN_ENABLED": "true",
"DEVICE_GATEWAY_PUBLIC_INGRESS_ENABLED": "true",
"DEVICE_GATEWAY_TCP_HOST": "0.0.0.0",
"DEVICE_GATEWAY_PUBLIC_INGRESS_ENABLED": "false",
"DEVICE_GATEWAY_TCP_HOST": "127.0.0.1",
"DEVICE_GATEWAY_TCP_PORT": "9921",
"DEVICE_GATEWAY_CORE_URL": "http://device-control-core:18120",
"DEVICE_GATEWAY_CORE_TOKEN_FILE":
@ -15182,7 +15182,7 @@ def plan_artifact(artifact):
)
print("device_postgres_volume=preserved:nodedc-device-plane-postgres-data")
if device_plane_b2_ingress_preflight is not None:
print("device_gateway_public_ingress=discovery-only:tcp:9921")
print("device_gateway_public_ingress=disabled:loopback-test:tcp:9921")
print("device_control_core_discovery_ingest=enabled:authenticated")
else:
print("device_gateway_public_ingress=disabled")
@ -15284,7 +15284,7 @@ def plan_artifact(artifact):
print(
"device_plane_actual_ports="
"required:127.0.0.1:18120,127.0.0.1:18121,"
"0.0.0.0:9921/tcp"
"127.0.0.1:9921/tcp"
)
print(
"device_gateway_framing="
@ -16622,7 +16622,7 @@ def component_healthchecks(component, entries=None, services=None):
"service": "nodedc-device-gateway",
"framing": "verified-read-only",
"tcpListener": "discovery-only",
"publicIngress": "discovery-only",
"publicIngress": "disabled",
"commandTransport": "disabled",
},
},

View File

@ -68,7 +68,7 @@ class DevicePlaneB2DiscoveryIngressArtifactTest(unittest.TestCase):
)
self.assertEqual(
first["transition"],
"verified-b2-discovery-only",
"verified-b2-loopback-discovery-only",
)
self.assertEqual(
first["services"],
@ -119,10 +119,11 @@ class DevicePlaneB2DiscoveryIngressArtifactTest(unittest.TestCase):
stateless,
)
self.assertIn(
'DEVICE_GATEWAY_PUBLIC_INGRESS_ENABLED: "true"',
'DEVICE_GATEWAY_PUBLIC_INGRESS_ENABLED: "false"',
stateless,
)
self.assertIn('"0.0.0.0:9921:9921"', stateless)
self.assertIn('"127.0.0.1:9921:9921"', stateless)
self.assertNotIn('"0.0.0.0:9921:9921"', stateless)
self.assertNotIn("DEVICE_GATEWAY_COMMAND", compose)
self.assertNotIn("POSTGRES_PASSWORD:", compose)
@ -145,7 +146,7 @@ class DevicePlaneB2DiscoveryIngressArtifactTest(unittest.TestCase):
)
self.assertEqual(
checks[1]["expected_json"]["publicIngress"],
"discovery-only",
"disabled",
)
self.assertEqual(
checks[1]["expected_json"]["commandTransport"],
@ -207,14 +208,14 @@ class DevicePlaneB2DiscoveryIngressArtifactTest(unittest.TestCase):
"HostPort": "18121",
}],
"9921/tcp": [{
"HostIp": "0.0.0.0",
"HostIp": "127.0.0.1",
"HostPort": "9921",
}],
},
environment={
"DEVICE_GATEWAY_LISTEN_ENABLED": "true",
"DEVICE_GATEWAY_PUBLIC_INGRESS_ENABLED": "true",
"DEVICE_GATEWAY_TCP_HOST": "0.0.0.0",
"DEVICE_GATEWAY_PUBLIC_INGRESS_ENABLED": "false",
"DEVICE_GATEWAY_TCP_HOST": "127.0.0.1",
"DEVICE_GATEWAY_TCP_PORT": "9921",
"DEVICE_GATEWAY_CORE_URL":
"http://device-control-core:18120",