feat(device-plane): activate canonical outbound edge channel
This commit is contained in:
@@ -4,13 +4,16 @@
|
||||
"action": "upgrade",
|
||||
"predecessor": {
|
||||
"kind": "release",
|
||||
"patchId": "device-manager-release-20260811-009",
|
||||
"artifactSha256": "e0cc696e9387d26f005d342529f0e7c08be54113b9725271aecd9baf58b3726d"
|
||||
"patchId": "device-manager-release-20260811-010",
|
||||
"artifactSha256": "d4132993216eb674967dc6fc65d9670cfc2a9efdf46186ca019030f259de2d0e"
|
||||
},
|
||||
"service": "device-manager",
|
||||
"publicIngress": "reverse-proxy-only",
|
||||
"deviceCoreManagementApi": "file-token-authenticated",
|
||||
"launcherTrust": "file-token-scoped-to-device-core-handoff",
|
||||
"edgeChannel": "core-initiated-pinned-mtls-enabled-zero-or-more-registered-edges",
|
||||
"edgeChannelIdentity": "runner-managed-host-local-private-key-public-certificate-export",
|
||||
"edgeChannelEgress": "dedicated-core-only-bridge-no-host-ingress-public-ipv4-tcp-8443-registration-policy",
|
||||
"healthGate": "bounded-container-grace+core-contract",
|
||||
"commandTransport": "disabled",
|
||||
"gelios": "untouched",
|
||||
|
||||
@@ -3,6 +3,12 @@ services:
|
||||
environment:
|
||||
DEVICE_MANAGEMENT_API_ENABLED: "true"
|
||||
DEVICE_MANAGEMENT_CORE_TOKEN_FILE: /run/nodedc-secrets/management-core-token
|
||||
DEVICE_EDGE_CHANNEL_ENABLED: "true"
|
||||
DEVICE_EDGE_CHANNEL_CORE_KEY_FILE: /run/nodedc-secrets/device-edge-channel/core-private-key.pem
|
||||
DEVICE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE: /run/nodedc-secrets/device-edge-channel/core-certificate.pem
|
||||
DEVICE_EDGE_CHANNEL_TRUST_ROOT: /run/nodedc-secrets/device-edge-channel/peers
|
||||
DEVICE_EDGE_CHANNEL_MAX_EDGES: "32"
|
||||
DEVICE_EDGE_CHANNEL_RECONCILE_INTERVAL_MS: "15000"
|
||||
volumes:
|
||||
- type: bind
|
||||
source: /volume1/docker/nodedc-device-plane/secrets/management-core-token
|
||||
@@ -10,6 +16,26 @@ services:
|
||||
read_only: true
|
||||
bind:
|
||||
create_host_path: false
|
||||
- type: bind
|
||||
source: /volume1/docker/nodedc-device-plane/secrets/device-edge-channel/core-private-key.pem
|
||||
target: /run/nodedc-secrets/device-edge-channel/core-private-key.pem
|
||||
read_only: true
|
||||
bind:
|
||||
create_host_path: false
|
||||
- type: bind
|
||||
source: /volume1/docker/nodedc-device-plane/secrets/device-edge-channel/core-certificate.pem
|
||||
target: /run/nodedc-secrets/device-edge-channel/core-certificate.pem
|
||||
read_only: true
|
||||
bind:
|
||||
create_host_path: false
|
||||
- type: bind
|
||||
source: /volume1/docker/nodedc-device-plane/secrets/device-edge-channel/peers
|
||||
target: /run/nodedc-secrets/device-edge-channel/peers
|
||||
read_only: true
|
||||
bind:
|
||||
create_host_path: false
|
||||
networks:
|
||||
- device-plane-egress
|
||||
|
||||
device-manager:
|
||||
image: nodedc/device-manager:local
|
||||
@@ -71,6 +97,10 @@ services:
|
||||
start_period: 10s
|
||||
|
||||
networks:
|
||||
device-plane-egress:
|
||||
name: nodedc-device-plane-egress
|
||||
driver: bridge
|
||||
internal: false
|
||||
platform-edge:
|
||||
external: true
|
||||
name: nodedc-platform_edge
|
||||
|
||||
@@ -12,7 +12,7 @@ const devicePlaneRoot = resolve(platformRoot, "device-plane");
|
||||
const designRoot = resolve(process.env.NODEDC_DEVICE_MANAGER_SOURCE_ROOT || resolve(platformRoot, "../NODEDC_DESIGN_GUIDELINE"));
|
||||
const managerRoot = resolve(designRoot, "apps/device-manager");
|
||||
const artifactDir = resolve(process.env.NODEDC_DEPLOY_ARTIFACT_DIR || resolve(scriptDir, "../deploy-artifacts"));
|
||||
const [patchId = "device-manager-release-20260811-010", ...extra] = process.argv.slice(2);
|
||||
const [patchId = "device-manager-release-20260811-011", ...extra] = process.argv.slice(2);
|
||||
if (extra.length || !/^[A-Za-z0-9._-]{1,96}$/.test(patchId)) throw new Error("usage: build-device-manager-control-plane-artifact.mjs [patch-id]");
|
||||
|
||||
const entries = [
|
||||
@@ -88,11 +88,20 @@ try {
|
||||
"DEVICE_MANAGEMENT_API_ENABLED: \"true\"",
|
||||
"NODEDC_LAUNCHER_INTERNAL_TOKEN_FILE: /run/nodedc-secrets/device-core-internal-token",
|
||||
"NODEDC_DEVICE_CORE_TOKEN_FILE: /run/nodedc-secrets/management-core-token",
|
||||
"DEVICE_EDGE_CHANNEL_ENABLED: \"true\"",
|
||||
"DEVICE_EDGE_CHANNEL_CORE_KEY_FILE: /run/nodedc-secrets/device-edge-channel/core-private-key.pem",
|
||||
"DEVICE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE: /run/nodedc-secrets/device-edge-channel/core-certificate.pem",
|
||||
"DEVICE_EDGE_CHANNEL_TRUST_ROOT: /run/nodedc-secrets/device-edge-channel/peers",
|
||||
"source: /volume1/docker/nodedc-device-plane/secrets/device-edge-channel/core-private-key.pem",
|
||||
"source: /volume1/docker/nodedc-device-plane/secrets/device-edge-channel/core-certificate.pem",
|
||||
"source: /volume1/docker/nodedc-device-plane/secrets/device-edge-channel/peers",
|
||||
"name: nodedc-device-plane-egress",
|
||||
"name: nodedc-platform_edge",
|
||||
]) if (!compose.includes(required)) throw new Error(`device_manager_compose_contract_missing:${required}`);
|
||||
for (const forbidden of [
|
||||
"NODEDC_INTERNAL_ACCESS_TOKEN:",
|
||||
"NODEDC_PLATFORM_SERVICE_TOKEN:",
|
||||
"PRIVATE KEY",
|
||||
"0.0.0.0:18122",
|
||||
"0.0.0.0:9921:9921",
|
||||
"- \"9921:9921\"",
|
||||
@@ -113,6 +122,9 @@ try {
|
||||
|| !/^[A-Za-z0-9._-]{1,96}$/.test(predecessor.patchId || "")
|
||||
|| !/^[a-f0-9]{64}$/.test(predecessor.artifactSha256 || "")
|
||||
|| (descriptor.action === "activate") !== (predecessor.kind === "reconciliation")
|
||||
|| descriptor.edgeChannel !== "core-initiated-pinned-mtls-enabled-zero-or-more-registered-edges"
|
||||
|| descriptor.edgeChannelIdentity !== "runner-managed-host-local-private-key-public-certificate-export"
|
||||
|| descriptor.edgeChannelEgress !== "dedicated-core-only-bridge-no-host-ingress-public-ipv4-tcp-8443-registration-policy"
|
||||
|| descriptor.healthGate !== "bounded-container-grace+core-contract"
|
||||
|| descriptor.rollback !== "restore-preapply-snapshot"
|
||||
) throw new Error("device_manager_activation_successor_contract_mismatch");
|
||||
|
||||
@@ -72,6 +72,27 @@ DEVICE_PLANE_IDENTIFIER_PEPPER_FILE = DEVICE_PLANE_SECRET_DIR / "identifier-pepp
|
||||
DEVICE_PLANE_MANAGEMENT_CORE_TOKEN_FILE = (
|
||||
DEVICE_PLANE_SECRET_DIR / "management-core-token"
|
||||
)
|
||||
DEVICE_PLANE_EDGE_CHANNEL_SECRET_DIR = (
|
||||
DEVICE_PLANE_SECRET_DIR / "device-edge-channel"
|
||||
)
|
||||
DEVICE_PLANE_EDGE_CHANNEL_CORE_PRIVATE_KEY_FILE = (
|
||||
DEVICE_PLANE_EDGE_CHANNEL_SECRET_DIR / "core-private-key.pem"
|
||||
)
|
||||
DEVICE_PLANE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE = (
|
||||
DEVICE_PLANE_EDGE_CHANNEL_SECRET_DIR / "core-certificate.pem"
|
||||
)
|
||||
DEVICE_PLANE_EDGE_CHANNEL_PEER_TRUST_DIR = (
|
||||
DEVICE_PLANE_EDGE_CHANNEL_SECRET_DIR / "peers"
|
||||
)
|
||||
DEVICE_PLANE_EDGE_CHANNEL_EXPORT_DIR = (
|
||||
DEVICE_PLANE_ROOT / "enrollment" / "device-edge-channel"
|
||||
)
|
||||
DEVICE_PLANE_EDGE_CHANNEL_EXPORTED_CORE_CERTIFICATE_FILE = (
|
||||
DEVICE_PLANE_EDGE_CHANNEL_EXPORT_DIR / "core-certificate.pem"
|
||||
)
|
||||
DEVICE_PLANE_EDGE_CHANNEL_EXPORTED_CORE_FINGERPRINT_FILE = (
|
||||
DEVICE_PLANE_EDGE_CHANNEL_EXPORT_DIR / "core-certificate.sha256"
|
||||
)
|
||||
DEVICE_PLANE_CONTROL_CORE_IMAGE = "nodedc/device-control-core:local"
|
||||
DEVICE_PLANE_GATEWAY_IMAGE = "nodedc/device-gateway:local"
|
||||
DEVICE_PLANE_MANAGER_IMAGE = "nodedc/device-manager:local"
|
||||
@@ -221,7 +242,7 @@ DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL = (
|
||||
)
|
||||
DEVICE_PLANE_MANAGER_COMPOSE_REL = "docker-compose.device-manager.yml"
|
||||
DEVICE_PLANE_MANAGER_COMPOSE_SHA256 = (
|
||||
"4954120aaddc999798b64c304d8cf692b79714feb727d873117bd1f3434e865e"
|
||||
"369a2acf9c1a1030b9e1c6c366144b1eaf8900aef0ee59bb6bf23250b7b371b9"
|
||||
)
|
||||
DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_ENTRIES = (
|
||||
".dockerignore",
|
||||
@@ -556,6 +577,7 @@ DEVICE_PLANE_FOUNDATION_NETWORK_PUBLICATION_COMPOSE_SHA256 = (
|
||||
)
|
||||
DEVICE_PLANE_PRIVATE_NETWORK = "nodedc-device-plane-private"
|
||||
DEVICE_PLANE_CONTROL_NETWORK = "nodedc-device-plane-control"
|
||||
DEVICE_PLANE_EGRESS_NETWORK = "nodedc-device-plane-egress"
|
||||
DEVICE_PLANE_FOUNDATION_PREDECESSOR_CONTAINER_IDS = {
|
||||
"device-control-core": (
|
||||
"ccdaeee71472a557ccecb485e81b31f5a4ee37b1b3f3cb4d5edbcf12d02bb08b"
|
||||
@@ -2065,7 +2087,7 @@ def ensure_external_data_plane_provisioner_secret():
|
||||
def resolve_openssl_binary():
|
||||
candidate = shutil.which("openssl")
|
||||
if not candidate:
|
||||
die("openssl is required to provision the Engine credential issuer")
|
||||
die("openssl is required to provision runtime identities")
|
||||
path = Path(candidate).resolve()
|
||||
try:
|
||||
path_stat = path.lstat()
|
||||
@@ -2091,6 +2113,214 @@ def run_openssl(arguments, label):
|
||||
die(f"openssl {label} failed")
|
||||
|
||||
|
||||
def capture_openssl(arguments, label):
|
||||
result = subprocess.run(
|
||||
[str(resolve_openssl_binary()), *arguments],
|
||||
check=False,
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL,
|
||||
timeout=30,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
die(f"openssl {label} failed")
|
||||
return result.stdout
|
||||
|
||||
|
||||
def ensure_safe_runtime_directory(path, mode, label):
|
||||
try:
|
||||
path_stat = path.lstat()
|
||||
except FileNotFoundError:
|
||||
path.mkdir(parents=True, exist_ok=False)
|
||||
path_stat = path.lstat()
|
||||
if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISDIR(path_stat.st_mode):
|
||||
die(f"{label} directory is unsafe: {path}")
|
||||
os.chown(path, 0, MAP_GATEWAY_RUNTIME_GID)
|
||||
path.chmod(mode)
|
||||
|
||||
|
||||
def device_edge_channel_certificate_fingerprint(path):
|
||||
raw = capture_openssl(
|
||||
["x509", "-in", str(path), "-noout", "-fingerprint", "-sha256"],
|
||||
"Device Edge channel certificate fingerprint",
|
||||
).decode("ascii", errors="strict").strip()
|
||||
match = re.fullmatch(
|
||||
r"SHA256 Fingerprint=((?:[A-F0-9]{2}:){31}[A-F0-9]{2})",
|
||||
raw,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
if not match:
|
||||
die("Device Edge channel certificate fingerprint is invalid")
|
||||
return match.group(1).upper()
|
||||
|
||||
|
||||
def install_public_runtime_export(source, destination, mode, label):
|
||||
source_bytes = source.read_bytes()
|
||||
if b"PRIVATE KEY" in source_bytes:
|
||||
die(f"{label} contains private key material")
|
||||
try:
|
||||
destination_stat = destination.lstat()
|
||||
except FileNotFoundError:
|
||||
destination_stat = None
|
||||
if destination_stat is not None and (
|
||||
stat.S_ISLNK(destination_stat.st_mode)
|
||||
or not stat.S_ISREG(destination_stat.st_mode)
|
||||
):
|
||||
die(f"{label} destination is unsafe: {destination}")
|
||||
temporary = destination.parent / (
|
||||
f".{destination.name}.{os.getpid()}.{time.time_ns()}.tmp"
|
||||
)
|
||||
descriptor = None
|
||||
try:
|
||||
descriptor = os.open(
|
||||
str(temporary),
|
||||
os.O_WRONLY | os.O_CREAT | os.O_EXCL,
|
||||
mode,
|
||||
)
|
||||
os.write(descriptor, source_bytes)
|
||||
os.fsync(descriptor)
|
||||
os.fchown(descriptor, 0, MAP_GATEWAY_RUNTIME_GID)
|
||||
os.fchmod(descriptor, mode)
|
||||
os.close(descriptor)
|
||||
descriptor = None
|
||||
os.replace(temporary, destination)
|
||||
fsync_directory(destination.parent)
|
||||
finally:
|
||||
if descriptor is not None:
|
||||
os.close(descriptor)
|
||||
if temporary.exists():
|
||||
temporary.unlink()
|
||||
|
||||
|
||||
def ensure_device_edge_channel_core_identity():
|
||||
# The Core private key is born on Synology and never enters a deployment
|
||||
# artifact, Compose environment, Ops, or runner output. Only the matching
|
||||
# public certificate and fingerprint are exported for the explicit VPS
|
||||
# trust handoff.
|
||||
ensure_safe_runtime_directory(
|
||||
DEVICE_PLANE_EDGE_CHANNEL_SECRET_DIR,
|
||||
0o710,
|
||||
"Device Edge channel identity",
|
||||
)
|
||||
ensure_safe_runtime_directory(
|
||||
DEVICE_PLANE_EDGE_CHANNEL_PEER_TRUST_DIR,
|
||||
0o710,
|
||||
"Device Edge channel peer trust",
|
||||
)
|
||||
private_key = DEVICE_PLANE_EDGE_CHANNEL_CORE_PRIVATE_KEY_FILE
|
||||
certificate = DEVICE_PLANE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE
|
||||
private_exists = private_key.exists() or private_key.is_symlink()
|
||||
certificate_exists = certificate.exists() or certificate.is_symlink()
|
||||
if private_exists != certificate_exists:
|
||||
die("Device Edge channel Core identity is incomplete")
|
||||
|
||||
created = False
|
||||
if not private_exists:
|
||||
private_tmp = private_key.with_name(
|
||||
f".{private_key.name}.{os.getpid()}.{time.time_ns()}.tmp"
|
||||
)
|
||||
certificate_tmp = certificate.with_name(
|
||||
f".{certificate.name}.{os.getpid()}.{time.time_ns()}.tmp"
|
||||
)
|
||||
try:
|
||||
run_openssl([
|
||||
"req", "-x509", "-newkey", "ed25519", "-nodes",
|
||||
"-days", "3650",
|
||||
"-subj", "/CN=nodedc-device-control-core",
|
||||
"-addext", "basicConstraints=critical,CA:FALSE",
|
||||
"-addext", "keyUsage=critical,digitalSignature",
|
||||
"-addext", "extendedKeyUsage=clientAuth",
|
||||
"-keyout", str(private_tmp),
|
||||
"-out", str(certificate_tmp),
|
||||
], "Device Edge channel Core identity generation")
|
||||
os.chown(private_tmp, 0, MAP_GATEWAY_RUNTIME_GID)
|
||||
private_tmp.chmod(0o640)
|
||||
os.chown(certificate_tmp, 0, MAP_GATEWAY_RUNTIME_GID)
|
||||
certificate_tmp.chmod(0o640)
|
||||
fsync_file(private_tmp)
|
||||
fsync_file(certificate_tmp)
|
||||
os.replace(private_tmp, private_key)
|
||||
os.replace(certificate_tmp, certificate)
|
||||
fsync_directory(DEVICE_PLANE_EDGE_CHANNEL_SECRET_DIR)
|
||||
created = True
|
||||
finally:
|
||||
if private_tmp.exists():
|
||||
private_tmp.unlink()
|
||||
if certificate_tmp.exists():
|
||||
certificate_tmp.unlink()
|
||||
|
||||
for path, label in (
|
||||
(private_key, "private key"),
|
||||
(certificate, "certificate"),
|
||||
):
|
||||
path_stat = path.lstat()
|
||||
if (
|
||||
stat.S_ISLNK(path_stat.st_mode)
|
||||
or not stat.S_ISREG(path_stat.st_mode)
|
||||
or path_stat.st_uid != 0
|
||||
or path_stat.st_gid != MAP_GATEWAY_RUNTIME_GID
|
||||
or stat.S_IMODE(path_stat.st_mode) != 0o640
|
||||
or path_stat.st_size < 64
|
||||
or path_stat.st_size > 32 * 1024
|
||||
):
|
||||
die(f"Device Edge channel Core {label} boundary mismatch")
|
||||
|
||||
private_text = private_key.read_text(encoding="ascii")
|
||||
certificate_text = certificate.read_text(encoding="ascii")
|
||||
if "PRIVATE KEY" not in private_text or "PRIVATE KEY" in certificate_text:
|
||||
die("Device Edge channel Core private/public boundary mismatch")
|
||||
if certificate_text.count("-----BEGIN CERTIFICATE-----") != 1:
|
||||
die("Device Edge channel Core certificate cardinality mismatch")
|
||||
run_openssl(["pkey", "-in", str(private_key), "-check", "-noout"],
|
||||
"Device Edge channel Core private key validation")
|
||||
run_openssl(["x509", "-in", str(certificate), "-noout", "-checkend", "604800"],
|
||||
"Device Edge channel Core certificate lifetime validation")
|
||||
run_openssl([
|
||||
"verify", "-purpose", "sslclient", "-CAfile", str(certificate),
|
||||
str(certificate),
|
||||
], "Device Edge channel Core certificate purpose validation")
|
||||
certificate_public = capture_openssl(
|
||||
["x509", "-in", str(certificate), "-pubkey", "-noout"],
|
||||
"Device Edge channel Core certificate public key",
|
||||
)
|
||||
private_public = capture_openssl(
|
||||
["pkey", "-in", str(private_key), "-pubout"],
|
||||
"Device Edge channel Core private key public derivation",
|
||||
)
|
||||
if certificate_public != private_public:
|
||||
die("Device Edge channel Core certificate/private key mismatch")
|
||||
|
||||
ensure_safe_runtime_directory(
|
||||
DEVICE_PLANE_EDGE_CHANNEL_EXPORT_DIR,
|
||||
0o755,
|
||||
"Device Edge channel public export",
|
||||
)
|
||||
install_public_runtime_export(
|
||||
certificate,
|
||||
DEVICE_PLANE_EDGE_CHANNEL_EXPORTED_CORE_CERTIFICATE_FILE,
|
||||
0o444,
|
||||
"Device Edge channel Core certificate export",
|
||||
)
|
||||
fingerprint = device_edge_channel_certificate_fingerprint(certificate)
|
||||
fingerprint_tmp = DEVICE_PLANE_EDGE_CHANNEL_EXPORT_DIR / (
|
||||
f".core-certificate.sha256.{os.getpid()}.{time.time_ns()}.tmp"
|
||||
)
|
||||
try:
|
||||
fingerprint_tmp.write_text(f"SHA256={fingerprint}\n", encoding="ascii")
|
||||
os.chown(fingerprint_tmp, 0, MAP_GATEWAY_RUNTIME_GID)
|
||||
fingerprint_tmp.chmod(0o444)
|
||||
fsync_file(fingerprint_tmp)
|
||||
os.replace(
|
||||
fingerprint_tmp,
|
||||
DEVICE_PLANE_EDGE_CHANNEL_EXPORTED_CORE_FINGERPRINT_FILE,
|
||||
)
|
||||
fsync_directory(DEVICE_PLANE_EDGE_CHANNEL_EXPORT_DIR)
|
||||
finally:
|
||||
if fingerprint_tmp.exists():
|
||||
fingerprint_tmp.unlink()
|
||||
return "created" if created else "reused"
|
||||
|
||||
|
||||
def validate_ed25519_public_key_file(path):
|
||||
try:
|
||||
value = path.read_text(encoding="ascii")
|
||||
@@ -8859,6 +9089,16 @@ def expected_device_plane_manager_release_boundaries():
|
||||
"publicIngress": "reverse-proxy-only",
|
||||
"deviceCoreManagementApi": "file-token-authenticated",
|
||||
"launcherTrust": "file-token-scoped-to-device-core-handoff",
|
||||
"edgeChannel": (
|
||||
"core-initiated-pinned-mtls-enabled-zero-or-more-registered-edges"
|
||||
),
|
||||
"edgeChannelIdentity": (
|
||||
"runner-managed-host-local-private-key-public-certificate-export"
|
||||
),
|
||||
"edgeChannelEgress": (
|
||||
"dedicated-core-only-bridge-no-host-ingress-"
|
||||
"public-ipv4-tcp-8443-registration-policy"
|
||||
),
|
||||
"healthGate": "bounded-container-grace+core-contract",
|
||||
"commandTransport": "disabled",
|
||||
"gelios": "untouched",
|
||||
@@ -9062,6 +9302,20 @@ def validate_device_plane_manager_control_plane_payload(
|
||||
"/run/nodedc-secrets/device-core-internal-token",
|
||||
"NODEDC_DEVICE_CORE_TOKEN_FILE: "
|
||||
"/run/nodedc-secrets/management-core-token",
|
||||
'DEVICE_EDGE_CHANNEL_ENABLED: "true"',
|
||||
"DEVICE_EDGE_CHANNEL_CORE_KEY_FILE: "
|
||||
"/run/nodedc-secrets/device-edge-channel/core-private-key.pem",
|
||||
"DEVICE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE: "
|
||||
"/run/nodedc-secrets/device-edge-channel/core-certificate.pem",
|
||||
"DEVICE_EDGE_CHANNEL_TRUST_ROOT: "
|
||||
"/run/nodedc-secrets/device-edge-channel/peers",
|
||||
"source: /volume1/docker/nodedc-device-plane/secrets/"
|
||||
"device-edge-channel/core-private-key.pem",
|
||||
"source: /volume1/docker/nodedc-device-plane/secrets/"
|
||||
"device-edge-channel/core-certificate.pem",
|
||||
"source: /volume1/docker/nodedc-device-plane/secrets/"
|
||||
"device-edge-channel/peers",
|
||||
"name: nodedc-device-plane-egress",
|
||||
"name: nodedc-platform_edge",
|
||||
):
|
||||
if required not in compose:
|
||||
@@ -12317,10 +12571,27 @@ def validate_device_manager_control_plane_runtime():
|
||||
manager = inspect_device_plane_container(manager_ids[0])
|
||||
core_environment = container_environment(core, "Device Control Core")
|
||||
manager_environment = container_environment(manager, "Device Manager")
|
||||
if (
|
||||
core_environment.get("DEVICE_MANAGEMENT_API_ENABLED") != "true"
|
||||
or core_environment.get("DEVICE_MANAGEMENT_CORE_TOKEN_FILE")
|
||||
!= "/run/nodedc-secrets/management-core-token"
|
||||
expected_core_environment = {
|
||||
"DEVICE_MANAGEMENT_API_ENABLED": "true",
|
||||
"DEVICE_MANAGEMENT_CORE_TOKEN_FILE": (
|
||||
"/run/nodedc-secrets/management-core-token"
|
||||
),
|
||||
"DEVICE_EDGE_CHANNEL_ENABLED": "true",
|
||||
"DEVICE_EDGE_CHANNEL_CORE_KEY_FILE": (
|
||||
"/run/nodedc-secrets/device-edge-channel/core-private-key.pem"
|
||||
),
|
||||
"DEVICE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE": (
|
||||
"/run/nodedc-secrets/device-edge-channel/core-certificate.pem"
|
||||
),
|
||||
"DEVICE_EDGE_CHANNEL_TRUST_ROOT": (
|
||||
"/run/nodedc-secrets/device-edge-channel/peers"
|
||||
),
|
||||
"DEVICE_EDGE_CHANNEL_MAX_EDGES": "32",
|
||||
"DEVICE_EDGE_CHANNEL_RECONCILE_INTERVAL_MS": "15000",
|
||||
}
|
||||
if any(
|
||||
core_environment.get(key) != value
|
||||
for key, value in expected_core_environment.items()
|
||||
):
|
||||
die("Device Control Core management runtime mismatch")
|
||||
expected_manager_environment = {
|
||||
@@ -12371,6 +12642,35 @@ def validate_device_manager_control_plane_runtime():
|
||||
or core_management_mount.get("RW") is not False
|
||||
):
|
||||
die("Device Control Core management secret mount mismatch")
|
||||
expected_core_channel_mounts = {
|
||||
"/run/nodedc-secrets/device-edge-channel/core-private-key.pem": (
|
||||
DEVICE_PLANE_EDGE_CHANNEL_CORE_PRIVATE_KEY_FILE
|
||||
),
|
||||
"/run/nodedc-secrets/device-edge-channel/core-certificate.pem": (
|
||||
DEVICE_PLANE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE
|
||||
),
|
||||
"/run/nodedc-secrets/device-edge-channel/peers": (
|
||||
DEVICE_PLANE_EDGE_CHANNEL_PEER_TRUST_DIR
|
||||
),
|
||||
}
|
||||
for destination, source in expected_core_channel_mounts.items():
|
||||
mount = core_mounts.get(destination)
|
||||
if (
|
||||
mount is None
|
||||
or mount.get("Type") != "bind"
|
||||
or mount.get("Source") != str(source)
|
||||
or mount.get("RW") is not False
|
||||
):
|
||||
die("Device Control Core Edge channel mount mismatch")
|
||||
core_networks = set(
|
||||
((core.get("NetworkSettings") or {}).get("Networks") or {}).keys()
|
||||
)
|
||||
if core_networks != {
|
||||
DEVICE_PLANE_PRIVATE_NETWORK,
|
||||
DEVICE_PLANE_CONTROL_NETWORK,
|
||||
DEVICE_PLANE_EGRESS_NETWORK,
|
||||
}:
|
||||
die("Device Control Core Edge channel network boundary mismatch")
|
||||
manager_mounts = {
|
||||
mount.get("Destination"): mount
|
||||
for mount in manager.get("Mounts") or []
|
||||
@@ -12405,6 +12705,7 @@ def validate_device_manager_control_plane_runtime():
|
||||
}:
|
||||
die("Device Manager network boundary mismatch")
|
||||
validate_device_plane_runtime_secret_metadata(include_management=True)
|
||||
ensure_device_edge_channel_core_identity()
|
||||
ensure_platform_runtime_secret(
|
||||
PLATFORM_DEVICE_CORE_INTERNAL_TOKEN_FILE,
|
||||
MAP_GATEWAY_SECRET_RE,
|
||||
@@ -18147,6 +18448,14 @@ def plan_artifact(artifact):
|
||||
"runtime_secret=runner-managed:"
|
||||
f"{PLATFORM_DEVICE_CORE_INTERNAL_TOKEN_FILE}"
|
||||
)
|
||||
print(
|
||||
"runtime_private_key=runner-managed-host-local:"
|
||||
f"{DEVICE_PLANE_EDGE_CHANNEL_CORE_PRIVATE_KEY_FILE}"
|
||||
)
|
||||
print(
|
||||
"runtime_public_certificate_export=runner-managed:"
|
||||
f"{DEVICE_PLANE_EDGE_CHANNEL_EXPORTED_CORE_CERTIFICATE_FILE}"
|
||||
)
|
||||
print(
|
||||
"device_postgres="
|
||||
f"{device_plane_postgres_plan_selection(device_plane_postgres_preflight)}"
|
||||
@@ -18327,6 +18636,20 @@ def plan_artifact(artifact):
|
||||
"device-backhaul-target"
|
||||
)
|
||||
print("device_manager_health_gate=bounded-grace+contract")
|
||||
print(
|
||||
"device_edge_channel="
|
||||
"enabled:core-initiated:pinned-mtls:registered-edges-only"
|
||||
)
|
||||
print(
|
||||
"device_edge_channel_egress="
|
||||
"dedicated-core-only-bridge:no-host-ingress"
|
||||
)
|
||||
print(
|
||||
"device_edge_channel_identity="
|
||||
"host-local-private-key:public-certificate-export-only"
|
||||
)
|
||||
print("device_edge_channel_registrations=preserved")
|
||||
print("device_edge_channel_commands=disabled")
|
||||
print("device_manager_public_route=unchanged:absent")
|
||||
print("device_gateway_tcp_9921=preserved:loopback-only")
|
||||
print(
|
||||
@@ -19818,6 +20141,7 @@ def prepare_component_runtime(component, entries=None):
|
||||
MAP_GATEWAY_SECRET_RE,
|
||||
"Device Core Hub handoff",
|
||||
)
|
||||
ensure_device_edge_channel_core_identity()
|
||||
if is_device_plane_backhaul_vps_enrollment_slice(
|
||||
component,
|
||||
entries,
|
||||
|
||||
@@ -189,6 +189,18 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
|
||||
self.assertNotIn("device-postgres", result["services"])
|
||||
self.assertIn("docker-compose.device-manager.yml", entries)
|
||||
self.assertNotIn("docker-compose.device-plane.yml", entries)
|
||||
compose = (
|
||||
PLATFORM_ROOT / "device-plane/docker-compose.device-manager.yml"
|
||||
).read_text(encoding="utf-8")
|
||||
for required in (
|
||||
'DEVICE_EDGE_CHANNEL_ENABLED: "true"',
|
||||
"DEVICE_EDGE_CHANNEL_CORE_KEY_FILE: ",
|
||||
"DEVICE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE: ",
|
||||
"DEVICE_EDGE_CHANNEL_TRUST_ROOT: ",
|
||||
"name: nodedc-device-plane-egress",
|
||||
):
|
||||
self.assertIn(required, compose)
|
||||
self.assertNotIn("PRIVATE KEY", compose)
|
||||
checks = RUNNER.component_healthchecks("device-plane", entries, tuple(result["services"]))
|
||||
self.assertEqual(checks[0]["expected_json"]["managementApi"], "enabled")
|
||||
self.assertEqual(checks[0]["expected_json"]["discoveryIngest"], "enabled")
|
||||
@@ -215,7 +227,16 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
|
||||
[RUNNER.PLATFORM_DEVICE_CORE_INTERNAL_TOKEN_FILE],
|
||||
)
|
||||
|
||||
with mock.patch.object(RUNNER, "ensure_platform_runtime_secret") as ensure:
|
||||
with (
|
||||
mock.patch.object(
|
||||
RUNNER,
|
||||
"ensure_platform_runtime_secret",
|
||||
) as ensure,
|
||||
mock.patch.object(
|
||||
RUNNER,
|
||||
"ensure_device_edge_channel_core_identity",
|
||||
) as ensure_edge_identity,
|
||||
):
|
||||
RUNNER.prepare_component_runtime(
|
||||
"device-plane",
|
||||
RUNNER.DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES,
|
||||
@@ -230,6 +251,7 @@ class DeviceManagerControlPlaneArtifactsTest(unittest.TestCase):
|
||||
RUNNER.PLATFORM_DEVICE_CORE_INTERNAL_TOKEN_FILE,
|
||||
],
|
||||
)
|
||||
ensure_edge_identity.assert_called_once_with()
|
||||
|
||||
def test_apply_gate_checks_exact_services_core_contract_and_runtime_boundary(self):
|
||||
entries = RUNNER.DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES
|
||||
|
||||
Reference in New Issue
Block a user