Security: Versions-Downgrade-Schutz im Agent-Update-Mechanismus
checkin() und downloadSetup() liefern jetzt nie eine ältere Version aus, als der Agent laut DB/eigenem Report bereits installiert hat (clampToNotOlder). Schützt gegen genau den Bug von heute (kaputte AGENT_VERSION env-Var führte zu echtem Downgrade auf v2.0.0 bei 17 Agents) — selbst bei künftigen Konfigurationsfehlern kann der Server keinen Downgrade mehr anstoßen. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,24 @@ const crypto = require('crypto');
|
|||||||
// Downgrade-Bug für Agents ohne Patch-Gruppe oder ohne ?hostname-Parameter.)
|
// Downgrade-Bug für Agents ohne Patch-Gruppe oder ohne ?hostname-Parameter.)
|
||||||
const FALLBACK_AGENT_VERSION = '2.8.0';
|
const FALLBACK_AGENT_VERSION = '2.8.0';
|
||||||
|
|
||||||
|
// Vergleicht zwei "x.y.z"-Versionsstrings. >0 wenn a>b, <0 wenn a<b, 0 wenn gleich.
|
||||||
|
function compareVersions(a, b) {
|
||||||
|
const pa = String(a || '0').split('.').map(n => parseInt(n, 10) || 0);
|
||||||
|
const pb = String(b || '0').split('.').map(n => parseInt(n, 10) || 0);
|
||||||
|
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
||||||
|
const diff = (pa[i] || 0) - (pb[i] || 0);
|
||||||
|
if (diff !== 0) return diff;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nie eine ältere Version als die aktuell installierte vorschlagen/ausliefern — verhindert
|
||||||
|
// Downgrades durch fehlerhafte ENV-Konfiguration, falsch gesetzte Gruppen-Zielversionen o.ä.
|
||||||
|
function clampToNotOlder(targetVersion, currentVersion) {
|
||||||
|
if (!currentVersion) return targetVersion;
|
||||||
|
return compareVersions(targetVersion, currentVersion) < 0 ? currentVersion : targetVersion;
|
||||||
|
}
|
||||||
|
|
||||||
// POST /api/monitoring/enroll — Agent tauscht geteilten Bootstrap-Key gegen individuellen Per-Device-Key.
|
// POST /api/monitoring/enroll — Agent tauscht geteilten Bootstrap-Key gegen individuellen Per-Device-Key.
|
||||||
// Idempotent: ein bereits enrollter Agent bekommt seinen bestehenden Key einfach erneut zurück.
|
// Idempotent: ein bereits enrollter Agent bekommt seinen bestehenden Key einfach erneut zurück.
|
||||||
const enroll = asyncHandler(async (req, res) => {
|
const enroll = asyncHandler(async (req, res) => {
|
||||||
@@ -143,6 +161,8 @@ const checkin = asyncHandler(async (req, res) => {
|
|||||||
if (groupRow?.target_agent_version) targetVersion = groupRow.target_agent_version;
|
if (groupRow?.target_agent_version) targetVersion = groupRow.target_agent_version;
|
||||||
} catch { /* kein Gruppe zugewiesen → global */ }
|
} catch { /* kein Gruppe zugewiesen → global */ }
|
||||||
|
|
||||||
|
targetVersion = clampToNotOlder(targetVersion, req.body.agent_version);
|
||||||
|
|
||||||
const announcements = getForAgent(agent.id);
|
const announcements = getForAgent(agent.id);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
@@ -254,7 +274,7 @@ const downloadSetup = asyncHandler(async (req, res) => {
|
|||||||
if (hostname) {
|
if (hostname) {
|
||||||
try {
|
try {
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
const agent = db.prepare('SELECT id FROM monitoring_agents WHERE hostname = ?').get(hostname);
|
const agent = db.prepare('SELECT id, agent_version FROM monitoring_agents WHERE hostname = ?').get(hostname);
|
||||||
const groupRow = agent && db.prepare(`
|
const groupRow = agent && db.prepare(`
|
||||||
SELECT pg.target_agent_version
|
SELECT pg.target_agent_version
|
||||||
FROM patch_agent_groups pag
|
FROM patch_agent_groups pag
|
||||||
@@ -264,6 +284,7 @@ const downloadSetup = asyncHandler(async (req, res) => {
|
|||||||
LIMIT 1
|
LIMIT 1
|
||||||
`).get(agent.id);
|
`).get(agent.id);
|
||||||
if (groupRow?.target_agent_version) version = groupRow.target_agent_version;
|
if (groupRow?.target_agent_version) version = groupRow.target_agent_version;
|
||||||
|
version = clampToNotOlder(version, agent?.agent_version);
|
||||||
} catch { /* kein Gruppe zugewiesen → global */ }
|
} catch { /* kein Gruppe zugewiesen → global */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user