Skip to main content
Intermediate

Create an exportable function to validate a device setting for any device type

  • March 10, 2025
  • 0 replies
  • 62 views
  • Translate

Christopher
Employee
Forum|alt.badge.img+3

Is often necessary to audit the same setting across multiple vendors.

For example, the NTP server setting differs across vendors.

You can write an exportable function that returns the NTP servers for ANY device type.

The following NQE query creates a reusable function called getNtpServers with the export command. The function takes a single argument device and returns a list of NTP servers based on the device type. A different function is called using the when statement based on the OS. 

Note that the NTP server configuration in Cisco devices is in the device.files.config file (the running configuration), but for Fortinet and F5 devices you need the result of a Custom Command to see the NTP configuration.

Note also that the same nested function getCiscoNtpServers is used for multiple Cisco OS types.

If you find an OS type or pattern that isn’t identified, you can add in new patterns and functions, or alter or expand the patterns already included.

/**
 * @intent Helper function getNtpServers(device: Device) returns a list of NTP servers for a given device.
 * @description Helper function getNtpServers(device: Device) returns a list of NTP servers for a given device.
 * 
 * NOTE - A custom command must be added for the function to work for the following device types:
 * 
 * Fortinet - show system ntp
 * F5 - list sys ntp
 */

patternFortinet =
  ```
config system ntp
  config ntpserver
    edit {number}
      set server {ntpServer:string}
```;

patternPaloAlto =
  ```
config
  devices
    localhost.localdomain
      deviceconfig
        system
          ntp-servers
            primary-ntp-server
              ntp-server-address {primaryNtpServer:string}
            secondary-ntp-server
              ntp-server-address {secondaryNtpServer:string}
```;

patternCisco = ```
ntp server
```;

patternIosXr =
  ```
ntp
  server vrf {vrf:string} {ntpServer:string} {string*}
```;

patternArista = ```
ntp server
```;

patternF5 = ```
sys ntp
  servers {ntpServer:(string*)}
```;

patternJunos = ```
system
  ntp
    server {ntpServer:string}
```;

patternDell = ```
sntp server {ntpServer:string}
```;

patternDell2 = ```
ntp server {ntpServer:string}
```;

patternVersa = ```
ntp
  server {ntpServer:string}
```;

getFortinetNtpServers(device) =
  foreach command in device.outputs.commands
  where command.commandText == "show system ntp"
  let commandString = command.response
  let editedCommandString = replace(commandString, "\"", "")
  let parsedCommand = parseConfigBlocks(OS.FORTINET, editedCommandString)
  foreach match in blockMatches(parsedCommand, patternFortinet)
  select { serverId: match.data.ntpServer };

getPaloAltoNtpServers(device) =
  foreach match in blockMatches(device.files.config, patternPaloAlto)
  let serverList = [match.data.primaryNtpServer, match.data.secondaryNtpServer]
  foreach server in serverList
  select { serverId: server };

getCiscoNtpServers(device) =
  foreach x in [0]
  let lines = (foreach match in blockMatches(device.files.config, patternCisco)
               select match.blocks)
  foreach line in lines
  let lineString = toString(line)
  let validRegex = re`^ntp server(?: vrf \S+)? (\d{1,3}(?:\.\d{1,3}){3}|[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}|[a-zA-Z0-9.\-]+)`
  foreach serverMatch in regexMatches(lineString, validRegex)
  select { serverId: serverMatch.data["1"] };

getIosXrNtpServers(device) =
  foreach match in blockMatches(device.files.config, patternIosXr)
  select { serverId: match.data.ntpServer };

getAristaNtpServers(device) =
  foreach x in [0]
  let lines = (foreach match in blockMatches(device.files.config, patternArista)
               select match.blocks)
  foreach line in lines
  let lineString = toString(line)
  let validRegex = re`^ntp server(?: vrf \S+)? (\d{1,3}(?:\.\d{1,3}){3}|[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}|[a-zA-Z0-9.\-]+)`
  foreach serverMatch in regexMatches(lineString, validRegex)
  select { serverId: serverMatch.data["1"] };

getF5NtpServers(device) =
  foreach command in device.outputs.commands
  where command.commandText == "list sys ntp"
  let commandString = command.response
  let editedCommandString = replace(commandString, "{", "")
  let editedCommandString = replace(editedCommandString, "}", "")
  let parsedCommand = parseConfigBlocks(OS.F5, editedCommandString)
  foreach match in blockMatches(parsedCommand, patternF5)
  foreach entry in match.data.ntpServer
  select { serverId: entry };

getJunosNtpServers(device) =
  foreach match in blockMatches(device.files.config, patternJunos)
  select { serverId: match.data.ntpServer };

getDellNtpServers(device) =
  foreach x in [0]
  let serverList1 = (foreach match
                       in blockMatches(device.files.config, patternDell)
                     select match.data.ntpServer)
  let serverList2 = (foreach match
                       in blockMatches(device.files.config, patternDell2)
                     select match.data.ntpServer)
  let serverList = if length(serverList1) > length(serverList2)
                   then serverList1
                   else serverList2
  foreach server in serverList
  select { serverId: server };

getVersaNtpServers(device) =
  foreach match in blockMatches(device.files.config, patternVersa)
  select { serverId: match.data.ntpServer };

export getNtpServers(device: Device) =
  foreach x in [0]
  let ntpServers = when device.platform.os is
                     FORTINET -> getFortinetNtpServers(device);
                     PAN_OS -> getPaloAltoNtpServers(device);
                     IOS -> getCiscoNtpServers(device);
                     NXOS -> getCiscoNtpServers(device);
                     IOS_XR -> getIosXrNtpServers(device);
                     IOS_XE -> getCiscoNtpServers(device);
                     ASA -> getCiscoNtpServers(device);
                     ARISTA_EOS -> getAristaNtpServers(device);
                     F5 -> getF5NtpServers(device);
                     JUNOS -> getJunosNtpServers(device);
                     DELL_OS6 -> getDellNtpServers(device);
                     DELL_OS9 -> getDellNtpServers(device);
                     DELL_OS10 -> getDellNtpServers(device);
                     DELL_SONIC -> getDellNtpServers(device);
                     VERSA -> getVersaNtpServers(device);
                     otherwise -> [{ serverId: null : String }]
  foreach server in ntpServers
  select server.serverId;

Create a folder called Helper Functions in the root of your Org Repository, and save the above NQE query as Get NTP Servers.

Then create another query that imports and uses the function as follows:

import "Helper Functions/Get NTP Servers";

foreach device in network.devices
where device.platform.vendor != Vendor.FORWARD_CUSTOM
let ntpServers = getNtpServers(device)
select {
  violation: !isPresent(max(ntpServers)),
  device: device.name,
  vendor: device.platform.vendor,
  OS: device.platform.os,
  "NTP Servers": ntpServers
}

This query will show a violation if the list ntpServers is empty. Also note that Forward Networks synthetic devices are ignored, since they have no NTP configuration.

Notice the reusable simplicity of the line:

 let ntpServers = getNtpServers(device)

The function can be used within any other NQE query to find the NTP Servers for a given device.

You can create Helper Functions for settings like DNS, login banner, SNMP config, and more and add them to the Helper Functions folder.

A similar approach to that above for getting DNS servers can be seen here:

 

Did this topic help you find an answer to your question?

0 replies

Be the first to reply!

Reply


Cookie policy

We use cookies to enhance and personalize your experience. If you accept you agree to our full cookie policy. Learn more about our cookies.

 
Cookie settings