mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
fix(gateway): scope tailscale tokenless auth to websocket
This commit is contained in:
@@ -2060,7 +2060,7 @@ See [Plugins](/tools/plugin).
|
|||||||
- **Auth**: required by default. Non-loopback binds require a shared token/password. Onboarding wizard generates a token by default.
|
- **Auth**: required by default. Non-loopback binds require a shared token/password. Onboarding wizard generates a token by default.
|
||||||
- `auth.mode: "none"`: explicit no-auth mode. Use only for trusted local loopback setups; this is intentionally not offered by onboarding prompts.
|
- `auth.mode: "none"`: explicit no-auth mode. Use only for trusted local loopback setups; this is intentionally not offered by onboarding prompts.
|
||||||
- `auth.mode: "trusted-proxy"`: delegate auth to an identity-aware reverse proxy and trust identity headers from `gateway.trustedProxies` (see [Trusted Proxy Auth](/gateway/trusted-proxy-auth)).
|
- `auth.mode: "trusted-proxy"`: delegate auth to an identity-aware reverse proxy and trust identity headers from `gateway.trustedProxies` (see [Trusted Proxy Auth](/gateway/trusted-proxy-auth)).
|
||||||
- `auth.allowTailscale`: when `true`, Tailscale Serve identity headers satisfy auth (verified via `tailscale whois`). This tokenless flow assumes the gateway host is trusted. Defaults to `true` when `tailscale.mode = "serve"`.
|
- `auth.allowTailscale`: when `true`, Tailscale Serve identity headers can satisfy Control UI/WebSocket auth (verified via `tailscale whois`); HTTP API endpoints still require token/password auth. This tokenless flow assumes the gateway host is trusted. Defaults to `true` when `tailscale.mode = "serve"`.
|
||||||
- `auth.rateLimit`: optional failed-auth limiter. Applies per client IP and per auth scope (shared-secret and device-token are tracked independently). Blocked attempts return `429` + `Retry-After`.
|
- `auth.rateLimit`: optional failed-auth limiter. Applies per client IP and per auth scope (shared-secret and device-token are tracked independently). Blocked attempts return `429` + `Retry-After`.
|
||||||
- `auth.rateLimit.exemptLoopback` defaults to `true`; set `false` when you intentionally want localhost traffic rate-limited too (for test setups or strict proxy deployments).
|
- `auth.rateLimit.exemptLoopback` defaults to `true`; set `false` when you intentionally want localhost traffic rate-limited too (for test setups or strict proxy deployments).
|
||||||
- `tailscale.mode`: `serve` (tailnet only, loopback bind) or `funnel` (public, requires auth).
|
- `tailscale.mode`: `serve` (tailnet only, loopback bind) or `funnel` (public, requires auth).
|
||||||
|
|||||||
@@ -122,9 +122,10 @@ Short version: **keep the Gateway loopback-only** unless you’re sure you need
|
|||||||
- **Non-loopback binds** (`lan`/`tailnet`/`custom`, or `auto` when loopback is unavailable) must use auth tokens/passwords.
|
- **Non-loopback binds** (`lan`/`tailnet`/`custom`, or `auto` when loopback is unavailable) must use auth tokens/passwords.
|
||||||
- `gateway.remote.token` is **only** for remote CLI calls — it does **not** enable local auth.
|
- `gateway.remote.token` is **only** for remote CLI calls — it does **not** enable local auth.
|
||||||
- `gateway.remote.tlsFingerprint` pins the remote TLS cert when using `wss://`.
|
- `gateway.remote.tlsFingerprint` pins the remote TLS cert when using `wss://`.
|
||||||
- **Tailscale Serve** can authenticate via identity headers when `gateway.auth.allowTailscale: true`.
|
- **Tailscale Serve** can authenticate Control UI/WebSocket traffic via identity
|
||||||
This tokenless flow assumes the gateway host is trusted. Set it to `false` if you
|
headers when `gateway.auth.allowTailscale: true`; HTTP API endpoints still
|
||||||
want tokens/passwords instead.
|
require token/password auth. This tokenless flow assumes the gateway host is
|
||||||
|
trusted. Set it to `false` if you want tokens/passwords everywhere.
|
||||||
- Treat browser control like operator access: tailnet-only + deliberate node pairing.
|
- Treat browser control like operator access: tailnet-only + deliberate node pairing.
|
||||||
|
|
||||||
Deep dive: [Security](/gateway/security).
|
Deep dive: [Security](/gateway/security).
|
||||||
|
|||||||
@@ -532,12 +532,14 @@ Rotation checklist (token/password):
|
|||||||
### 0.6) Tailscale Serve identity headers
|
### 0.6) Tailscale Serve identity headers
|
||||||
|
|
||||||
When `gateway.auth.allowTailscale` is `true` (default for Serve), OpenClaw
|
When `gateway.auth.allowTailscale` is `true` (default for Serve), OpenClaw
|
||||||
accepts Tailscale Serve identity headers (`tailscale-user-login`) as
|
accepts Tailscale Serve identity headers (`tailscale-user-login`) for Control
|
||||||
authentication. OpenClaw verifies the identity by resolving the
|
UI/WebSocket authentication. OpenClaw verifies the identity by resolving the
|
||||||
`x-forwarded-for` address through the local Tailscale daemon (`tailscale whois`)
|
`x-forwarded-for` address through the local Tailscale daemon (`tailscale whois`)
|
||||||
and matching it to the header. This only triggers for requests that hit loopback
|
and matching it to the header. This only triggers for requests that hit loopback
|
||||||
and include `x-forwarded-for`, `x-forwarded-proto`, and `x-forwarded-host` as
|
and include `x-forwarded-for`, `x-forwarded-proto`, and `x-forwarded-host` as
|
||||||
injected by Tailscale.
|
injected by Tailscale.
|
||||||
|
HTTP API endpoints (for example `/v1/*`, `/tools/invoke`, and `/api/channels/*`)
|
||||||
|
still require token/password auth.
|
||||||
|
|
||||||
**Trust assumption:** tokenless Serve auth assumes the gateway host is trusted.
|
**Trust assumption:** tokenless Serve auth assumes the gateway host is trusted.
|
||||||
Do not treat this as protection against hostile same-host processes. If untrusted
|
Do not treat this as protection against hostile same-host processes. If untrusted
|
||||||
|
|||||||
@@ -26,13 +26,15 @@ Set `gateway.auth.mode` to control the handshake:
|
|||||||
- `password` (shared secret via `OPENCLAW_GATEWAY_PASSWORD` or config)
|
- `password` (shared secret via `OPENCLAW_GATEWAY_PASSWORD` or config)
|
||||||
|
|
||||||
When `tailscale.mode = "serve"` and `gateway.auth.allowTailscale` is `true`,
|
When `tailscale.mode = "serve"` and `gateway.auth.allowTailscale` is `true`,
|
||||||
valid Serve proxy requests can authenticate via Tailscale identity headers
|
Control UI/WebSocket auth can use Tailscale identity headers
|
||||||
(`tailscale-user-login`) without supplying a token/password. OpenClaw verifies
|
(`tailscale-user-login`) without supplying a token/password. OpenClaw verifies
|
||||||
the identity by resolving the `x-forwarded-for` address via the local Tailscale
|
the identity by resolving the `x-forwarded-for` address via the local Tailscale
|
||||||
daemon (`tailscale whois`) and matching it to the header before accepting it.
|
daemon (`tailscale whois`) and matching it to the header before accepting it.
|
||||||
OpenClaw only treats a request as Serve when it arrives from loopback with
|
OpenClaw only treats a request as Serve when it arrives from loopback with
|
||||||
Tailscale’s `x-forwarded-for`, `x-forwarded-proto`, and `x-forwarded-host`
|
Tailscale’s `x-forwarded-for`, `x-forwarded-proto`, and `x-forwarded-host`
|
||||||
headers.
|
headers.
|
||||||
|
HTTP API endpoints (for example `/v1/*`, `/tools/invoke`, and `/api/channels/*`)
|
||||||
|
still require token/password auth.
|
||||||
This tokenless flow assumes the gateway host is trusted. If untrusted local code
|
This tokenless flow assumes the gateway host is trusted. If untrusted local code
|
||||||
may run on the same host, disable `gateway.auth.allowTailscale` and require
|
may run on the same host, disable `gateway.auth.allowTailscale` and require
|
||||||
token/password auth instead.
|
token/password auth instead.
|
||||||
|
|||||||
@@ -348,7 +348,7 @@ The wizard opens your browser with a clean (non-tokenized) dashboard URL right a
|
|||||||
|
|
||||||
**Not on localhost:**
|
**Not on localhost:**
|
||||||
|
|
||||||
- **Tailscale Serve** (recommended): keep bind loopback, run `openclaw gateway --tailscale serve`, open `https://<magicdns>/`. If `gateway.auth.allowTailscale` is `true`, identity headers satisfy auth (no token, assumes trusted gateway host).
|
- **Tailscale Serve** (recommended): keep bind loopback, run `openclaw gateway --tailscale serve`, open `https://<magicdns>/`. If `gateway.auth.allowTailscale` is `true`, identity headers satisfy Control UI/WebSocket auth (no token, assumes trusted gateway host); HTTP APIs still require token/password.
|
||||||
- **Tailnet bind**: run `openclaw gateway --bind tailnet --token "<token>"`, open `http://<tailscale-ip>:18789/`, paste token in dashboard settings.
|
- **Tailnet bind**: run `openclaw gateway --bind tailnet --token "<token>"`, open `http://<tailscale-ip>:18789/`, paste token in dashboard settings.
|
||||||
- **SSH tunnel**: `ssh -N -L 18789:127.0.0.1:18789 user@host` then open `http://127.0.0.1:18789/` and paste the token in Control UI settings.
|
- **SSH tunnel**: `ssh -N -L 18789:127.0.0.1:18789 user@host` then open `http://127.0.0.1:18789/` and paste the token in Control UI settings.
|
||||||
|
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ Open: `https://<magicdns>/`
|
|||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- Serve keeps the Gateway loopback-only and authenticates via Tailscale identity headers (tokenless auth assumes trusted gateway host).
|
- Serve keeps the Gateway loopback-only and authenticates Control UI/WebSocket traffic via Tailscale identity headers (tokenless auth assumes trusted gateway host; HTTP APIs still require token/password).
|
||||||
- To require token/password instead, set `gateway.auth.allowTailscale: false` or use `gateway.auth.mode: "password"`.
|
- To require token/password instead, set `gateway.auth.allowTailscale: false` or use `gateway.auth.mode: "password"`.
|
||||||
|
|
||||||
**Option C: Tailnet bind (no Serve)**
|
**Option C: Tailnet bind (no Serve)**
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ Open:
|
|||||||
|
|
||||||
- `https://<magicdns>/` (or your configured `gateway.controlUi.basePath`)
|
- `https://<magicdns>/` (or your configured `gateway.controlUi.basePath`)
|
||||||
|
|
||||||
By default, Serve requests can authenticate via Tailscale identity headers
|
By default, Control UI/WebSocket Serve requests can authenticate via Tailscale identity headers
|
||||||
(`tailscale-user-login`) when `gateway.auth.allowTailscale` is `true`. OpenClaw
|
(`tailscale-user-login`) when `gateway.auth.allowTailscale` is `true`. OpenClaw
|
||||||
verifies the identity by resolving the `x-forwarded-for` address with
|
verifies the identity by resolving the `x-forwarded-for` address with
|
||||||
`tailscale whois` and matching it to the header, and only accepts these when the
|
`tailscale whois` and matching it to the header, and only accepts these when the
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ Prefer localhost, Tailscale Serve, or an SSH tunnel.
|
|||||||
|
|
||||||
- **Localhost**: open `http://127.0.0.1:18789/`.
|
- **Localhost**: open `http://127.0.0.1:18789/`.
|
||||||
- **Token source**: `gateway.auth.token` (or `OPENCLAW_GATEWAY_TOKEN`); the UI stores a copy in localStorage after you connect.
|
- **Token source**: `gateway.auth.token` (or `OPENCLAW_GATEWAY_TOKEN`); the UI stores a copy in localStorage after you connect.
|
||||||
- **Not localhost**: use Tailscale Serve (tokenless if `gateway.auth.allowTailscale: true`, assumes trusted gateway host), tailnet bind with a token, or an SSH tunnel. See [Web surfaces](/web).
|
- **Not localhost**: use Tailscale Serve (tokenless for Control UI/WebSocket if `gateway.auth.allowTailscale: true`, assumes trusted gateway host; HTTP APIs still need token/password), tailnet bind with a token, or an SSH tunnel. See [Web surfaces](/web).
|
||||||
|
|
||||||
## If you see “unauthorized” / 1008
|
## If you see “unauthorized” / 1008
|
||||||
|
|
||||||
|
|||||||
@@ -101,8 +101,9 @@ Open:
|
|||||||
- The UI sends `connect.params.auth.token` or `connect.params.auth.password`.
|
- The UI sends `connect.params.auth.token` or `connect.params.auth.password`.
|
||||||
- The Control UI sends anti-clickjacking headers and only accepts same-origin browser
|
- The Control UI sends anti-clickjacking headers and only accepts same-origin browser
|
||||||
websocket connections unless `gateway.controlUi.allowedOrigins` is set.
|
websocket connections unless `gateway.controlUi.allowedOrigins` is set.
|
||||||
- With Serve, Tailscale identity headers can satisfy auth when
|
- With Serve, Tailscale identity headers can satisfy Control UI/WebSocket auth
|
||||||
`gateway.auth.allowTailscale` is `true` (no token/password required). Set
|
when `gateway.auth.allowTailscale` is `true` (no token/password required).
|
||||||
|
HTTP API endpoints still require token/password. Set
|
||||||
`gateway.auth.allowTailscale: false` to require explicit credentials. See
|
`gateway.auth.allowTailscale: false` to require explicit credentials. See
|
||||||
[Tailscale](/gateway/tailscale) and [Security](/gateway/security). This
|
[Tailscale](/gateway/tailscale) and [Security](/gateway/security). This
|
||||||
tokenless flow assumes the gateway host is trusted.
|
tokenless flow assumes the gateway host is trusted.
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ describe("gateway auth", () => {
|
|||||||
expect(res.method).toBe("token");
|
expect(res.method).toBe("token");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows tailscale identity to satisfy token mode auth", async () => {
|
it("does not allow tailscale identity to satisfy token mode auth by default", async () => {
|
||||||
const res = await authorizeGatewayConnect({
|
const res = await authorizeGatewayConnect({
|
||||||
auth: { mode: "token", token: "secret", allowTailscale: true },
|
auth: { mode: "token", token: "secret", allowTailscale: true },
|
||||||
connectAuth: null,
|
connectAuth: null,
|
||||||
@@ -206,6 +206,29 @@ describe("gateway auth", () => {
|
|||||||
} as never,
|
} as never,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
expect(res.ok).toBe(false);
|
||||||
|
expect(res.reason).toBe("token_missing");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows tailscale identity when header auth is explicitly enabled", async () => {
|
||||||
|
const res = await authorizeGatewayConnect({
|
||||||
|
auth: { mode: "token", token: "secret", allowTailscale: true },
|
||||||
|
connectAuth: null,
|
||||||
|
tailscaleWhois: async () => ({ login: "peter", name: "Peter" }),
|
||||||
|
allowTailscaleHeaderAuth: true,
|
||||||
|
req: {
|
||||||
|
socket: { remoteAddress: "127.0.0.1" },
|
||||||
|
headers: {
|
||||||
|
host: "gateway.local",
|
||||||
|
"x-forwarded-for": "100.64.0.1",
|
||||||
|
"x-forwarded-proto": "https",
|
||||||
|
"x-forwarded-host": "ai-hub.bone-egret.ts.net",
|
||||||
|
"tailscale-user-login": "peter",
|
||||||
|
"tailscale-user-name": "Peter",
|
||||||
|
},
|
||||||
|
} as never,
|
||||||
|
});
|
||||||
|
|
||||||
expect(res.ok).toBe(true);
|
expect(res.ok).toBe(true);
|
||||||
expect(res.method).toBe("tailscale");
|
expect(res.method).toBe("tailscale");
|
||||||
expect(res.user).toBe("peter");
|
expect(res.user).toBe("peter");
|
||||||
|
|||||||
@@ -325,6 +325,11 @@ export async function authorizeGatewayConnect(params: {
|
|||||||
req?: IncomingMessage;
|
req?: IncomingMessage;
|
||||||
trustedProxies?: string[];
|
trustedProxies?: string[];
|
||||||
tailscaleWhois?: TailscaleWhoisLookup;
|
tailscaleWhois?: TailscaleWhoisLookup;
|
||||||
|
/**
|
||||||
|
* Opt-in for accepting Tailscale Serve identity headers as primary auth.
|
||||||
|
* Default is disabled for HTTP surfaces; WS connect enables this explicitly.
|
||||||
|
*/
|
||||||
|
allowTailscaleHeaderAuth?: boolean;
|
||||||
/** Optional rate limiter instance; when provided, failed attempts are tracked per IP. */
|
/** Optional rate limiter instance; when provided, failed attempts are tracked per IP. */
|
||||||
rateLimiter?: AuthRateLimiter;
|
rateLimiter?: AuthRateLimiter;
|
||||||
/** Client IP used for rate-limit tracking. Falls back to proxy-aware request IP resolution. */
|
/** Client IP used for rate-limit tracking. Falls back to proxy-aware request IP resolution. */
|
||||||
@@ -334,6 +339,7 @@ export async function authorizeGatewayConnect(params: {
|
|||||||
}): Promise<GatewayAuthResult> {
|
}): Promise<GatewayAuthResult> {
|
||||||
const { auth, connectAuth, req, trustedProxies } = params;
|
const { auth, connectAuth, req, trustedProxies } = params;
|
||||||
const tailscaleWhois = params.tailscaleWhois ?? readTailscaleWhoisIdentity;
|
const tailscaleWhois = params.tailscaleWhois ?? readTailscaleWhoisIdentity;
|
||||||
|
const allowTailscaleHeaderAuth = params.allowTailscaleHeaderAuth === true;
|
||||||
const localDirect = isLocalDirectRequest(req, trustedProxies);
|
const localDirect = isLocalDirectRequest(req, trustedProxies);
|
||||||
|
|
||||||
if (auth.mode === "trusted-proxy") {
|
if (auth.mode === "trusted-proxy") {
|
||||||
@@ -376,7 +382,7 @@ export async function authorizeGatewayConnect(params: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auth.allowTailscale && !localDirect) {
|
if (allowTailscaleHeaderAuth && auth.allowTailscale && !localDirect) {
|
||||||
const tailscaleCheck = await resolveVerifiedTailscaleUser({
|
const tailscaleCheck = await resolveVerifiedTailscaleUser({
|
||||||
req,
|
req,
|
||||||
tailscaleWhois,
|
tailscaleWhois,
|
||||||
|
|||||||
79
src/gateway/http-auth-helpers.test.ts
Normal file
79
src/gateway/http-auth-helpers.test.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||||
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import type { ResolvedGatewayAuth } from "./auth.js";
|
||||||
|
import { authorizeGatewayBearerRequestOrReply } from "./http-auth-helpers.js";
|
||||||
|
|
||||||
|
vi.mock("./auth.js", () => ({
|
||||||
|
authorizeGatewayConnect: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("./http-common.js", () => ({
|
||||||
|
sendGatewayAuthFailure: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("./http-utils.js", () => ({
|
||||||
|
getBearerToken: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { authorizeGatewayConnect } = await import("./auth.js");
|
||||||
|
const { sendGatewayAuthFailure } = await import("./http-common.js");
|
||||||
|
const { getBearerToken } = await import("./http-utils.js");
|
||||||
|
|
||||||
|
describe("authorizeGatewayBearerRequestOrReply", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disables tailscale header auth for HTTP bearer checks", async () => {
|
||||||
|
vi.mocked(getBearerToken).mockReturnValue(null);
|
||||||
|
vi.mocked(authorizeGatewayConnect).mockResolvedValue({
|
||||||
|
ok: false,
|
||||||
|
reason: "token_missing",
|
||||||
|
});
|
||||||
|
|
||||||
|
const ok = await authorizeGatewayBearerRequestOrReply({
|
||||||
|
req: {} as IncomingMessage,
|
||||||
|
res: {} as ServerResponse,
|
||||||
|
auth: {
|
||||||
|
mode: "token",
|
||||||
|
token: "secret",
|
||||||
|
password: undefined,
|
||||||
|
allowTailscale: true,
|
||||||
|
} satisfies ResolvedGatewayAuth,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(ok).toBe(false);
|
||||||
|
expect(vi.mocked(authorizeGatewayConnect)).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
allowTailscaleHeaderAuth: false,
|
||||||
|
connectAuth: null,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(vi.mocked(sendGatewayAuthFailure)).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("forwards bearer token and returns true on successful auth", async () => {
|
||||||
|
vi.mocked(getBearerToken).mockReturnValue("abc");
|
||||||
|
vi.mocked(authorizeGatewayConnect).mockResolvedValue({ ok: true, method: "token" });
|
||||||
|
|
||||||
|
const ok = await authorizeGatewayBearerRequestOrReply({
|
||||||
|
req: {} as IncomingMessage,
|
||||||
|
res: {} as ServerResponse,
|
||||||
|
auth: {
|
||||||
|
mode: "token",
|
||||||
|
token: "secret",
|
||||||
|
password: undefined,
|
||||||
|
allowTailscale: true,
|
||||||
|
} satisfies ResolvedGatewayAuth,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(ok).toBe(true);
|
||||||
|
expect(vi.mocked(authorizeGatewayConnect)).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
allowTailscaleHeaderAuth: false,
|
||||||
|
connectAuth: { token: "abc", password: "abc" },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(vi.mocked(sendGatewayAuthFailure)).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -17,6 +17,7 @@ export async function authorizeGatewayBearerRequestOrReply(params: {
|
|||||||
connectAuth: token ? { token, password: token } : null,
|
connectAuth: token ? { token, password: token } : null,
|
||||||
req: params.req,
|
req: params.req,
|
||||||
trustedProxies: params.trustedProxies,
|
trustedProxies: params.trustedProxies,
|
||||||
|
allowTailscaleHeaderAuth: false,
|
||||||
rateLimiter: params.rateLimiter,
|
rateLimiter: params.rateLimiter,
|
||||||
});
|
});
|
||||||
if (!authResult.ok) {
|
if (!authResult.ok) {
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ async function authorizeCanvasRequest(params: {
|
|||||||
connectAuth: { token, password: token },
|
connectAuth: { token, password: token },
|
||||||
req,
|
req,
|
||||||
trustedProxies,
|
trustedProxies,
|
||||||
|
allowTailscaleHeaderAuth: false,
|
||||||
rateLimiter,
|
rateLimiter,
|
||||||
});
|
});
|
||||||
if (authResult.ok) {
|
if (authResult.ok) {
|
||||||
@@ -532,6 +533,7 @@ export function createGatewayHttpServer(opts: {
|
|||||||
connectAuth: token ? { token, password: token } : null,
|
connectAuth: token ? { token, password: token } : null,
|
||||||
req,
|
req,
|
||||||
trustedProxies,
|
trustedProxies,
|
||||||
|
allowTailscaleHeaderAuth: false,
|
||||||
rateLimiter,
|
rateLimiter,
|
||||||
});
|
});
|
||||||
if (!authResult.ok) {
|
if (!authResult.ok) {
|
||||||
|
|||||||
@@ -351,6 +351,7 @@ export function attachGatewayWsMessageHandler(params: {
|
|||||||
connectAuth: connectParams.auth,
|
connectAuth: connectParams.auth,
|
||||||
req: upgradeReq,
|
req: upgradeReq,
|
||||||
trustedProxies,
|
trustedProxies,
|
||||||
|
allowTailscaleHeaderAuth: true,
|
||||||
rateLimiter: hasDeviceTokenCandidate ? undefined : rateLimiter,
|
rateLimiter: hasDeviceTokenCandidate ? undefined : rateLimiter,
|
||||||
clientIp,
|
clientIp,
|
||||||
rateLimitScope: AUTH_RATE_LIMIT_SCOPE_SHARED_SECRET,
|
rateLimitScope: AUTH_RATE_LIMIT_SCOPE_SHARED_SECRET,
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ export async function handleToolsInvokeHttpRequest(
|
|||||||
connectAuth: token ? { token, password: token } : null,
|
connectAuth: token ? { token, password: token } : null,
|
||||||
req,
|
req,
|
||||||
trustedProxies: opts.trustedProxies ?? cfg.gateway?.trustedProxies,
|
trustedProxies: opts.trustedProxies ?? cfg.gateway?.trustedProxies,
|
||||||
|
allowTailscaleHeaderAuth: false,
|
||||||
rateLimiter: opts.rateLimiter,
|
rateLimiter: opts.rateLimiter,
|
||||||
});
|
});
|
||||||
if (!authResult.ok) {
|
if (!authResult.ok) {
|
||||||
|
|||||||
Reference in New Issue
Block a user