#! /usr/bin/env ruby # frozen_string_literal: true # # check-banner # # DESCRIPTION: # Connect to a TCP port on one or more hosts, read till a char (EOF, EOL, etc) # and test output against a pattern. # # Useful for SSH, ZooKeeper, etc. # # OUTPUT: # plain text # # PLATFORMS: # Linux # # DEPENDENCIES: # gem: sensu-plugin # # USAGE: # # To check there is only one zk leader # ./check-banner.rb -h 'zk01,z02,zk03' -p 2181 -q leader -c 1 -w mntr -r EOF -O "ZK has one leader" # -C "ZK has zero or more than one leader" # # NOTES: # By default, checks for openssh on localhost port 22 and reads till EOL # # # LICENSE: # Copyright 2012 Sonian, Inc # Modified by Raghu Udiyar # Released under the same terms as Sensu (the MIT license); see LICENSE # for details. require 'openssl' require 'sensu-plugin/check/cli' require 'socket' require 'timeout' # # Check Banner # class CheckBanner < Sensu::Plugin::Check::CLI option :hosts, short: '-H HOSTNAME(S)', long: '--hostnames HOSTNAME(S)', description: 'Host(s) to connect to, comma seperated', default: 'localhost' option :port, short: '-p PORT', long: '--port PORT', proc: proc(&:to_i), default: 22 option :write, short: '-w STRING', long: '--write STRING', description: 'write STRING to the socket' option :exclude_newline, short: '-e', long: '--exclude_newline', description: 'Exclude newline character at end of write STRING', boolean: true, default: false option :pattern, short: '-q PAT', long: '--pattern PAT', description: 'Pattern to search for' option :timeout, short: '-t SECS', long: '--timeout SECS', description: 'Connection timeout', proc: proc(&:to_i), default: 30 option :count_match, short: '-c NUMBER', long: '--count NUMBER', description: 'Number of successful matches, default(1)', proc: proc(&:to_i), default: 1 option :read_till, short: '-r CHAR', long: '--readtill CHAR', description: 'Read till CHAR is reached', default: "\n" option :ok_message, short: '-O MESSAGE', long: '--okmessage MESSAGE', description: 'Custom ok message to send' option :crit_message, short: '-C MESSAGE', long: '--critmessage MESSAGE', description: 'Custom critical message to send' option :ssl, short: '-S', long: '--ssl', description: 'Enable SSL socket for secure connection' def acquire_banner(host) Timeout.timeout(config[:timeout]) do sock = TCPSocket.new(host, config[:port]) if config[:ssl] ssl_context = OpenSSL::SSL::SSLContext.new sock = OpenSSL::SSL::SSLSocket.new(sock, ssl_context) sock.connect end if config[:write] sock.write config[:write] sock.write "\n" unless config[:exclude_newline] end if config[:read_till] == 'EOF' sock.gets(nil) else sock.gets(config[:read_till]) end end rescue Errno::ECONNREFUSED critical "Connection refused by #{host}:#{config[:port]}" rescue Timeout::Error critical 'Connection or read timed out' rescue Errno::EHOSTUNREACH critical 'Check failed to run: No route to host' rescue EOFError critical 'Connection closed unexpectedly' end def acquire_no_banner(host) Timeout.timeout(config[:timeout]) do TCPSocket.new(host, config[:port]) end rescue Errno::ECONNREFUSED critical "Connection refused by #{host}:#{config[:port]}" rescue Timeout::Error critical 'Connection or read timed out' rescue Errno::EHOSTUNREACH critical 'Check failed to run: No route to host' rescue EOFError critical 'Connection closed unexpectedly' end def run hosts = config[:hosts].split(',') okarray = [] hosts.each do |host| case config[:pattern] when nil banner = acquire_no_banner host okarray << 'ok' if banner else banner = acquire_banner host okarray << 'ok' if banner =~ /#{config[:pattern]}/ end if okarray.size == config[:count_match] && !config[:pattern].nil? ok "pattern #{config[:pattern]} matched" unless config[:ok_message] ok config[:ok_message] elsif okarray.size == config[:count_match] && config[:pattern].nil? ok "port #{config[:port]} open" unless config[:ok_message] ok config[:ok_message] else critical "port count or pattern #{config[:pattern]} does not match" unless config[:crit_message] critical config[:crit_message] end end end end