From 6e12f38b7bb073c8e6a655dc66dafd8b9f98f272 Mon Sep 17 00:00:00 2001 From: Michael Lucka Date: Thu, 5 Feb 2026 13:32:56 +0100 Subject: [PATCH 1/4] add system global location probe Add a new probe that retrieves device latitude and longitude from /api/v2/cmdb/system/global and exposes them as an info metric: fortigate_location_info{latitude, longitude} 1 The probe is best-effort and returns success even when the location metadata is not configured. Signed-off-by: Michael Lucka --- pkg/probe/probe.go | 1 + pkg/probe/system_global_location.go | 75 ++++++ pkg/probe/system_global_location_test.go | 41 +++ .../testdata/system-global-location.jsonnet | 248 ++++++++++++++++++ 4 files changed, 365 insertions(+) create mode 100644 pkg/probe/system_global_location.go create mode 100755 pkg/probe/system_global_location_test.go create mode 100644 pkg/probe/testdata/system-global-location.jsonnet diff --git a/pkg/probe/probe.go b/pkg/probe/probe.go index 24513f6e..6195135d 100644 --- a/pkg/probe/probe.go +++ b/pkg/probe/probe.go @@ -147,6 +147,7 @@ func (p *Collector) Probe(ctx context.Context, target map[string]string, hc *htt {"System/AvailableCertificates", probeSystemAvailableCertificates}, {"System/Central-Management/Status", probeSystemCentralManagementStatus}, {"System/Fortimanager/Status", probeSystemFortimanagerStatus}, + {"System/Global/Location",probeSystemGlobalLocation}, {"System/HAStatistics", probeSystemHAStatistics}, {"System/Interface", probeSystemInterface}, {"System/Interface/Transceivers", probeSystemInterfaceTransceivers}, diff --git a/pkg/probe/system_global_location.go b/pkg/probe/system_global_location.go new file mode 100644 index 00000000..1844c01c --- /dev/null +++ b/pkg/probe/system_global_location.go @@ -0,0 +1,75 @@ +// Copyright The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package probe + +import ( + "encoding/json" + "log" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus-community/fortigate_exporter/pkg/http" +) + +func probeSystemGlobalLocation(c http.FortiHTTP, _ *TargetMetadata) ([]prometheus.Metric, bool) { + + location := prometheus.NewDesc( + "fortigate_location_info", + "System geographic location (static metadata)", + []string{"latitude", "longitude"}, + nil, + ) + + type SystemGlobalLocation struct { + Latitude string `json:"gui-device-latitude"` + Longitude string `json:"gui-device-longitude"` + } + + type systemGlobalLocationResponse struct { + Results json.RawMessage `json:"results"` + } + + var resp systemGlobalLocationResponse + + if err := c.Get("api/v2/cmdb/system/global", "vdom=root", &resp); err != nil { + log.Printf("Error: %v", err) + return nil, false + } + + var loc SystemGlobalLocation + + // Try object first + if err := json.Unmarshal(resp.Results, &loc); err != nil { + // Fallback to array + var arr []SystemGlobalLocation + if err := json.Unmarshal(resp.Results, &arr); err != nil || len(arr) == 0 { + return nil, true + } + loc = arr[0] + } + + if loc.Latitude == "" || loc.Longitude == "" { + return nil, true + } + + m := []prometheus.Metric{ + prometheus.MustNewConstMetric( + location, + prometheus.GaugeValue, + 1, + loc.Latitude, + loc.Longitude, + ), + } + return m, true +} diff --git a/pkg/probe/system_global_location_test.go b/pkg/probe/system_global_location_test.go new file mode 100755 index 00000000..90faea9a --- /dev/null +++ b/pkg/probe/system_global_location_test.go @@ -0,0 +1,41 @@ +// Copyright The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package probe + +import ( + "strings" + "testing" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" +) + +func TestSystemGlobalLocation(t *testing.T) { + c := newFakeClient() + c.prepare("api/v2/cmdb/system/global", "testdata/system-global-location.jsonnet") + r := prometheus.NewPedanticRegistry() + if !testProbe(probeSystemGlobalLocation, c, r) { + t.Errorf("probeSystemGlobalLocation() returned non-success") + } + + em := ` + # HELP fortigate_location_info System geographic location (static metadata) + # TYPE fortigate_location_info gauge + fortigate_location_info{latitude="66.543508", longitude="25.8467468"} 1 + ` + + if err := testutil.GatherAndCompare(r, strings.NewReader(em)); err != nil { + t.Fatalf("metric compare: err %v", err) + } +} diff --git a/pkg/probe/testdata/system-global-location.jsonnet b/pkg/probe/testdata/system-global-location.jsonnet new file mode 100644 index 00000000..5861b531 --- /dev/null +++ b/pkg/probe/testdata/system-global-location.jsonnet @@ -0,0 +1,248 @@ +{ + "http_method": "GET", + "revision": "4c8a904dacbad878bcd3c7f132447340", + "results": { + "language": "english", + "gui-ipv6": "disable", + "gui-replacement-message-groups": "disable", + "gui-local-out": "disable", + "gui-certificates": "enable", + "gui-custom-language": "disable", + "gui-wireless-opensecurity": "disable", + "gui-app-detection-sdwan": "disable", + "gui-display-hostname": "disable", + "gui-fortigate-cloud-sandbox": "disable", + "gui-firmware-upgrade-warning": "disable", + "gui-forticare-registration-setup-warning": "disable", + "gui-auto-upgrade-setup-warning": "disable", + "gui-workflow-management": "disable", + "gui-cdn-usage": "enable", + "admin-https-ssl-versions": "tlsv1-2 tlsv1-3", + "admin-https-ssl-ciphersuites": "TLS-AES-128-GCM-SHA256 TLS-AES-256-GCM-SHA384 TLS-CHACHA20-POLY1305-SHA256", + "admin-https-ssl-banned-ciphers": "", + "admintimeout": 30, + "admin-console-timeout": 0, + "admin-concurrent": "enable", + "admin-lockout-threshold": 3, + "admin-lockout-duration": 600, + "refresh": 0, + "interval": 5, + "failtime": 5, + "purdue-level": "3", + "daily-restart": "disable", + "restart-time": "00:00", + "wad-restart-mode": "none", + "wad-restart-start-time": "01:30", + "wad-restart-end-time": "04:00", + "radius-port": 1812, + "speedtestd-server-port": 5201, + "speedtestd-ctrl-port": 5200, + "admin-login-max": 100, + "remoteauthtimeout": 5, + "ldapconntimeout": 500, + "batch-cmdb": "enable", + "multi-factor-authentication": "optional", + "ssl-min-proto-version": "TLSv1-2", + "autorun-log-fsck": "disable", + "timezone": "Europe/Helsinki", + "traffic-priority": "tos", + "traffic-priority-level": "medium", + "quic-congestion-control-algo": "cubic", + "quic-max-datagram-size": 1500, + "quic-udp-payload-size-shaping-per-cid": "enable", + "quic-ack-thresold": 3, + "quic-pmtud": "enable", + "quic-tls-handshake-timeout": 5, + "anti-replay": "strict", + "send-pmtu-icmp": "enable", + "honor-df": "enable", + "pmtu-discovery": "disable", + "virtual-switch-vlan": "disable", + "revision-image-auto-backup": "disable", + "revision-backup-on-logout": "disable", + "management-vdom": "mgmt", + "hostname": "vpngw01", + "alias": "FortiGate-60F", + "strong-crypto": "enable", + "ssl-static-key-ciphers": "enable", + "snat-route-change": "disable", + "speedtest-server": "disable", + "cli-audit-log": "disable", + "dh-params": "2048", + "fds-statistics": "enable", + "fds-statistics-period": 60, + "tcp-option": "enable", + "lldp-transmission": "disable", + "lldp-reception": "disable", + "proxy-auth-timeout": 10, + "proxy-keep-alive-mode": "session", + "proxy-re-authentication-time": 30, + "proxy-auth-lifetime": "disable", + "proxy-auth-lifetime-timeout": 480, + "proxy-resource-mode": "disable", + "proxy-cert-use-mgmt-vdom": "disable", + "sys-perf-log-interval": 5, + "check-protocol-header": "loose", + "vip-arp-range": "restricted", + "reset-sessionless-tcp": "disable", + "allow-traffic-redirect": "enable", + "ipv6-allow-traffic-redirect": "enable", + "strict-dirty-session-check": "enable", + "tcp-halfclose-timer": 120, + "tcp-halfopen-timer": 10, + "tcp-timewait-timer": 1, + "tcp-rst-timer": 5, + "udp-idle-timer": 180, + "block-session-timer": 30, + "ip-src-port-range": "1024-25000", + "pre-login-banner": "disable", + "post-login-banner": "disable", + "tftp": "enable", + "av-failopen": "pass", + "av-failopen-session": "disable", + "memory-use-threshold-extreme": 95, + "memory-use-threshold-red": 88, + "memory-use-threshold-green": 82, + "ip-fragment-mem-thresholds": 32, + "cpu-use-threshold": 90, + "log-single-cpu-high": "disable", + "check-reset-range": "disable", + "vdom-mode": "multi-vdom", + "vdom-admin": "", + "long-vdom-name": "disable", + "edit-vdom-prompt": "enable", + "admin-port": 80, + "admin-sport": 443, + "admin-host": "", + "admin-https-redirect": "disable", + "admin-hsts-max-age": 63072000, + "admin-ssh-password": "enable", + "admin-restrict-local": "disable", + "admin-ssh-port": 22, + "admin-ssh-grace-time": 120, + "admin-ssh-v1": "disable", + "admin-telnet": "enable", + "admin-telnet-port": 23, + "admin-forticloud-sso-login": "disable", + "admin-forticloud-sso-default-profile": "", + "default-service-source-port": "1-65535", + "admin-reset-button": "enable", + "admin-server-cert": "Fortinet_GUI_Server", + "admin-https-pki-required": "disable", + "wifi-certificate": "Fortinet_Wifi", + "dhcp-lease-backup-interval": 60, + "wifi-ca-certificate": "Fortinet_Wifi_CA", + "auth-http-port": 1000, + "auth-https-port": 1003, + "auth-ike-saml-port": 1001, + "auth-keepalive": "disable", + "policy-auth-concurrent": 0, + "auth-session-limit": "block-new", + "auth-cert": "Fortinet_Factory", + "clt-cert-req": "disable", + "fortiservice-port": 8013, + "cfg-save": "manual", + "cfg-revert-timeout": 600, + "reboot-upon-config-restore": "enable", + "admin-scp": "enable", + "security-rating-run-on-schedule": "enable", + "wireless-controller": "disable", + "wireless-controller-port": 5246, + "fortiextender-data-port": 25246, + "fortiextender": "disable", + "extender-controller-reserved-network": "192.168.2.1 255.255.255.0", + "fortiextender-discovery-lockdown": "disable", + "fortiextender-vlan-mode": "disable", + "fortiextender-provision-on-authorization": "disable", + "switch-controller": "disable", + "switch-controller-reserved-network": "192.168.1.1 255.255.255.0", + "dnsproxy-worker-count": 1, + "url-filter-count": 1, + "proxy-worker-count": 0, + "scanunit-count": 0, + "proxy-hardware-acceleration": "enable", + "fgd-alert-subscription": "", + "ipsec-hmac-offload": "enable", + "ipv6-accept-dad": 1, + "ipv6-allow-anycast-probe": "disable", + "ipv6-allow-multicast-probe": "disable", + "ipv6-allow-local-in-silent-drop": "enable", + "csr-ca-attribute": "enable", + "wimax-4g-usb": "disable", + "cert-chain-max": 8, + "sslvpn-max-worker-count": 0, + "vpn-ems-sn-check": "disable", + "sslvpn-web-mode": "disable", + "two-factor-ftk-expiry": 60, + "two-factor-email-expiry": 60, + "two-factor-sms-expiry": 60, + "two-factor-fac-expiry": 60, + "two-factor-ftm-expiry": 72, + "wad-worker-count": 0, + "wad-csvc-cs-count": 1, + "wad-csvc-db-count": 0, + "wad-source-affinity": "enable", + "wad-memory-change-granularity": 10, + "login-timestamp": "disable", + "ip-conflict-detection": "disable", + "miglogd-children": 0, + "special-file-23-support": "disable", + "log-uuid-address": "disable", + "log-ssl-connection": "disable", + "gui-rest-api-cache": "disable", + "http-request-limit": 524288000, + "http-unauthenticated-request-limit": 131072, + "rest-api-key-url-query": "disable", + "arp-max-entry": 131072, + "ha-affinity": "1", + "bfd-affinity": "1", + "cmdbsvr-affinity": "1", + "ndp-max-entry": 0, + "br-fdb-max-entry": 8192, + "max-route-cache-size": 0, + "ipsec-asic-offload": "enable", + "ipsec-round-robin": "disable", + "device-idle-timeout": 300, + "user-device-store-max-devices": 20109, + "user-device-store-max-users": 20109, + "user-device-store-max-unified-mem": 100547993, + "gui-device-latitude": "66.543508", + "gui-device-longitude": "25.8467468", + "private-data-encryption": "disable", + "auto-auth-extension-device": "enable", + "gui-theme": "neutrino", + "gui-date-format": "yyyy/MM/dd", + "gui-date-time-source": "system", + "igmp-state-limit": 3200, + "cloud-communication": "disable", + "ipsec-ha-seqjump-rate": 10, + "fortitoken-cloud": "enable", + "fortitoken-cloud-push-status": "enable", + "fortitoken-cloud-region": "", + "fortitoken-cloud-sync-interval": 24, + "faz-disk-buffer-size": 0, + "irq-time-accounting": "auto", + "management-ip": "", + "management-port": 443, + "management-port-use-admin-sport": "enable", + "forticonverter-integration": "disable", + "forticonverter-config-upload": "disable", + "internet-service-database": "standard", + "internet-service-download-list": [], + "early-tcp-npu-session": "disable", + "npu-neighbor-update": "disable", + "delay-tcp-npu-session": "disable", + "interface-subnet-usage": "enable", + "sflowd-max-children-num": 6, + "fortigslb-integration": "disable" + }, + "vdom": "root", + "path": "system", + "name": "global", + "action": "", + "status": "success", + "http_status": 200, + "serial": "FGT60F0123456789", + "version": "v7.4.8", + "build": 2795 +} From f60193a79bc2d0efe41c94c10d1948139b5696dd Mon Sep 17 00:00:00 2001 From: Michael Lucka Date: Thu, 5 Feb 2026 14:26:20 +0100 Subject: [PATCH 2/4] probe System/Global/Location added to README.md Signed-off-by: Michael Lucka --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6a671b28..ca576b08 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,7 @@ To improve security, limit permissions to required ones only (least privilege pr |System/AvailableCertificates | *any* |api/v2/monitor/system/available-certificates | |System/Central-management/Status | sysgrp.cfg |api/v2/monitor/system/central-management/status| |System/Fortimanager/Status | sysgrp.cfg |api/v2/monitor/system/fortimanager/status | +|System/Global/Location | sysgrp.cfg |api/v2/cmdb/system/global | |System/HAStatistics | sysgrp.cfg |api/v2/monitor/system/ha-statistics
api/v2/cmdb/system/ha | |System/Interface | netgrp.cfg |api/v2/monitor/system/interface/select | |System/Interface/Transceivers| *any* |api/v2/monitor/system/interface/transceivers | From 6fab51baa3ab567ee991be568cfe4b49bf463c95 Mon Sep 17 00:00:00 2001 From: Michael Lucka Date: Thu, 5 Feb 2026 16:12:53 +0100 Subject: [PATCH 3/4] metric fortigate_location_info added to metrics.md Signed-off-by: Michael Lucka --- metrics.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/metrics.md b/metrics.md index 018ea954..79b77611 100644 --- a/metrics.md +++ b/metrics.md @@ -4,6 +4,8 @@ Global: * _Network/Dns/Latency_ * `fortigate_network_dns_latency_` + * _System/Global/Location_ + * `fortigate_location_info` * _System/SensorInfo_ * `fortigate_sensor_alarm_status` * `fortigate_sensor_fan_rpm` From 8b1e6552dea9b7fefd27dc4202279ab0bf4b57a6 Mon Sep 17 00:00:00 2001 From: Michael Lucka Date: Fri, 6 Feb 2026 09:08:11 +0100 Subject: [PATCH 4/4] README.md - Fortigate Configuration section corrected/updated Signed-off-by: Michael Lucka --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ca576b08..98fbe8aa 100644 --- a/README.md +++ b/README.md @@ -191,19 +191,23 @@ To improve security, limit permissions to required ones only (least privilege pr |System/Ntp/Status | netgrp.cfg |api/v2/monitor/system/ntp/status | |System/Resource/Usage | sysgrp.cfg |api/v2/monitor/system/resource/usage | |System/Resource/Usage/VDOM | sysgrp.cfg |api/v2/monitor/system/resource/usage | +|System/SDNConnector | sysgrp.cfg |api/v2/monitor/system/sdn-connector/status | |System/SensorInfo | sysgrp.cfg |api/v2/monitor/system/sensor-info | |System/Status | *any* |api/v2/monitor/system/status | |System/Time/Clock | sysgrp.cfg |api/v2/monitor/system/time | -|System/System/VDOMResource | sysgrp.cfg |api/v2/monitor/system/vdom-resource | +|System/VDOMResource | sysgrp.cfg |api/v2/monitor/system/vdom-resource | +|System/HAChecksum | sysgrp.cfg |api/v2/monitor/system/ha-checksums | |User/Fsso | authgrp |api/v2/monitor/user/fsso | |VPN/IPSec | vpngrp |api/v2/monitor/vpn/ipsec | |VPN/Ssl/Connections | vpngrp |api/v2/monitor/vpn/ssl | |VPN/Ssl/Stats | vpngrp |api/v2/monitor/vpn/ssl/stats | |VirtualWAN/HealthCheck | netgrp.cfg |api/v2/monitor/virtual-wan/health-check | +|WebUI/State | sysgrp.cfg |api/v2/monitor/web-ui/state | |Wifi/APStatus | wifi |api/v2/monitor/wifi/ap_status | |Wifi/Clients | wifi |api/v2/monitor/wifi/client | |Wifi/ManagedAP | wifi |api/v2/monitor/wifi/managed_ap | |Switch/ManagedSwitch | switch |api/v2/monitor/switch-controller/managed-switch| +|OSPF/Neighbors | netgrp.route-cfg |api/v2/monitor/router/ospf/neighbors | If you omit to grant some of these permissions you will receive log messages warning about 403 errors and relevant metrics will be unavailable, but other metrics will still work. If you do not need some probes to be run, do not grant permission for them and use `include/exclude` feature (see `Usage` section).