Network interface ipv4 subnet does not match across cluster members-fortinet-FortiOS

error
high-availability
fortios
fortinet
Network interface ipv4 subnet does not match across cluster members-fortinet-FortiOS
0

#1

Network interface ipv4 subnet does not match across cluster members-fortinet-FortiOS

Vendor: fortinet

OS: FortiOS

Description:
Indeni will identify when two devices are part of a cluster and alert if their network interface ipv4 subnet are different.

Remediation Steps:
Ensure the network interface ipv4 subnet setting matches across devices in a cluster.

How does this work?
This script logs into the Fortinet firewall using SSH and retrieves the output of the “get system interface physical” and “get system interface” FortiOS commands. The output includes all the interface related information and statistics.

Why is this important?
Capture the interface IPv4 subnet mask.

Without Indeni how would you find this?
It is possible to poll this data through SNMP.

fortios-get-system-interface

#! META
name: fortios-get-system-interface
description: Fortinet firewall check interface stats
type: monitoring
monitoring_interval: 1 minute
requires:
    vendor: fortinet
    os.name: FortiOS
    product: firewall
    vdom_enabled: false
    vdom_root: true

# --------------------------------------------------------------------------------------------------
# The script (fortios-get-system-interface) is using two different commands
# and publishes the following metrics (note that all metrics have the "name" tag)
#
# A. For the physical interfaces (get system interface physical):
#
# [network-interface-state]         [number (0|1), the up/down status ] (live-config)
# [network-interface-speed]         [string, speed of the interface e.g 1000Mbps, 100Mbps]
# [network-interface-duplex]        [string, the duplex interface type e.g. full, half duplex]
# [network-interface-mode]          [string, the interface ip addressing type: mode {static | dhcp | pppoe}]
# [network-interface-type]          [string, the interface type e.g. physical, tunnel, vdom-link]
# [network-interface-ipv4-address]  [string, the ipv4 address]
# [network-interface-ipv4-subnet]   [string, the mask]
# [network-interface-admin-state]   [1 always, this info is not pvovided via a get command]
#
# B. For the non-physical interfaces (get system interface):
#
# [network-interface-state]         [number (0|1), the up/down status] (live-config)
# [network-interface-type]          [string, the interface type]
# [network-interface-ipv4-address]  [string, the ipv4 address]
# [network-interface-ipv4-subnet]   [string, the mask]
# [network-interface-admin-state]   [1 always, this info is not pvovided via a get command]
# --------------------------------------------------------------------------------------------------

#! COMMENTS
network-interface-state:
    why: |
        Capture the physical interface state. If a physical interface transitions from up to down an alert will be raised. More details can be found to the next link:http://help.fortinet.com/cli/fos50hlp/56/Content/FortiOS/fortiOS-cli-ref-56/config/system/interface.htm
    how: |
        This script logs into the Fortinet firewall using SSH and retrieves the output of the "get system interface physical" and "get system interface" FortiOS commands. The output includes all the interface related information and statistics.
    without-indeni: |
        It is possible to poll this data through SNMP. Interface state transitions will generate a syslog event.
    can-with-snmp: true
    can-with-syslog: true

network-interface-speed:
    why: |
        Capture the physical interface speed in human readable format such as 1G, 10G, etc. More details can be found to the next link: http://help.fortinet.com/cli/fos50hlp/56/Content/FortiOS/fortiOS-cli-ref-56/config/system/interface.htm
    how: |
        This script logs into the Fortinet firewall using SSH and retrieves the output of the "get system interface physical" and "get system interface" FortiOS commands. The output includes all the interface related information and statistics.
    without-indeni: |
        It is possible to poll this data through SNMP.
    can-with-snmp: true
    can-with-syslog: false

network-interface-duplex:
    why: |
        Capture the physical interface duplex in human readable format such as full or half. In modern network environments it is very uncommon to see half-duplex interfaces, and that should be an indication for a potential exception.  More details can be found to the next link: http://help.fortinet.com/cli/fos50hlp/56/Content/FortiOS/fortiOS-cli-ref-56/config/system/interface.htm
    how: |
        This script logs into the Fortinet firewall using SSH and retrieves the output of the "get system interface physical" FortiOS command. The output includes all the interface related information and statistics.
    without-indeni: |
        It is possible to poll this data through SNMP.
    can-with-snmp: true
    can-with-syslog: false

