Files
esh-pfi-infrastructure/scripts/discover-fortigate.sh
T
vh 6f998a83c3 fleet: retire NH3-Firewall (Fortigate 101F @ 10.100.250.1)
Device has been removed from the NH3 site. Drop the homepage card
and the corresponding example in discover-fortigate.sh.

Note left in services.yaml so whoever adds the replacement edge
device knows where the old entry lived.
2026-04-21 13:48:26 -07:00

100 lines
3.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# discover-fortigate.sh — pull DHCP lease list from a FortiGate.
#
# SSHes to a FortiGate admin account, runs `execute dhcp lease-list`,
# parses the output into TSV (IP, MAC, hostname, vdom, source).
#
# Usage:
# scripts/discover-fortigate.sh <fortigate-host>
#
# Env overrides:
# FORTIGATE_SSH_USER default: admin
#
# Output: TSV on stdout, one lease per line:
# IP MAC HOSTNAME VDOM SOURCE
# Where SOURCE is "fortigate:<host>" so multiple runs can be concatenated
# and still identified.
#
# Example:
# scripts/discover-fortigate.sh ana-fw.phasefinal.com > leases-ana.tsv
#
# Requires: ssh config (or `~/.ssh/config` host alias) for the FortiGate,
# with key auth OR interactive password. FortiGate's SSH expects admin-level
# credentials.
set -euo pipefail
if [ -z "${1:-}" ]; then
echo "usage: $(basename "$0") <fortigate-host>" >&2
exit 2
fi
ARG="$1"
# Accept either "host" or "user@host" — don't double-prefix the user.
if [[ "$ARG" == *@* ]]; then
TARGET="$ARG"
HOST="${ARG##*@}"
else
TARGET="${FORTIGATE_SSH_USER:-admin}@${ARG}"
HOST="$ARG"
fi
USER="${FORTIGATE_SSH_USER:-admin}"
# FortiGate CLI command. `execute dhcp lease-list all` dumps every vdom.
# If the device is single-vdom, `execute dhcp lease-list` (no arg) also works.
#
# Do NOT suppress stderr — FortiGate's error messages are the main debugging
# signal when the command returns empty. Let them flow to the caller.
raw=""
for cmd in 'execute dhcp lease-list all' 'execute dhcp lease-list'; do
raw=$(ssh -o StrictHostKeyChecking=accept-new "$TARGET" "$cmd" || true)
if [ -n "$raw" ] && ! grep -qiE 'unknown action|parse error|command fail' <<<"$raw"; then
break
fi
done
if [ -z "$raw" ]; then
echo "error: no lease data from $HOST" >&2
echo " — try: ssh $TARGET then run: execute dhcp lease-list" >&2
exit 1
fi
# Parse.
#
# Real FortiOS output looks like:
#
# ana-gw # internal
# IP MAC-Address Hostname VCI SSID AP SERVER-ID Expiry
# 10.250.0.105 90:5a:08:98:e2:29 ana-ml2 4 Sun Apr 26 17:02:23 2026
# mgmt
# IP MAC-Address Hostname …
# 10.250.250.50 7c:c2:55:60:fe:8a ANA-ML2-BMC udhcp 1.32.1 6 Tue Apr 28 18:56:08 2026
#
# Interfaces are flush-left (no indent); lease rows + column headers are
# indented. First line embeds the shell prompt ("ana-gw # ") followed by
# the first interface name. Hostnames don't contain spaces in practice.
awk -v src="fortigate:${HOST}" '
# Flush-left non-blank line = interface name (sometimes preceded by prompt)
/^[^[:space:]]/ && NF >= 1 {
# "<prompt> # <iface>" form — take the last field
if (NF >= 3 && $2 == "#") { iface = $NF; next }
# plain "<iface>" form
if (NF == 1) { iface = $1; next }
# prompt-with-trailing-% or similar closing lines — skip
next
}
# Column header row
/^[[:space:]]+IP[[:space:]]/ { next }
# Blank / whitespace-only
/^[[:space:]]*$/ { next }
# Lease row
$1 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ && $2 ~ /^[0-9a-fA-F]{2}:/ {
ip = $1
mac = $2
host = (NF >= 3 && $3 !~ /^[0-9]+$/) ? $3 : "-"
printf "%s\t%s\t%s\t%s\t%s\n", ip, mac, host, (iface==""?"-":iface), src
}
' <<<"$raw"