feat(edp): provision Foundry reader grants

This commit is contained in:
Codex
2026-07-19 15:03:48 +03:00
parent 847a08da93
commit def9a24e0d
17 changed files with 891 additions and 7 deletions
+198
View File
@@ -63,6 +63,10 @@ ENGINE_EDP_MANAGED_PROVISIONER_SECRET_DIR = MAP_GATEWAY_SECRET_DIR / "engine-edp
ENGINE_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE = ENGINE_EDP_MANAGED_PROVISIONER_SECRET_DIR / "private-key.pem"
ENGINE_EDP_MANAGED_PROVISIONER_TRUST_DIR = Path("/volume1/docker/nodedc-platform/trust/engine-managed-provisioner")
ENGINE_EDP_MANAGED_PROVISIONER_PUBLIC_KEY_FILE = ENGINE_EDP_MANAGED_PROVISIONER_TRUST_DIR / "public-key.pem"
FOUNDRY_EDP_MANAGED_PROVISIONER_SECRET_DIR = MAP_GATEWAY_SECRET_DIR / "foundry-edp-managed-provisioner"
FOUNDRY_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE = FOUNDRY_EDP_MANAGED_PROVISIONER_SECRET_DIR / "private-key.pem"
FOUNDRY_EDP_MANAGED_PROVISIONER_TRUST_DIR = Path("/volume1/docker/nodedc-platform/trust/foundry-managed-provisioner")
FOUNDRY_EDP_MANAGED_PROVISIONER_PUBLIC_KEY_FILE = FOUNDRY_EDP_MANAGED_PROVISIONER_TRUST_DIR / "public-key.pem"
EXTERNAL_DATA_PLANE_READER_GRANTS_DIR = MAP_GATEWAY_SECRET_DIR / "external-data-plane-reader-grants"
FOUNDRY_BINDING_GRANTS_DIR = MAP_GATEWAY_SECRET_DIR / "foundry-binding-grants"
N8N_PRIVATE_EXTENSION_RELEASES_ROOT = Path("/volume1/docker/nodedc-platform/n8n-private-extensions")
@@ -1159,6 +1163,168 @@ def ensure_engine_edp_managed_provisioner_keypair():
if temporary_public.exists():
temporary_public.unlink()
return "created"
return "created"
def ensure_foundry_edp_managed_provisioner_keypair():
# Foundry receives its own signing identity. It must never reuse or read
# the Engine private key; EDP trusts the matching public key separately.
openssl = shutil.which("openssl")
if not openssl:
die("openssl is required to manage the Foundry EDP signing key")
try:
secret_parent_stat = MAP_GATEWAY_SECRET_DIR.lstat()
except FileNotFoundError:
MAP_GATEWAY_SECRET_DIR.mkdir(parents=True, exist_ok=False)
secret_parent_stat = MAP_GATEWAY_SECRET_DIR.lstat()
if stat.S_ISLNK(secret_parent_stat.st_mode) or not stat.S_ISDIR(secret_parent_stat.st_mode):
die("Foundry EDP signing key parent directory is unsafe")
os.chown(MAP_GATEWAY_SECRET_DIR, 0, MAP_GATEWAY_RUNTIME_GID)
MAP_GATEWAY_SECRET_DIR.chmod(0o710)
try:
private_dir_stat = FOUNDRY_EDP_MANAGED_PROVISIONER_SECRET_DIR.lstat()
except FileNotFoundError:
FOUNDRY_EDP_MANAGED_PROVISIONER_SECRET_DIR.mkdir(parents=False, exist_ok=False)
private_dir_stat = FOUNDRY_EDP_MANAGED_PROVISIONER_SECRET_DIR.lstat()
if stat.S_ISLNK(private_dir_stat.st_mode) or not stat.S_ISDIR(private_dir_stat.st_mode):
die("Foundry EDP signing key directory is unsafe")
os.chown(FOUNDRY_EDP_MANAGED_PROVISIONER_SECRET_DIR, 0, 0)
FOUNDRY_EDP_MANAGED_PROVISIONER_SECRET_DIR.chmod(0o700)
trust_parent = FOUNDRY_EDP_MANAGED_PROVISIONER_TRUST_DIR.parent
try:
trust_parent_stat = trust_parent.lstat()
except FileNotFoundError:
trust_parent.mkdir(parents=True, exist_ok=False)
trust_parent_stat = trust_parent.lstat()
if stat.S_ISLNK(trust_parent_stat.st_mode) or not stat.S_ISDIR(trust_parent_stat.st_mode):
die("Foundry EDP trust parent directory is unsafe")
os.chown(trust_parent, 0, EXTERNAL_DATA_PLANE_RUNTIME_GID)
trust_parent.chmod(0o710)
try:
trust_dir_stat = FOUNDRY_EDP_MANAGED_PROVISIONER_TRUST_DIR.lstat()
except FileNotFoundError:
FOUNDRY_EDP_MANAGED_PROVISIONER_TRUST_DIR.mkdir(parents=False, exist_ok=False)
trust_dir_stat = FOUNDRY_EDP_MANAGED_PROVISIONER_TRUST_DIR.lstat()
if stat.S_ISLNK(trust_dir_stat.st_mode) or not stat.S_ISDIR(trust_dir_stat.st_mode):
die("Foundry EDP trust directory is unsafe")
os.chown(FOUNDRY_EDP_MANAGED_PROVISIONER_TRUST_DIR, 0, EXTERNAL_DATA_PLANE_RUNTIME_GID)
FOUNDRY_EDP_MANAGED_PROVISIONER_TRUST_DIR.chmod(0o550)
try:
private_stat = FOUNDRY_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE.lstat()
except FileNotFoundError:
private_stat = None
try:
public_stat = FOUNDRY_EDP_MANAGED_PROVISIONER_PUBLIC_KEY_FILE.lstat()
except FileNotFoundError:
public_stat = None
if private_stat is None and public_stat is not None:
die("Foundry EDP public key exists without its private key")
private_created = False
if private_stat is None:
temporary_private = (
FOUNDRY_EDP_MANAGED_PROVISIONER_SECRET_DIR
/ f".private-key.{os.getpid()}.{time.time_ns()}.tmp"
)
try:
subprocess.run(
[openssl, "genpkey", "-algorithm", "ED25519", "-out", str(temporary_private)],
check=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
text=True,
)
temporary_stat = temporary_private.lstat()
if (stat.S_ISLNK(temporary_stat.st_mode) or not stat.S_ISREG(temporary_stat.st_mode)
or temporary_stat.st_size < 80 or temporary_stat.st_size > 8192):
die("generated Foundry EDP private key is invalid")
os.chown(temporary_private, 0, 0)
temporary_private.chmod(0o400)
with temporary_private.open("rb") as handle:
os.fsync(handle.fileno())
os.replace(temporary_private, FOUNDRY_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE)
fsync_directory(FOUNDRY_EDP_MANAGED_PROVISIONER_SECRET_DIR)
private_created = True
except (OSError, subprocess.CalledProcessError) as error:
die(f"failed to generate Foundry EDP signing key: {type(error).__name__}")
finally:
if temporary_private.exists():
temporary_private.unlink()
private_stat = FOUNDRY_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE.lstat()
if (stat.S_ISLNK(private_stat.st_mode) or not stat.S_ISREG(private_stat.st_mode)
or private_stat.st_uid != 0 or private_stat.st_gid != 0
or stat.S_IMODE(private_stat.st_mode) != 0o400
or private_stat.st_size < 80 or private_stat.st_size > 8192):
die("Foundry EDP private key has unsafe ownership, mode, or size")
try:
derived = subprocess.run(
[openssl, "pkey", "-in", str(FOUNDRY_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE), "-pubout"],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
).stdout
derived_der = subprocess.run(
[
openssl,
"pkey",
"-in",
str(FOUNDRY_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE),
"-pubout",
"-outform",
"DER",
],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
).stdout
except (OSError, subprocess.CalledProcessError) as error:
die(f"Foundry EDP private key validation failed: {type(error).__name__}")
if (not derived.startswith(b"-----BEGIN PUBLIC KEY-----\n")
or not derived.rstrip().endswith(b"-----END PUBLIC KEY-----")
or len(derived) > 8192
or len(derived_der) != 44
or not derived_der.startswith(bytes.fromhex("302a300506032b6570032100"))):
die("Foundry EDP signing key must be Ed25519")
if public_stat is not None:
if (stat.S_ISLNK(public_stat.st_mode) or not stat.S_ISREG(public_stat.st_mode)
or public_stat.st_uid != 0
or public_stat.st_gid != EXTERNAL_DATA_PLANE_RUNTIME_GID
or stat.S_IMODE(public_stat.st_mode) != 0o440
or public_stat.st_size < 80 or public_stat.st_size > 8192):
die("Foundry EDP public key has unsafe ownership, mode, or size")
if FOUNDRY_EDP_MANAGED_PROVISIONER_PUBLIC_KEY_FILE.read_bytes() != derived:
die("Foundry EDP public key does not match the installed private key")
return "created" if private_created else "reused"
temporary_public = (
FOUNDRY_EDP_MANAGED_PROVISIONER_TRUST_DIR
/ f".public-key.{os.getpid()}.{time.time_ns()}.tmp"
)
descriptor = None
try:
descriptor = os.open(str(temporary_public), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o440)
os.write(descriptor, derived)
os.fsync(descriptor)
os.fchown(descriptor, 0, EXTERNAL_DATA_PLANE_RUNTIME_GID)
os.fchmod(descriptor, 0o440)
os.close(descriptor)
descriptor = None
os.replace(temporary_public, FOUNDRY_EDP_MANAGED_PROVISIONER_PUBLIC_KEY_FILE)
fsync_directory(FOUNDRY_EDP_MANAGED_PROVISIONER_TRUST_DIR)
finally:
if descriptor is not None:
os.close(descriptor)
if temporary_public.exists():
temporary_public.unlink()
return "created"
def ensure_engine_data_product_grant_private_state(include_reader=False):
@@ -6394,9 +6560,12 @@ def plan_artifact(artifact):
if component == "module-foundry":
print(f"runtime_grants=runner-managed:{EXTERNAL_DATA_PLANE_READER_GRANTS_DIR}")
print(f"runtime_grants=runner-managed:{FOUNDRY_BINDING_GRANTS_DIR}")
print(f"runtime_private_key=runner-managed:{FOUNDRY_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE}")
print(f"runtime_public_trust=runner-managed:{FOUNDRY_EDP_MANAGED_PROVISIONER_PUBLIC_KEY_FILE}")
if touches_external_data_plane:
print(f"runtime_secret=runner-managed:{EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_FILE}")
print(f"runtime_grants=runner-managed:{EXTERNAL_DATA_PLANE_READER_GRANTS_DIR}")
print(f"runtime_public_trust=runner-managed:{FOUNDRY_EDP_MANAGED_PROVISIONER_PUBLIC_KEY_FILE}")
if touches_engine_credential_sink(component, entries):
print(f"runtime_identity_key_id={ENGINE_CREDENTIAL_PROVISIONER_KEY_ID}")
print(f"runtime_private_key=runner-managed:{ENGINE_CREDENTIAL_PROVISIONER_PRIVATE_KEY_FILE}")
@@ -7136,6 +7305,7 @@ def prepare_component_runtime(component, entries=None):
if component == "module-foundry":
ensure_map_gateway_admin_secret()
ensure_foundry_edp_managed_provisioner_keypair()
ensure_root_owned_grant_directory(
EXTERNAL_DATA_PLANE_READER_GRANTS_DIR,
"external data plane reader grants",
@@ -7195,6 +7365,7 @@ def prepare_component_runtime(component, entries=None):
if touches_external_data_plane:
ensure_external_data_plane_provisioner_secret()
ensure_engine_edp_managed_provisioner_keypair()
ensure_foundry_edp_managed_provisioner_keypair()
ensure_root_owned_grant_directory(
EXTERNAL_DATA_PLANE_READER_GRANTS_DIR,
"external data plane reader grants",
@@ -7292,12 +7463,37 @@ def external_data_plane_healthcheck(require_managed=True):
expected_json["managedWriterBindingLifetime"] = "explicit-revoke"
if 'managedReaderBindingLifetime: config.managedProvisionerApiEnabled ? "explicit-revoke" : "disabled"' in server_text:
expected_json["managedReaderBindingLifetime"] = "explicit-revoke"
if 'foundryReaderBindingProvisioning: config.foundryProvisionerApiEnabled ? "digest+server-resolved-source" : "disabled"' in server_text:
expected_json["foundryReaderBindingProvisioning"] = "digest+server-resolved-source"
expected_json["foundryReaderBindingLifetime"] = "explicit-revoke"
return {
"url": "http://127.0.0.1:18106/healthz",
"expected_json": expected_json,
}
def module_foundry_healthcheck():
expected_json = {
"status": "ok",
"service": "nodedc-module-foundry",
}
server_source = component_root("module-foundry") / "server/catalog-server.mjs"
try:
server_text = server_source.read_text(encoding="utf-8")
except (OSError, UnicodeDecodeError):
server_text = ""
if "dataProductConsumerProvisioner" in server_text:
expected_json["dataProductConsumerProvisioner"] = {
"configured": True,
"auth": "dedicated-ed25519-service-identity",
"sourceScope": "external-data-plane-resolved",
}
return {
"url": "http://172.22.0.222:9920/healthz",
"expected_json": expected_json,
}
def component_healthchecks(component, entries=None, services=None):
if is_engine_n8n_transition(component, entries):
# The transition does not restart the Engine UI/backend generation.
@@ -7311,6 +7507,8 @@ def component_healthchecks(component, entries=None, services=None):
return ("http://127.0.0.1:3001/health",)
if component == "platform" and entries is not None and is_platform_provider_catalog_only(entries):
return ()
if component == "module-foundry":
return (module_foundry_healthcheck(),)
if (
(
is_engine_data_product_publish_grant_slice(component, entries)