Harden edge broker defaults and compose exposure

This commit is contained in:
Jeppe B
2026-06-01 22:10:14 +02:00
parent 12205eefe8
commit f2db92de09
6 changed files with 60 additions and 29 deletions
+2 -1
View File
@@ -49,7 +49,8 @@ ECONOMIC_API_APP_SECRET_TOKEN=
# Edge broker defaults for shell relay and gateway dispatch.
EDGE_BROKER_URL=http://edge-broker:4300
EDGE_BROKER_SHARED_SECRET=truckwash-edge-dev
EDGE_BROKER_SHARED_SECRET=
EDGE_AUTH_MODE=strict
# Redis credentials
REDIS_CONFIG_HOST=redis
+5 -4
View File
@@ -52,9 +52,10 @@ services:
dockerfile: services/edge-broker/Dockerfile
container_name: edge-broker
environment:
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET}
EDGE_AUTH_MODE: ${EDGE_AUTH_MODE:-strict}
ports:
- "4300:4300"
- "127.0.0.1:4300:4300"
caddy:
image: caddy:2.7.6-alpine
@@ -98,7 +99,7 @@ services:
environment:
AUTO_COMPOSER_INSTALL: "true"
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET}
volumes:
- ./services/nginx/app:/var/www/html
- ./services/edge-agent/dist:/services/edge-agent/dist:ro
@@ -120,7 +121,7 @@ services:
environment:
AUTO_COMPOSER_INSTALL: "false"
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET}
volumes:
- ./services/nginx/app:/var/www/html
- ./services/edge-agent/dist:/services/edge-agent/dist:ro
+10 -9
View File
@@ -86,9 +86,10 @@ services:
dockerfile: services/edge-broker/Dockerfile
container_name: edge-broker
environment:
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET}
EDGE_AUTH_MODE: ${EDGE_AUTH_MODE:-strict}
ports:
- "4300:4300"
- "127.0.0.1:4300:4300"
caddy:
image: caddy:2.7.6-alpine
@@ -213,7 +214,7 @@ services:
environment:
AUTO_COMPOSER_INSTALL: "true"
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET}
volumes:
- ./services/nginx/app:/var/www/html
- ./services/edge-agent/dist:/services/edge-agent/dist:ro
@@ -234,7 +235,7 @@ services:
environment:
AUTO_COMPOSER_INSTALL: "false"
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET}
volumes:
- ./services/nginx/app:/var/www/html
- ./services/edge-agent/dist:/services/edge-agent/dist:ro
@@ -255,7 +256,7 @@ services:
environment:
AUTO_COMPOSER_INSTALL: "false"
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET}
volumes:
- ./services/nginx/app:/var/www/html
- ./services/edge-agent/dist:/services/edge-agent/dist:ro
@@ -276,7 +277,7 @@ services:
environment:
AUTO_COMPOSER_INSTALL: "false"
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET}
volumes:
- ./services/nginx/app:/var/www/html
- ./services/edge-agent/dist:/services/edge-agent/dist:ro
@@ -297,7 +298,7 @@ services:
environment:
AUTO_COMPOSER_INSTALL: "false"
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET}
volumes:
- ./services/nginx/app:/var/www/html
- ./services/edge-agent/dist:/services/edge-agent/dist:ro
@@ -318,7 +319,7 @@ services:
environment:
AUTO_COMPOSER_INSTALL: "false"
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET}
volumes:
- ./services/nginx/staging:/var/www/html
- ./services/edge-agent/dist:/services/edge-agent/dist:ro
@@ -339,7 +340,7 @@ services:
environment:
AUTO_COMPOSER_INSTALL: "false"
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET}
volumes:
- ./services/nginx/app:/var/www/html
- ./services/edge-agent/dist:/services/edge-agent/dist:ro
+13 -8
View File
@@ -25,15 +25,24 @@ function jsonResponse(res, statusCode, body) {
}
export function createBrokerServer(options = {}) {
const authMode = options.authMode || process.env.EDGE_AUTH_MODE || "stub";
const authMode = options.authMode || process.env.EDGE_AUTH_MODE || "strict";
const sharedSecret = options.sharedSecret ?? process.env.EDGE_BROKER_SHARED_SECRET ?? "";
const commandTimeoutMs = options.commandTimeoutMs ?? 10000;
const agents = new Map();
const pendingCommands = new Map();
const browserSessions = new Map();
const isStubMode = authMode === "stub";
const validateAgent = options.validateAgent || (async ({ gatewayId }) => ({ id: gatewayId }));
const validateShellSession = options.validateShellSession || (async ({ token }) => ({ id: token, gateway_id: 1, reason: "stub" }));
const validateAgent = options.validateAgent || (isStubMode
? (async ({ gatewayId }) => ({ id: gatewayId }))
: (async () => {
throw new Error("Unauthorized");
}));
const validateShellSession = options.validateShellSession || (isStubMode
? (async ({ token }) => ({ id: token, gateway_id: 1, reason: "stub" }))
: (async () => {
throw new Error("Unauthorized");
}));
const closeShellSession = options.closeShellSession || (async () => ({}));
const markBrowserSessionsClosed = (gatewayId, reason) => {
@@ -113,9 +122,7 @@ export function createBrokerServer(options = {}) {
socket.destroy();
return;
}
if (authMode !== "stub") {
await validateAgent({ gatewayId, token, headers: req.headers });
}
wss.handleUpgrade(req, socket, head, (ws) => {
ws.gatewayId = gatewayId;
agents.set(gatewayId, ws);
@@ -130,9 +137,7 @@ export function createBrokerServer(options = {}) {
socket.destroy();
return;
}
const session = authMode === "stub"
? await validateShellSession({ token })
: await validateShellSession({ token, headers: req.headers });
const session = await validateShellSession({ token, headers: req.headers });
wss.handleUpgrade(req, socket, head, (ws) => {
ws.sessionToken = token;
+23
View File
@@ -24,6 +24,18 @@ function waitForClose(socket) {
});
}
function waitForCloseOrError(socket) {
return new Promise((resolve) => {
const onDone = () => {
socket.off("error", onDone);
socket.off("close", onDone);
resolve();
};
socket.once("error", onDone);
socket.once("close", onDone);
});
}
async function waitFor(predicate, { timeoutMs = 1000, intervalMs = 10, description = "condition" } = {}) {
const deadline = Date.now() + timeoutMs;
@@ -215,3 +227,14 @@ test("broker closes browser shell sessions when the agent disconnects before she
await broker.close();
});
test("broker defaults to strict auth when no validators are configured", async () => {
const broker = createBrokerServer();
const address = await broker.listen(0);
const port = address.port;
const agent = new WebSocket(`ws://127.0.0.1:${port}/ws/agent?gatewayId=701&token=agent-token`);
await waitForCloseOrError(agent);
await broker.close();
});
+6 -6
View File
@@ -24,19 +24,19 @@ test("traefik does not expose a dedicated public edge broker port", () => {
assert.doesNotMatch(traefikSource, /edge-broker:\s*\n\s*address:\s*":4300"/);
});
test("base docker compose exposes the edge broker service on port 4300", () => {
test("base docker compose restricts edge broker host binding to loopback", () => {
assert.match(baseComposeSource, /\bedge-broker:\b/);
assert.match(baseComposeSource, /edge-broker:\s*\n[\s\S]*?\n\s+ports:\s*\n\s+- "4300:4300"/);
assert.match(baseComposeSource, /edge-broker:\s*\n[\s\S]*?\n\s+ports:\s*\n\s+- "127\.0\.0\.1:4300:4300"/);
});
test("example docker compose exposes the edge broker service on port 4300", () => {
test("example docker compose restricts edge broker host binding to loopback", () => {
assert.match(exampleComposeSource, /\bedge-broker:\b/);
assert.match(exampleComposeSource, /edge-broker:\s*\n[\s\S]*?\n\s+ports:\s*\n\s+- "4300:4300"/);
assert.match(exampleComposeSource, /edge-broker:\s*\n[\s\S]*?\n\s+ports:\s*\n\s+- "127\.0\.0\.1:4300:4300"/);
});
test("php services receive broker websocket environment defaults", () => {
test("compose config does not provide insecure broker secret defaults", () => {
for (const composeSource of [baseComposeSource, exampleComposeSource]) {
assert.match(composeSource, /EDGE_BROKER_URL:\s*\$\{EDGE_BROKER_URL:-http:\/\/edge-broker:4300\}/);
assert.match(composeSource, /EDGE_BROKER_SHARED_SECRET:\s*\$\{EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev\}/);
assert.match(composeSource, /EDGE_BROKER_SHARED_SECRET:\s*\$\{EDGE_BROKER_SHARED_SECRET\}/);
}
});