fix(cluster): shared CLUSTER_READ_TOKEN so mam-api sees containers on ALL nodes
/cluster/containers only returned the primary's containers: mam-api fanned out to each node-agent with a single NODE_AGENT_TOKEN, but each node-agent only accepted its own bound NODE_TOKEN, so remote nodes returned 401 and were silently dropped (UI showed 'only zampp1'). node-agent now ALSO accepts a shared CLUSTER_READ_TOKEN (= mam-api's NODE_AGENT_TOKEN) for the read-only container/log endpoints, so the aggregate container view + per-container logs work across the whole cluster.
This commit is contained in:
parent
d6b0b3a9a6
commit
70c873ae95
2 changed files with 20 additions and 8 deletions
|
|
@ -47,6 +47,10 @@ services:
|
||||||
environment:
|
environment:
|
||||||
MAM_API_URL: ${MAM_API_URL}
|
MAM_API_URL: ${MAM_API_URL}
|
||||||
NODE_TOKEN: ${NODE_TOKEN:-}
|
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_ROLE: ${NODE_ROLE:-worker}
|
||||||
# NODE_NAME pins the cluster identity (heartbeat key). Set it per-node so
|
# 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
|
# cloned VMs that share /etc/hostname don't collide on the same
|
||||||
|
|
|
||||||
|
|
@ -941,19 +941,27 @@ async function handleSidecarStatus(containerId, res) {
|
||||||
// When NODE_TOKEN is configured, privileged control endpoints (driver install)
|
// When NODE_TOKEN is configured, privileged control endpoints (driver install)
|
||||||
// require a matching `Authorization: Bearer <NODE_TOKEN>`. mam-api forwards the
|
// require a matching `Authorization: Bearer <NODE_TOKEN>`. mam-api forwards the
|
||||||
// node's stored token. If NODE_TOKEN is empty (dev), auth is not enforced.
|
// 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) {
|
function checkAgentAuth(req) {
|
||||||
if (!NODE_TOKEN) return true;
|
if (!NODE_TOKEN && !CLUSTER_READ_TOKEN) return true;
|
||||||
const hdr = req.headers['authorization'] || '';
|
const hdr = req.headers['authorization'] || '';
|
||||||
const m = /^Bearer\s+(.+)$/i.exec(hdr);
|
const m = /^Bearer\s+(.+)$/i.exec(hdr);
|
||||||
if (!m) return false;
|
if (!m) return false;
|
||||||
|
|
||||||
const token = m[1];
|
const token = m[1];
|
||||||
if (token.length !== NODE_TOKEN.length) return false;
|
return _bearerEq(token, NODE_TOKEN) || _bearerEq(token, CLUSTER_READ_TOKEN);
|
||||||
try {
|
|
||||||
return crypto.timingSafeEqual(Buffer.from(token), Buffer.from(NODE_TOKEN));
|
|
||||||
} catch (_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Driver/SDK install ────────────────────────────────────────────────────
|
// ── Driver/SDK install ────────────────────────────────────────────────────
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue