Create a Consistent Report Across Multiple Fortinet Devices and Versions

  • 5 April 2024
  • 1 reply

Userlevel 3

Keeping your systems up-to-date with the current operating system versions is hard enough.  Firewall vendors, with IPS, URL filtering, and now AI/ML functionality adds a bit more complexity of ensuring that all the policy enforcement points are consistent and up to date. 

The purpose of this NQE is to unify the Fortinet Version output for comparison and inventory analysis.  Version consistency, license consistency, and uptime is easily observed in this tabular NQE report. 

Building upon @cariddir examples of using device specific commands, or adding Custom commands, this NQE uses techniques shown in his posts.

The example of using device specific commands shown in the prior examples is powerful to extend the functionality of NQE.  As well as to pull reports that may be tedious to process if performed manually.  Since this information is already available in the Forward Networks platform, why not use the platform to data mine the insights desired.

The output of Fortinet’s Version contains many variables.  Depending upon the Model and Licenses enabled, each system will have specific Version output values that are unique.  OK, let’s parse the values out either in block mode or individually. 

* @intent For Fortinet devices, show the Version information.
* @description Show Version information for Fortinet devices. Some Version information is consistent across all platforms. Some output is contingent upon license and platform details.
// Consistent Version output for all Fortinet platforms is in this Block.
// Note that for a blockMatch, all values must be present.
pattern_version =
Version: {platform:(string) version:(string) ga:(string)}
Serial-Number: {serial:string}
Hostname: {hostname:string}
Operation Mode: {opermode:string}
Current virtual domain: {currvirt:string}
FIPS-CC mode: {fipscc:string}
Branch point: {branch:string}
Release Version Information: {versioninfo:string}

System time: {systemtime:(string*)}
/* These Version output values are variable. In other words, they may not exist on all platforms or could be license dependent.
Note that some versions of Fortinet have a space prior to the word "Log" which results in an exact blockMatches() failure.
Log hard disk: {loghdd:(string*)}
Virus-DB: {virusdb:(string*)}
IPS-DB: {ipsdb:(string*)}
APP-DB: {appdb:(string*)}
Uptime: {uptime:(string*)}
Extreme DB: {extremedb: (string *)}
Botnet DB: {botnet: (string *)}
License Status: {licstatus: (string)}
VM Resources: {vmresources: (string *)}
System Part-Number: {partnum: (string)}
Cluster uptime: {clustuptime: (string *)}
Cluster state change time: {clustchgtime: (string *)}
FortiOS x86-64: {fortios: (string)}
Virtual domain status: {virtdomstatus: (string*)}
Virtual domain configuration: {virtdomconf: (string)}
Security Level: {seclevel: {string}}
foreach device in network.devices
// where device.platform.os == OS.FORTINET
where device.platform.vendor == Vendor.FORTINET
let platform = device.platform
let outputs = device.outputs
foreach command in outputs.commands
where command.commandType == CommandType.VERSION
let configurations = parseConfigBlocks(OS.FORTINET, command.response)
foreach version_child in blockMatches(configurations, pattern_version)
let systemtime = join(" ",
// ---- Determined is that not all values are output in the "get system status" output.
// ---- These values may or may not show up in the output.

let loghdd = (
foreach line in configurations
let loghdd = patternMatch(line.text, `Log hard disk: {loghdd:(string*)}`)
where isPresent(loghdd)
let loghdd = join(" ", loghdd.loghdd)
select loghdd)

let virusdb = (
foreach line in configurations
let virusdb = patternMatch(line.text, `Virus-DB: {virusdb:(string*)}`)
where isPresent(virusdb)
let virusdb = join(" ", virusdb.virusdb)
select virusdb)

let ipsdb = (
foreach line in configurations
let ipsdb = patternMatch(line.text, `IPS-DB: {ipsdb:(string*)}`)
where isPresent(ipsdb)
let ipsdb = join(" ", ipsdb.ipsdb)
select ipsdb)

let appdb = (
foreach line in configurations
let appdb = patternMatch(line.text, `APP-DB: {appdb:(string*)}`)
where isPresent(appdb)
let appdb = join(" ",appdb.appdb)
select appdb)

let extremedb = (
foreach line in configurations
let extremedb = patternMatch(line.text, `Extreme DB: {extremedb:(string*)}`)
where isPresent(extremedb)
let extremedb = join(" ", extremedb.extremedb)
select extremedb)

let botnetdb = (
foreach line in configurations
let botnetdb = patternMatch(line.text, `Botnet DB: {botnetdb:(string*)}`)
where isPresent(botnetdb)
let botnetdb = join(" ", botnetdb.botnetdb)
select botnetdb)

let licstatus = (
foreach line in configurations
let licstatus = patternMatch(line.text, `License Status: {licstatus: (string)}`)
where isPresent(licstatus)
select licstatus.licstatus)

let vmresources = (
foreach line in configurations
let vmresrouces = patternMatch(line.text, `VM Resources: {vmresources: (string *)}`)
where isPresent(vmresrouces)
let vmresourcesdata = join(", ", vmresrouces.vmresources)
select vmresourcesdata)

let partnum = (
foreach line in configurations
let partnum = patternMatch(line.text, `System Part-Number: {partnum: (string)}`)
where isPresent(partnum)
select partnum)

let clusteruptime = (
foreach line in configurations
let clusteruptime = patternMatch(line.text, `Cluster uptime: {clustuptime: (string *)}`)
where isPresent(clusteruptime)
let clusteruptime = join(" ", clusteruptime.clustuptime)
select clusteruptime)

let clustchgtime = (
foreach line in configurations
let clustchgtime = patternMatch(line.text, `Cluster state change time: {clustchgtime: (string *)}`)
where isPresent(clustchgtime)
let clustchgtime = join(" ", clustchgtime.clustchgtime)
select clustchgtime)

let fortios = (
foreach line in configurations
let fortios = patternMatch(line.text, `FortiOS x86-64: {fortios: (string)}`)
where isPresent(fortios)
select fortios.fortios)

let extendeddb = (
foreach line in configurations
let extendeddb = patternMatch(line.text, `Extended DB: {extendeddb:(string*)}`)
where isPresent(extendeddb)
let extendeddb = join(" ", extendeddb.extendeddb)
select extendeddb)

let ipsetdb = (
foreach line in configurations
let ipsetdb = patternMatch(line.text, `IPS-ETDB: {ipsetdb:(string*)}`)
where isPresent(ipsetdb)
let ipsetdb = join(" ", ipsetdb.ipsetdb)
select ipsetdb)

let industrialdb = (
foreach line in configurations
let industrialdb = patternMatch(line.text, `INDUSTRIAL-DB: {industrialdb:(string*)}`)
where isPresent(industrialdb)
let industrialdb = join(" ", industrialdb.industrialdb)
select industrialdb)

let ips = (
foreach line in configurations
let ips = patternMatch(line.text, `IPS Malicious URL Database: {ips:(string*)}`)
where isPresent(ips)
let ips = join(" ", ips.ips)
select ips)

let maxdom = (
foreach line in configurations
let maxdom = patternMatch(line.text, `Max number of virtual domains: {maxdom:(string)}`)
where isPresent(maxdom)
select maxdom.maxdom)

let virtdomstatus = (
foreach line in configurations
let virtdomstatus = patternMatch(line.text, `Virtual domains status: {virtdomstatus:(string*)}`)
where isPresent(virtdomstatus)
let virtdomstatus = join(" ", virtdomstatus.virtdomstatus)
select virtdomstatus)

let virtdomconfig = (
foreach line in configurations
let virtdomconfig = patternMatch(line.text, `Virtual domain configuration: {virtdomconfig:string}`)
where isPresent(virtdomconfig)
select virtdomconfig.virtdomconfig)

let hamode = (
foreach line in configurations
let hamode = patternMatch(line.text, `Current HA mode: {hamode:(string*)}`)
where isPresent(hamode)
let hamode = join(" ", hamode.hamode)
select hamode)

let privencrypt = (
foreach line in configurations
let privencrypt = patternMatch(line.text, `Private Encryption: {privencrypt:string}`)
where isPresent(privencrypt)
select privencrypt.privencrypt)

let seclevel = (
foreach line in configurations
let seclevel = patternMatch(line.text, `Security Level: {seclevel:string}`)
where isPresent(seclevel)
select seclevel.seclevel)

let uptime = (
foreach line in configurations
let uptime = patternMatch(line.text, `Uptime: {uptime:(string*)}`)
where isPresent(uptime)
let uptime = join(" ", uptime.uptime)
select uptime)

select {
"Security Level": seclevel,
"Virus-DB": virusdb,
"Extended DB": extendeddb,
"Extreme DB": extremedb,
"Botnet DB:": botnetdb,
"IPS-DB": ipsdb,
"IPS-ETDB": ipsetdb,
"APP-DB": appdb,
"INDUSTRIAL-DB": industrialdb,
"IPS Malicious URL Database": ips,
"License Status": licstatus,
"VM Resources": vmresources,
"Log hard disk": loghdd,
"Private Encryption": privencrypt,
"Operation Mode":,
"Current virtual domain":,
"Max number of virtual domains": maxdom,
"Virtual domains status": virtdomstatus,
"Virtual domain configuration": virtdomconfig,
"FIPS-CC mode":,
"Current HA mode": hamode,
"Cluster uptime": clusteruptime,
"Cluster state change time": clustchgtime,
"Branch point":,
"Release Version Information":,
"FortiOS x86-64": fortios,
"System time": systemtime,
"Uptime": uptime

Note that the values that are consistent across all platforms uses the blockMatches() function.
The variable values use individual patternMatch() function.

To find which commands that the CommandType is associated with, use the IDE’s Data Model Search Bar.  This example output shows the commands associated with the VERSION CommandType for all platforms vendors.


If you happen to know the exact command, enter the command with a ‘ wrapped around it.  For example:
‘get system status’ reveals that this Fortinet command is associated with CommandType.VERSION.

What else could you do with this NQE?  How about determining how many licenses are in use for cost analysis?  Or, whether the FW is in standalone mode or active but in backup mode?  Or, something basic as making sure the system time is consistent?   Or, is the the private-data-encryption setting enabled? 

Feel free to give this a try and provide feedback.  If the NQE does not work, perhaps the blockMatches values need to be reduced. Remember that the entire blockMatches block needs to match.  Testing was performed on FN 24.3.x and the the Fortinet systems on hand.  In other words, there may be more values for the Version output.  Documentation seems to be a bit sparse that lists all possibilities. 

1 reply

Userlevel 3
Badge +2

This is great and worked out of the box. Looking to expand this to other platforms!