mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 06:32:43 +00:00
Gateway: add SecretRef support for gateway.auth.token with auth-mode guardrails (#35094)
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
import { readConfigFileSnapshot, resolveGatewayPort } from "../config/config.js";
|
||||
import type { OpenClawConfig } from "../config/types.js";
|
||||
import { resolveSecretInputRef } from "../config/types.secrets.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,
|
||||
@@ -13,6 +17,69 @@ type DashboardOptions = {
|
||||
noOpen?: boolean;
|
||||
};
|
||||
|
||||
function readGatewayTokenEnv(env: NodeJS.ProcessEnv): string | undefined {
|
||||
const primary = env.OPENCLAW_GATEWAY_TOKEN?.trim();
|
||||
if (primary) {
|
||||
return primary;
|
||||
}
|
||||
const legacy = env.CLAWDBOT_GATEWAY_TOKEN?.trim();
|
||||
return legacy || undefined;
|
||||
}
|
||||
|
||||
async function resolveDashboardToken(
|
||||
cfg: OpenClawConfig,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): Promise<{
|
||||
token?: string;
|
||||
source?: "config" | "env" | "secretRef";
|
||||
unresolvedRefReason?: string;
|
||||
tokenSecretRefConfigured: boolean;
|
||||
}> {
|
||||
const { ref } = resolveSecretInputRef({
|
||||
value: cfg.gateway?.auth?.token,
|
||||
defaults: cfg.secrets?.defaults,
|
||||
});
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function dashboardCommand(
|
||||
runtime: RuntimeEnv = defaultRuntime,
|
||||
options: DashboardOptions = {},
|
||||
@@ -23,7 +90,8 @@ export async function dashboardCommand(
|
||||
const bind = cfg.gateway?.bind ?? "loopback";
|
||||
const basePath = cfg.gateway?.controlUi?.basePath;
|
||||
const customBindHost = cfg.gateway?.customBindHost;
|
||||
const token = cfg.gateway?.auth?.token ?? process.env.OPENCLAW_GATEWAY_TOKEN ?? "";
|
||||
const resolvedToken = await resolveDashboardToken(cfg, process.env);
|
||||
const token = resolvedToken.token ?? "";
|
||||
|
||||
// LAN URLs fail secure-context checks in browsers.
|
||||
// Coerce only lan->loopback and preserve other bind modes.
|
||||
@@ -33,12 +101,25 @@ export async function dashboardCommand(
|
||||
customBindHost,
|
||||
basePath,
|
||||
});
|
||||
// Avoid embedding externally managed SecretRef tokens in terminal/clipboard/browser args.
|
||||
const includeTokenInUrl = token.length > 0 && !resolvedToken.tokenSecretRefConfigured;
|
||||
// Prefer URL fragment to avoid leaking auth tokens via query params.
|
||||
const dashboardUrl = token
|
||||
const dashboardUrl = includeTokenInUrl
|
||||
? `${links.httpUrl}#token=${encodeURIComponent(token)}`
|
||||
: links.httpUrl;
|
||||
|
||||
runtime.log(`Dashboard URL: ${dashboardUrl}`);
|
||||
if (resolvedToken.tokenSecretRefConfigured && token) {
|
||||
runtime.log(
|
||||
"Token auto-auth is disabled for SecretRef-managed gateway.auth.token; use your external token source if prompted.",
|
||||
);
|
||||
}
|
||||
if (resolvedToken.unresolvedRefReason) {
|
||||
runtime.log(`Token auto-auth unavailable: ${resolvedToken.unresolvedRefReason}`);
|
||||
runtime.log(
|
||||
"Set OPENCLAW_GATEWAY_TOKEN in this shell or resolve your secret provider, then rerun `openclaw dashboard`.",
|
||||
);
|
||||
}
|
||||
|
||||
const copied = await copyToClipboard(dashboardUrl).catch(() => false);
|
||||
runtime.log(copied ? "Copied to clipboard." : "Copy to clipboard unavailable.");
|
||||
@@ -54,7 +135,7 @@ export async function dashboardCommand(
|
||||
hint = formatControlUiSshHint({
|
||||
port,
|
||||
basePath,
|
||||
token: token || undefined,
|
||||
token: includeTokenInUrl ? token || undefined : undefined,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user