mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-20 19:51:39 +00:00
fix: diagnose Windows LAN Gateway firewall blocks (#98666)
* Diagnose Windows LAN Gateway firewall blocks * Fix Windows firewall diagnostic lint * fix: gate gateway firewall diagnostics to local targets * fix: keep firewall inspection off critical flows
This commit is contained in:
@@ -59,6 +59,13 @@ const loadInstalledPluginIndexInstallRecords = vi.fn<
|
||||
const readGatewayRestartHandoffSync = vi.fn<
|
||||
(_env?: NodeJS.ProcessEnv) => GatewayRestartHandoff | null
|
||||
>(() => null);
|
||||
const inspectWindowsGatewayFirewall = vi.fn<(opts?: unknown) => Promise<unknown>>(async () => ({
|
||||
applies: false,
|
||||
severity: "info" as const,
|
||||
code: "windows_firewall_not_applicable",
|
||||
message: "Windows LAN firewall diagnostics do not apply.",
|
||||
details: [],
|
||||
}));
|
||||
const auditGatewayServiceConfig = vi.fn(async (_opts?: unknown) => undefined);
|
||||
const serviceIsLoaded = vi.fn(async (_opts?: unknown) => true);
|
||||
const serviceReadRuntime = vi.fn<
|
||||
@@ -224,6 +231,10 @@ vi.mock("../../infra/tls/gateway.js", () => ({
|
||||
loadGatewayTlsRuntime: (cfg: unknown) => loadGatewayTlsRuntime(cfg),
|
||||
}));
|
||||
|
||||
vi.mock("../../infra/windows-gateway-firewall-diagnostics.js", () => ({
|
||||
inspectWindowsGatewayFirewall: (opts: unknown) => inspectWindowsGatewayFirewall(opts),
|
||||
}));
|
||||
|
||||
vi.mock("./probe.js", () => ({
|
||||
probeGatewayStatus: (opts: unknown) => callGatewayStatusProbe(opts),
|
||||
}));
|
||||
@@ -281,6 +292,14 @@ describe("gatherDaemonStatus", () => {
|
||||
loadGatewayTlsRuntime.mockClear();
|
||||
inspectGatewayRestart.mockClear();
|
||||
inspectPortConnections.mockClear();
|
||||
inspectWindowsGatewayFirewall.mockClear();
|
||||
inspectWindowsGatewayFirewall.mockResolvedValue({
|
||||
applies: false,
|
||||
severity: "info",
|
||||
code: "windows_firewall_not_applicable",
|
||||
message: "Windows LAN firewall diagnostics do not apply.",
|
||||
details: [],
|
||||
});
|
||||
readGatewayRestartHandoffSync.mockClear();
|
||||
readConfigFileSnapshotCalls.mockClear();
|
||||
loadConfigCalls.mockClear();
|
||||
@@ -335,6 +354,31 @@ describe("gatherDaemonStatus", () => {
|
||||
expect(status.cli?.entrypoint).toBe(process.argv[1]);
|
||||
}
|
||||
expect(inspectGatewayRestart).not.toHaveBeenCalled();
|
||||
expect(inspectWindowsGatewayFirewall).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("includes Windows firewall diagnostics during deep LAN gateway status", async () => {
|
||||
inspectWindowsGatewayFirewall.mockResolvedValueOnce({
|
||||
applies: true,
|
||||
severity: "warning",
|
||||
code: "windows_firewall_local_rules_ignored",
|
||||
message: "Windows Firewall may ignore local Gateway allow rules for this network profile.",
|
||||
details: ["Windows reports LocalFirewallRules as N/A (GPO-store only)."],
|
||||
});
|
||||
|
||||
const status = await gatherDaemonStatus({
|
||||
rpc: {},
|
||||
probe: false,
|
||||
deep: true,
|
||||
});
|
||||
|
||||
expect(inspectWindowsGatewayFirewall).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ bind: "lan", mode: "quick", port: 19001 }),
|
||||
);
|
||||
expect(status.gateway?.windowsFirewall).toMatchObject({
|
||||
severity: "warning",
|
||||
code: "windows_firewall_local_rules_ignored",
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to probe version when server metadata is unavailable", async () => {
|
||||
@@ -653,6 +697,7 @@ describe("gatherDaemonStatus", () => {
|
||||
daemonLoadedConfig = {
|
||||
gateway: {
|
||||
mode: "remote",
|
||||
bind: "lan",
|
||||
remote: { url: "wss://gateway.example" },
|
||||
},
|
||||
};
|
||||
@@ -664,6 +709,7 @@ describe("gatherDaemonStatus", () => {
|
||||
});
|
||||
|
||||
expect(inspectPortConnections).not.toHaveBeenCalled();
|
||||
expect(inspectWindowsGatewayFirewall).not.toHaveBeenCalled();
|
||||
expect(loadInstalledPluginIndexInstallRecords).not.toHaveBeenCalled();
|
||||
expect(status.connections).toBeUndefined();
|
||||
expect(status.pluginVersionDrift).toBeUndefined();
|
||||
|
||||
@@ -46,6 +46,10 @@ import {
|
||||
readGatewayRestartHandoffSync,
|
||||
type GatewayRestartHandoff,
|
||||
} from "../../infra/restart-handoff.js";
|
||||
import {
|
||||
inspectWindowsGatewayFirewall,
|
||||
type WindowsGatewayFirewallDiagnostic,
|
||||
} from "../../infra/windows-gateway-firewall-diagnostics.js";
|
||||
import { resolveConfiguredLogFilePath } from "../../logging/log-file-path.js";
|
||||
import { loadInstalledPluginIndexInstallRecords } from "../../plugins/installed-plugin-index-record-reader.js";
|
||||
import {
|
||||
@@ -77,6 +81,7 @@ type GatewayStatusSummary = {
|
||||
controlUiLinks?: { httpUrl: string; wsUrl: string };
|
||||
probeNote?: string;
|
||||
version?: string | null;
|
||||
windowsFirewall?: WindowsGatewayFirewallDiagnostic;
|
||||
};
|
||||
|
||||
type PortStatusSummary = {
|
||||
@@ -599,6 +604,16 @@ export async function gatherDaemonStatus(
|
||||
commandProgramArguments: command?.programArguments,
|
||||
rpcUrlOverride: opts.rpc.url,
|
||||
});
|
||||
const shouldInspectLocalGateway = daemonCfg.gateway?.mode !== "remote" && !probeUrlOverride;
|
||||
const windowsFirewall =
|
||||
opts.deep === true && shouldInspectLocalGateway
|
||||
? await inspectWindowsGatewayFirewall({
|
||||
bind: gateway.bindMode,
|
||||
mode: "quick",
|
||||
port: daemonPort,
|
||||
platform: process.platform,
|
||||
})
|
||||
: undefined;
|
||||
const { portStatus, portCliStatus } = await inspectDaemonPortStatuses({
|
||||
daemonPort,
|
||||
cliPort,
|
||||
@@ -731,7 +746,7 @@ export async function gatherDaemonStatus(
|
||||
// diagnostics instead.
|
||||
// Best-effort: unreadable install records omit this advisory report.
|
||||
let pluginVersionDrift: PluginVersionDriftReport | undefined;
|
||||
if (daemonCfg.gateway?.mode !== "remote" && !probeUrlOverride) {
|
||||
if (shouldInspectLocalGateway) {
|
||||
try {
|
||||
const installRecords = await loadInstalledPluginIndexInstallRecords({
|
||||
env: mergedDaemonEnv as NodeJS.ProcessEnv,
|
||||
@@ -767,6 +782,7 @@ export async function gatherDaemonStatus(
|
||||
},
|
||||
gateway: {
|
||||
...gateway,
|
||||
...(windowsFirewall?.applies ? { windowsFirewall } : {}),
|
||||
...(opts.probe
|
||||
? {
|
||||
version: gatewayVersion,
|
||||
|
||||
@@ -183,6 +183,40 @@ describe("printDaemonStatus", () => {
|
||||
expectMockLineContains(runtime.log, "protocol mismatch after rollback");
|
||||
});
|
||||
|
||||
it("prints Windows firewall diagnostics in gateway status output", () => {
|
||||
printDaemonStatus(
|
||||
{
|
||||
service: {
|
||||
label: "LaunchAgent",
|
||||
loaded: true,
|
||||
loadedText: "loaded",
|
||||
notLoadedText: "not loaded",
|
||||
runtime: { status: "running", pid: 8000 },
|
||||
},
|
||||
gateway: {
|
||||
bindMode: "lan",
|
||||
bindHost: "0.0.0.0",
|
||||
port: 18789,
|
||||
portSource: "env/config",
|
||||
probeUrl: "ws://127.0.0.1:18789",
|
||||
windowsFirewall: {
|
||||
applies: true,
|
||||
severity: "warning",
|
||||
code: "windows_firewall_local_rules_ignored",
|
||||
message:
|
||||
"Windows Firewall may ignore local Gateway allow rules for this network profile.",
|
||||
details: ["Windows reports LocalFirewallRules as N/A (GPO-store only)."],
|
||||
},
|
||||
},
|
||||
extraServices: [],
|
||||
},
|
||||
{ json: false, deep: true },
|
||||
);
|
||||
|
||||
expectMockLineContains(runtime.error, "Windows firewall: Windows Firewall may ignore");
|
||||
expectMockLineContains(runtime.error, "GPO-store only");
|
||||
});
|
||||
|
||||
it("uses service command env for WSL systemd unavailable hints", () => {
|
||||
const originalPlatform = process.platform;
|
||||
Object.defineProperty(process, "platform", { value: "linux" });
|
||||
|
||||
@@ -223,6 +223,12 @@ export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean; d
|
||||
if (status.gateway.probeNote) {
|
||||
defaultRuntime.log(`${label("Probe note:")} ${infoText(status.gateway.probeNote)}`);
|
||||
}
|
||||
if (status.gateway.windowsFirewall?.severity === "warning") {
|
||||
defaultRuntime.error(warnText(`Windows firewall: ${status.gateway.windowsFirewall.message}`));
|
||||
for (const detail of status.gateway.windowsFirewall.details) {
|
||||
defaultRuntime.error(warnText(` ${detail}`));
|
||||
}
|
||||
}
|
||||
spacer();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user