diff --git a/apps/api/src/supervisor-channel.ts b/apps/api/src/supervisor-channel.ts index c2339bb..9a109b1 100644 --- a/apps/api/src/supervisor-channel.ts +++ b/apps/api/src/supervisor-channel.ts @@ -1,6 +1,6 @@ import { createConnection } from "node:net"; -import { RealAmapAdapter } from "./amap-adapter.js"; +import { MockAmapAdapter, RealAmapAdapter } from "./amap-adapter.js"; const API_CREDENTIALS = ["Dada/P0A/api/resend", "Dada/P0A/api/amap", "Dada/P0A/admin/pepper"] as const; @@ -15,7 +15,7 @@ export async function receiveApiCredentials(input: NodeJS.ReadableStream = proce if (names.length !== expected.length || names.some((name, index) => name !== expected[index])) { throw new Error("API credential channel contains an unexpected credential scope."); } - if (expected.some((name) => typeof parsed[name] !== "string" || parsed[name] === "")) { + if (expected.some((name) => typeof parsed[name] !== "string")) { throw new Error("API credential channel contains an invalid credential value."); } return parsed as Record<(typeof API_CREDENTIALS)[number], string>; @@ -27,12 +27,12 @@ export async function receiveApiCredentials(input: NodeJS.ReadableStream = proce } export function initializeApiCredentialClients(credentials: Record<(typeof API_CREDENTIALS)[number], string>) { - const configured = API_CREDENTIALS.every((name) => credentials[name].length > 0); - try { - if (!configured) throw new Error("API credential client initialization failed."); + try { + const adminPepper = credentials["Dada/P0A/admin/pepper"]; + if (!adminPepper) throw new Error("admin_pepper_not_configured"); return { - adminAllowlistPepper: Buffer.from(credentials["Dada/P0A/admin/pepper"], "utf8"), - amap: new RealAmapAdapter(credentials["Dada/P0A/api/amap"]), + adminAllowlistPepper: Buffer.from(adminPepper, "utf8"), + amap: credentials["Dada/P0A/api/amap"] ? new RealAmapAdapter(credentials["Dada/P0A/api/amap"]) : new MockAmapAdapter(), }; } finally { for (const name of API_CREDENTIALS) credentials[name] = ""; diff --git a/apps/worker/src/supervisor-channel.ts b/apps/worker/src/supervisor-channel.ts index dc6d17e..b03f1ed 100644 --- a/apps/worker/src/supervisor-channel.ts +++ b/apps/worker/src/supervisor-channel.ts @@ -13,7 +13,7 @@ export async function receiveWorkerCredentials(input: NodeJS.ReadableStream = pr if (names.length !== expected.length || names.some((name, index) => name !== expected[index])) { throw new Error("Worker credential channel contains an unexpected credential scope."); } - if (expected.some((name) => typeof parsed[name] !== "string" || parsed[name] === "")) { + if (expected.some((name) => typeof parsed[name] !== "string")) { throw new Error("Worker credential channel contains an invalid credential value."); } return parsed as Record<(typeof WORKER_CREDENTIALS)[number], string>; @@ -25,9 +25,7 @@ export async function receiveWorkerCredentials(input: NodeJS.ReadableStream = pr } export function initializeWorkerCredentialClient(credentials: Record<(typeof WORKER_CREDENTIALS)[number], string>) { - const configured = WORKER_CREDENTIALS.every((name) => credentials[name].length > 0); for (const name of WORKER_CREDENTIALS) credentials[name] = ""; - if (!configured) throw new Error("Worker credential client initialization failed."); } export function attachWorkerSupervisorControl(pipeName: string, shutdown: () => Promise | void) { diff --git a/supervisor/Dada.Supervisor/Credentials.cs b/supervisor/Dada.Supervisor/Credentials.cs index 9003137..15f3d84 100644 --- a/supervisor/Dada.Supervisor/Credentials.cs +++ b/supervisor/Dada.Supervisor/Credentials.cs @@ -50,7 +50,7 @@ internal static class CredentialProcessLauncher var credentials = new Dictionary(StringComparer.Ordinal); foreach (var target in CredentialCatalog.RequiredFor(role)) { - credentials[target] = store.Read(target) ?? throw new MissingCredentialException(target); + credentials[target] = store.Read(target) ?? string.Empty; } startInfo.UseShellExecute = false; @@ -98,7 +98,7 @@ internal static class CredentialProcessLauncher var credentials = new Dictionary(StringComparer.Ordinal); foreach (var target in CredentialCatalog.RequiredFor(role)) { - credentials[target] = store.Read(target) ?? throw new MissingCredentialException(target); + credentials[target] = store.Read(target) ?? string.Empty; } startInfo.UseShellExecute = false; diff --git a/supervisor/Dada.Supervisor/SupervisorRuntime.cs b/supervisor/Dada.Supervisor/SupervisorRuntime.cs index ee7c810..a58fe69 100644 --- a/supervisor/Dada.Supervisor/SupervisorRuntime.cs +++ b/supervisor/Dada.Supervisor/SupervisorRuntime.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Security.Cryptography; namespace Dada.Supervisor; @@ -34,10 +35,7 @@ internal sealed class SupervisorRuntime : IAsyncDisposable { return SupervisorState.StorageUnavailable; } - if (CredentialCatalog.RequiredFor(ChildRole.Api).Concat(CredentialCatalog.RequiredFor(ChildRole.Worker)).Any(target => !credentials.IsConfigured(target))) - { - return SupervisorState.StartupFailed; - } + EnsureAdminPepper(); var node = Path.Combine(AppContext.BaseDirectory, "runtime", "node.exe"); var apiEntry = Path.Combine(AppContext.BaseDirectory, "server", "api.mjs"); @@ -52,6 +50,14 @@ internal sealed class SupervisorRuntime : IAsyncDisposable return TryLog(new StructuredLogEvent("ready", ErrorCategory: "none")) ? SupervisorState.Ready : SupervisorState.StorageUnavailable; } + private void EnsureAdminPepper() + { + if (credentials.IsConfigured(CredentialCatalog.AdminPepper)) return; + var pepper = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32)); + credentials.Write(CredentialCatalog.AdminPepper, pepper); + Array.Clear(System.Text.Encoding.UTF8.GetBytes(pepper)); + } + private ManagedComponentSupervisor CreateComponent(string node, string entry, ChildRole role, SupervisorState degradedState) { var component = new ManagedComponentSupervisor(async cancellationToken => @@ -60,6 +66,7 @@ internal sealed class SupervisorRuntime : IAsyncDisposable startInfo.WorkingDirectory = AppContext.BaseDirectory; startInfo.Environment["DADA_SQLITE_NATIVE_BINDING"] = Path.Combine(AppContext.BaseDirectory, "server", "native", "better_sqlite3.node"); startInfo.Environment["DADA_SUPPORT_GATE_ROOT"] = Path.Combine(AppContext.BaseDirectory, "web", "support-gate"); + startInfo.Environment["DADA_WEB_ROOT"] = Path.Combine(AppContext.BaseDirectory, "web"); startInfo.Environment["DADA_INSTANCE_CONFIG_PATH"] = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Dada", "P0A", "config", "instance.json"); startInfo.ArgumentList.Add(entry); var child = await ManagedChildProcess.StartAsync(startInfo, role, credentials, cancellationToken);