import { readFileSync } from "node:fs"; import { loadManagedProvisionerPublicKeyFile, validateManagedProvisionerAudience, validateManagedProvisionerIdentity, } from "./managed-provisioner-auth.mjs"; export function readConfig(env = process.env) { const provisionerApiEnabled = boolean(env.EXTERNAL_DATA_PLANE_PROVISIONING_ENABLED, false); const managedProvisionerApiEnabled = boolean(env.EXTERNAL_DATA_PLANE_MANAGED_PROVISIONING_ENABLED, false); const foundryProvisionerApiEnabled = boolean(env.EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONING_ENABLED, false); const managedProvisionerMaxSkewSeconds = integer( env.EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_MAX_SKEW_SECONDS, 60, 5, 300, ); const config = { port: integer(env.PORT, 18106, 1, 65535), databaseUrl: required(env.EXTERNAL_DATA_PLANE_DATABASE_URL, "EXTERNAL_DATA_PLANE_DATABASE_URL"), databasePoolSize: integer(env.EXTERNAL_DATA_PLANE_DATABASE_POOL_SIZE, 10, 1, 50), internalAccessToken: optional(env.NODEDC_INTERNAL_ACCESS_TOKEN), provisionerApiEnabled, managedProvisionerApiEnabled, foundryProvisionerApiEnabled, provisionerAccessToken: provisionerApiEnabled ? secretFile(env.EXTERNAL_DATA_PLANE_PROVISIONER_TOKEN_FILE) : "", managedProvisionerPublicKey: managedProvisionerApiEnabled ? loadManagedProvisionerPublicKeyFile(env.EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_PUBLIC_KEY_FILE) : null, managedProvisionerServiceId: managedProvisionerApiEnabled ? validateManagedProvisionerIdentity( required(env.EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_SERVICE_ID, "EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_SERVICE_ID"), "service_id", ) : "", managedProvisionerKeyId: managedProvisionerApiEnabled ? validateManagedProvisionerIdentity( required(env.EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_KEY_ID, "EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_KEY_ID"), "key_id", ) : "", managedProvisionerAudience: managedProvisionerApiEnabled ? validateManagedProvisionerAudience( required(env.EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_AUDIENCE, "EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_AUDIENCE"), ) : "", managedProvisionerMaxSkewMs: managedProvisionerMaxSkewSeconds * 1000, managedProvisionerReplayCacheMaxEntries: integer( env.EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_REPLAY_CACHE_MAX_ENTRIES, 10_000, 100, 100_000, ), foundryProvisionerPublicKey: foundryProvisionerApiEnabled ? loadManagedProvisionerPublicKeyFile(env.EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_PUBLIC_KEY_FILE) : null, foundryProvisionerServiceId: foundryProvisionerApiEnabled ? validateManagedProvisionerIdentity( required(env.EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_SERVICE_ID, "EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_SERVICE_ID"), "service_id", ) : "", foundryProvisionerKeyId: foundryProvisionerApiEnabled ? validateManagedProvisionerIdentity( required(env.EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_KEY_ID, "EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_KEY_ID"), "key_id", ) : "", foundryProvisionerAudience: foundryProvisionerApiEnabled ? validateManagedProvisionerAudience( required(env.EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_AUDIENCE, "EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_AUDIENCE"), ) : "", foundryProvisionerMaxSkewMs: integer( env.EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_MAX_SKEW_SECONDS, 60, 5, 300, ) * 1000, foundryProvisionerReplayCacheMaxEntries: integer( env.EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_REPLAY_CACHE_MAX_ENTRIES, 10_000, 100, 100_000, ), rawRetentionDays: integer(env.EXTERNAL_DATA_PLANE_RAW_RETENTION_DAYS, 14, 1, 3650), maxBatchBytes: integer(env.EXTERNAL_DATA_PLANE_MAX_BATCH_BYTES, 5 * 1024 * 1024, 1024, 50 * 1024 * 1024), maxFactsPerPublish: integer(env.EXTERNAL_DATA_PLANE_MAX_FACTS_PER_PUBLISH, 5000, 1, 100_000), maxAttributesBytesPerFact: integer(env.EXTERNAL_DATA_PLANE_MAX_ATTRIBUTES_BYTES_PER_FACT, 64 * 1024, 256, 1024 * 1024), maxPatchOperations: integer(env.EXTERNAL_DATA_PLANE_MAX_PATCH_OPERATIONS, 500, 1, 5000), maxPatchBytes: integer(env.EXTERNAL_DATA_PLANE_MAX_PATCH_BYTES, 256 * 1024, 16 * 1024, 5 * 1024 * 1024), patchRetentionMs: integer(env.EXTERNAL_DATA_PLANE_PATCH_RETENTION_MS, 60 * 60 * 1000, 60 * 1000, 7 * 24 * 60 * 60 * 1000), receiptRetentionMs: integer(env.EXTERNAL_DATA_PLANE_RECEIPT_RETENTION_MS, 7 * 24 * 60 * 60 * 1000, 24 * 60 * 60 * 1000, 90 * 24 * 60 * 60 * 1000), retentionDeleteLimit: integer(env.EXTERNAL_DATA_PLANE_RETENTION_DELETE_LIMIT, 10_000, 100, 100_000), streamHeartbeatMs: integer(env.EXTERNAL_DATA_PLANE_STREAM_HEARTBEAT_MS, 20_000, 5_000, 60_000), streamPollMs: integer(env.EXTERNAL_DATA_PLANE_STREAM_POLL_MS, 1_000, 250, 10_000), maxReaderStreams: integer(env.EXTERNAL_DATA_PLANE_MAX_READER_STREAMS, 10, 1, 1000), writerBindingMaxTtlDays: integer(env.EXTERNAL_DATA_PLANE_WRITER_BINDING_MAX_TTL_DAYS, 90, 1, 365), maxFutureSkewSeconds: integer(env.EXTERNAL_DATA_PLANE_MAX_FUTURE_SKEW_SECONDS, 300, 0, 86400), retentionSweepMs: integer(env.EXTERNAL_DATA_PLANE_RETENTION_SWEEP_MS, 60 * 60 * 1000, 60 * 1000, 24 * 60 * 60 * 1000), legacyIntakeEnabled: boolean(env.EXTERNAL_DATA_PLANE_LEGACY_INTAKE_ENABLED, false), }; if (config.internalAccessToken && config.provisionerAccessToken && config.internalAccessToken === config.provisionerAccessToken) { throw new Error("provisioner_token_must_differ_from_internal_token"); } return config; } function required(value, name) { const normalized = optional(value); if (!normalized) throw new Error(`${name}_required`); return normalized; } function optional(value) { const normalized = String(value ?? "").trim(); return normalized || ""; } function integer(value, fallback, min, max) { const candidate = optional(value); if (!candidate) return fallback; const number = Number.parseInt(candidate, 10); if (!Number.isInteger(number) || number < min || number > max) throw new Error(`invalid_integer:${candidate}`); return number; } function boolean(value, fallback) { const normalized = optional(value).toLowerCase(); if (!normalized) return fallback; if (normalized === "true") return true; if (normalized === "false") return false; throw new Error(`invalid_boolean:${normalized}`); } function secretFile(value) { const path = optional(value); if (!path) return ""; let secret = ""; try { secret = readFileSync(path, "utf8").trim(); } catch { throw new Error("external_data_plane_provisioner_token_file_unreadable"); } if (!/^[A-Za-z0-9_-]{48,256}$/.test(secret)) { throw new Error("external_data_plane_provisioner_token_file_invalid"); } return secret; }