sensu-plugins/sensu-plugins-ssl

View on GitHub
bin/check-ssl-anchor.rb

Summary

Maintainability
A
35 mins
Test Coverage
#! /usr/bin/env ruby
# frozen_string_literal: false

#
#   check-ssl-anchor
#
# DESCRIPTION:
#   Check that a certificate is chained to a specific root certificate
#
# OUTPUT:
#   plain text
#
# PLATFORMS:
#   Linux
#
# DEPENDENCIES:
#   gem: sensu-plugin
#
# USAGE:
#
#   Check that a specific website is chained to a specific root certificate (Let's Encrypt for instance)
#       ./check-ssl-anchor.rb \
#           -u example.com \
#           -a "i:/O=Digital Signature Trust Co./CN=DST Root CA X3"
#
# NOTES:
#   This is basically a ruby wrapper around the following openssl command.
#
#       openssl s_client -connect example.com:443 -servername example.com
#
#
#
#   Use the -s flag if you need to override SNI (Server Name Indication). If you
#   are seeing discrepencies between `openssl s_client` and browser, that's a good
#   indication to use this flag.
#
# LICENSE:
#   Copyright 2017 Phil Porada <philporada@gmail.com>
#
#   Released under the same terms as Sensu (the MIT license); see LICENSE
#   for details.
#

require 'sensu-plugin/check/cli'

#
# Check certificate is anchored to a specific root
#
class CheckSSLAnchor < Sensu::Plugin::Check::CLI
  option :host,
         description: 'Host to check',
         short: '-h',
         long: '--host HOST',
         required: true

  option :anchor,
         description: 'An anchor looks something like /O=Digital Signature Trust Co./CN=DST Root CA X3',
         short: '-a',
         long: '--anchor ANCHOR_VAL',
         required: true

  option :regexp,
         description: 'Treat the anchor as a regexp',
         short: '-r',
         long: '--regexp',
         default: false,
         boolean: true,
         required: false

  option :servername,
         description: 'Set the TLS SNI (Server Name Indication) extension',
         short: '-s',
         long: '--servername SERVER'

  option :port,
         description: 'Port on server to check',
         short: '-p',
         long: '--port PORT',
         default: 443

  def validate_opts
    config[:servername] = config[:host] unless config[:servername]
  end

  # Do the actual work and massage some data
  def anchor_information
    data = `openssl s_client \
                -connect #{config[:host]}:#{config[:port]} \
                -servername #{config[:servername]} < /dev/null 2>&1`.match(/Certificate chain(.*)---\nServer certificate/m)[1].split(/$/).map(&:strip)
    data = data.reject(&:empty?)

    unless data[0] =~ /0 s:\/?CN ?=.*/m
      data = 'NOTOK'
    end
    data
  end

  def run
    validate_opts
    data = anchor_information
    if data == 'NOTOK'
      critical 'An error was encountered while trying to retrieve the certificate chain.'
    end
    puts config[:regexp]
    # rubocop:disable Style/IfInsideElse
    if config[:regexp]
      anchor_regexp = Regexp.new(config[:anchor].to_s)
      if data[-1] =~ anchor_regexp
        ok 'Root anchor has been found.'
      else
        critical 'Root anchor did not match regexp /' + config[:anchor].to_s + "/\nFound \"" + data[-1] + '" instead.'
      end
    else
      if data[-1] == config[:anchor].to_s
        ok 'Root anchor has been found.'
      else
        critical 'Root anchor did not match string "' + config[:anchor].to_s + "\"\nFound \"" + data[-1] + '" instead.'
      end
    end
    # rubocop:enable Style/IfInsideElse
  end
end