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", "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", "predecessorPatchId": "device-plane-foundation-network-publication-20260725-003",
"predecessorArtifactSha256": "6fdd5a12c310786db1753882fc1378184fe378d2cc533633a8c73c951521b7bf", "predecessorArtifactSha256": "6fdd5a12c310786db1753882fc1378184fe378d2cc533633a8c73c951521b7bf",
"sourceAction": "publish-verified-b2-discovery-ingress-source", "sourceAction": "publish-verified-b2-loopback-discovery-source",
"runtimeAction": "build-and-recreate-stateless-services", "runtimeAction": "build-and-recreate-stateless-services",
"selectedServices": [ "selectedServices": [
"device-control-core", "device-control-core",
@ -17,7 +17,7 @@
"publishedPorts": [ "publishedPorts": [
"127.0.0.1:18120:18120", "127.0.0.1:18120:18120",
"127.0.0.1:18121:18121", "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", "protocolProfile": "arusnavi.b2.internal.v1",
"framingSpecification": "arusnavi.internal.protocol-sheet.gid-12.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_HOST: 0.0.0.0
DEVICE_GATEWAY_HEALTH_PORT: "18121" DEVICE_GATEWAY_HEALTH_PORT: "18121"
DEVICE_GATEWAY_LISTEN_ENABLED: "true" DEVICE_GATEWAY_LISTEN_ENABLED: "true"
DEVICE_GATEWAY_PUBLIC_INGRESS_ENABLED: "true" DEVICE_GATEWAY_PUBLIC_INGRESS_ENABLED: "false"
DEVICE_GATEWAY_TCP_HOST: 0.0.0.0 DEVICE_GATEWAY_TCP_HOST: 127.0.0.1
DEVICE_GATEWAY_TCP_PORT: "9921" DEVICE_GATEWAY_TCP_PORT: "9921"
DEVICE_GATEWAY_CORE_URL: http://device-control-core:18120 DEVICE_GATEWAY_CORE_URL: http://device-control-core:18120
DEVICE_GATEWAY_CORE_TOKEN_FILE: /run/nodedc-secrets/gateway-core-token DEVICE_GATEWAY_CORE_TOKEN_FILE: /run/nodedc-secrets/gateway-core-token
@ -120,7 +120,7 @@ services:
create_host_path: false create_host_path: false
ports: ports:
- "127.0.0.1:18121:18121" - "127.0.0.1:18121:18121"
- "0.0.0.0:9921:9921" - "127.0.0.1:9921:9921"
networks: networks:
- device-plane-private - device-plane-private
- device-plane-control - device-plane-control

View File

@ -193,10 +193,12 @@ raw TCP remain forbidden until separate reviewed acceptance slices.
through the standalone root administrative gate. through the standalone root administrative gate.
3. Plan/apply the exact discovery-ingress artifact and independently prove: 3. Plan/apply the exact discovery-ingress artifact and independently prove:
Core/Gateway/PostgreSQL health, PostgreSQL generation preservation, Core/Gateway/PostgreSQL health, PostgreSQL generation preservation,
`0.0.0.0:9921` publication, verified framing, quarantine-only Core ingest and `127.0.0.1:9921` loopback-only publication, verified framing,
disabled command transport. quarantine-only Core ingest and disabled command transport.
4. Only after runtime acceptance, allow TCP 9921 in DSM firewall and configure 4. Do not publish raw device TCP from the multi-service Synology. Design and
WAN-to-NAS TCP forwarding. Keep the HTTP(S) Reverse Proxy out of this path. independently review an isolated edge/DMZ/VPS ingress with no lateral NAS
5. Add the NDC route to one approved B2 free server slot while preserving access, explicit firewall/NAT evidence, acceptance and rollback.
Gelios, then prove HEADER/discovery/PACKAGE acknowledgement. Claim and 5. Only after that separate edge gate, add the NDC route to one approved B2
tenant assignment remain a later explicit platform-admin operation. 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"), || resolve(scriptDir, "../deploy-artifacts"),
); );
const [ const [
patchId = "device-plane-b2-discovery-ingress-20260726-001", patchId = "device-plane-b2-discovery-loopback-20260726-002",
...extra ...extra
] = process.argv.slice(2); ] = process.argv.slice(2);
@ -99,7 +99,7 @@ try {
artifact: target, artifact: target,
sha256: digest, sha256: digest,
component: "device-plane", component: "device-plane",
transition: "verified-b2-discovery-only", transition: "verified-b2-loopback-discovery-only",
entries: files, entries: files,
services: ["device-control-core", "device-gateway"], services: ["device-control-core", "device-gateway"],
preservedRuntime: [ preservedRuntime: [
@ -109,8 +109,8 @@ try {
], ],
ingress: { ingress: {
transport: "tcp", transport: "tcp",
published: "0.0.0.0:9921:9921", published: "127.0.0.1:9921:9921",
mode: "discovery-only", mode: "loopback-discovery-only",
framing: "verified-read-only", framing: "verified-read-only",
lifecycle: "quarantine", lifecycle: "quarantine",
commandTransport: "disabled", commandTransport: "disabled",
@ -137,12 +137,12 @@ async function assertBoundary() {
for (const fragment of [ for (const fragment of [
'DEVICE_DISCOVERY_INGEST_ENABLED: "true"', 'DEVICE_DISCOVERY_INGEST_ENABLED: "true"',
'DEVICE_GATEWAY_LISTEN_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_URL: http://device-control-core:18120",
"DEVICE_GATEWAY_CORE_TOKEN_FILE: /run/nodedc-secrets/gateway-core-token", "DEVICE_GATEWAY_CORE_TOKEN_FILE: /run/nodedc-secrets/gateway-core-token",
'"127.0.0.1:18120:18120"', '"127.0.0.1:18120:18120"',
'"127.0.0.1:18121:18121"', '"127.0.0.1:18121:18121"',
'"0.0.0.0:9921:9921"', '"127.0.0.1:9921:9921"',
"name: nodedc-device-plane-private", "name: nodedc-device-plane-private",
"internal: true", "internal: true",
"name: nodedc-device-plane-control", "name: nodedc-device-plane-control",
@ -180,12 +180,12 @@ async function assertBoundary() {
)); ));
const expected = { const expected = {
schemaVersion: "nodedc.device-plane.b2-discovery-ingress.v1", schemaVersion: "nodedc.device-plane.b2-discovery-ingress.v1",
mode: "verified-b2-discovery-only", mode: "verified-b2-loopback-discovery-only",
predecessorPatchId: predecessorPatchId:
"device-plane-foundation-network-publication-20260725-003", "device-plane-foundation-network-publication-20260725-003",
predecessorArtifactSha256: predecessorArtifactSha256:
"6fdd5a12c310786db1753882fc1378184fe378d2cc533633a8c73c951521b7bf", "6fdd5a12c310786db1753882fc1378184fe378d2cc533633a8c73c951521b7bf",
sourceAction: "publish-verified-b2-discovery-ingress-source", sourceAction: "publish-verified-b2-loopback-discovery-source",
runtimeAction: "build-and-recreate-stateless-services", runtimeAction: "build-and-recreate-stateless-services",
selectedServices: ["device-control-core", "device-gateway"], selectedServices: ["device-control-core", "device-gateway"],
preservedServices: ["device-postgres"], preservedServices: ["device-postgres"],
@ -194,7 +194,7 @@ async function assertBoundary() {
publishedPorts: [ publishedPorts: [
"127.0.0.1:18120:18120", "127.0.0.1:18120:18120",
"127.0.0.1:18121:18121", "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", protocolProfile: "arusnavi.b2.internal.v1",
framingSpecification: framingSpecification:

View File

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

View File

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