[#389] GLPI→OpenVAS feed pilot: script (fixture-tested, 63 targets) + GVM app-test compose + pipeline docs
ci / audit (push) Successful in 20s
ci / audit (push) Successful in 20s
Feed reads GLPI computers read-only (mglpi identity), parses ts=/mgmt IP comment convention, emits CSV or GMP create_target fragments. Dual shellcheck green. GVM stack = Greenbone community containers, loopback. https://projects.knownelement.com/issues/389
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
# GVM (OpenVAS) app-test lane — loopback single-host stack [#389]
|
||||
#
|
||||
# Greenbone Community Containers. App-test only (loopback bind): the scan
|
||||
# engine + feeds are heavyweight; bring-up downloads several GB of feeds on
|
||||
# first start. Production scan engine placement (kali-tsys VM per #389) is a
|
||||
# separate deploy under an approved GLPI CR.
|
||||
#
|
||||
# Bring-up:
|
||||
# docker compose -f docker-compose.yml up -d # feeds sync on start
|
||||
# watch: docker compose logs -f gvmd | grep -i feed # wait for "Feed ... ok"
|
||||
# gsad UI: http://127.0.0.1:9392 (admin/admin on first login — change)
|
||||
# GMP socket: unix volume `run_gvmd` (gvm-tools service below)
|
||||
#
|
||||
# Feed targets from GLPI:
|
||||
# ../../openvas/glpi-to-openvas.sh --format gmp > /tmp/targets.gmp
|
||||
# docker compose run --rm gvm-tools script -c "<read(/tmp/targets.gmp)>" ...
|
||||
# (see openvas/README.md for the full pipeline)
|
||||
#
|
||||
# IMAGE PINNING: version tags now; digest-pin at bring-up proof (house rule).
|
||||
|
||||
services:
|
||||
redis:
|
||||
image: redis:7.2-bookworm
|
||||
command: redis-server /etc/redis/redis.conf
|
||||
volumes:
|
||||
- redis_socket:/run/redis
|
||||
restart: unless-stopped
|
||||
|
||||
pg-gvm:
|
||||
image: greenbone/pg-gvm:22.4-stable
|
||||
environment:
|
||||
POSTGRES_USER: gvmd
|
||||
volumes:
|
||||
- psql_data:/var/lib/postgresql
|
||||
restart: unless-stopped
|
||||
|
||||
gvmd:
|
||||
image: greenbone/gvmd:22.5-stable
|
||||
depends_on: [redis, pg-gvm]
|
||||
volumes:
|
||||
- gvmd_data:/var/lib/gvm
|
||||
- run_gvmd:/run/gvmd
|
||||
restart: unless-stopped
|
||||
|
||||
ospd-openvas:
|
||||
image: greenbone/ospd-openvas:22.5-stable
|
||||
depends_on: [redis]
|
||||
volumes:
|
||||
- run_gvmd:/run/gvmd
|
||||
- ospd_openvas_socket:/run/ospd
|
||||
- plugins_data:/var/lib/openvas_plugin_cache
|
||||
- nvts_data:/var/lib/gvm
|
||||
restart: unless-stopped
|
||||
|
||||
gsad:
|
||||
image: greenbone/gsad:22.4-stable
|
||||
depends_on: [gvmd]
|
||||
ports:
|
||||
- "127.0.0.1:9392:9392" # loopback only (app-test lane rule)
|
||||
restart: unless-stopped
|
||||
|
||||
# one-shot helper container for GMP imports (targets/tasks) via gvm-tools
|
||||
gvm-tools:
|
||||
image: greenbone/gvm-tools:22.9-stable
|
||||
profiles: [tools]
|
||||
entrypoint: ["sleep", "infinity"]
|
||||
volumes:
|
||||
- run_gvmd:/run/gvmd
|
||||
|
||||
networks: {}
|
||||
|
||||
volumes:
|
||||
redis_socket:
|
||||
psql_data:
|
||||
gvmd_data:
|
||||
run_gvmd:
|
||||
ospd_openvas_socket:
|
||||
plugins_data:
|
||||
nvts_data:
|
||||
@@ -0,0 +1,40 @@
|
||||
# openvas/ — vulnerability scanning fed from the GLPI CMDB
|
||||
|
||||
> Ticket [#389](https://projects.knownelement.com/issues/389) (Greenbone on
|
||||
> kali-tsys) · umbrella [#311](https://projects.knownelement.com/issues/311) ·
|
||||
> lane index https://community.turnsys.com/t/297
|
||||
|
||||
## Pipeline (GLPI → scan targets)
|
||||
|
||||
```
|
||||
GLPI CMDB (computers, read-only via mglpi identity)
|
||||
└─ glpi-to-openvas.sh --format csv # review artifact
|
||||
└─ glpi-to-openvas.sh --format gmp # <create_target> fragments
|
||||
└─ GVM (OpenVAS) — app-test stack: ../docker/gvm-test/
|
||||
```
|
||||
|
||||
- `glpi-to-openvas.sh` reads the computer inventory (GLPI REST, read-only),
|
||||
parses the `ts=<ip> | mgmt <ip>` comment convention (mixed `=` and space —
|
||||
both accepted), and emits GMP `<create_target>` fragments or a review CSV.
|
||||
- `--fixture FILE` transforms a saved GLPI response offline (deterministic
|
||||
test path — `fixtures/glpi-computers-sample.json` is a real 80-record pull).
|
||||
- Verified 2026-09-05: 63 scan targets parsed from 80 GLPI records (the rest
|
||||
have no parsable IP and are skipped; `--include-no-ip` overrides).
|
||||
|
||||
## Rules of engagement (non-negotiable)
|
||||
|
||||
1. Scans against PROD hosts require an approved GLPI Change + an Uptime Kuma
|
||||
maintenance window. sectestbed VMs are always fair game (that is their job).
|
||||
2. Tailscale addresses are included in targets deliberately — scanning runs
|
||||
over tailnet, not LAN, unless a CR says otherwise.
|
||||
3. Findings stay in this PRIVATE repo and Redmine. Never leave the boundary.
|
||||
4. App-test GVM binds loopback only (127.0.0.1:9392). Production engine
|
||||
placement = kali-tsys VM per #389, deployed under its own CR.
|
||||
|
||||
## Status
|
||||
|
||||
- [x] GLPI feed script, dual-shellcheck green, fixture-tested (2026-09-05)
|
||||
- [x] GVM app-test compose (loopback, pinned version tags; digest-pin at proof)
|
||||
- [ ] GVM bring-up + first feed import (next run: feeds download several GB)
|
||||
- [ ] First sectestbed scan + findings triage into Redmine
|
||||
- [ ] kali-tsys production engine under CR (#389)
|
||||
File diff suppressed because one or more lines are too long
Executable
+89
@@ -0,0 +1,89 @@
|
||||
#!/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
|
||||
|
||||
# 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 '<!-- GMP create_target fragments generated from GLPI CMDB %s. Review hosts before importing. -->\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 '<create_target><name>glpi-%s-%s</name><hosts>%s</hosts><comment>GLPI #%s inv=%s agent=%s</comment><alive_tests>Scan Config Default</alive_tests></create_target>\n' \
|
||||
"$id" "$(printf '%s' "$name" | tr -c '[:alnum:].-' '-')" "$hosts" "$id" "$inv" "$dyn"
|
||||
done
|
||||
;;
|
||||
*) echo "bad format: $FORMAT" >&2; exit 1 ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user