network-interface-mode:
    why: |
        Capture the the interface IP addressing allocation type: static, from external dhcp or external pppoe. More details can be found to the  next link: http://help.fortinet.com/cli/fos50hlp/56/Content/FortiOS/fortiOS-cli-ref-56/config/system/interface.htm
    how: |
        This script logs into the Fortinet firewall using SSH and retrieves the output of the "get system interface physical" FortiOS command. The output includes all the interface related information and statistics.
    without-indeni: |
        It is possible to poll this data through SNMP.
    can-with-snmp: false
    can-with-syslog: false

network-interface-type:
    why: |
        Capture the the interface type e.g. vdom-link, tunnel, physical. More details can be found to the  next link: http://help.fortinet.com/cli/fos50hlp/56/Content/FortiOS/fortiOS-cli-ref-56/config/system/interface.htm
    how: |
        This script logs into the Fortinet firewall using SSH and retrieves the output of the "get system interface physical" and "get system interface" FortiOS commands. The output includes all the interface related information and statistics.
    without-indeni: |
        It is possible to poll this data through SNMP.
    can-with-snmp: true
    can-with-syslog: false

network-interface-ipv4-address:
    why: |
        Capture the physical interface IPv4 address. 
    how: |
        This script logs into the Fortinet firewall using SSH and retrieves the output of the "get system interface physical" and "get system interface" FortiOS commands. The output includes all the interface related information and statistics.
    without-indeni: |
        It is possible to poll this data through SNMP.
    can-with-snmp: true
    can-with-syslog: false

network-interface-ipv4-subnet:
    why: |
        Capture the interface IPv4 subnet mask.
    how: |
        This script logs into the Fortinet firewall using SSH and retrieves the output of the "get system interface physical" and "get system interface" FortiOS commands. The output includes all the interface related information and statistics.
    without-indeni: |
        It is possible to poll this data through SNMP.
    can-with-snmp: true
    can-with-syslog: false

network-interface-admin-state:
    why: |
       This metric is set manually equals to 1. This is needed to overcome the limitation that the interface admin status information is not provided by the "get system interface" command output. 
    how: |
    without-indeni: |
    can-with-snmp: true
    can-with-syslog: false


#! REMOTE::SSH
get system interface physical
get system interface


#! PARSER::AWK

#----------------------------------------------------------------------------------------------------------
#
# Helper function used to extract a specific part of a string [key1: value1 key2: value2.... keyN: valueN] string.
# Example: Retrieve the 'type' value from the string [status: up type: tunnel  sflow-sampler: disable  ...]
#   stringLine:     is the full line from where we will extract the value
#   stringKey:      is the key that we will use for extraction (example: 'type:')
#   indexOfValue:   is the index of the value (there is a special case of the ip value that has two parts)
#
#----------------------------------------------------------------------------------------------------------
function getValueOf(stringLine, stringKey, indexOfValue){

    # Set default value of indexOfValue to '2'
    if (length(indexOfValue) == 0) {
        indexOfValue = 2
    }

    # Find the index of the expected 'key'
    indexStart = index(stringLine, stringKey)

    if (indexStart !=0){
        # if the key exist then substring and take only the needed part
        stringToUse = substr(stringLine, indexStart)
        gsub(/[ ]{2,}/, " ", stringToUse)

        # split string using /\s/, and return the complete array. The second item is the value
        lengthOfArray = split(stringToUse, array_string, /\s/)
        if (indexOfValue <= lengthOfArray) {
            return array_string[indexOfValue]
        }
        return ""
    }
    return ""
}


BEGIN {

    # We store in a table all the physical-interface-metrics. The table-index is 'table_index_physical'
    table_index_physical = 0

    # We store in a table all the non-physical-interface-metrics. The table-index is 'table_index_non_physical'
    table_index_non_physical = 0

    # A flag is set to '1' ONLY if the processing line belongs to the first command (section-a).
    # By default is section-a, meaning that we are processing data of the physical interfaces
    is_in_section_a = 1

}

