sensu-plugins/sensu-plugins-tripwire

View on GitHub
bin/check-tripwire.rb

Summary

Maintainability
A
55 mins
Test Coverage
#! /usr/bin/env ruby
#
#   check-tripwire
#
# DESCRIPTION:
#   This plugin periodically runs a check of the tripwire intrusion detection tool and
#   posts events for each violation found.
#
#   The plugin assumes that tripwire has been configured and that a tripwire database
#   is available that contains the desired state of the system.
#
#   The plugin does note require that the database be on the target machine. If an http
#   url is supplied via the -d option then the database will be retrieved via http before
#   the check is run and deleted afterward.
#
# OUTPUT:
#   plain text
#
# PLATFORMS:
#   Linux
#
# DEPENDENCIES:
#   gem: sensu-plugin
#   tripwire
#
# USAGE:
#   there are sensible defaults for each of the options so the check can reasonably
#   be run with no options. It is configurably for most modes of use though and the
#   option descriptions below are fairly self explanatory.
#
# NOTES:
#
# LICENSE:
#   Copyright 2013 Steve Gargan
#   Released under the same terms as Sensu (the MIT license); see LICENSE
#   for details.
#

require 'sensu-plugin/check/cli'
require 'json'
require 'open-uri'
require 'securerandom'

class TripwireCheck < Sensu::Plugin::Check::CLI
  option :binary,
         short: '-b path/to/tripwire',
         long: '--binary path/to/tripwire',
         description: 'tripwire binary to use, in case you hide yours',
         required: false,
         default: 'tripwire'

  option :sitekey,
         short: '-s path/to/sitekey',
         long: '--site-key path/to/sitekey',
         description: 'Site key used to decrypt the database that will be used in the validation',
         required: false

  option :password,
         short: '-P PASSWORD',
         long: '--password PASSWORD',
         description: 'Password to unlock the keyfile',
         required: false

  option :configfile,
         short: '-f path/to/configfile',
         long: '--config-file path/to/configfile',
         description: 'Configuration to use for the check',
         required: false

  option :database,
         short: '-d path_or_url_to_database',
         long: '--database path_or_url_to_database. if an http url is supplied the database will be retrieved prior to the check',
         description: 'Database to use for the check',
         required: false

  option :critical,
         short: '-c critical severity',
         long: '--critical critical severity',
         description: 'Tripwire severity greater than this is a critical error',
         required: false,
         default: '100'

  option :warn,
         short: '-w warn severity',
         long: '--warn warning severity',
         description: 'Tripwire severity greater than this is warning',
         required: false,
         default: '66'

  def run_tripwire
    command = "#{config[:binary]} --check"
    command << " -S #{config[:sitekey]}" if config[:sitekey]
    command << " -c #{config[:configfile]}" if config[:configfile]
    database = retrieve_database
    command << " -d #{database}" if database
    `#{command}`
  end

  def retrieve_database
    database = config[:database]

    if database && database.start_with?('http')
      id = SecureRandom.uuid
      tmp_db = "./twd-#{id}"
      begin
        open(tmp_db, 'wb') do |db|
          db << open(database).read
        end
      rescue StandardError => e
        critical "Error loading database from #{database}. Message #{e.message}"
        exit 1
      end
      database = tmp_db
    end
    database
  end

  def cleanup
    Dir.glob('./twd-*') do |db|
      File.delete(db)
    end
  end

  def parse_violations(report)
    rule_match = 'Rule Name: (.*)'
    severity_level = 'Severity Level: (\d*)'
    violation_type = '(Added|Modified|Removed).*'
    quoted = '"([^"]*)"'

    violations = {}
    current_violation = nil
    current_list = nil
    report.each do |line|
      if m = line.match(rule_match) # rubocop:disable AssignmentInCondition
        name = m[1]
        current_violation = { name: name }
        violations[:name] = current_violation
      end

      if (m = line.match(severity_level))
        current_violation[:level] = m[1].to_i
      end

      if (m = line.match(violation_type)) && current_violation
        current_list = []
        current_violation[m[1]] = current_list
      end

      if (m = line.match(quoted)) && current_list
        current_list << m[1]
      end
    end
    violations
  end

  def run
    begin
      report = run_tripwire.split("\n")
      violations = parse_violations report
      cleanup
    rescue StandardError => e
      cleanup
      warning "Error running tripwire. #{e. message}"
      exit 1
    end

    violations.each_value do |violation|
      if violation[:level] >= config[:critical].to_i
        critical violation.to_json
      elsif violation[:level] >= config[:warn].to_i
        warning violation.to_json
      end
    end
    ok 'no violations' if violations.size.zero?
    ok 'minor violations'
  end
end