From 380eb1c072c1a992e8f4c567f6294881c6c103d4 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 8 Mar 2026 02:56:31 +0000 Subject: [PATCH] refactor: reuse shared gateway probe auth --- src/cli/daemon-cli/status.gather.test.ts | 32 ++++++ src/cli/daemon-cli/status.gather.ts | 125 +++-------------------- src/gateway/probe-auth.test.ts | 34 +++++- src/gateway/probe-auth.ts | 18 ++++ 4 files changed, 96 insertions(+), 113 deletions(-) diff --git a/src/cli/daemon-cli/status.gather.test.ts b/src/cli/daemon-cli/status.gather.test.ts index d29a6ff163f..9b4d6428d1e 100644 --- a/src/cli/daemon-cli/status.gather.test.ts +++ b/src/cli/daemon-cli/status.gather.test.ts @@ -283,6 +283,38 @@ describe("gatherDaemonStatus", () => { ); }); + it("keeps remote probe auth strict when remote token is missing", async () => { + daemonLoadedConfig = { + gateway: { + mode: "remote", + remote: { + url: "wss://gateway.example", + password: "remote-password", // pragma: allowlist secret + }, + auth: { + mode: "token", + token: "local-token", + password: "local-password", // pragma: allowlist secret + }, + }, + }; + process.env.OPENCLAW_GATEWAY_TOKEN = "env-token"; + process.env.OPENCLAW_GATEWAY_PASSWORD = "env-password"; // pragma: allowlist secret + + await gatherDaemonStatus({ + rpc: {}, + probe: true, + deep: false, + }); + + expect(callGatewayStatusProbe).toHaveBeenCalledWith( + expect.objectContaining({ + token: undefined, + password: "env-password", // pragma: allowlist secret + }), + ); + }); + it("skips TLS runtime loading when probe is disabled", async () => { const status = await gatherDaemonStatus({ rpc: {}, diff --git a/src/cli/daemon-cli/status.gather.ts b/src/cli/daemon-cli/status.gather.ts index 3818a020835..a44ef93c656 100644 --- a/src/cli/daemon-cli/status.gather.ts +++ b/src/cli/daemon-cli/status.gather.ts @@ -9,10 +9,6 @@ import type { GatewayBindMode, GatewayControlUiConfig, } from "../../config/types.js"; -import { - hasConfiguredSecretInput, - normalizeSecretInputString, -} from "../../config/types.secrets.js"; import { readLastGatewayErrorLine } from "../../daemon/diagnostics.js"; import type { FindExtraGatewayServicesOptions } from "../../daemon/inspect.js"; import { findExtraGatewayServices } from "../../daemon/inspect.js"; @@ -20,13 +16,9 @@ import type { ServiceConfigAudit } from "../../daemon/service-audit.js"; import { auditGatewayServiceConfig } from "../../daemon/service-audit.js"; import type { GatewayServiceRuntime } from "../../daemon/service-runtime.js"; import { resolveGatewayService } from "../../daemon/service.js"; -import { - readGatewayPasswordEnv, - readGatewayTokenEnv, - trimToUndefined, -} from "../../gateway/credentials.js"; +import { trimToUndefined } from "../../gateway/credentials.js"; import { resolveGatewayBindHost } from "../../gateway/net.js"; -import { resolveRequiredConfiguredSecretRefInputString } from "../../gateway/resolve-configured-secret-input-string.js"; +import { resolveGatewayProbeAuthWithSecretInputs } from "../../gateway/probe-auth.js"; import { parseStrictPositiveInteger } from "../../infra/parse-finite-number.js"; import { formatPortDiagnostics, @@ -258,92 +250,6 @@ async function inspectDaemonPortStatuses(params: { }; } -async function resolveDaemonProbeToken(params: { - daemonCfg: OpenClawConfig; - mergedDaemonEnv: Record; - explicitToken?: string; - explicitPassword?: string; -}): Promise { - const explicitToken = trimToUndefined(params.explicitToken); - if (explicitToken) { - return explicitToken; - } - const envToken = readGatewayTokenEnv(params.mergedDaemonEnv as NodeJS.ProcessEnv); - if (envToken) { - return envToken; - } - const defaults = params.daemonCfg.secrets?.defaults; - const configured = params.daemonCfg.gateway?.auth?.token; - const authMode = params.daemonCfg.gateway?.auth?.mode; - if (authMode === "password" || authMode === "none" || authMode === "trusted-proxy") { - return undefined; - } - if (authMode !== "token") { - const passwordCandidate = - trimToUndefined(params.explicitPassword) || - readGatewayPasswordEnv(params.mergedDaemonEnv as NodeJS.ProcessEnv) || - (hasConfiguredSecretInput(params.daemonCfg.gateway?.auth?.password, defaults) - ? "__configured__" - : undefined); - if (passwordCandidate) { - return undefined; - } - } - const resolvedToken = await resolveRequiredConfiguredSecretRefInputString({ - config: params.daemonCfg, - env: params.mergedDaemonEnv as NodeJS.ProcessEnv, - value: configured, - path: "gateway.auth.token", - }); - if (resolvedToken) { - return resolvedToken; - } - return normalizeSecretInputString(configured); -} - -async function resolveDaemonProbePassword(params: { - daemonCfg: OpenClawConfig; - mergedDaemonEnv: Record; - explicitToken?: string; - explicitPassword?: string; -}): Promise { - const explicitPassword = trimToUndefined(params.explicitPassword); - if (explicitPassword) { - return explicitPassword; - } - const envPassword = readGatewayPasswordEnv(params.mergedDaemonEnv as NodeJS.ProcessEnv); - if (envPassword) { - return envPassword; - } - const defaults = params.daemonCfg.secrets?.defaults; - const configured = params.daemonCfg.gateway?.auth?.password; - const authMode = params.daemonCfg.gateway?.auth?.mode; - if (authMode === "token" || authMode === "none" || authMode === "trusted-proxy") { - return undefined; - } - if (authMode !== "password") { - const tokenCandidate = - trimToUndefined(params.explicitToken) || - readGatewayTokenEnv(params.mergedDaemonEnv as NodeJS.ProcessEnv) || - (hasConfiguredSecretInput(params.daemonCfg.gateway?.auth?.token, defaults) - ? "__configured__" - : undefined); - if (tokenCandidate) { - return undefined; - } - } - const resolvedPassword = await resolveRequiredConfiguredSecretRefInputString({ - config: params.daemonCfg, - env: params.mergedDaemonEnv as NodeJS.ProcessEnv, - value: configured, - path: "gateway.auth.password", - }); - if (resolvedPassword) { - return resolvedPassword; - } - return normalizeSecretInputString(configured); -} - export async function gatherDaemonStatus( opts: { rpc: GatewayRpcOpts; @@ -395,28 +301,23 @@ export async function gatherDaemonStatus( const tlsRuntime = shouldUseLocalTlsRuntime ? await loadGatewayTlsRuntime(daemonCfg.gateway?.tls) : undefined; - const daemonProbePassword = opts.probe - ? await resolveDaemonProbePassword({ - daemonCfg, - mergedDaemonEnv, - explicitToken: opts.rpc.token, - explicitPassword: opts.rpc.password, - }) - : undefined; - const daemonProbeToken = opts.probe - ? await resolveDaemonProbeToken({ - daemonCfg, - mergedDaemonEnv, - explicitToken: opts.rpc.token, - explicitPassword: opts.rpc.password, + const daemonProbeAuth = opts.probe + ? await resolveGatewayProbeAuthWithSecretInputs({ + cfg: daemonCfg, + mode: daemonCfg.gateway?.mode === "remote" ? "remote" : "local", + env: mergedDaemonEnv as NodeJS.ProcessEnv, + explicitAuth: { + token: opts.rpc.token, + password: opts.rpc.password, + }, }) : undefined; const rpc = opts.probe ? await probeGatewayStatus({ url: gateway.probeUrl, - token: daemonProbeToken, - password: daemonProbePassword, + token: daemonProbeAuth?.token, + password: daemonProbeAuth?.password, tlsFingerprint: shouldUseLocalTlsRuntime && tlsRuntime?.enabled ? tlsRuntime.fingerprintSha256 diff --git a/src/gateway/probe-auth.test.ts b/src/gateway/probe-auth.test.ts index 3ff1fb991cc..e31dd4856ad 100644 --- a/src/gateway/probe-auth.test.ts +++ b/src/gateway/probe-auth.test.ts @@ -1,6 +1,9 @@ import { describe, expect, it } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; -import { resolveGatewayProbeAuthSafe } from "./probe-auth.js"; +import { + resolveGatewayProbeAuthSafe, + resolveGatewayProbeAuthWithSecretInputs, +} from "./probe-auth.js"; describe("resolveGatewayProbeAuthSafe", () => { it("returns probe auth credentials when available", () => { @@ -79,3 +82,32 @@ describe("resolveGatewayProbeAuthSafe", () => { }); }); }); + +describe("resolveGatewayProbeAuthWithSecretInputs", () => { + it("resolves local probe SecretRef values before shared credential selection", async () => { + const auth = await resolveGatewayProbeAuthWithSecretInputs({ + cfg: { + gateway: { + auth: { + mode: "token", + token: { source: "env", provider: "default", id: "DAEMON_GATEWAY_TOKEN" }, + }, + }, + secrets: { + providers: { + default: { source: "env" }, + }, + }, + } as OpenClawConfig, + mode: "local", + env: { + DAEMON_GATEWAY_TOKEN: "resolved-daemon-token", + } as NodeJS.ProcessEnv, + }); + + expect(auth).toEqual({ + token: "resolved-daemon-token", + password: undefined, + }); + }); +}); diff --git a/src/gateway/probe-auth.ts b/src/gateway/probe-auth.ts index a6f6e6f8ef1..a651e5afa60 100644 --- a/src/gateway/probe-auth.ts +++ b/src/gateway/probe-auth.ts @@ -1,5 +1,7 @@ import type { OpenClawConfig } from "../config/config.js"; +import { resolveGatewayCredentialsWithSecretInputs } from "./call.js"; import { + type ExplicitGatewayAuth, isGatewaySecretRefUnavailableError, resolveGatewayCredentialsFromConfig, } from "./credentials.js"; @@ -18,6 +20,22 @@ export function resolveGatewayProbeAuth(params: { }); } +export async function resolveGatewayProbeAuthWithSecretInputs(params: { + cfg: OpenClawConfig; + mode: "local" | "remote"; + env?: NodeJS.ProcessEnv; + explicitAuth?: ExplicitGatewayAuth; +}): Promise<{ token?: string; password?: string }> { + return await resolveGatewayCredentialsWithSecretInputs({ + config: params.cfg, + env: params.env, + explicitAuth: params.explicitAuth, + modeOverride: params.mode, + includeLegacyEnv: false, + remoteTokenFallback: "remote-only", + }); +} + export function resolveGatewayProbeAuthSafe(params: { cfg: OpenClawConfig; mode: "local" | "remote";