refactor: unify gateway SecretRef auth resolution paths
This commit is contained in:
@@ -12,7 +12,6 @@ import type {
|
||||
import {
|
||||
hasConfiguredSecretInput,
|
||||
normalizeSecretInputString,
|
||||
resolveSecretInputRef,
|
||||
} from "../../config/types.secrets.js";
|
||||
import { readLastGatewayErrorLine } from "../../daemon/diagnostics.js";
|
||||
import type { FindExtraGatewayServicesOptions } from "../../daemon/inspect.js";
|
||||
@@ -27,6 +26,7 @@ import {
|
||||
trimToUndefined,
|
||||
} from "../../gateway/credentials.js";
|
||||
import { resolveGatewayBindHost } from "../../gateway/net.js";
|
||||
import { resolveRequiredConfiguredSecretRefInputString } from "../../gateway/resolve-configured-secret-input-string.js";
|
||||
import {
|
||||
formatPortDiagnostics,
|
||||
inspectPortUsage,
|
||||
@@ -35,8 +35,6 @@ import {
|
||||
} from "../../infra/ports.js";
|
||||
import { pickPrimaryTailnetIPv4 } from "../../infra/tailnet.js";
|
||||
import { loadGatewayTlsRuntime } from "../../infra/tls/gateway.js";
|
||||
import { secretRefKey } from "../../secrets/ref-contract.js";
|
||||
import { resolveSecretRefValues } from "../../secrets/resolve.js";
|
||||
import { probeGatewayStatus } from "./probe.js";
|
||||
import { normalizeListenerAddress, parsePortFromArgs, pickProbeHostForBind } from "./shared.js";
|
||||
import type { GatewayRpcOpts } from "./types.js";
|
||||
@@ -127,13 +125,6 @@ async function resolveDaemonProbeToken(params: {
|
||||
}
|
||||
const defaults = params.daemonCfg.secrets?.defaults;
|
||||
const configured = params.daemonCfg.gateway?.auth?.token;
|
||||
const { ref } = resolveSecretInputRef({
|
||||
value: configured,
|
||||
defaults,
|
||||
});
|
||||
if (!ref) {
|
||||
return normalizeSecretInputString(configured);
|
||||
}
|
||||
const authMode = params.daemonCfg.gateway?.auth?.mode;
|
||||
if (authMode === "password" || authMode === "none" || authMode === "trusted-proxy") {
|
||||
return undefined;
|
||||
@@ -149,15 +140,16 @@ async function resolveDaemonProbeToken(params: {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
const resolved = await resolveSecretRefValues([ref], {
|
||||
const resolvedToken = await resolveRequiredConfiguredSecretRefInputString({
|
||||
config: params.daemonCfg,
|
||||
env: params.mergedDaemonEnv as NodeJS.ProcessEnv,
|
||||
value: configured,
|
||||
path: "gateway.auth.token",
|
||||
});
|
||||
const token = trimToUndefined(resolved.get(secretRefKey(ref)));
|
||||
if (!token) {
|
||||
throw new Error("gateway.auth.token resolved to an empty or non-string value.");
|
||||
if (resolvedToken) {
|
||||
return resolvedToken;
|
||||
}
|
||||
return token;
|
||||
return normalizeSecretInputString(configured);
|
||||
}
|
||||
|
||||
async function resolveDaemonProbePassword(params: {
|
||||
@@ -176,13 +168,6 @@ async function resolveDaemonProbePassword(params: {
|
||||
}
|
||||
const defaults = params.daemonCfg.secrets?.defaults;
|
||||
const configured = params.daemonCfg.gateway?.auth?.password;
|
||||
const { ref } = resolveSecretInputRef({
|
||||
value: configured,
|
||||
defaults,
|
||||
});
|
||||
if (!ref) {
|
||||
return normalizeSecretInputString(configured);
|
||||
}
|
||||
const authMode = params.daemonCfg.gateway?.auth?.mode;
|
||||
if (authMode === "token" || authMode === "none" || authMode === "trusted-proxy") {
|
||||
return undefined;
|
||||
@@ -198,15 +183,16 @@ async function resolveDaemonProbePassword(params: {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
const resolved = await resolveSecretRefValues([ref], {
|
||||
const resolvedPassword = await resolveRequiredConfiguredSecretRefInputString({
|
||||
config: params.daemonCfg,
|
||||
env: params.mergedDaemonEnv as NodeJS.ProcessEnv,
|
||||
value: configured,
|
||||
path: "gateway.auth.password",
|
||||
});
|
||||
const password = trimToUndefined(resolved.get(secretRefKey(ref)));
|
||||
if (!password) {
|
||||
throw new Error("gateway.auth.password resolved to an empty or non-string value.");
|
||||
if (resolvedPassword) {
|
||||
return resolvedPassword;
|
||||
}
|
||||
return password;
|
||||
return normalizeSecretInputString(configured);
|
||||
}
|
||||
|
||||
export async function gatherDaemonStatus(
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import type { Command } from "commander";
|
||||
import qrcode from "qrcode-terminal";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { hasConfiguredSecretInput, resolveSecretInputRef } from "../config/types.secrets.js";
|
||||
import { hasConfiguredSecretInput } from "../config/types.secrets.js";
|
||||
import { readGatewayPasswordEnv, readGatewayTokenEnv } from "../gateway/credentials.js";
|
||||
import { resolveRequiredConfiguredSecretRefInputString } from "../gateway/resolve-configured-secret-input-string.js";
|
||||
import { resolvePairingSetupFromConfig, encodePairingSetupCode } from "../pairing/setup-code.js";
|
||||
import { runCommandWithTimeout } from "../process/exec.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { secretRefKey } from "../secrets/ref-contract.js";
|
||||
import { resolveSecretRefValues } from "../secrets/resolve.js";
|
||||
import { formatDocsLink } from "../terminal/links.js";
|
||||
import { theme } from "../terminal/theme.js";
|
||||
import { resolveCommandSecretRefsViaGateway } from "./command-secret-gateway.js";
|
||||
@@ -66,26 +65,19 @@ function shouldResolveLocalGatewayPasswordSecret(
|
||||
async function resolveLocalGatewayPasswordSecretIfNeeded(
|
||||
cfg: ReturnType<typeof loadConfig>,
|
||||
): Promise<void> {
|
||||
const authPassword = cfg.gateway?.auth?.password;
|
||||
const { ref } = resolveSecretInputRef({
|
||||
value: authPassword,
|
||||
defaults: cfg.secrets?.defaults,
|
||||
});
|
||||
if (!ref) {
|
||||
return;
|
||||
}
|
||||
const resolved = await resolveSecretRefValues([ref], {
|
||||
const resolvedPassword = await resolveRequiredConfiguredSecretRefInputString({
|
||||
config: cfg,
|
||||
env: process.env,
|
||||
value: cfg.gateway?.auth?.password,
|
||||
path: "gateway.auth.password",
|
||||
});
|
||||
const value = resolved.get(secretRefKey(ref));
|
||||
if (typeof value !== "string" || value.trim().length === 0) {
|
||||
throw new Error("gateway.auth.password resolved to an empty or non-string value.");
|
||||
if (!resolvedPassword) {
|
||||
return;
|
||||
}
|
||||
if (!cfg.gateway?.auth) {
|
||||
return;
|
||||
}
|
||||
cfg.gateway.auth.password = value.trim();
|
||||
cfg.gateway.auth.password = resolvedPassword;
|
||||
}
|
||||
|
||||
function emitQrSecretResolveDiagnostics(diagnostics: string[], opts: QrCliOptions): void {
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { readConfigFileSnapshot, resolveGatewayPort } from "../config/config.js";
|
||||
import type { OpenClawConfig } from "../config/types.js";
|
||||
import { resolveSecretInputRef } from "../config/types.secrets.js";
|
||||
import { readGatewayTokenEnv } from "../gateway/credentials.js";
|
||||
import { resolveConfiguredSecretInputWithFallback } from "../gateway/resolve-configured-secret-input-string.js";
|
||||
import { copyToClipboard } from "../infra/clipboard.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { secretRefKey } from "../secrets/ref-contract.js";
|
||||
import { resolveSecretRefValues } from "../secrets/resolve.js";
|
||||
import {
|
||||
detectBrowserOpenSupport,
|
||||
formatControlUiSshHint,
|
||||
@@ -27,49 +25,26 @@ async function resolveDashboardToken(
|
||||
unresolvedRefReason?: string;
|
||||
tokenSecretRefConfigured: boolean;
|
||||
}> {
|
||||
const { ref } = resolveSecretInputRef({
|
||||
const resolved = await resolveConfiguredSecretInputWithFallback({
|
||||
config: cfg,
|
||||
env,
|
||||
value: cfg.gateway?.auth?.token,
|
||||
defaults: cfg.secrets?.defaults,
|
||||
path: "gateway.auth.token",
|
||||
readFallback: () => readGatewayTokenEnv(env),
|
||||
});
|
||||
const configToken =
|
||||
ref || typeof cfg.gateway?.auth?.token !== "string"
|
||||
? undefined
|
||||
: cfg.gateway.auth.token.trim() || undefined;
|
||||
if (configToken) {
|
||||
return { token: configToken, source: "config", tokenSecretRefConfigured: false };
|
||||
}
|
||||
if (!ref) {
|
||||
const envToken = readGatewayTokenEnv(env);
|
||||
return envToken
|
||||
? { token: envToken, source: "env", tokenSecretRefConfigured: false }
|
||||
: { tokenSecretRefConfigured: false };
|
||||
}
|
||||
const refLabel = `${ref.source}:${ref.provider}:${ref.id}`;
|
||||
try {
|
||||
const resolved = await resolveSecretRefValues([ref], {
|
||||
config: cfg,
|
||||
env,
|
||||
});
|
||||
const value = resolved.get(secretRefKey(ref));
|
||||
if (typeof value === "string" && value.trim().length > 0) {
|
||||
return { token: value.trim(), source: "secretRef", tokenSecretRefConfigured: true };
|
||||
}
|
||||
const envToken = readGatewayTokenEnv(env);
|
||||
return envToken
|
||||
? { token: envToken, source: "env", tokenSecretRefConfigured: true }
|
||||
: {
|
||||
unresolvedRefReason: `gateway.auth.token SecretRef is unresolved (${refLabel}).`,
|
||||
tokenSecretRefConfigured: true,
|
||||
};
|
||||
} catch {
|
||||
const envToken = readGatewayTokenEnv(env);
|
||||
return envToken
|
||||
? { token: envToken, source: "env", tokenSecretRefConfigured: true }
|
||||
: {
|
||||
unresolvedRefReason: `gateway.auth.token SecretRef is unresolved (${refLabel}).`,
|
||||
tokenSecretRefConfigured: true,
|
||||
};
|
||||
}
|
||||
return {
|
||||
token: resolved.value,
|
||||
source:
|
||||
resolved.source === "config"
|
||||
? "config"
|
||||
: resolved.source === "secretRef"
|
||||
? "secretRef"
|
||||
: resolved.source === "fallback"
|
||||
? "env"
|
||||
: undefined,
|
||||
unresolvedRefReason: resolved.unresolvedRefReason,
|
||||
tokenSecretRefConfigured: resolved.secretRefConfigured,
|
||||
};
|
||||
}
|
||||
|
||||
export async function dashboardCommand(
|
||||
|
||||
@@ -1,49 +1,30 @@
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveSecretInputRef } from "../config/types.secrets.js";
|
||||
export { shouldRequireGatewayTokenForInstall } from "../gateway/auth-install-policy.js";
|
||||
import { readGatewayTokenEnv } from "../gateway/credentials.js";
|
||||
import { secretRefKey } from "../secrets/ref-contract.js";
|
||||
import { resolveSecretRefValues } from "../secrets/resolve.js";
|
||||
import { resolveConfiguredSecretInputWithFallback } from "../gateway/resolve-configured-secret-input-string.js";
|
||||
|
||||
export async function resolveGatewayAuthTokenForService(
|
||||
cfg: OpenClawConfig,
|
||||
env: NodeJS.ProcessEnv,
|
||||
): Promise<{ token?: string; unavailableReason?: string }> {
|
||||
const { ref } = resolveSecretInputRef({
|
||||
const resolved = await resolveConfiguredSecretInputWithFallback({
|
||||
config: cfg,
|
||||
env,
|
||||
value: cfg.gateway?.auth?.token,
|
||||
defaults: cfg.secrets?.defaults,
|
||||
path: "gateway.auth.token",
|
||||
unresolvedReasonStyle: "detailed",
|
||||
readFallback: () => readGatewayTokenEnv(env),
|
||||
});
|
||||
const configToken =
|
||||
ref || typeof cfg.gateway?.auth?.token !== "string"
|
||||
? undefined
|
||||
: cfg.gateway.auth.token.trim() || undefined;
|
||||
if (configToken) {
|
||||
return { token: configToken };
|
||||
if (resolved.value) {
|
||||
return { token: resolved.value };
|
||||
}
|
||||
if (ref) {
|
||||
try {
|
||||
const resolved = await resolveSecretRefValues([ref], {
|
||||
config: cfg,
|
||||
env,
|
||||
});
|
||||
const value = resolved.get(secretRefKey(ref));
|
||||
if (typeof value === "string" && value.trim().length > 0) {
|
||||
return { token: value.trim() };
|
||||
}
|
||||
const envToken = readGatewayTokenEnv(env);
|
||||
if (envToken) {
|
||||
return { token: envToken };
|
||||
}
|
||||
return { unavailableReason: "gateway.auth.token SecretRef resolved to an empty value." };
|
||||
} catch (err) {
|
||||
const envToken = readGatewayTokenEnv(env);
|
||||
if (envToken) {
|
||||
return { token: envToken };
|
||||
}
|
||||
return {
|
||||
unavailableReason: `gateway.auth.token SecretRef is configured but unresolved (${String(err)}).`,
|
||||
};
|
||||
}
|
||||
if (!resolved.secretRefConfigured) {
|
||||
return {};
|
||||
}
|
||||
return { token: readGatewayTokenEnv(env) };
|
||||
if (resolved.unresolvedRefReason?.includes("resolved to an empty value")) {
|
||||
return { unavailableReason: resolved.unresolvedRefReason };
|
||||
}
|
||||
return {
|
||||
unavailableReason: `gateway.auth.token SecretRef is configured but unresolved (${resolved.unresolvedRefReason ?? "unknown reason"}).`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { GatewayAuthConfig, OpenClawConfig } from "../config/config.js";
|
||||
import { resolveSecretInputRef } from "../config/types.secrets.js";
|
||||
import { secretRefKey } from "../secrets/ref-contract.js";
|
||||
import { resolveSecretRefValues } from "../secrets/resolve.js";
|
||||
import { resolveRequiredConfiguredSecretRefInputString } from "./resolve-configured-secret-input-string.js";
|
||||
|
||||
export function withGatewayAuthPassword(cfg: OpenClawConfig, password: string): OpenClawConfig {
|
||||
return {
|
||||
@@ -57,13 +56,14 @@ export async function resolveGatewayPasswordSecretRef(params: {
|
||||
) {
|
||||
return params.cfg;
|
||||
}
|
||||
const resolved = await resolveSecretRefValues([ref], {
|
||||
const value = await resolveRequiredConfiguredSecretRefInputString({
|
||||
config: params.cfg,
|
||||
env: params.env,
|
||||
value: authPassword,
|
||||
path: "gateway.auth.password",
|
||||
});
|
||||
const value = resolved.get(secretRefKey(ref));
|
||||
if (typeof value !== "string" || value.trim().length === 0) {
|
||||
throw new Error("gateway.auth.password resolved to an empty or non-string value.");
|
||||
if (!value) {
|
||||
return params.cfg;
|
||||
}
|
||||
return withGatewayAuthPassword(params.cfg, value.trim());
|
||||
return withGatewayAuthPassword(params.cfg, value);
|
||||
}
|
||||
|
||||
137
src/gateway/resolve-configured-secret-input-string.test.ts
Normal file
137
src/gateway/resolve-configured-secret-input-string.test.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/types.js";
|
||||
import {
|
||||
resolveConfiguredSecretInputWithFallback,
|
||||
resolveRequiredConfiguredSecretRefInputString,
|
||||
} from "./resolve-configured-secret-input-string.js";
|
||||
|
||||
function createConfig(value: unknown): OpenClawConfig {
|
||||
return {
|
||||
gateway: {
|
||||
auth: {
|
||||
token: value,
|
||||
},
|
||||
},
|
||||
secrets: {
|
||||
providers: {
|
||||
default: { source: "env" },
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
}
|
||||
|
||||
describe("resolveConfiguredSecretInputWithFallback", () => {
|
||||
it("returns plaintext config value when present", async () => {
|
||||
const resolved = await resolveConfiguredSecretInputWithFallback({
|
||||
config: createConfig("config-token"),
|
||||
env: {} as NodeJS.ProcessEnv,
|
||||
value: "config-token",
|
||||
path: "gateway.auth.token",
|
||||
readFallback: () => "env-token",
|
||||
});
|
||||
|
||||
expect(resolved).toEqual({
|
||||
value: "config-token",
|
||||
source: "config",
|
||||
secretRefConfigured: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("returns fallback value when config is empty and no SecretRef is configured", async () => {
|
||||
const resolved = await resolveConfiguredSecretInputWithFallback({
|
||||
config: createConfig(""),
|
||||
env: {} as NodeJS.ProcessEnv,
|
||||
value: "",
|
||||
path: "gateway.auth.token",
|
||||
readFallback: () => "env-token",
|
||||
});
|
||||
|
||||
expect(resolved).toEqual({
|
||||
value: "env-token",
|
||||
source: "fallback",
|
||||
secretRefConfigured: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("returns resolved SecretRef value", async () => {
|
||||
const resolved = await resolveConfiguredSecretInputWithFallback({
|
||||
config: createConfig("${CUSTOM_GATEWAY_TOKEN}"),
|
||||
env: { CUSTOM_GATEWAY_TOKEN: "resolved-token" } as NodeJS.ProcessEnv,
|
||||
value: "${CUSTOM_GATEWAY_TOKEN}",
|
||||
path: "gateway.auth.token",
|
||||
readFallback: () => undefined,
|
||||
});
|
||||
|
||||
expect(resolved).toEqual({
|
||||
value: "resolved-token",
|
||||
source: "secretRef",
|
||||
secretRefConfigured: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back when SecretRef cannot be resolved", async () => {
|
||||
const resolved = await resolveConfiguredSecretInputWithFallback({
|
||||
config: createConfig("${MISSING_GATEWAY_TOKEN}"),
|
||||
env: {} as NodeJS.ProcessEnv,
|
||||
value: "${MISSING_GATEWAY_TOKEN}",
|
||||
path: "gateway.auth.token",
|
||||
readFallback: () => "env-fallback-token",
|
||||
});
|
||||
|
||||
expect(resolved).toEqual({
|
||||
value: "env-fallback-token",
|
||||
source: "fallback",
|
||||
secretRefConfigured: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("returns unresolved reason when SecretRef cannot be resolved and no fallback exists", async () => {
|
||||
const resolved = await resolveConfiguredSecretInputWithFallback({
|
||||
config: createConfig("${MISSING_GATEWAY_TOKEN}"),
|
||||
env: {} as NodeJS.ProcessEnv,
|
||||
value: "${MISSING_GATEWAY_TOKEN}",
|
||||
path: "gateway.auth.token",
|
||||
});
|
||||
|
||||
expect(resolved.value).toBeUndefined();
|
||||
expect(resolved.source).toBeUndefined();
|
||||
expect(resolved.secretRefConfigured).toBe(true);
|
||||
expect(resolved.unresolvedRefReason).toContain("gateway.auth.token SecretRef is unresolved");
|
||||
expect(resolved.unresolvedRefReason).toContain("MISSING_GATEWAY_TOKEN");
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveRequiredConfiguredSecretRefInputString", () => {
|
||||
it("returns undefined when no SecretRef is configured", async () => {
|
||||
const value = await resolveRequiredConfiguredSecretRefInputString({
|
||||
config: createConfig("plain-token"),
|
||||
env: {} as NodeJS.ProcessEnv,
|
||||
value: "plain-token",
|
||||
path: "gateway.auth.token",
|
||||
});
|
||||
|
||||
expect(value).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns resolved SecretRef value", async () => {
|
||||
const value = await resolveRequiredConfiguredSecretRefInputString({
|
||||
config: createConfig("${CUSTOM_GATEWAY_TOKEN}"),
|
||||
env: { CUSTOM_GATEWAY_TOKEN: "resolved-token" } as NodeJS.ProcessEnv,
|
||||
value: "${CUSTOM_GATEWAY_TOKEN}",
|
||||
path: "gateway.auth.token",
|
||||
});
|
||||
|
||||
expect(value).toBe("resolved-token");
|
||||
});
|
||||
|
||||
it("throws when SecretRef cannot be resolved", async () => {
|
||||
await expect(
|
||||
resolveRequiredConfiguredSecretRefInputString({
|
||||
config: createConfig("${MISSING_GATEWAY_TOKEN}"),
|
||||
env: {} as NodeJS.ProcessEnv,
|
||||
value: "${MISSING_GATEWAY_TOKEN}",
|
||||
path: "gateway.auth.token",
|
||||
}),
|
||||
).rejects.toThrow(/MISSING_GATEWAY_TOKEN/i);
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,7 @@ import { secretRefKey } from "../secrets/ref-contract.js";
|
||||
import { resolveSecretRefValues } from "../secrets/resolve.js";
|
||||
|
||||
export type SecretInputUnresolvedReasonStyle = "generic" | "detailed"; // pragma: allowlist secret
|
||||
export type ConfiguredSecretInputSource = "config" | "secretRef" | "fallback";
|
||||
|
||||
function trimToUndefined(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
@@ -87,3 +88,98 @@ export async function resolveConfiguredSecretInputString(params: {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function resolveConfiguredSecretInputWithFallback(params: {
|
||||
config: OpenClawConfig;
|
||||
env: NodeJS.ProcessEnv;
|
||||
value: unknown;
|
||||
path: string;
|
||||
unresolvedReasonStyle?: SecretInputUnresolvedReasonStyle;
|
||||
readFallback?: () => string | undefined;
|
||||
}): Promise<{
|
||||
value?: string;
|
||||
source?: ConfiguredSecretInputSource;
|
||||
unresolvedRefReason?: string;
|
||||
secretRefConfigured: boolean;
|
||||
}> {
|
||||
const { ref } = resolveSecretInputRef({
|
||||
value: params.value,
|
||||
defaults: params.config.secrets?.defaults,
|
||||
});
|
||||
const configValue = !ref ? trimToUndefined(params.value) : undefined;
|
||||
if (configValue) {
|
||||
return {
|
||||
value: configValue,
|
||||
source: "config",
|
||||
secretRefConfigured: false,
|
||||
};
|
||||
}
|
||||
if (!ref) {
|
||||
const fallback = params.readFallback?.();
|
||||
if (fallback) {
|
||||
return {
|
||||
value: fallback,
|
||||
source: "fallback",
|
||||
secretRefConfigured: false,
|
||||
};
|
||||
}
|
||||
return { secretRefConfigured: false };
|
||||
}
|
||||
|
||||
const resolved = await resolveConfiguredSecretInputString({
|
||||
config: params.config,
|
||||
env: params.env,
|
||||
value: params.value,
|
||||
path: params.path,
|
||||
unresolvedReasonStyle: params.unresolvedReasonStyle,
|
||||
});
|
||||
if (resolved.value) {
|
||||
return {
|
||||
value: resolved.value,
|
||||
source: "secretRef",
|
||||
secretRefConfigured: true,
|
||||
};
|
||||
}
|
||||
|
||||
const fallback = params.readFallback?.();
|
||||
if (fallback) {
|
||||
return {
|
||||
value: fallback,
|
||||
source: "fallback",
|
||||
secretRefConfigured: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
unresolvedRefReason: resolved.unresolvedRefReason,
|
||||
secretRefConfigured: true,
|
||||
};
|
||||
}
|
||||
|
||||
export async function resolveRequiredConfiguredSecretRefInputString(params: {
|
||||
config: OpenClawConfig;
|
||||
env: NodeJS.ProcessEnv;
|
||||
value: unknown;
|
||||
path: string;
|
||||
unresolvedReasonStyle?: SecretInputUnresolvedReasonStyle;
|
||||
}): Promise<string | undefined> {
|
||||
const { ref } = resolveSecretInputRef({
|
||||
value: params.value,
|
||||
defaults: params.config.secrets?.defaults,
|
||||
});
|
||||
if (!ref) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const resolved = await resolveConfiguredSecretInputString({
|
||||
config: params.config,
|
||||
env: params.env,
|
||||
value: params.value,
|
||||
path: params.path,
|
||||
unresolvedReasonStyle: params.unresolvedReasonStyle,
|
||||
});
|
||||
if (resolved.value) {
|
||||
return resolved.value;
|
||||
}
|
||||
throw new Error(resolved.unresolvedRefReason ?? `${params.path} resolved to an empty value.`);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,7 @@ import type {
|
||||
OpenClawConfig,
|
||||
} from "../config/config.js";
|
||||
import { writeConfigFile } from "../config/config.js";
|
||||
import { hasConfiguredSecretInput, resolveSecretInputRef } from "../config/types.secrets.js";
|
||||
import { secretRefKey } from "../secrets/ref-contract.js";
|
||||
import { resolveSecretRefValues } from "../secrets/resolve.js";
|
||||
import { hasConfiguredSecretInput } from "../config/types.secrets.js";
|
||||
import { assertExplicitGatewayAuthModeWhenBothConfigured } from "./auth-mode-policy.js";
|
||||
import { resolveGatewayAuth, type ResolvedGatewayAuth } from "./auth.js";
|
||||
import {
|
||||
@@ -15,6 +13,7 @@ import {
|
||||
hasGatewayTokenEnvCandidate,
|
||||
readGatewayTokenEnv,
|
||||
} from "./credentials.js";
|
||||
import { resolveRequiredConfiguredSecretRefInputString } from "./resolve-configured-secret-input-string.js";
|
||||
|
||||
export function mergeGatewayAuthConfig(
|
||||
base?: GatewayAuthConfig,
|
||||
@@ -167,26 +166,15 @@ async function resolveGatewayTokenSecretRef(
|
||||
env: NodeJS.ProcessEnv,
|
||||
authOverride?: GatewayAuthConfig,
|
||||
): Promise<string | undefined> {
|
||||
const authToken = cfg.gateway?.auth?.token;
|
||||
const { ref } = resolveSecretInputRef({
|
||||
value: authToken,
|
||||
defaults: cfg.secrets?.defaults,
|
||||
});
|
||||
if (!ref) {
|
||||
return undefined;
|
||||
}
|
||||
if (!shouldResolveGatewayTokenSecretRef({ cfg, env, authOverride })) {
|
||||
return undefined;
|
||||
}
|
||||
const resolved = await resolveSecretRefValues([ref], {
|
||||
return await resolveRequiredConfiguredSecretRefInputString({
|
||||
config: cfg,
|
||||
env,
|
||||
value: cfg.gateway?.auth?.token,
|
||||
path: "gateway.auth.token",
|
||||
});
|
||||
const value = resolved.get(secretRefKey(ref));
|
||||
if (typeof value !== "string" || value.trim().length === 0) {
|
||||
throw new Error("gateway.auth.token resolved to an empty or non-string value.");
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
function shouldResolveGatewayPasswordSecretRef(params: {
|
||||
@@ -216,26 +204,15 @@ async function resolveGatewayPasswordSecretRef(
|
||||
env: NodeJS.ProcessEnv,
|
||||
authOverride?: GatewayAuthConfig,
|
||||
): Promise<string | undefined> {
|
||||
const authPassword = cfg.gateway?.auth?.password;
|
||||
const { ref } = resolveSecretInputRef({
|
||||
value: authPassword,
|
||||
defaults: cfg.secrets?.defaults,
|
||||
});
|
||||
if (!ref) {
|
||||
return undefined;
|
||||
}
|
||||
if (!shouldResolveGatewayPasswordSecretRef({ cfg, env, authOverride })) {
|
||||
return undefined;
|
||||
}
|
||||
const resolved = await resolveSecretRefValues([ref], {
|
||||
return await resolveRequiredConfiguredSecretRefInputString({
|
||||
config: cfg,
|
||||
env,
|
||||
value: cfg.gateway?.auth?.password,
|
||||
path: "gateway.auth.password",
|
||||
});
|
||||
const value = resolved.get(secretRefKey(ref));
|
||||
if (typeof value !== "string" || value.trim().length === 0) {
|
||||
throw new Error("gateway.auth.password resolved to an empty or non-string value.");
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
export async function ensureGatewayStartupAuth(params: {
|
||||
|
||||
@@ -7,8 +7,7 @@ import {
|
||||
resolveSecretInputRef,
|
||||
} from "../config/types.secrets.js";
|
||||
import { assertExplicitGatewayAuthModeWhenBothConfigured } from "../gateway/auth-mode-policy.js";
|
||||
import { secretRefKey } from "../secrets/ref-contract.js";
|
||||
import { resolveSecretRefValues } from "../secrets/resolve.js";
|
||||
import { resolveRequiredConfiguredSecretRefInputString } from "../gateway/resolve-configured-secret-input-string.js";
|
||||
import { resolveGatewayBindUrl } from "../shared/gateway-bind-url.js";
|
||||
import { isCarrierGradeNatIpv4Address, isRfc1918Ipv4Address } from "../shared/net/ip.js";
|
||||
import { resolveTailnetHostWithRunner } from "../shared/tailscale-status.js";
|
||||
@@ -209,14 +208,6 @@ async function resolveGatewayTokenSecretRef(
|
||||
cfg: OpenClawConfig,
|
||||
env: NodeJS.ProcessEnv,
|
||||
): Promise<OpenClawConfig> {
|
||||
const authToken = cfg.gateway?.auth?.token;
|
||||
const { ref } = resolveSecretInputRef({
|
||||
value: authToken,
|
||||
defaults: cfg.secrets?.defaults,
|
||||
});
|
||||
if (!ref) {
|
||||
return cfg;
|
||||
}
|
||||
const hasTokenEnvCandidate = Boolean(resolveGatewayTokenFromEnv(env));
|
||||
if (hasTokenEnvCandidate) {
|
||||
return cfg;
|
||||
@@ -233,13 +224,14 @@ async function resolveGatewayTokenSecretRef(
|
||||
return cfg;
|
||||
}
|
||||
}
|
||||
const resolved = await resolveSecretRefValues([ref], {
|
||||
const token = await resolveRequiredConfiguredSecretRefInputString({
|
||||
config: cfg,
|
||||
env,
|
||||
value: cfg.gateway?.auth?.token,
|
||||
path: "gateway.auth.token",
|
||||
});
|
||||
const value = resolved.get(secretRefKey(ref));
|
||||
if (typeof value !== "string" || value.trim().length === 0) {
|
||||
throw new Error("gateway.auth.token resolved to an empty or non-string value.");
|
||||
if (!token) {
|
||||
return cfg;
|
||||
}
|
||||
return {
|
||||
...cfg,
|
||||
@@ -247,7 +239,7 @@ async function resolveGatewayTokenSecretRef(
|
||||
...cfg.gateway,
|
||||
auth: {
|
||||
...cfg.gateway?.auth,
|
||||
token: value.trim(),
|
||||
token,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -257,14 +249,6 @@ async function resolveGatewayPasswordSecretRef(
|
||||
cfg: OpenClawConfig,
|
||||
env: NodeJS.ProcessEnv,
|
||||
): Promise<OpenClawConfig> {
|
||||
const authPassword = cfg.gateway?.auth?.password;
|
||||
const { ref } = resolveSecretInputRef({
|
||||
value: authPassword,
|
||||
defaults: cfg.secrets?.defaults,
|
||||
});
|
||||
if (!ref) {
|
||||
return cfg;
|
||||
}
|
||||
const hasPasswordEnvCandidate = Boolean(resolveGatewayPasswordFromEnv(env));
|
||||
if (hasPasswordEnvCandidate) {
|
||||
return cfg;
|
||||
@@ -281,13 +265,14 @@ async function resolveGatewayPasswordSecretRef(
|
||||
return cfg;
|
||||
}
|
||||
}
|
||||
const resolved = await resolveSecretRefValues([ref], {
|
||||
const password = await resolveRequiredConfiguredSecretRefInputString({
|
||||
config: cfg,
|
||||
env,
|
||||
value: cfg.gateway?.auth?.password,
|
||||
path: "gateway.auth.password",
|
||||
});
|
||||
const value = resolved.get(secretRefKey(ref));
|
||||
if (typeof value !== "string" || value.trim().length === 0) {
|
||||
throw new Error("gateway.auth.password resolved to an empty or non-string value.");
|
||||
if (!password) {
|
||||
return cfg;
|
||||
}
|
||||
return {
|
||||
...cfg,
|
||||
@@ -295,7 +280,7 @@ async function resolveGatewayPasswordSecretRef(
|
||||
...cfg.gateway,
|
||||
auth: {
|
||||
...cfg.gateway?.auth,
|
||||
password: value.trim(),
|
||||
password,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user