# Important: this section must be in the top. Checks if we are in section-b (the second command output)
# If a line starts with 'name', then we are processing section-b, and mark the flag 'is_in_section_a' to false
#name: wan1   mode: static    ip: 212.205.216.194 255.255.255.240   status: up    netbios-forward: disable    type: physical   netflow-sampler: disable    sflow-sampler: disable    scan-botnet-connections: block    src-check: enable    mtu-override: disable    wccp: disable    drop-overlapped-fragment: disable    drop-fragment: disable
/^name/ {

    # we set the flag to false
    is_in_section_a = 0

}

# A. This part is for parsing the first command (get system interface physical)
# Parse the interface name. This is also the "start" of the table-row.
# So we increment the table-index
#        ==[mgmt]
/\s==\[/ && (is_in_section_a == 1) {

    # Increase table index, and store the name of the physical-interface
    table_index_physical++
    table_interface_physical["name", table_index_physical] = substr($1, 4, length($1)-4)
}

# A. This part is for parsing the first command (get system interface physical)
#                mode: static
/\smode: / && (is_in_section_a == 1) {

    # Store the mode of the physical-interface
    table_interface_physical["mode", table_index_physical] = $2

}

# A. This part is for parsing the first command (get system interface physical)
#                ip: 10.10.8.59 255.255.255.0
/\sip: / && (is_in_section_a == 1) {

    # Store the ip and subnet of the physical-interface
    table_interface_physical["ip-address", table_index_physical] = $2
    table_interface_physical["ip-subnet", table_index_physical] = $3

}

# A. This part is for parsing the first command (get system interface physical)
#                status: up
/status: / && (is_in_section_a == 1){


    # Store the status (1 | 0) of the physical-interface

    # Set default value to '0'. Set to 1 only if the value is "up"
    interface_state = 0
    if(tolower(trim($2)) == "up"){
        interface_state = 1
    }
    table_interface_physical["state", table_index_physical] = interface_state

}

# A. This part is for parsing the first command (get system interface physical)
#                speed: n/a
#                speed: 1000Mbps (Duplex: full)
/speed:\s/ && (is_in_section_a == 1){

    table_interface_physical["speed", table_index_physical] = $2

    # Parse "Duplex" only if the word "Duplex" exist
    if (NF == 4 && $3 == "(Duplex:") {

        duplex_type = tolower($4)

        # removing last character ')' from duplex type
        duplex_type = substr(duplex_type, 1, length(duplex_type)-1)
        table_interface_physical["duplex", table_index_physical] = duplex_type

    }
}


# B. This part is for parsing the second command (get system interface)
#Example of none-physical type of interfaces
#name: ssl.root   ip: 0.0.0.0 0.0.0.0   status: up    netbios-forward: disable    type: tunnel   netflow-sampler: disable    sflow-sampler: disable    scan-botnet-connections: disable    src-check: enable    wccp: disable
#name: ssl.dmgmt-vdom   ip: 0.0.0.0 0.0.0.0   status: up    netbios-forward: disable    type: tunnel   netflow-sampler: disable    sflow-sampler: disable    scan-botnet-connections: disable    src-check: enable    explicit-web-proxy: disable    explicit-ftp-proxy: disable    proxy-captive-portal: disable    wccp: disable
/^name:/{

    # Read interface type. Parse only if it is NOT "physical"
    interface_type = getValueOf($0, "type:")

    if (interface_type != "physical" && interface_type != "") {

        table_index_non_physical++
        table_interface_non_physical["name", table_index_non_physical] = $2

        # Read interface_status (up | down) and convert to number
        interface_status = getValueOf($0, "status:")
        interface_status_number = 0
        if (tolower(interface_status) == "up") {
            interface_status_number = 1
        }

        ip_address = getValueOf($0, "ip:", 2)
        ip_subnet = getValueOf($0, "ip:", 3)
        interface_type = getValueOf($0, "type:")

        table_interface_non_physical["type", table_index_non_physical] = interface_type
        table_interface_non_physical["state", table_index_non_physical] = interface_status_number
        table_interface_non_physical["ip-address", table_index_non_physical] = ip_address
        table_interface_non_physical["ip-subnet", table_index_non_physical] = ip_subnet

    }
}

END {

    # Publish physical interface metrics
    for (table_i = 1; table_i < table_index_physical + 1; table_i++) {
        tags["name"] = table_interface_physical["name", table_i]

        writeDoubleMetricWithLiveConfig("network-interface-state", tags, "gauge", 60, table_interface_physical["state", table_i], "Physical Network Interface status", "state", "name")
        writeDoubleMetric("network-interface-admin-state", tags, "gauge", 60, 1)
        writeComplexMetricString("network-interface-duplex", tags,  table_interface_physical["duplex", table_i])
        writeComplexMetricString("network-interface-mode", tags,  table_interface_physical["mode", table_i])
        writeComplexMetricString("network-interface-type", tags,  "physical")
        writeComplexMetricString("network-interface-ipv4-address", tags,  table_interface_physical["ip-address", table_i])
        writeComplexMetricString("network-interface-ipv4-subnet", tags,  table_interface_physical["ip-subnet", table_i])

        # due to alert requirement, we add different tag for speed
        speed_tags["name"] = table_interface_physical["name", table_i]
        speed_tags["alert-item-port-speed"] = speed_tags["name"] "-" table_interface_physical["speed", table_i]
        writeComplexMetricString("network-interface-speed", speed_tags,  table_interface_physical["speed", table_i])
    }

    # Publish non-physical interface metrics
    for (table_i = 1; table_i < table_index_non_physical + 1; table_i++) {
        tags["name"] = table_interface_non_physical["name", table_i]

        writeDoubleMetricWithLiveConfig("network-interface-state", tags, "gauge", 60, table_interface_non_physical["state", table_i], "Non-Physical Network Interfaces status", "state", "name")
        writeDoubleMetric("network-interface-admin-state", tags, "gauge", 60, 1)

        writeComplexMetricString("network-interface-type", tags,  table_interface_non_physical["type", table_i])
        writeComplexMetricString("network-interface-ipv4-address", tags,  table_interface_non_physical["ip-address", table_i])
        writeComplexMetricString("network-interface-ipv4-subnet", tags,  table_interface_non_physical["ip-subnet", table_i])
    }
}




CrossVendorClusterInterfaceIpv4SubnetVsx

package com.indeni.server.rules.library.templatebased.crossvendor

import com.indeni.server.rules.RuleContext
import com.indeni.server.rules.library.{ConditionalRemediationSteps, SnapshotComparisonTemplateRule}
import com.indeni.server.common.data.conditions.{Equals => DataEquals}

/**
  *
  */
case class CrossVendorClusterInterfaceIpv4SubnetVsx() extends SnapshotComparisonTemplateRule(
  ruleName = "CrossVendorClusterInterfaceIpv4SubnetVsx",
  ruleFriendlyName = "Clustered Devices: Network interface ipv4 subnet does not match across cluster members",
  ruleDescription = "Indeni will identify when two devices are part of a cluster and alert if their network interface ipv4 subnet are different.",
  metricName = "network-interface-ipv4-subnet",
  applicableMetricTag = "name",
  descriptionMetricTag = "vs.name",
  metaCondition = DataEquals("vsx", "true"),
  isArray = false,
  alertDescription = "Devices that are part of a cluster must have the same network interface ipv4 subnet setting. Review the differences below.",
  baseRemediationText = "Ensure the network interface ipv4 subnet setting matches across devices in a cluster.")()


case class CrossVendorClusterInterfaceIpv4SubnetNonVsx() extends SnapshotComparisonTemplateRule(
  ruleName = "CrossVendorClusterInterfaceIpv4SubnetNonVsx",
  ruleFriendlyName = "Clustered Devices: Network interface ipv4 subnet does not match across cluster members",
  ruleDescription = "Indeni will identify when two devices are part of a cluster and alert if their network interface ipv4 subnet are different.",
  metricName = "network-interface-ipv4-subnet",
  applicableMetricTag = "name",
  metaCondition = !DataEquals("vsx", "true"),
  isArray = false,
  alertDescription = "Devices that are part of a cluster must have the same network interface ipv4 subnet setting. Review the differences below.",
  baseRemediationText = "Ensure the network interface ipv4 subnet setting matches across devices in a cluster.")()