class Riemann::Tools::TLSCheck
Constants
- OPENSSL_ERROR_STRINGS
Ruby OpenSSL does not expose ERR_error_string(3), and depending on the version of OpenSSL the available values change. Build a local list of mappings from include/openssl/x509_vfy.h.in and crypto/x509/x509_txt.c for lookups.
Public Class Methods
new()
click to toggle source
Calls superclass method
Riemann::Tools::new
# File lib/riemann/tools/tls_check.rb, line 239 def initialize super opts[:connect_timeout] ||= [10, opts[:interval] / 2].min @resolve_queue = Queue.new @work_queue = Queue.new opts[:resolvers].times do Thread.new do loop do uri = @resolve_queue.pop host = uri.host addresses = if host == 'localhost' Socket.ip_address_list.select { |address| address.ipv6_loopback? || address.ipv4_loopback? }.map(&:ip_address) else Resolv::DNS.new.getaddresses(host) end if addresses.empty? host = host[1...-1] if host[0] == '[' && host[-1] == ']' begin addresses << IPAddr.new(host) rescue IPAddr::InvalidAddressError # Ignore end end @work_queue.push([uri, addresses]) end end end opts[:workers].times do Thread.new do loop do uri, addresses = @work_queue.pop test_uri_addresses(uri, addresses) end end end end
Public Instance Methods
imap_tls_socket(uri, address)
click to toggle source
# File lib/riemann/tools/tls_check.rb, line 522 def imap_tls_socket(uri, address) socket = tcp_socket(address, uri.port) read_socket_lines_until_prefix_matched(socket, '* OK') socket.send(". CAPABILITY\r\n", 0) read_socket_lines_until_prefix_matched(socket, '. OK', also_accept_prefixes: ['* CAPABILITY']) socket.send(". STARTTLS\r\n", 0) read_socket_lines_until_prefix_matched(socket, '. OK') tls_handshake(socket, uri.host) end
ldap_tls_socket(uri, address)
click to toggle source
# File lib/riemann/tools/tls_check.rb, line 533 def ldap_tls_socket(uri, address) socket = tcp_socket(address, uri.port) socket.write(['301d02010177188016312e332e362e312e342e312e313436362e3230303337'].pack('H*')) expected_res = ['300c02010178070a010004000400'].pack('H*') res = socket.read(expected_res.length) return nil unless res == expected_res tls_handshake(socket, uri.host) end
my_hostname()
click to toggle source
# File lib/riemann/tools/tls_check.rb, line 506 def my_hostname Addrinfo.tcp(Socket.gethostname, 8023).getnameinfo.first rescue SocketError Socket.gethostname end
mysql_tls_socket(uri, address)
click to toggle source
# File lib/riemann/tools/tls_check.rb, line 471 def mysql_tls_socket(uri, address) socket = tcp_socket(address, uri.port) length = "#{socket.read(3)}\0".unpack1('L*') _sequence = socket.read(1) body = socket.read(length) initial_handshake_packet = body.unpack('cZ*La8aScSS') capabilities = initial_handshake_packet[5] | (initial_handshake_packet[8] << 16) ssl_flag = 1 << 11 raise 'No TLS support' if (capabilities & ssl_flag).zero? socket.write(['2000000185ae7f0000000001210000000000000000000000000000000000000000000000'].pack('H*')) tls_handshake(socket, uri.host) end
not_after_state(tls_check_result)
click to toggle source
not_before not_after |<----------------------------->| validity_duration |<--------->| renewal_duration | ⅓ | ⅓ | ⅓ |
…oooooooooooooooooooooooooooooooowwwwcccccccccc… not_after_state
time --->>>>
# File lib/riemann/tools/tls_check.rb, line 440 def not_after_state(tls_check_result) if tls_check_result.expired_or_expire_soon? 'critical' elsif tls_check_result.expire_soonish? 'warning' else 'ok' end end
not_before_state(tls_check_result)
click to toggle source
not_before not_after |<----------------------------->| validity_duration
…ccccccccoooooooooooooooooooooooooooooooooooooo… not_before_state
time --->>>>
# File lib/riemann/tools/tls_check.rb, line 429 def not_before_state(tls_check_result) tls_check_result.not_valid_yet? ? 'critical' : 'ok' end
postgres_tls_socket(uri, address)
click to toggle source
# File lib/riemann/tools/tls_check.rb, line 487 def postgres_tls_socket(uri, address) socket = tcp_socket(address, uri.port) socket.write(['0000000804d2162f'].pack('H*')) raise 'Unexpected reply' unless socket.read(1) == 'S' tls_handshake(socket, uri.host) end
raw_tls_socket(uri, address)
click to toggle source
# File lib/riemann/tools/tls_check.rb, line 544 def raw_tls_socket(uri, address) raise "No default port for #{uri.scheme} scheme" unless uri.port socket = tcp_socket(address, uri.port) tls_handshake(socket, uri.host) end
read_socket_lines_until_prefix_matched(socket, prefix, also_accept_prefixes: [])
click to toggle source
# File lib/riemann/tools/tls_check.rb, line 512 def read_socket_lines_until_prefix_matched(socket, prefix, also_accept_prefixes: []) loop do line = socket.gets break if line.start_with?(prefix) next if also_accept_prefixes.map { |accepted_prefix| line.start_with?(accepted_prefix) }.any? raise UnexpectedMessage, line end end
report_availability(tls_check_result)
click to toggle source
# File lib/riemann/tools/tls_check.rb, line 334 def report_availability(tls_check_result) if tls_check_result.exception report( service: "#{tls_endpoint_name(tls_check_result)} availability", state: 'critical', description: tls_check_result.exception.message, ) else issues = [] issues << 'Certificate is not valid yet' if tls_check_result.not_valid_yet? issues << 'Certificate has expired' if tls_check_result.expired? issues << 'Certificate identity could not be verified' unless tls_check_result.valid_identity? issues << 'Certificate is not trusted' unless tls_check_result.trusted? issues << 'Certificate OCSP verification failed' if tls_check_result.ocsp? && !tls_check_result.valid_ocsp? report( service: "#{tls_endpoint_name(tls_check_result)} availability", state: issues.empty? ? 'ok' : 'critical', description: issues.join("\n"), ) end end
report_identity(tls_check_result)
click to toggle source
# File lib/riemann/tools/tls_check.rb, line 384 def report_identity(tls_check_result) report( service: "#{tls_endpoint_name(tls_check_result)} identity", state: tls_check_result.valid_identity? ? 'ok' : 'critical', description: "Valid for:\n#{tls_check_result.acceptable_identities.join("\n")}", ) end
report_not_after(tls_check_result)
click to toggle source
# File lib/riemann/tools/tls_check.rb, line 366 def report_not_after(tls_check_result) report( service: "#{tls_endpoint_name(tls_check_result)} not after", state: not_after_state(tls_check_result), metric: tls_check_result.not_after_ago, description: tls_check_result.not_after_ago_in_words, ) end
report_not_before(tls_check_result)
click to toggle source
# File lib/riemann/tools/tls_check.rb, line 375 def report_not_before(tls_check_result) report( service: "#{tls_endpoint_name(tls_check_result)} not before", state: not_before_state(tls_check_result), metric: tls_check_result.not_before_away, description: tls_check_result.not_before_away_in_words, ) end
report_ocsp(tls_check_result)
click to toggle source
# File lib/riemann/tools/tls_check.rb, line 414 def report_ocsp(tls_check_result) return unless tls_check_result.ocsp? report( service: "#{tls_endpoint_name(tls_check_result)} OCSP status", state: tls_check_result.valid_ocsp? ? 'ok' : 'critical', description: tls_check_result.ocsp_status, ) end
report_trust(tls_check_result)
click to toggle source
# File lib/riemann/tools/tls_check.rb, line 392 def report_trust(tls_check_result) commont_attrs = { service: "#{tls_endpoint_name(tls_check_result)} trust", } extra_attrs = if tls_check_result.exception { state: 'critical', description: tls_check_result.exception.message, } else { state: tls_check_result.trusted? ? 'ok' : 'critical', description: if OPENSSL_ERROR_STRINGS[tls_check_result.verify_result] format('%<code>d - %<msg>s', code: tls_check_result.verify_result, msg: OPENSSL_ERROR_STRINGS[tls_check_result.verify_result]) else tls_check_result.verify_result.to_s end, } end report(commont_attrs.merge(extra_attrs)) end
smtp_tls_socket(uri, address)
click to toggle source
# File lib/riemann/tools/tls_check.rb, line 495 def smtp_tls_socket(uri, address) socket = tcp_socket(address, uri.port) read_socket_lines_until_prefix_matched(socket, '220 ', also_accept_prefixes: ['220-']) socket.send("EHLO #{my_hostname}\r\n", 0) read_socket_lines_until_prefix_matched(socket, '250 ', also_accept_prefixes: ['250-']) socket.send("STARTTLS\r\n", 0) socket.gets tls_handshake(socket, uri.host) end
ssl_context()
click to toggle source
# File lib/riemann/tools/tls_check.rb, line 570 def ssl_context @ssl_context ||= begin ctx = OpenSSL::SSL::SSLContext.new ctx.cert_store = store ctx.verify_hostname = false ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE ctx end end
store()
click to toggle source
# File lib/riemann/tools/tls_check.rb, line 580 def store @store ||= begin store = OpenSSL::X509::Store.new store.set_default_paths opts[:trust].each do |path| if File.directory?(path) store.add_path(path) else store.add_file(path) end end store end end
tcp_socket(host, port)
click to toggle source
# File lib/riemann/tools/tls_check.rb, line 450 def tcp_socket(host, port) Socket.tcp(host, port, connect_timeout: opts[:connect_timeout]) end
test_uri_address(uri, address)
click to toggle source
# File lib/riemann/tools/tls_check.rb, line 319 def test_uri_address(uri, address) socket = tls_socket(uri, address) tls_check_result = TLSCheckResult.new(uri, address, socket, self) report_availability(tls_check_result) return unless socket.peer_cert report_not_before(tls_check_result) if opts[:checks].include?('not-before') report_not_after(tls_check_result) if opts[:checks].include?('not-after') report_identity(tls_check_result) if opts[:checks].include?('identity') report_trust(tls_check_result) if opts[:checks].include?('trust') report_ocsp(tls_check_result) if opts[:checks].include?('ocsp') rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, UnexpectedMessage => e report_unavailability(uri, address, e) end
test_uri_addresses(uri, addresses)
click to toggle source
# File lib/riemann/tools/tls_check.rb, line 313 def test_uri_addresses(uri, addresses) addresses.each do |address| test_uri_address(uri, address.to_s) end end
tick()
click to toggle source
# File lib/riemann/tools/tls_check.rb, line 282 def tick report( service: 'riemann tls-check resolvers utilization', metric: (opts[:resolvers].to_f - @resolve_queue.num_waiting) / opts[:resolvers], state: @resolve_queue.num_waiting.positive? ? 'ok' : 'critical', tags: %w[riemann], ) report( service: 'riemann tls-check resolvers saturation', metric: @resolve_queue.length, state: @resolve_queue.empty? ? 'ok' : 'critical', tags: %w[riemann], ) report( service: 'riemann tls-check workers utilization', metric: (opts[:workers].to_f - @work_queue.num_waiting) / opts[:workers], state: @work_queue.num_waiting.positive? ? 'ok' : 'critical', tags: %w[riemann], ) report( service: 'riemann tls-check workers saturation', metric: @work_queue.length, state: @work_queue.empty? ? 'ok' : 'critical', tags: %w[riemann], ) opts[:uri].each do |uri| @resolve_queue.push(URI(uri)) end end
tls_endpoint_name(tls_check_result)
click to toggle source
# File lib/riemann/tools/tls_check.rb, line 595 def tls_endpoint_name(tls_check_result) tls_endpoint_name2(tls_check_result.uri, tls_check_result.address) end
tls_endpoint_name2(uri, address)
click to toggle source
# File lib/riemann/tools/tls_check.rb, line 599 def tls_endpoint_name2(uri, address) "TLS certificate #{uri} #{endpoint_name(IPAddr.new(address), uri.port)}" end
tls_handshake(raw_socket, hostname)
click to toggle source
# File lib/riemann/tools/tls_check.rb, line 551 def tls_handshake(raw_socket, hostname) tls_socket = OpenSSL::SSL::SSLSocket.new(raw_socket, ssl_context) tls_socket.hostname = hostname begin tls_socket.connect rescue OpenSSL::SSL::SSLError => e # This may fail for example if a client certificate is required but # not provided. In this case, the remote certificate is available and # we can ignore this issue. In other cases, the remote certificate is # not available, in this case we want to stop and report the issue # (e.g. connecting to a host with a SNI for a name not handled by # that host). tls_socket.define_singleton_method(:exception) do e end end tls_socket end
tls_socket(uri, address)
click to toggle source
# File lib/riemann/tools/tls_check.rb, line 454 def tls_socket(uri, address) case uri.scheme when 'smtp' smtp_tls_socket(uri, address) when 'imap' imap_tls_socket(uri, address) when 'ldap' ldap_tls_socket(uri, address) when 'mysql' mysql_tls_socket(uri, address) when 'postgres' postgres_tls_socket(uri, address) else raw_tls_socket(uri, address) end end