whotwagner/suricata

View on GitHub
lib/suricata/nagios.rb

Summary

Maintainability
B
4 hrs
Test Coverage
#--
# Copyright (C) 2016 Wolfgang Hotwagner <code@toscom.at>       
#                                                                
# This file is part of the suricata gem                                            
# 
# This mindwave gem 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 gem 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.
# 
# You should have received a copy of the GNU General Public License          
# along with this gem; if not, write to the 
# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, 
# Boston, MA  02110-1301  USA 
#++

module Suricata

require 'suricata/logfile'
require 'suricata/fast'
require 'optparse'


# This class offers all functionalities for a suricata-nagios-plugin
class Nagios


# @!attribute fast
#  this attribute stores the Suricata::Logfile-object
# @!attribute found_str
#  this attribute stores the string found by search() in the Logfile-object
# @!attribute search_str
#  the search-pattern is stored in this attribute
attr_reader :fast, :found_str, :search_str

# @!attribute whitelist
#  this whitelist can be used to exclude results from the search
# @!attribute alertfile
#  this alertfile(fast.log) is used for the search
# @!attribute return_found
#  this value is returned from search() on succes. (Default: 2)
# @!attribute return_notfound
#  this value is returned from search() on failure (Default: 0)
# @!attribute ack
#  it is possible to acknowlege alerts, so that they will be 
#  excluded from the next search. Use this member to set the acknowlege-file.
#  Default ack-file is: /tmp/surack.lst
attr_accessor :whitelist, :alertfile, :return_found, :return_notfound, :ack

# constructor 
# @param [String] alertfile path to the suricata-log-file(default: /var/log/suricata/fast.log)
# @param [String] whitelist path to the whitelist(default: nil)
def initialize(alertfile="/var/log/suricata/fast.log",whitelist=nil)
    @whitelist = whitelist
    @alertfile = alertfile
    @return_found = 2
    @return_notfound = 0
    @ack = "/tmp/surack.lst"
end

# this method initializes the Suricata::Logfile(@fast) and opens 
# the @alertfile 
# @see alertfile
def init_log
    @fast = Suricata::Logfile.new(@alertfile)
end

# this is the check_suricata-application. this function exits with 3 
# on error
# @param [Array] args typically ARGV
# @return [Integer] @return_found if searchstring was found
# @return [Integer] @return_notfound if searchstring was not found
# @see return_found
# @see return_notfound
def runApp(args)
    help = nil
    interactive = false

    OptionParser.new do |opt|
        opt.banner = "Usage: #{$PROGRAM_NAME} [ -a alertfile ] [ -w whitelistfile ] -e searchstring"
        opt.on('-h', '--help', 'This help screen') do
              $stderr.puts opt
            exit 3
          end
        opt.on('-a','--alertfile ALERTFILE','alertfile(default: /var/log/suricata/fast.log)') { |o| @alertfile = o }
        opt.on('-w','--whitelist WHITELISTFILE','whitelistfile') { |o| @whitelist = o }
        opt.on('-e','--search STRING','searchstring') { |o| @search_str = o }
        opt.on('-i','--interactive','interactive acknowleges') { |o| interactive = o }
        opt.on('-k','--ackfile ACKFILE','ackfile(default: /tmp/surack.lst)') { |o| @ack = o }
        help = opt.help
    end.parse!(args)

    if @search_str.nil?
        $stderr.puts help
        exit 3
    end
    
    if interactive
        acknowlege(@search_str)
        exit 3
    end
    
    ret = search(@search_str)
    if ret > 0
        puts "FOUND"
    else
        puts "OK"
    end

    exit ret
end

# this method performs a search(str). It will ask interactively for ever
# hit if it should be acknowleged. In case of "yes", the routine will
# add a shortform of the entry to the acknowlege-file
# @param [String] str string to search
# @see ack
def acknowlege(str)

    if @fast.nil?
        init_log
    end

    list = File.open(@ack,'a')

    @fast.readline_parse do |fast_entry|
        if fast_entry.description =~ /#{str}/
            if not search_list("#{fast_entry.timestamp} #{fast_entry.id} #{fast_entry.conn}",@ack)
                 printf("Acknowlege the following entry:\n")
                 printf("#{fast_entry}\n")
                 printf("Acknowlege(y|n): ")
                 answer = STDIN.gets
                 if answer == "y\n"
                     list.write("#{fast_entry.timestamp} #{fast_entry.id} #{fast_entry.conn}\n")
                 end
            end    
        end
    end

    list.close

end

# this function performs a search for a string(str) 
# in the alert-file. If a whitelistfile is given,
# or a acknowlege-file, it will search those files
# too and eventually exclude the hit from the result.
# @param [String] str search-query
# @return [Integer] @return_found on success
# @return [Integer] @return_notfound on failure
# @see return_found
# @see return_notfound
# @see ack
# @see whitelist
def search(str)
    @search_str = str
    @found_str = nil

    if @fast.nil?
        init_log
    end    

    wl_found = false
    ack_found = false

    @fast.readline_parse do |fast_entry|
        if fast_entry.description =~ /#{@search_str}/
            if not @whitelist.nil?
                wl_found =  search_list(fast_entry.description,@whitelist)
            end

            if not @ack.nil? and File.file?(@ack)
                ack_found = search_list("#{fast_entry.timestamp} #{fast_entry.id} #{fast_entry.conn}",@ack)
            end

            if wl_found == false and ack_found == false
                @found_str = fast_entry.description
                return @return_found
            end
        end
    end
    @fast.close

    return @return_notfound
end

private

# this function performs a search for a line in a file
# @param [String] str search-query
# @param [String] listfile file to search
# @return [Boolean] true if it succeded
# @return [Boolean] false if it did not succed
def search_list(str,listfile)
    list = File.open(listfile,'r')
    begin
    while entry = list.readline
        entry = entry.chomp
        if str =~ /#{entry}/
            list.close
            return true
        end
    end
    rescue EOFError
    end
    list.close
    return false

end

end

end