#!/usr/bin/env bash # glpi-to-openvas.sh — feed OpenVAS/GVM scan targets from the GLPI CMDB [#389] # # Pulls the computer inventory from GLPI (REST, read-only) and emits: # --format csv id,name,ts_ip,mgmt_ip,serial,last_inventory (audit/review) # --format gmp XML fragments ready for gvm-tools/GMP # # GLPI convention: IPs live in the computer comment field as # "ts= | mgmt | src=..." (mixed = and space, see KNEL/inventory seed) # Records without any parsable IP are skipped unless --include-no-ip. # # Usage: # glpi-to-openvas.sh --format gmp [--env-file ~/.creds/mglpi.env] [--range 0-499] # glpi-to-openvas.sh --format csv --fixture FILE.json # offline transform test # # The GMP output is imported with e.g.: # gvm-tools script --gmp-username user --gmp-password pass feed.gmp # Creds: GLPI tokens come from the env file (never on the command line). set -uo pipefail FORMAT="csv"; ENV_FILE="${MGLPI_ENV:-$HOME/.creds/mglpi.env}"; RANGE="0-499" FIXTURE=""; INCLUDE_NO_IP=0; ONLY_DYNAMIC=0 while [ $# -gt 0 ]; do case "$1" in --format) FORMAT="$2"; shift 2 ;; --env-file) ENV_FILE="$2"; shift 2 ;; --range) RANGE="$2"; shift 2 ;; --fixture) FIXTURE="$2"; shift 2 ;; --include-no-ip) INCLUDE_NO_IP=1; shift ;; --only-dynamic) ONLY_DYNAMIC=1; shift ;; # is_dynamic=1 only (agent-fed) -h|--help) sed -n '2,20p' "${BASH_SOURCE[0]}" >&2; exit 0 ;; *) echo "unknown arg: $1" >&2; exit 1 ;; esac done fetch_inventory() { # shellcheck disable=SC1090 [ -f "$ENV_FILE" ] && . "$ENV_FILE" : "${MGLPI_URL:?MGLPI_URL missing}"; : "${MGLPI_APP_TOKEN:?}"; : "${MGLPI_USER_TOKEN:?}" local S S=$(curl -sS --max-time 30 -H "Content-Type: application/json" \ -H "App-Token: ${MGLPI_APP_TOKEN}" -H "Authorization: user_token ${MGLPI_USER_TOKEN}" \ "${MGLPI_URL}/initSession" | jq -r '.session_token // empty') [ -n "$S" ] || { echo "FAIL: GLPI session init" >&2; return 1; } curl -sS --max-time 60 -H "App-Token: ${MGLPI_APP_TOKEN}" -H "Session-Token: $S" \ "${MGLPI_URL}/Computer?range=${RANGE}&is_deleted=0" \ | jq -c '[.[] | select(type == "object")]' curl -sS --max-time 30 -X DELETE -H "App-Token: ${MGLPI_APP_TOKEN}" \ -H "Session-Token: $S" "${MGLPI_URL}/killSession" >/dev/null } INVENTORY_JSON="/tmp/glpi-computers.json" if [ -n "$FIXTURE" ]; then INVENTORY_JSON="$FIXTURE" else fetch_inventory > "$INVENTORY_JSON" || exit 1 fi # Normalize: one record per line with parsed IPs NORMALIZED=$(jq -r ' .[] | select( ($ONLY_DYNAMIC == 0 or .is_dynamic == 1) ) | ( .comment // "" ) as $c | ( [$c | scan("ts=([0-9.]+)")] | first | first // "" ) as $ts | ( [$c | scan("mgmt[ =]([0-9.]+)")] | first | first // "" ) as $mgmt | select( $INCLUDE_NO_IP == 1 or $ts != "" or $mgmt != "" ) | [ (.id|tostring), .name, $ts, $mgmt, (.serial // "-"), (.last_inventory_update // "-"), ((.is_dynamic // 0)|tostring) ] | @tsv' --argjson ONLY_DYNAMIC "$ONLY_DYNAMIC" --argjson INCLUDE_NO_IP "$INCLUDE_NO_IP" "$INVENTORY_JSON") [ -n "$NORMALIZED" ] || { echo "no scan targets parsed from GLPI" >&2; exit 2; } case "$FORMAT" in csv) printf 'glpi_id,name,ts_ip,mgmt_ip,serial,last_inventory,agent_fed\n' printf '%s\n' "$NORMALIZED" ;; gmp) printf '\n' "$(date -Is)" printf '%s\n' "$NORMALIZED" | while IFS="$(printf '\t')" read -r id name ts mgmt _serial inv dyn; do hosts="$ts" if [ -n "$mgmt" ]; then hosts="${hosts:+$hosts,}$mgmt"; fi [ -n "$hosts" ] || continue printf 'glpi-%s-%s%sGLPI #%s inv=%s agent=%sScan Config Default\n' \ "$id" "$(printf '%s' "$name" | tr -c '[:alnum:].-' '-')" "$hosts" "$id" "$inv" "$dyn" done ;; *) echo "bad format: $FORMAT" >&2; exit 1 ;; esac