Files
compliance/openvas/glpi-to-openvas.sh
T
mrcharles ffc82467a9
ci / audit (push) Successful in 24s
[#389] GVM target import: gvm-script (gmp.send_command, lxml parse) + IANA port-list injection; feed jq rewrite (record-bound, no IFS collapse)
63/63 GLPI targets imported into gvmd (0 failures). Admin creds in
~/.creds/gvm-test.env. Import is idempotent (deletes glpi-* first).
https://projects.knownelement.com/issues/389
2026-09-05 11:21:36 -05:00

81 lines
4.1 KiB
Bash
Executable File

#!/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 <create_target> XML fragments ready for gvm-tools/GMP
#
# GLPI convention: IPs live in the computer comment field as
# "ts=<tailscale-ip> | mgmt <lan-ip> | 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
case "$FORMAT" in
csv)
printf 'glpi_id,name,ts_ip,mgmt_ip,serial,last_inventory,agent_fed\n'
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) ]
| @csv' --argjson ONLY_DYNAMIC "$ONLY_DYNAMIC" --argjson INCLUDE_NO_IP "$INCLUDE_NO_IP" "$INVENTORY_JSON"
;;
gmp)
printf '<!-- GMP create_target fragments generated from GLPI CMDB %s. Review hosts before importing. -->\n' "$(date -Is)"
# jq emits the complete element per record (host list built in jq — avoids
# bash read() IFS-collapse on empty TSV fields; record bound via . as $rec)
jq -r ' .[] | . as $rec | ( $rec.comment // "" ) as $c | ( [$c | scan("ts=([0-9.]+)")] | first | first // "" ) as $ts | ( [$c | scan("mgmt[ =]([0-9.]+)")] | first | first // "" ) as $mgmt | [ $ts, $mgmt ] | map(select(length > 0)) | join(",") as $hosts | select( $hosts != "" ) | $rec.name as $n | "<create_target><name>glpi-\($rec.id)-\($n | gsub("[^a-zA-Z0-9.-]"; "-"))</name><hosts>\($hosts)</hosts><comment>GLPI #\($rec.id) agent=\($rec.is_dynamic // 0)</comment></create_target>"' --argjson ONLY_DYNAMIC "$ONLY_DYNAMIC" --argjson INCLUDE_NO_IP "$INCLUDE_NO_IP" "$INVENTORY_JSON"
;;
*) echo "bad format: $FORMAT" >&2; exit 1 ;;
esac