Da wir hier im Hause diverse Fortinet FortiGate, teilweise als Active/Passive Cluster, betreiben, liegt es natürlich nahe, dass diese Appliances ebenfalls mit in die Überwachung aufgenommen werden.
Kurzer Exkurs:
Die bei uns eingesetzten Fortinet FortiGates gehören zur Klasse der UTM Geräte (Unified Threat Management) und bieten unter anderem folgende Features:
Da mir die existierenden Fortinet Checks nicht wirklich gefallen haben (Code-Qualität, Art der Aufrufe, Funktionsumfang), habe ich mich entschieden ein eigenes Plugin in Perl zu schreiben (Download auf exchange.nagios.org):
Zum jetztigen Stand unterstützt es:
NAME
Check Fortinet FortiGate Appliances
SYNOPSIS
check_fortigate.pl -H -C -T [-w|-c|-S|-s|-R|-M|-V|-?]
Options:
-H --host STRING or IPADDRESS Check interface on the indicated host
-C --community STRING Community-String for SNMP
-T -- type STRING CPU, MEM, Ses, VPN, Cluster
-S --serial STRING Primary serial number
-s --slave get values of slave
-w --warning INTEGER Warning threshold, applies to cpu, mem, session.
-c --critical INTEGER Critical threshold, applies to cpu, mem, session.
-R --reset Resets ip file (cluster only)
-M --mode STRING Output-Mode: 0 => just print, 1 => print and show failed tunnel, 2 => critical
-V --vpnmode STRING VPN-Mode: both => IPSec & SSL/OpenVPN, ipsec => IPSec only, ssl => SSL/OpenVPN only
-? --help Returns full help text
OPTIONS
-H--host
STRING or IPADDRESS - Check interface on the indicated host.
-C|--community
STRING - Community-String for SNMP
-T|--type
STRING - CPU, MEM, Ses, VPN, Cluster
-S|--serial
STRING - Primary serial number.
-s|--slave
BOOL - Get values of slave
-w|--warning
INTEGER - Warning threshold, applies to cpu, mem, session.
-c|--critical
INTEGER - Critical threshold, applies to cpu, mem, session.
-R|--reset
BOOL - Resets ip file (cluster only)
-M|--mode
STRING - Output-Mode: 0 => just print, 1 => print and show
failed tunnel, 2 => critical
-V|--vpnmode
STRING - VPN-Mode: both => IPSec & SSL/OpenVPN, ipsec => IPSec
only, ssl => SSL/OpenVPN only
Auf den Geräten muss natürlich SNMP aktiviert werden:
Via Web:
Damit das Plugin auch schön in Nagios eingebunden werden kann, hier noch die commands.cfg.
Achtung, ich nutze hier Host Variablen http://nagios.sourceforge.net/docs/3_0/customobjectvars.html
Die ganzen Sachen gibt es natürlich auch direkt zum Download über exchange.nagios.org
Bei Fragen bitte wie immer melden.
Mal sehen, was die Recherche zu Juniper Plugins betrifft, ggf. wird der nächste Eintrag über dieses Thema sein :-)
Kurzer Exkurs:
Die bei uns eingesetzten Fortinet FortiGates gehören zur Klasse der UTM Geräte (Unified Threat Management) und bieten unter anderem folgende Features:
- IPS - Intrusion Prevention
- AntiVirus - Automatisierte Überprüfung auf Viren für z.B: HTTP, SMTP, POP3, IMAP, FTP
- VPN - IPSec / SSL, inkl. 2-Faktor Authentifizierung
- Proxy - inkl. Web-Filtering, SSO über LDAP/Radius bzw. SSO Client auf DC
- Deep Packet Inspection
- Firewall
- Routing
- IPv4 & IPv6
Da mir die existierenden Fortinet Checks nicht wirklich gefallen haben (Code-Qualität, Art der Aufrufe, Funktionsumfang), habe ich mich entschieden ein eigenes Plugin in Perl zu schreiben (Download auf exchange.nagios.org):
Zum jetztigen Stand unterstützt es:
- CPU
- Memory
- Sessions
- VPN
- Cluster-Zustand
Beschreibung:
NAME
Check Fortinet FortiGate Appliances
SYNOPSIS
check_fortigate.pl -H -C -T [-w|-c|-S|-s|-R|-M|-V|-?]
Options:
-H --host STRING or IPADDRESS Check interface on the indicated host
-C --community STRING Community-String for SNMP
-T -- type STRING CPU, MEM, Ses, VPN, Cluster
-S --serial STRING Primary serial number
-s --slave get values of slave
-w --warning INTEGER Warning threshold, applies to cpu, mem, session.
-c --critical INTEGER Critical threshold, applies to cpu, mem, session.
-R --reset Resets ip file (cluster only)
-M --mode STRING Output-Mode: 0 => just print, 1 => print and show failed tunnel, 2 => critical
-V --vpnmode STRING VPN-Mode: both => IPSec & SSL/OpenVPN, ipsec => IPSec only, ssl => SSL/OpenVPN only
-? --help Returns full help text
OPTIONS
-H--host
STRING or IPADDRESS - Check interface on the indicated host.
-C|--community
STRING - Community-String for SNMP
-T|--type
STRING - CPU, MEM, Ses, VPN, Cluster
-S|--serial
STRING - Primary serial number.
-s|--slave
BOOL - Get values of slave
-w|--warning
INTEGER - Warning threshold, applies to cpu, mem, session.
-c|--critical
INTEGER - Critical threshold, applies to cpu, mem, session.
-R|--reset
BOOL - Resets ip file (cluster only)
-M|--mode
STRING - Output-Mode: 0 => just print, 1 => print and show
failed tunnel, 2 => critical
-V|--vpnmode
STRING - VPN-Mode: both => IPSec & SSL/OpenVPN, ipsec => IPSec
only, ssl => SSL/OpenVPN only
Der Code:
#!/usr/bin/perl # This Plugin checks the cluster state of FortiGate # Tested on: FortiGate 100D / FortiGate 300C (both 5.0.3) # # Author: Oliver Skibbe (oliskibbe (at) gmail.com) # Date: 2013-07-29 # # Changelog: # - initial release (cluster, cpu, memory, session support) # - added vpn support, based on check_fortigate_vpn.pl: Copyright (c) 2009 Gerrit Doornenbal, g(dot)doornenbal(at)hccnet(dot)nl # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # If you wish to receive a copy of the GNU General Public License, # write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-130 # Description: use strict; use Net::SNMP; use List::Compare; use Switch; use Getopt::Long qw(:config no_ignore_case bundling); use Pod::Usage; # Parse out the arguments... my ($ip, $community, $type, $warn, $crit, $slave, $pri_serial, $reset_file, $mode, $vpnmode) = parse_args(); # Initialize variables.... my $net_snmp_debug_level = 0x0; # See http://search.cpan.org/~dtown/Net-SNMP-v6.0.1/lib/Net/SNMP.pm#debug()_-_set_or_get_the_debug_mode_for_the_module # for more information. my %status = ( 'UNKNOWN' => '-1', # Enumeration for the output Nagios states 'OK' => '0', 'WARNING' => '1', 'CRITICAL' => '2' ); ## SNMP ## my ($session, $error) = get_snmp_session($ip, $community); # Open an SNMP connection... ## OIDs ## my $oid_unitdesc = ".1.3.6.1.2.1.1.1.0"; # Location of Fortinet device description... (String) my $oid_serial = ".1.3.6.1.2.1.1.5.0"; # Location of Fortinet serial number (String) my $oid_cluster_type = ".1.3.6.1.4.1.12356.101.13.1.1.0"; # Location of Fortinet serial number (String) my $oid_cluster_serials = ".1.3.6.1.4.1.12356.101.13.2.1.1.2"; # Location of Cluster serials (String) # Generic my $oid_cpu = ".1.3.6.1.4.1.12356.101.4.1.3.0"; # Location of CPU (%) my $oid_mem = ".1.3.6.1.4.1.12356.101.13.2.1.1.4"; # Location of cluster member Mem (%) # Generic my $oid_mem = ".1.3.6.1.4.1.12356.101.4.1.4.0"; # Location of Mem (%) my $oid_cpu = ".1.3.6.1.4.1.12356.101.13.2.1.1.1"; # Location of cluster member CPU (%) # Generic my $oid_ses = ".1.3.6.1.4.1.12356.101.4.1.8.0"; # Location of Sessions (int) my $oid_ses = ".1.3.6.1.4.1.12356.101.13.2.1.1.6"; # Location of cluster member Sessions (int) # VPN OIDs my $oid_ActiveSSL = ".1.3.6.1.4.1.12356.101.12.2.3.1.2.1"; # Location of Fortinet firewall SSL VPN Tunnel connection count my $oid_ActiveSSLTunnel = ".1.3.6.1.4.1.12356.101.12.2.3.1.6.1"; # Location of Fortinet firewall SSL VPN Tunnel connection count my $oid_ipsectuntableroot = ".1.3.6.1.4.1.12356.101.12.2.2.1"; # Table of IPSec VPN tunnels my $oidf_tunstatus = ".20"; # Location of a tunnel's connection status my $oidf_tunndx = ".1"; # Location of a tunnel's index... my $oidf_tunname = ".3"; # Location of a tunnel's name... ## Stuff ## my $state; # return state my $path = "/usr/lib/nagios/plugins/FortiSerial"; # path to store serial filenames my $filename = $path . "/" . $ip; # file name to store serials my $oid; # helper var my $value; # helper var my $string; # return string my $perf; # performance data # Check SNMP connection and get the description of the device... my $curr_device = get_snmp_value($session, $oid_unitdesc); # Check SNMP connection and get the serial of the device... my $curr_serial = get_snmp_value($session, $oid_serial); switch ( lc($type) ) { case "cpu" { ($state, $string) = get_health_value($oid_cpu, "CPU", "%"); } case "mem" { ($state, $string) = get_health_value($oid_mem, "Memory", "%"); } case "ses" { ($state, $string) = get_health_value($oid_ses, "Session", ""); } case "vpn" { ($state, $string) = get_vpn_state(); } else { ($state, $string) = get_cluster_state(); } } # Close the connection close_snmp_session($session); # exit with a return code matching the state... print $string."\n"; exit($status{$state}); ######################################################################## ## Subroutines below here.... ######################################################################## sub get_snmp_session{ my $ip = $_[0]; my $community = $_[1]; my ($session, $error) = Net::SNMP->session( -hostname => $ip, -community => $community, -port => 161, -timeout => 5, -retries => 3, -debug => $net_snmp_debug_level, -version => 2, -translate => [-timeticks => 0x0] #schaltet Umwandlung von Timeticks in Zeitformat aus ); return ($session, $error); } # end get snmp session sub get_health_value { my $label = $_[1]; my $UOM = $_[2]; if ( $slave == 1 ) { $oid = $_[0] . ".2"; $label = "slave_" . $label; } else { $oid = $_[0] . ".1"; } $value = get_snmp_value($session, $oid); if ( $value > $crit ) { $state = "CRITICAL"; $string = $label . " is critical: " . $value . $UOM; } elsif ( $value > $warn ) { $state = "WARNING"; $string = $label . " is warning: " . $value . $UOM; } else { $state = "OK"; $string = $label . " is okay: " . $value. $UOM; } $perf = "|'" . lc($label) . "'=" . $value . $UOM . ";" . $warn . ";" . $crit; $string = $state . ": " . $curr_device . " (Master: " . $curr_serial .") " . $string . $perf; return ($state, $string); } # end health value sub get_cluster_state { my @help_serials; # helper array # get all cluster member serials my %snmp_serials = %{get_snmp_table($session, $oid_cluster_serials)}; my $cluster_type = get_snmp_value($session, $oid_cluster_type); my %cluster_types = (1 => "Standalone", 2 => "Active/Active", 3 => "Active/Passive"); # first time, write cluster members to helper file if ( ! -e $filename || $reset_file ) { # open file handle to write (create/truncate) open (SERIALHANDLE,"+>$filename") || die "Error while creating $filename"; # write serials to file while (($oid, $value) = each (%snmp_serials)) { print (SERIALHANDLE $value . "\n"); } } # snmp serials while (($oid, $value) = each (%snmp_serials)) { chomp; # remove "\n" if exists push @help_serials, $value; } # if less then 2 nodes found: critical if ( scalar(@help_serials) < 2 ) { $string = "HA (" . $cluster_types{$cluster_type} . ") inactive, single node found: " . $curr_serial; $state = "CRITICAL"; # else check if there are differences in ha nodes } else { # open existing serials open ( SERIALHANDLE, "$filename") || die "Error while opening file $filename"; my @file_serials = <SERIALHANDLE>; # push lines into file_serials chomp(@file_serials); # remove "\n" if exists in array elements close (SERIALHANDLE); # close file handle # compare serial arrays my $comparedList = List::Compare->new('--unsorted', \@help_serials, \@file_serials); if ( $comparedList->is_LequivalentR ) { $string = "HA (" . $cluster_types{$cluster_type} . ") is active"; $state = "OK"; } else { $string = "Unknown node in active HA (" . $cluster_types{$cluster_type} . ") found"; $state = "WARNING"; } } # end scalar count # if preferred master serial is not master if ( $pri_serial && ( $pri_serial ne $curr_serial ) ) { $string = $string . ", preferred master " . $pri_serial . " is not master!"; $state = "CRITICAL"; } # Write an output string... $string = $state . ": " . $curr_device . " (Master: " . $curr_serial . ", Slave: " . @help_serials[$#help_serials] . "): " . $string; return ($state, $string); } # end cluster state sub get_vpn_state { my $ipstunsdown = 0; my $ipstuncount = 0; my $ipstunsopen = 0; my $ActiveSSL = 0; my $ActiveSSLTunnel = 0; my $string_errors = ""; my %entitystate = ( '1' => 'down', # Enumeration for the tunnel up/down states '2' => 'up' ); $state = "OK"; # Unless specifically requesting IPSec checks only, do an SSL connection check if ($vpnmode ne "ipsec"){ $ActiveSSL = get_snmp_value($session, $oid_ActiveSSL); $ActiveSSLTunnel = get_snmp_value($session, $oid_ActiveSSLTunnel); } # Unless specifically requesting SSL checks only, do an IPSec tunnel check if ($vpnmode ne "ssl"){ # Get just the top level tunnel data my %tunnels = %{get_snmp_table($session, $oid_ipsectuntableroot . $oidf_tunndx)}; while (($oid, $value) = each (%tunnels)) { #Bump the total tunnel count $ipstuncount++; #print "Tunnel name (" . $oid_ipsectuntableroot . $oidf_tunname . "." . $ipstuncount . ") is: " . get_snmp_value($session, $oid_ipsectuntableroot . $oidf_tunname . "." . $ipstuncount) . "\n"; #print "Tunnel status (" . $oid_ipsectuntableroot . $oidf_tunstatus . "." . $ipstuncount . ") is: " . get_snmp_value($session, $oid_ipsectuntableroot . $oidf_tunstatus . "." . $ipstuncount) . "\n"; #If the tunnel is up, bump the connected tunnel count if ( $entitystate{get_snmp_value($session, $oid_ipsectuntableroot . $oidf_tunstatus . "." . $ipstuncount)} eq "up" ) { $ipstunsopen++; } else { #Tunnel is down. Add it to the failed counter $ipstunsdown++; # If we're counting failures and/or monitoring, put together an output error string of the tunnel name and its status if ($mode >= 1){ $string_errors .= ", "; $string_errors .= get_snmp_value($session, $oid_ipsectuntableroot . $oidf_tunname . "." . $ipstuncount)." ".$entitystate{get_snmp_value($session, $oid_ipsectuntableroot . $oidf_tunstatus . "." . $ipstuncount)}; } } } } #Set Unitstate my $unitstate="OK"; if (($mode >= 2 ) && ($vpnmode ne "ssl")) { if ($ipstunsdown == 1) { $unitstate="WARNING"; } if ($ipstunsdown >= 2) { $unitstate="CRITICAL"; } } # Write an output string... $string = $unitstate . ": " . $curr_device . " (Master: " . $curr_serial .")"; if ($vpnmode ne "ipsec") { #Add the SSL tunnel count $string = $string . ": Active SSL-VPN Connections/Tunnels: " . $ActiveSSL."/".$ActiveSSLTunnel.""; } if ($vpnmode ne "ssl") { #Add the IPSec tunnel count and any errors.... $string = $string . ": IPSEC Tunnels: Configured/Active: " . $ipstuncount . "/" . $ipstunsopen. " " . $string_errors; } # Create performance data $perf="|'ActiveSSL-VPN'=".$ActiveSSL." 'ActiveIPSEC'=".$ipstunsopen; $string = $string.$perf; # Check to see if the output string contains either "unkw", "WARNING" or "down", and set an output state accordingly... if($string =~/uknw/){ $state = "UNKNOWN"; } if($string =~/WARNING/){ $state = "WARNING"; } if($string =~/down/){ $state = "CRITICAL"; } return ($state, $string); } # end vpn state sub close_snmp_session{ my $session = $_[0]; $session->close(); } # end close snmp session sub get_snmp_value{ my $session = $_[0]; my $oid = $_[1]; my (%result) = %{get_snmp_request($session, $oid) or die ("SNMP service is not available on ".$ip) }; return $result{$oid}; } # end get snmp value sub get_snmp_request{ my $session = $_[0]; my $oid = $_[1]; return $session->get_request($oid) || die ("SNMP service not responding"); } # end get snmp request sub get_snmp_table{ my $session = $_[0]; my $oid = $_[1]; return $session->get_table( -baseoid =>$oid ); } # end get snmp table sub parse_args { my $ip = ""; my $community = "public"; my $pri_serial = ""; my $reset_file = ""; my $type = "status"; my $warn = 80; my $crit = 90; my $slave = 0; my $vpnmode = "both"; my $mode = 2; my $help = 0; pod2usage(-message => "UNKNOWN: No Arguments given", -exitval => 3, -verbose => 0) if ( !@ARGV ); GetOptions( 'host|H=s' => \$ip, 'type|T=s' => \$type, 'community|C:s' => \$community, 'serial|S:s' => \$pri_serial, 'vpnmode|V:s' => \$vpnmode, 'mode|M:s' => \$mode, 'warning|w:i' => \$warn, 'critical|c:i' => \$crit, 'slave|s:1' => \$slave, 'reset|R:1' => \$reset_file, 'help|?!' => \$help, ) or pod2usage(-exitval => 3, -verbose => 0); pod2usage(-exitval => 3, -verbose => 2) if $help; return ($ip, $community, $type, $warn, $crit, $slave, $pri_serial, $reset_file, $mode, $vpnmode); } __END__ =head1 NAME Check Fortinet FortiGate Appliances =head1 SYNOPSIS =item S<check_fortigate.pl -H -C -T [-w|-c|-S|-s|-R|-M|-V|-?]> Options: -H --host STRING or IPADDRESS Check interface on the indicated host -C --community STRING Community-String for SNMP -T -- type STRING CPU, MEM, Ses, VPN, Cluster -S --serial STRING Primary serial number -s --slave get values of slave -w --warning INTEGER Warning threshold, applies to cpu, mem, session. -c --critical INTEGER Critical threshold, applies to cpu, mem, session. -R --reset Resets ip file (cluster only) -M --mode STRING Output-Mode: 0 => just print, 1 => print and show failed tunnel, 2 => critical -V --vpnmode STRING VPN-Mode: both => IPSec & SSL/OpenVPN, ipsec => IPSec only, ssl => SSL/OpenVPN only -? --help Returns full help text =head1 OPTIONS =over 8 =item B<-H--host> STRING or IPADDRESS - Check interface on the indicated host. =item B<-C|--community> STRING - Community-String for SNMP =item B<-T|--type> STRING - CPU, MEM, Ses, VPN, Cluster =item B<-S|--serial> STRING - Primary serial number. =item B<-s|--slave> BOOL - Get values of slave =item B<-w|--warning> INTEGER - Warning threshold, applies to cpu, mem, session. =item B<-c|--critical> INTEGER - Critical threshold, applies to cpu, mem, session. =item B<-R|--reset> BOOL - Resets ip file (cluster only) =item B<-M|--mode> STRING - Output-Mode: 0 => just print, 1 => print and show failed tunnel, 2 => critical =item B<-V|--vpnmode> STRING - VPN-Mode: both => IPSec & SSL/OpenVPN, ipsec => IPSec only, ssl => SSL/OpenVPN only =back =head1 DESCRIPTION This plugin checks Fortinet FortiGate devices via SNMP =head2 From Web: =item 1. Select Network -> Interface -> Local interface =item 2. Administrative Access: Enable SNMP =item 3. Select Config -> SNMP =item 4. Enable SNMP, fill your details =item 5. SNMP v1/v2c: Create new =item 6. Configure for your needs, Traps are not required for this plugin! =head2 From CLI: config system interface edit "internal" set allowaccess ping https ssh snmp fgfm next end config system snmp sysinfo set description "DMZ1 FortiGate 300C" set location "Room 404" set conctact-info "BOFH" set status enable end config system snmp community edit 1 set events cpu-high mem-low fm-if-change config hosts edit 1 set interface "internal" set ip %SNMP Client IP% next end set name "public" set trap-v1-status disable set trap-v2c-status disable next end Thats it! =cut
Auf den Geräten muss natürlich SNMP aktiviert werden:
Via Web:
- Network -> Interface -> Interface, welches auf SNMP lauschen soll, auswählen. Achtung, nicht auf das externe Interface freigeben!
- Administrative Access: Häckchen bei SNMP setzen
- Config -> SNMP auswählen
- Häckchen bei SNMP
- SNMP v1/v2c: Create new
- Traps werden nicht benötigt, Rest kann nach eigenem Willen konfiguriert werden
- Login via SSH oder Web-CLI
config system interface edit "internal" set allowaccess ping https ssh snmp fgfm next end config system snmp sysinfo set description "DMZ1 FortiGate 300C" set location "Room 404" set conctact-info "BOFH" set status enable end config system snmp community edit 1 set events cpu-high mem-low fm-if-change config hosts edit 1 set interface "internal" set ip %SNMP Client IP% next end set name "public" set trap-v1-status disable set trap-v2c-status disable next end
Damit das Plugin auch schön in Nagios eingebunden werden kann, hier noch die commands.cfg.
Achtung, ich nutze hier Host Variablen http://nagios.sourceforge.net/docs/3_0/customobjectvars.html
commands.cfg:
define command { command_name check_fortigate_cpu command_line $USER1$/check_fortigate.pl -H $HOSTADDRESS$ -C $_HOSTSNMP_COMMUNITY$ -T cpu } define command { command_name check_fortigate_slave_cpu command_line $USER1$/check_fortigate.pl -H $HOSTADDRESS$ -C $_HOSTSNMP_COMMUNITY$ -T cpu -s } define command { command_name check_fortigate_mem command_line $USER1$/check_fortigate.pl -H $HOSTADDRESS$ -C $_HOSTSNMP_COMMUNITY$ -T mem } define command { command_name check_fortigate_slave_mem command_line $USER1$/check_fortigate.pl -H $HOSTADDRESS$ -C $_HOSTSNMP_COMMUNITY$ -T mem -s } define command { command_name check_fortigate_cluster command_line $USER1$/check_fortigate.pl -H $HOSTADDRESS$ -C $_HOSTSNMP_COMMUNITY$ -T cluster } define command { command_name check_fortigate_pri_cluster command_line $USER1$/check_fortigate.pl -H $HOSTADDRESS$ -C $_HOSTSNMP_COMMUNITY$ -T cluster -S $ARG1$ } define command { command_name check_fortigate_ses command_line $USER1$/check_fortigate.pl -H $HOSTADDRESS$ -C $_HOSTSNMP_COMMUNITY$ -T ses -w 250 -c 350 } define command { command_name check_fortigate_slave_ses command_line $USER1$/check_fortigate.pl -H $HOSTADDRESS$ -C $_HOSTSNMP_COMMUNITY$ -T ses -s -w 250 -c 350 } define command { command_name check_fortigate_vpn command_line $USER1$/check_fortigate.pl -H $HOSTADDRESS$ -C $_HOSTSNMP_COMMUNITY$ -T vpn -V $ARG1$ -M 0 }
Die ganzen Sachen gibt es natürlich auch direkt zum Download über exchange.nagios.org
Bei Fragen bitte wie immer melden.
Mal sehen, was die Recherche zu Juniper Plugins betrifft, ggf. wird der nächste Eintrag über dieses Thema sein :-)