Gateway: add SecretRef support for gateway.auth.token with auth-mode guardrails (#35094)

This commit is contained in:
Josh Avant
2026-03-05 12:53:56 -06:00
committed by GitHub
parent bc66a8fa81
commit 72cf9253fc
112 changed files with 5750 additions and 465 deletions

View File

@@ -0,0 +1,226 @@
import { describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import { withEnvAsync } from "../test-utils/env.js";
import {
resolveGatewayAuthTokenForService,
shouldRequireGatewayTokenForInstall,
} from "./doctor-gateway-auth-token.js";
describe("resolveGatewayAuthTokenForService", () => {
it("returns plaintext gateway.auth.token when configured", async () => {
const resolved = await resolveGatewayAuthTokenForService(
{
gateway: {
auth: {
token: "config-token",
},
},
} as OpenClawConfig,
{} as NodeJS.ProcessEnv,
);
expect(resolved).toEqual({ token: "config-token" });
});
it("resolves SecretRef-backed gateway.auth.token", async () => {
const resolved = await resolveGatewayAuthTokenForService(
{
gateway: {
auth: {
token: { source: "env", provider: "default", id: "CUSTOM_GATEWAY_TOKEN" },
},
},
secrets: {
providers: {
default: { source: "env" },
},
},
} as OpenClawConfig,
{
CUSTOM_GATEWAY_TOKEN: "resolved-token",
} as NodeJS.ProcessEnv,
);
expect(resolved).toEqual({ token: "resolved-token" });
});
it("resolves env-template gateway.auth.token via SecretRef resolution", async () => {
const resolved = await resolveGatewayAuthTokenForService(
{
gateway: {
auth: {
token: "${CUSTOM_GATEWAY_TOKEN}",
},
},
secrets: {
providers: {
default: { source: "env" },
},
},
} as OpenClawConfig,
{
CUSTOM_GATEWAY_TOKEN: "resolved-token",
} as NodeJS.ProcessEnv,
);
expect(resolved).toEqual({ token: "resolved-token" });
});
it("falls back to OPENCLAW_GATEWAY_TOKEN when SecretRef is unresolved", async () => {
const resolved = await resolveGatewayAuthTokenForService(
{
gateway: {
auth: {
token: { source: "env", provider: "default", id: "MISSING_GATEWAY_TOKEN" },
},
},
secrets: {
providers: {
default: { source: "env" },
},
},
} as OpenClawConfig,
{
OPENCLAW_GATEWAY_TOKEN: "env-fallback-token",
} as NodeJS.ProcessEnv,
);
expect(resolved).toEqual({ token: "env-fallback-token" });
});
it("falls back to OPENCLAW_GATEWAY_TOKEN when SecretRef resolves to empty", async () => {
const resolved = await resolveGatewayAuthTokenForService(
{
gateway: {
auth: {
token: { source: "env", provider: "default", id: "CUSTOM_GATEWAY_TOKEN" },
},
},
secrets: {
providers: {
default: { source: "env" },
},
},
} as OpenClawConfig,
{
CUSTOM_GATEWAY_TOKEN: " ",
OPENCLAW_GATEWAY_TOKEN: "env-fallback-token",
} as NodeJS.ProcessEnv,
);
expect(resolved).toEqual({ token: "env-fallback-token" });
});
it("returns unavailableReason when SecretRef is unresolved without env fallback", async () => {
const resolved = await resolveGatewayAuthTokenForService(
{
gateway: {
auth: {
token: { source: "env", provider: "default", id: "MISSING_GATEWAY_TOKEN" },
},
},
secrets: {
providers: {
default: { source: "env" },
},
},
} as OpenClawConfig,
{} as NodeJS.ProcessEnv,
);
expect(resolved.token).toBeUndefined();
expect(resolved.unavailableReason).toContain("gateway.auth.token SecretRef is configured");
});
});
describe("shouldRequireGatewayTokenForInstall", () => {
it("requires token when auth mode is token", () => {
const required = shouldRequireGatewayTokenForInstall(
{
gateway: {
auth: {
mode: "token",
},
},
} as OpenClawConfig,
{} as NodeJS.ProcessEnv,
);
expect(required).toBe(true);
});
it("does not require token when auth mode is password", () => {
const required = shouldRequireGatewayTokenForInstall(
{
gateway: {
auth: {
mode: "password",
},
},
} as OpenClawConfig,
{} as NodeJS.ProcessEnv,
);
expect(required).toBe(false);
});
it("requires token in inferred mode when password env exists only in shell", async () => {
await withEnvAsync({ OPENCLAW_GATEWAY_PASSWORD: "password-from-env" }, async () => {
const required = shouldRequireGatewayTokenForInstall(
{
gateway: {
auth: {},
},
} as OpenClawConfig,
process.env,
);
expect(required).toBe(true);
});
});
it("does not require token in inferred mode when password is configured", () => {
const required = shouldRequireGatewayTokenForInstall(
{
gateway: {
auth: {
password: { source: "env", provider: "default", id: "CUSTOM_GATEWAY_PASSWORD" },
},
},
secrets: {
providers: {
default: { source: "env" },
},
},
} as OpenClawConfig,
{} as NodeJS.ProcessEnv,
);
expect(required).toBe(false);
});
it("does not require token in inferred mode when password env is configured in config", () => {
const required = shouldRequireGatewayTokenForInstall(
{
gateway: {
auth: {},
},
env: {
vars: {
OPENCLAW_GATEWAY_PASSWORD: "configured-password",
},
},
} as OpenClawConfig,
{} as NodeJS.ProcessEnv,
);
expect(required).toBe(false);
});
it("requires token in inferred mode when no password candidate exists", () => {
const required = shouldRequireGatewayTokenForInstall(
{
gateway: {
auth: {},
},
} as OpenClawConfig,
{} as NodeJS.ProcessEnv,
);
expect(required).toBe(true);
});
});