Das Problem

“I felt a great disturbance in the Force, as if millions of API calls suddenly cried out in terror and were suddenly silenced.”

Nach dem Upgrade auf Zabbix 7.0 funktioniert dein selbstgeschriebenes Dashboard nicht mehr:

HTTP 502 Bad Gateway

Oder noch kryptischer:

{
  "error": {
    "code": -32602,
    "message": "Invalid params.",
    "data": "Invalid parameter \"/\": unexpected parameter \"selectHosts\"."
  }
}

Gestern hat noch alles funktioniert. Was ist passiert?

TL;DR

Zabbix 7.0 hat zwei Breaking Changes in der API:

  1. Auth-Token: Nur noch im JSON-Body, nicht mehr im Authorization Header
  2. selectHosts bei problem.get: Parameter entfernt, nutze event.get stattdessen

Diagnose: Ist mein Code betroffen?

Wenn dein Dashboard nach dem Zabbix 7.0 Upgrade kaputt ist, prüfe diese Punkte:

Test 1: Alte Auth-Methode (wird ignoriert)

curl -s 'http://zabbix.deathstar.lan:8080/api_jsonrpc.php' \
  -H 'Content-Type: application/json-rpc' \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{"jsonrpc":"2.0","method":"problem.get","params":{"recent":true},"id":1}'

Ergebnis: "Not authorised" oder "Invalid params"

Test 2: Neue Auth-Methode (funktioniert)

curl -s 'http://zabbix.deathstar.lan:8080/api_jsonrpc.php' \
  -H 'Content-Type: application/json-rpc' \
  -d '{"jsonrpc":"2.0","method":"problem.get","params":{"recent":true},"auth":"YOUR_TOKEN","id":1}'

Ergebnis: Array mit Problemen

Test 3: selectHosts mit problem.get (broken)

curl -s 'http://zabbix.deathstar.lan:8080/api_jsonrpc.php' \
  -H 'Content-Type: application/json-rpc' \
  -d '{"jsonrpc":"2.0","method":"problem.get","params":{"output":["eventid"],"selectHosts":["host"]},"auth":"YOUR_TOKEN","id":1}'

Ergebnis: "Invalid parameter \"/\" unexpected parameter \"selectHosts\""

Test 4: Workaround mit event.get (funktioniert)

curl -s 'http://zabbix.deathstar.lan:8080/api_jsonrpc.php' \
  -H 'Content-Type: application/json-rpc' \
  -d '{"jsonrpc":"2.0","method":"event.get","params":{"output":["eventid"],"selectHosts":["host"]},"auth":"YOUR_TOKEN","id":1}'

Ergebnis: Array mit Events + Hosts

Breaking Change 1: Auth-Token im Body

Vorher (Zabbix 6.x)

const response = await fetch(ZABBIX_URL, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json-rpc',
    'Authorization': `Bearer ${token}`,  // Token im Header
  },
  body: JSON.stringify({
    jsonrpc: '2.0',
    method: 'problem.get',
    params: { recent: true },
    id: 1,
  }),
});

Nachher (Zabbix 7.0+)

const response = await fetch(ZABBIX_URL, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json-rpc',
    // KEIN Authorization Header mehr!
  },
  body: JSON.stringify({
    jsonrpc: '2.0',
    method: 'problem.get',
    params: { recent: true },
    id: 1,
    auth: token,  // Token hier im Body!
  }),
});

Der Authorization: Bearer Header wird in Zabbix 7.0 komplett ignoriert. Das Resultat ist ein 502 Bad Gateway oder eine “Not authorised” Meldung.

Breaking Change 2: selectHosts bei problem.get

Viele Dashboards nutzen problem.get mit selectHosts um sowohl die Probleme als auch die betroffenen Hosts in einem Request zu bekommen:

// FUNKTIONIERT NICHT MEHR IN ZABBIX 7.0!
const problems = await zabbixRequest('problem.get', {
  output: ['eventid', 'name', 'severity'],
  selectHosts: ['host', 'name'],  // <-- Fehler!
  recent: true,
});

Der Workaround

In Zabbix 7.0 musst du zwei Requests machen:

// 1. Probleme holen (ohne Host-Info)
const problems = await zabbixRequest('problem.get', {
  output: ['eventid', 'objectid', 'clock', 'name', 'severity', 'acknowledged'],
  recent: true,
  sortfield: ['eventid'],
  sortorder: 'DESC',
  limit: 100,
});

if (problems.length === 0) return [];

// 2. Host-Info via event.get holen
// (selectHosts funktioniert bei event.get noch!)
const eventIds = problems.map(p => p.eventid);
const events = await zabbixRequest('event.get', {
  eventids: eventIds,
  output: ['eventid'],
  selectHosts: ['host', 'name'],
});

// 3. Zusammenführen
const eventHostMap = new Map();
for (const event of events) {
  eventHostMap.set(event.eventid, event.hosts || []);
}

const result = problems.map(p => ({
  ...p,
  hosts: eventHostMap.get(p.eventid) || [],
}));

Ja, das sind zwei API-Calls statt einem. Aber es funktioniert - wie der Kessel Run in unter 12 Parsec.

Vollständiges Beispiel (TypeScript)

async function getProblems(): Promise<ZabbixProblem[]> {
  // Probleme ohne Host-Info holen
  const problems = await zabbixRequest<Array<{
    eventid: string;
    objectid: string;
    clock: string;
    name: string;
    severity: string;
    acknowledged: string;
  }>>('problem.get', {
    output: ['eventid', 'objectid', 'clock', 'name', 'severity', 'acknowledged'],
    recent: true,
    sortfield: ['eventid'],
    sortorder: 'DESC',
    limit: 100,
  });

  if (problems.length === 0) return [];

  // Host-Info via event.get
  const eventIds = problems.map(p => p.eventid);
  const events = await zabbixRequest<Array<{
    eventid: string;
    hosts: { host: string; name: string }[];
  }>>('event.get', {
    eventids: eventIds,
    output: ['eventid'],
    selectHosts: ['host', 'name'],
  });

  // Merge
  const eventHostMap = new Map<string, { host: string; name: string }[]>();
  for (const event of events) {
    eventHostMap.set(event.eventid, event.hosts || []);
  }

  return problems.map(p => ({
    ...p,
    hosts: eventHostMap.get(p.eventid) || [],
  }));
}

// API Request Helper
async function zabbixRequest<T>(
  method: string,
  params: Record<string, unknown>
): Promise<T> {
  const response = await fetch(ZABBIX_URL, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json-rpc',
    },
    body: JSON.stringify({
      jsonrpc: '2.0',
      method,
      params,
      id: 1,
      auth: getToken(),  // Token im Body!
    }),
  });

  const data = await response.json();
  if (data.error) {
    throw new Error(`Zabbix API error: ${data.error.message}`);
  }
  return data.result;
}

Wie prüfe ich meine Zabbix-Version?

curl -s 'http://zabbix.deathstar.lan:8080/api_jsonrpc.php' \
  -H 'Content-Type: application/json-rpc' \
  -d '{"jsonrpc":"2.0","method":"apiinfo.version","params":[],"id":1}'

Antwort:

{"jsonrpc":"2.0","result":"7.0.22","id":1}

Alles ab 7.0.x ist betroffen - das Imperium hat die API geändert und niemandem Bescheid gesagt.

Zusammenfassung

WasZabbix 6.xZabbix 7.0+
Auth-TokenAuthorization: Bearer Headerauth Parameter im JSON-Body
problem.get + selectHostsFunktioniertEntfernt - nutze event.get
event.get + selectHostsFunktioniertFunktioniert
trigger.get + selectHostsFunktioniertFunktioniert

Diese Änderungen sind nicht in den Release Notes prominent dokumentiert, also spar dir die Frustration und check deinen Code jetzt.

Fazit

Das Imperium (Zabbix Inc.) hat die API geändert ohne die Rebellen-Allianz (uns Nutzer) ausreichend zu warnen. Aber mit dem Workaround über event.get kannst du dein Dashboard retten.

“Do. Or do not. There is no selectHosts.” — Yoda, vermutlich