mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-13 18:21:27 +00:00
fix(gateway): improve websocket auth logging
This commit is contained in:
@@ -27,6 +27,7 @@ let lastClientOptions: {
|
||||
token?: string;
|
||||
password?: string;
|
||||
tlsFingerprint?: string;
|
||||
clientDisplayName?: string;
|
||||
scopes?: string[];
|
||||
deviceIdentity?: unknown;
|
||||
onHelloOk?: (hello: { features?: { methods?: string[] } }) => void | Promise<void>;
|
||||
@@ -58,6 +59,7 @@ vi.mock("./client.js", () => ({
|
||||
url?: string;
|
||||
token?: string;
|
||||
password?: string;
|
||||
clientDisplayName?: string;
|
||||
scopes?: string[];
|
||||
onHelloOk?: (hello: { features?: { methods?: string[] } }) => void | Promise<void>;
|
||||
onClose?: (code: number, reason: string) => void;
|
||||
@@ -95,6 +97,7 @@ class StubGatewayClient {
|
||||
url?: string;
|
||||
token?: string;
|
||||
password?: string;
|
||||
clientDisplayName?: string;
|
||||
scopes?: string[];
|
||||
onHelloOk?: (hello: { features?: { methods?: string[] } }) => void | Promise<void>;
|
||||
onClose?: (code: number, reason: string) => void;
|
||||
@@ -452,6 +455,22 @@ describe("callGateway url resolution", () => {
|
||||
expect(lastClientOptions?.scopes).toEqual([]);
|
||||
});
|
||||
|
||||
it("labels default backend calls with the requested method", async () => {
|
||||
setLocalLoopbackGatewayConfig();
|
||||
|
||||
await callGateway({ method: "sessions.delete" });
|
||||
|
||||
expect(lastClientOptions?.clientDisplayName).toBe("gateway:sessions.delete");
|
||||
});
|
||||
|
||||
it("does not synthesize display names for CLI calls", async () => {
|
||||
setLocalLoopbackGatewayConfig();
|
||||
|
||||
await callGatewayCli({ method: "health" });
|
||||
|
||||
expect(lastClientOptions?.clientDisplayName).toBeUndefined();
|
||||
});
|
||||
|
||||
it("yields one event-loop turn before starting CLI pairing requests", async () => {
|
||||
setLocalLoopbackGatewayConfig();
|
||||
|
||||
|
||||
@@ -108,6 +108,19 @@ const gatewayCallDeps = {
|
||||
...defaultGatewayCallDeps,
|
||||
};
|
||||
|
||||
function resolveGatewayClientDisplayName(opts: CallGatewayBaseOptions): string | undefined {
|
||||
if (opts.clientDisplayName) {
|
||||
return opts.clientDisplayName;
|
||||
}
|
||||
const clientName = opts.clientName ?? GATEWAY_CLIENT_NAMES.CLI;
|
||||
const mode = opts.mode ?? GATEWAY_CLIENT_MODES.CLI;
|
||||
if (mode !== GATEWAY_CLIENT_MODES.BACKEND && clientName !== GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT) {
|
||||
return undefined;
|
||||
}
|
||||
const method = opts.method.trim();
|
||||
return method ? `gateway:${method}` : "gateway:request";
|
||||
}
|
||||
|
||||
function loadGatewayConfig(): OpenClawConfig {
|
||||
const loadConfigFn =
|
||||
typeof gatewayCallDeps.loadConfig === "function"
|
||||
@@ -745,7 +758,7 @@ async function executeGatewayRequestWithScopes<T>(params: {
|
||||
tlsFingerprint,
|
||||
instanceId: opts.instanceId ?? randomUUID(),
|
||||
clientName: opts.clientName ?? GATEWAY_CLIENT_NAMES.CLI,
|
||||
clientDisplayName: opts.clientDisplayName,
|
||||
clientDisplayName: resolveGatewayClientDisplayName(opts),
|
||||
clientVersion: opts.clientVersion ?? VERSION,
|
||||
platform: opts.platform,
|
||||
mode: opts.mode ?? GATEWAY_CLIENT_MODES.CLI,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { Socket } from "node:net";
|
||||
import type { WebSocket, WebSocketServer } from "ws";
|
||||
import { resolveCanvasHostUrl } from "../../infra/canvas-host-url.js";
|
||||
import { removeRemoteNodeInfo } from "../../infra/skills-remote.js";
|
||||
@@ -61,6 +62,45 @@ const sanitizeLogValue = (value: string | undefined): string | undefined => {
|
||||
return truncateUtf16Safe(cleaned, LOG_HEADER_MAX_LEN);
|
||||
};
|
||||
|
||||
function formatSocketEndpoint(
|
||||
address: string | undefined,
|
||||
port: number | undefined,
|
||||
): string | undefined {
|
||||
if (!address) {
|
||||
return undefined;
|
||||
}
|
||||
if (port === undefined) {
|
||||
return address;
|
||||
}
|
||||
return address.includes(":") ? `[${address}]:${port}` : `${address}:${port}`;
|
||||
}
|
||||
|
||||
function resolveSocketAddress(socket: WebSocket): {
|
||||
remoteAddr?: string;
|
||||
remotePort?: number;
|
||||
localAddr?: string;
|
||||
localPort?: number;
|
||||
endpoint?: string;
|
||||
} {
|
||||
const rawSocket = (socket as WebSocket & { _socket?: Socket })._socket;
|
||||
const remoteAddr = rawSocket?.remoteAddress;
|
||||
const remotePort = rawSocket?.remotePort;
|
||||
const localAddr = rawSocket?.localAddress;
|
||||
const localPort = rawSocket?.localPort;
|
||||
const remoteEndpoint = formatSocketEndpoint(remoteAddr, remotePort);
|
||||
const localEndpoint = formatSocketEndpoint(localAddr, localPort);
|
||||
return {
|
||||
remoteAddr,
|
||||
remotePort,
|
||||
localAddr,
|
||||
localPort,
|
||||
endpoint:
|
||||
remoteEndpoint && localEndpoint
|
||||
? `${remoteEndpoint}->${localEndpoint}`
|
||||
: (remoteEndpoint ?? localEndpoint),
|
||||
};
|
||||
}
|
||||
|
||||
export type GatewayWsSharedHandlerParams = {
|
||||
wss: WebSocketServer;
|
||||
clients: Set<GatewayWsClient>;
|
||||
@@ -127,8 +167,7 @@ export function attachGatewayWsConnectionHandler(params: AttachGatewayWsConnecti
|
||||
let closed = false;
|
||||
const openedAt = Date.now();
|
||||
const connId = randomUUID();
|
||||
const remoteAddr = (socket as WebSocket & { _socket?: { remoteAddress?: string } })._socket
|
||||
?.remoteAddress;
|
||||
const { remoteAddr, remotePort, localAddr, localPort, endpoint } = resolveSocketAddress(socket);
|
||||
const preauthBudgetKey = (
|
||||
socket as WebSocket & {
|
||||
__openclawPreauthBudgetClaimed?: boolean;
|
||||
@@ -159,7 +198,7 @@ export function attachGatewayWsConnectionHandler(params: AttachGatewayWsConnecti
|
||||
localAddress: upgradeReq.socket?.localAddress,
|
||||
});
|
||||
|
||||
logWs("in", "open", { connId, remoteAddr });
|
||||
logWs("in", "open", { connId, remoteAddr, remotePort, localAddr, localPort, endpoint });
|
||||
let handshakeState: "pending" | "connected" | "failed" = "pending";
|
||||
let holdsPreauthBudget = true;
|
||||
let closeCause: string | undefined;
|
||||
@@ -254,6 +293,11 @@ export function attachGatewayWsConnectionHandler(params: AttachGatewayWsConnecti
|
||||
origin: logOrigin,
|
||||
userAgent: logUserAgent,
|
||||
forwardedFor: logForwardedFor,
|
||||
remoteAddr,
|
||||
remotePort,
|
||||
localAddr,
|
||||
localPort,
|
||||
endpoint,
|
||||
...closeMeta,
|
||||
};
|
||||
if (!client) {
|
||||
@@ -261,7 +305,7 @@ export function attachGatewayWsConnectionHandler(params: AttachGatewayWsConnecti
|
||||
? logWsControl.debug
|
||||
: logWsControl.warn;
|
||||
logFn(
|
||||
`closed before connect conn=${connId} remote=${remoteAddr ?? "?"} fwd=${logForwardedFor || "n/a"} origin=${logOrigin || "n/a"} host=${logHost || "n/a"} ua=${logUserAgent || "n/a"} code=${code ?? "n/a"} reason=${logReason || "n/a"}`,
|
||||
`closed before connect conn=${connId} peer=${endpoint ?? "n/a"} remote=${remoteAddr ?? "?"} fwd=${logForwardedFor || "n/a"} origin=${logOrigin || "n/a"} host=${logHost || "n/a"} ua=${logUserAgent || "n/a"} code=${code ?? "n/a"} reason=${logReason || "n/a"}`,
|
||||
closeContext,
|
||||
);
|
||||
}
|
||||
@@ -293,6 +337,7 @@ export function attachGatewayWsConnectionHandler(params: AttachGatewayWsConnecti
|
||||
lastFrameType,
|
||||
lastFrameMethod,
|
||||
lastFrameId,
|
||||
endpoint,
|
||||
});
|
||||
close();
|
||||
});
|
||||
@@ -303,8 +348,11 @@ export function attachGatewayWsConnectionHandler(params: AttachGatewayWsConnecti
|
||||
handshakeState = "failed";
|
||||
setCloseCause("handshake-timeout", {
|
||||
handshakeMs: Date.now() - openedAt,
|
||||
endpoint,
|
||||
});
|
||||
logWsControl.warn(`handshake timeout conn=${connId} remote=${remoteAddr ?? "?"}`);
|
||||
logWsControl.warn(
|
||||
`handshake timeout conn=${connId} peer=${endpoint ?? "n/a"} remote=${remoteAddr ?? "?"}`,
|
||||
);
|
||||
close();
|
||||
}
|
||||
}, handshakeTimeoutMs);
|
||||
@@ -314,6 +362,10 @@ export function attachGatewayWsConnectionHandler(params: AttachGatewayWsConnecti
|
||||
upgradeReq,
|
||||
connId,
|
||||
remoteAddr,
|
||||
remotePort,
|
||||
localAddr,
|
||||
localPort,
|
||||
endpoint,
|
||||
forwardedFor,
|
||||
realIp,
|
||||
requestHost,
|
||||
|
||||
@@ -161,6 +161,10 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
upgradeReq: IncomingMessage;
|
||||
connId: string;
|
||||
remoteAddr?: string;
|
||||
remotePort?: number;
|
||||
localAddr?: string;
|
||||
localPort?: number;
|
||||
endpoint?: string;
|
||||
forwardedFor?: string;
|
||||
realIp?: string;
|
||||
requestHost?: string;
|
||||
@@ -197,6 +201,10 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
upgradeReq,
|
||||
connId,
|
||||
remoteAddr,
|
||||
remotePort,
|
||||
localAddr,
|
||||
localPort,
|
||||
endpoint,
|
||||
forwardedFor,
|
||||
realIp,
|
||||
requestHost,
|
||||
@@ -248,6 +256,7 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
trustedProxies,
|
||||
allowRealIpFallback,
|
||||
});
|
||||
const peerLabel = endpoint ?? remoteAddr ?? "n/a";
|
||||
|
||||
// If proxy headers are present but the remote address isn't trusted, don't treat
|
||||
// the connection as local. This prevents auth bypass when running behind a reverse
|
||||
@@ -369,7 +378,7 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
});
|
||||
} else {
|
||||
logWsControl.warn(
|
||||
`invalid handshake conn=${connId} remote=${remoteAddr ?? "?"} fwd=${forwardedFor ?? "n/a"} origin=${requestOrigin ?? "n/a"} host=${requestHost ?? "n/a"} ua=${requestUserAgent ?? "n/a"}`,
|
||||
`invalid handshake conn=${connId} peer=${formatForLog(peerLabel)} remote=${remoteAddr ?? "?"} fwd=${formatForLog(forwardedFor ?? "n/a")} origin=${formatForLog(requestOrigin ?? "n/a")} host=${formatForLog(requestHost ?? "n/a")} ua=${formatForLog(requestUserAgent ?? "n/a")}`,
|
||||
);
|
||||
}
|
||||
const closeReason = truncateCloseReason(handshakeError || "invalid handshake");
|
||||
@@ -389,6 +398,10 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
clientDisplayName: connectParams.client.displayName,
|
||||
mode: connectParams.client.mode,
|
||||
version: connectParams.client.version,
|
||||
platform: connectParams.client.platform,
|
||||
deviceFamily: connectParams.client.deviceFamily,
|
||||
modelIdentifier: connectParams.client.modelIdentifier,
|
||||
instanceId: connectParams.client.instanceId,
|
||||
};
|
||||
const markHandshakeFailure = (cause: string, meta?: Record<string, unknown>) => {
|
||||
setHandshakeState("failed");
|
||||
@@ -529,9 +542,17 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
authProvided,
|
||||
authReason: failedAuth.reason,
|
||||
allowTailscale: resolvedAuth.allowTailscale,
|
||||
peer: peerLabel,
|
||||
remoteAddr,
|
||||
remotePort,
|
||||
localAddr,
|
||||
localPort,
|
||||
role,
|
||||
scopeCount: scopes.length,
|
||||
hasDeviceIdentity: Boolean(device),
|
||||
});
|
||||
logWsControl.warn(
|
||||
`unauthorized conn=${connId} remote=${remoteAddr ?? "?"} client=${clientLabel} ${connectParams.client.mode} v${connectParams.client.version} reason=${failedAuth.reason ?? "unknown"}`,
|
||||
`unauthorized conn=${connId} peer=${formatForLog(peerLabel)} remote=${remoteAddr ?? "?"} client=${formatForLog(clientLabel)} ${connectParams.client.mode} v${formatForLog(connectParams.client.version)} role=${role} scopes=${scopes.length} auth=${authProvided} device=${device ? "yes" : "no"} platform=${formatForLog(connectParams.client.platform)} instance=${formatForLog(connectParams.client.instanceId ?? "n/a")} host=${formatForLog(requestHost ?? "n/a")} origin=${formatForLog(requestOrigin ?? "n/a")} ua=${formatForLog(requestUserAgent ?? "n/a")} reason=${failedAuth.reason ?? "unknown"}`,
|
||||
);
|
||||
const authMessage = formatGatewayAuthFailureMessage({
|
||||
authMode: resolvedAuth.mode,
|
||||
|
||||
Reference in New Issue
Block a user