From a3308c9b3db9a77bbc9eb1e73bf60784ed1aa1da Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 26 Jul 2026 01:05:14 +0300 Subject: [PATCH] fix(device-plane): constrain B2 discovery to loopback --- .../device-plane-b2-discovery-ingress-v1.json | 6 +++--- device-plane/docker-compose.device-plane.yml | 6 +++--- device-plane/docs/IMPLEMENTATION_BASELINE.md | 16 ++++++++------- ...ce-plane-b2-discovery-ingress-artifact.mjs | 18 ++++++++--------- infra/deploy-runner/nodedc-deploy | 20 +++++++++---------- ...ice_plane_b2_discovery_ingress_artifact.py | 15 +++++++------- 6 files changed, 42 insertions(+), 39 deletions(-) diff --git a/device-plane/deployment/device-plane-b2-discovery-ingress-v1.json b/device-plane/deployment/device-plane-b2-discovery-ingress-v1.json index d594f6e..6e9018d 100644 --- a/device-plane/deployment/device-plane-b2-discovery-ingress-v1.json +++ b/device-plane/deployment/device-plane-b2-discovery-ingress-v1.json @@ -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", diff --git a/device-plane/docker-compose.device-plane.yml b/device-plane/docker-compose.device-plane.yml index 4f080b5..3aae261 100644 --- a/device-plane/docker-compose.device-plane.yml +++ b/device-plane/docker-compose.device-plane.yml @@ -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 diff --git a/device-plane/docs/IMPLEMENTATION_BASELINE.md b/device-plane/docs/IMPLEMENTATION_BASELINE.md index 9eb1d4c..b619b1f 100644 --- a/device-plane/docs/IMPLEMENTATION_BASELINE.md +++ b/device-plane/docs/IMPLEMENTATION_BASELINE.md @@ -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. diff --git a/infra/deploy-runner/build-device-plane-b2-discovery-ingress-artifact.mjs b/infra/deploy-runner/build-device-plane-b2-discovery-ingress-artifact.mjs index 6ed336a..79348e9 100644 --- a/infra/deploy-runner/build-device-plane-b2-discovery-ingress-artifact.mjs +++ b/infra/deploy-runner/build-device-plane-b2-discovery-ingress-artifact.mjs @@ -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: diff --git a/infra/deploy-runner/nodedc-deploy b/infra/deploy-runner/nodedc-deploy index c213bbb..a59501a 100755 --- a/infra/deploy-runner/nodedc-deploy +++ b/infra/deploy-runner/nodedc-deploy @@ -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", }, }, diff --git a/infra/deploy-runner/test_device_plane_b2_discovery_ingress_artifact.py b/infra/deploy-runner/test_device_plane_b2_discovery_ingress_artifact.py index 0a8a7e4..ff406c5 100644 --- a/infra/deploy-runner/test_device_plane_b2_discovery_ingress_artifact.py +++ b/infra/deploy-runner/test_device_plane_b2_discovery_ingress_artifact.py @@ -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",