#! /usr/bin/env ruby # frozen_string_literal: false # # check-syncrepl # # DESCRIPTION: # This plugin checks that OpenLDAP Sync replication is working by # comparing the context change sequence number (contextCSN) of # a list of servers. # The plugin will attempt to use an unauthenticated connection if no # user name (with the -u or --user option) and password (with the -p # or --password option) are specified. # # OUTPUT: # plain text # # PLATFORMS: # Linux # # DEPENDENCIES: # gem: sensu-plugin # gem: net-ldap # # USAGE: # bind to LDAP without authorisation # ---------------------------------- # ./check-syncrepl.rb -h 'ldap1.domain,ldap2.domain' # will compare the contextCSN of the LDAP servers ldap1.domain and # ldap2.domain # # bind to LDAP requiring authorisation # ------------------------------------ # ./check-syncrepl.rb -h 'ldap1.domain,ldap2.domain' -u auser -p passwd # will bind to the LDAP servers ldap1.domain and ldap2.domain as user # auser with password passwd and compare the contextCSN # # NOTES: # # LICENSE: # Copyright (c) 2014, Justin Lambert # Released under the same terms as Sensu (the MIT license); see LICENSE # for details. # require 'sensu-plugin/check/cli' require 'net/ldap' class CheckSyncrepl < Sensu::Plugin::Check::CLI option :hosts, short: '-h HOSTS', long: '--hosts HOSTS', description: 'Comma separated list of hosts to compare', required: true, proc: proc { |hosts| hosts.split(',') } option :port, short: '-t PORT', long: '--port PORT', description: 'Port to connect to OpenLDAP on', default: 389, proc: proc(&:to_i) option :base, short: '-b BASE', long: '--base BASE', description: 'Base to fetch the ContextCSN for', required: true option :user, short: '-u USER', long: '--user USER', description: 'User to bind as' option :password, short: '-p PASSWORD', long: '--password PASSWORD', description: 'Password used to bind' option :insecure, short: '-i', long: '--insecure', description: 'Do not use encryption' option :encryption, short: '-e ENCRYPTION', long: '--encryption ENCRYPTION', description: 'Encryption method to use. Either simple_tls or start_tls', default: nil, proc: proc(&:to_sym) option :cacert, long: '--ca-certificate ENCRYPTION', description: 'Trusted CA certificate for checking the endpoint validity', default: nil option :cert, long: '--certificate ENCRYPTION', description: 'Client certificate', default: nil option :retries, short: '-r RETRIES', long: '--retries RETRIES', description: 'Number of times to retry (useful for environments with larger number of writes)', default: 0, proc: proc(&:to_i) def get_csns(host) if config[:user] # rubocop:disable Style/ConditionalAssignment ldap = Net::LDAP.new host: host, port: config[:port], auth: { method: :simple, username: config[:user], password: config[:password] } else ldap = Net::LDAP.new host: host, port: config[:port] end unless (config[:insecure] && config[:encryption].nil?) || config[:encryption] == :none config[:encryption] ||= :simple_tls tls_options = { verify_mode: (config[:insecure] ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER) } if config[:cacert] tls_options[:ca_file] = config[:cacert] end if config[:cert] tls_options[:cert] = open(config[:cert]) { |i| OpenSSL::X509::Certificate.new(i) } # rubocop:disable Security/Open end ldap.encryption(method: config[:encryption], tls_options: tls_options) end begin if ldap.bind ldap.search(base: config[:base], attributes: ['contextCSN'], return_result: true, scope: Net::LDAP::SearchScope_BaseObject) do |entry| return entry['contextcsn'] end else message = "Cannot connect to #{host}:#{config[:port]}" if config[:user] message += " as #{config[:user]}" end critical message end end rescue StandardError message = "Cannot connect to #{host}:#{config[:port]}" if config[:user] message += " as #{config[:user]}" end message += e.inspect critical message end def run unknown 'Cannot compare 1 node (to anything else).' if config[:hosts].length == 1 (config[:retries] + 1).times do # Build a list of contextCSNs from each host csns = {} config[:hosts].each do |host| csns[host] = get_csns host end # Compare all combinations of nodes @differences = [] combinations = csns.keys.combination(2).to_a combinations.each do |hosts| @differences << hosts if (csns[hosts[0]] - csns[hosts[1]]).length > 0 # rubocop:disable all end # If everything is OK, no need to retry ok 'All nodes are in sync' if @differences.length == 0 # rubocop:disable all end # Hit max retries, report latest differences message = 'ContextCSNs differ between: ' joined = [] @differences.each do |different| joined << different.sort.join(' and ') end message += joined.sort.join(', ') critical message end end