From ffc82467a92414696d1c3482771b9f6d7812e87c Mon Sep 17 00:00:00 2001 From: reachableceo Date: Sat, 5 Sep 2026 11:21:36 -0500 Subject: [PATCH] [#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 --- openvas/glpi-to-openvas.sh | 35 +++++++++++++---------------------- openvas/import_targets.gmp | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 22 deletions(-) create mode 100644 openvas/import_targets.gmp diff --git a/openvas/glpi-to-openvas.sh b/openvas/glpi-to-openvas.sh index d19f553..081f6dd 100755 --- a/openvas/glpi-to-openvas.sh +++ b/openvas/glpi-to-openvas.sh @@ -56,34 +56,25 @@ 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" + 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 '\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 + # 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 | "glpi-\($rec.id)-\($n | gsub("[^a-zA-Z0-9.-]"; "-"))\($hosts)GLPI #\($rec.id) agent=\($rec.is_dynamic // 0)"' --argjson ONLY_DYNAMIC "$ONLY_DYNAMIC" --argjson INCLUDE_NO_IP "$INCLUDE_NO_IP" "$INVENTORY_JSON" ;; *) echo "bad format: $FORMAT" >&2; exit 1 ;; esac diff --git a/openvas/import_targets.gmp b/openvas/import_targets.gmp new file mode 100644 index 0000000..77192bb --- /dev/null +++ b/openvas/import_targets.gmp @@ -0,0 +1,35 @@ +import re +from lxml import etree +data = open('/tmp/targets.gmp').read() +cmds = re.findall(r'.*?', data, re.S) + +pl = etree.fromstring(gmp.send_command('').encode()) +plist_id = None +for p in pl.findall('.//port_list'): + if 'All IANA assigned TCP' in (p.findtext('name') or ''): + plist_id = p.get('id') + break +if not plist_id: + raise SystemExit('no IANA TCP port list found') + +# clean previous glpi-* targets (idempotent re-import) +existing = etree.fromstring(gmp.send_command('').encode()) +for t in existing.findall('.//target'): + if (t.findtext('name') or '').startswith('glpi-'): + gmp.send_command(f'') + +ok = fail = 0 +for c in cmds: + frag = etree.fromstring(c.encode()) + el = etree.SubElement(frag, 'port_list') + el.set('id', plist_id) + root = etree.fromstring(gmp.send_command(etree.tostring(frag).decode()).encode()) + st = root.get('status') + if st == '201': + ok += 1 + else: + fail += 1 + if fail <= 3: + resp = root.findtext('.//response') + print('FAIL', st, (resp or etree.tostring(root).decode())[:180]) +print(f'imported={ok} failed={fail}')