bin/check-tripwire.rb
#! /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