diff --git a/docker-compose.worker.yml b/docker-compose.worker.yml index 65dd22e..1e1780d 100644 --- a/docker-compose.worker.yml +++ b/docker-compose.worker.yml @@ -47,6 +47,10 @@ services: environment: MAM_API_URL: ${MAM_API_URL} NODE_TOKEN: ${NODE_TOKEN:-} + # Shared cluster-read token: lets the primary mam-api fan-out read-only + # container/log queries to every node with one token (= mam-api's + # NODE_AGENT_TOKEN). Set identically across the cluster. + CLUSTER_READ_TOKEN: ${CLUSTER_READ_TOKEN:-} NODE_ROLE: ${NODE_ROLE:-worker} # NODE_NAME pins the cluster identity (heartbeat key). Set it per-node so # cloned VMs that share /etc/hostname don't collide on the same diff --git a/services/node-agent/index.js b/services/node-agent/index.js index 01e911d..282c632 100644 --- a/services/node-agent/index.js +++ b/services/node-agent/index.js @@ -941,19 +941,27 @@ async function handleSidecarStatus(containerId, res) { // When NODE_TOKEN is configured, privileged control endpoints (driver install) // require a matching `Authorization: Bearer `. mam-api forwards the // node's stored token. If NODE_TOKEN is empty (dev), auth is not enforced. +// +// A SHARED cluster-read token (CLUSTER_READ_TOKEN) is ALSO accepted so the +// primary mam-api can fan-out read-only cluster queries (container list, logs) +// to every node with ONE token, rather than tracking each node's bound token. +// It only grants the same endpoints NODE_TOKEN does; set it identically on +// mam-api (NODE_AGENT_TOKEN) and every node-agent. +const CLUSTER_READ_TOKEN = process.env.CLUSTER_READ_TOKEN || ''; + +function _bearerEq(token, secret) { + if (!secret || token.length !== secret.length) return false; + try { return crypto.timingSafeEqual(Buffer.from(token), Buffer.from(secret)); } + catch (_) { return false; } +} + function checkAgentAuth(req) { - if (!NODE_TOKEN) return true; + if (!NODE_TOKEN && !CLUSTER_READ_TOKEN) return true; const hdr = req.headers['authorization'] || ''; const m = /^Bearer\s+(.+)$/i.exec(hdr); if (!m) return false; - const token = m[1]; - if (token.length !== NODE_TOKEN.length) return false; - try { - return crypto.timingSafeEqual(Buffer.from(token), Buffer.from(NODE_TOKEN)); - } catch (_) { - return false; - } + return _bearerEq(token, NODE_TOKEN) || _bearerEq(token, CLUSTER_READ_TOKEN); } // ── Driver/SDK install ────────────────────────────────────────────────────