├── CONTRIBUTING.md ├── lib ├── sensu-plugins-ssl.rb └── sensu-plugins-ssl │ └── version.rb ├── test ├── fixtures │ ├── test_store.jks │ └── Class3SoftwarePublishers.crl ├── spec_helper.rb ├── check-ssl-anchor_spec.rb ├── check-ssl-root-issuer.rb ├── check-ssl-hsts-status_spec.rb ├── check-ssl-hsts-preloadable_spec.rb ├── check-ssl-crl_spec.rb └── check-java-keystore-cert_spec.rb ├── Gemfile ├── .gitignore ├── .github └── PULL_REQUEST_TEMPLATE.md ├── LICENSE ├── .travis.yml ├── Rakefile ├── .bonsai.yml ├── bin ├── check-ssl-hsts-preloadable.rb ├── check-ssl-crl.rb ├── check-java-keystore-cert.rb ├── check-ssl-hsts-status.rb ├── check-ssl-anchor.rb ├── check-ssl-cert.rb ├── check-ssl-root-issuer.rb ├── check-ssl-qualys.rb └── check-ssl-host.rb ├── sensu-plugins-ssl.gemspec ├── .rubocop.yml ├── README.md └── CHANGELOG.md /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | [Development Documentation](http://sensu-plugins.io/docs/developer_guidelines.html) 2 | -------------------------------------------------------------------------------- /lib/sensu-plugins-ssl.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'sensu-plugins-ssl/version' 4 | -------------------------------------------------------------------------------- /test/fixtures/test_store.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensu-plugins/sensu-plugins-ssl/HEAD/test/fixtures/test_store.jks -------------------------------------------------------------------------------- /test/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'codeclimate-test-reporter' 4 | CodeClimate::TestReporter.start 5 | -------------------------------------------------------------------------------- /test/fixtures/Class3SoftwarePublishers.crl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensu-plugins/sensu-plugins-ssl/HEAD/test/fixtures/Class3SoftwarePublishers.crl -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | # Specify your gem's dependencies in sensu-plugins-ssl.gemspec 6 | gemspec 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.o 13 | *.a 14 | mkmf.log 15 | .vagrant/* 16 | .DS_Store 17 | .idea/* 18 | *.gem 19 | -------------------------------------------------------------------------------- /lib/sensu-plugins-ssl/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SensuPluginsSSL 4 | module Version 5 | MAJOR = 3 6 | MINOR = 0 7 | PATCH = 2 8 | 9 | VER_STRING = [MAJOR, MINOR, PATCH].compact.join('.') 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Pull Request Checklist 2 | 3 | **Is this in reference to an existing issue?** 4 | 5 | #### General 6 | 7 | - [ ] Update Changelog following the conventions laid out [here](https://github.com/sensu-plugins/community/blob/master/HOW_WE_CHANGELOG.md) 8 | 9 | - [ ] Update README with any necessary configuration snippets 10 | 11 | - [ ] Binstubs are created if needed 12 | 13 | - [ ] RuboCop passes 14 | 15 | - [ ] Existing tests pass 16 | 17 | #### New Plugins 18 | 19 | - [ ] Tests 20 | 21 | - [ ] Add the plugin to the README 22 | 23 | - [ ] Does it have a complete header as outlined [here](http://sensu-plugins.io/docs/developer_guidelines.html#coding-style) 24 | 25 | #### Purpose 26 | 27 | #### Known Compatibility Issues 28 | -------------------------------------------------------------------------------- /test/check-ssl-anchor_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../bin/check-ssl-anchor.rb' 4 | 5 | describe CheckSSLAnchor do 6 | before(:all) do 7 | # Ensure the check isn't run when exiting (which is the default) 8 | CheckSSLAnchor.class_variable_set(:@@autorun, nil) 9 | end 10 | 11 | let(:check) do 12 | CheckSSLAnchor.new ['-h', 'philporada.com', '-a', 'i:\/?O ?= ?Digital Signature Trust Co.,? ?\/?CN ?= ?DST Root CA X3', '-r'] 13 | end 14 | 15 | it 'should pass check if the root anchor matches what the users -a flag' do 16 | expect(check).to receive(:ok).and_raise SystemExit 17 | expect { check.run }.to raise_error SystemExit 18 | end 19 | 20 | it 'should pass check if the root anchor matches what the users -a flag' do 21 | check.config[:anchor] = 'testdata' 22 | check.config[:regexp] = false 23 | expect(check).to receive(:critical).and_raise SystemExit 24 | expect { check.run }.to raise_error SystemExit 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/check-ssl-root-issuer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../bin/check-ssl-anchor.rb' 4 | 5 | describe CheckSSLRootIssuer do 6 | before(:all) do 7 | # Ensure the check isn't run when exiting (which is the default) 8 | CheckSSLRootIssuer.class_variable_set(:@@autorun, nil) 9 | end 10 | 11 | let(:check) do 12 | CheckSSLRootIssuer.new ['-u', 'https://philporada.com', '-i', '"CN=DST Root CA X3,O=Digital Signature Trust Co."'] 13 | end 14 | 15 | it 'should pass check if the root issuer matches what the users -i flag' do 16 | expect(check).to receive(:ok).and_raise SystemExit 17 | expect { check.run }.to raise_error SystemExit 18 | end 19 | 20 | it 'should pass check if the root issuer matches what the users -i flag' do 21 | check.config[:anchor] = 'testdata' 22 | check.config[:regexp] = false 23 | expect(check).to receive(:critical).and_raise SystemExit 24 | expect { check.run }.to raise_error SystemExit 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/check-ssl-hsts-status_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../bin/check-ssl-hsts-status.rb' 4 | ### 5 | # If these randomly start failing then it's probably due to a change in the HSTS of the domain 6 | # feel free to raise an issue and mention @rwky on github for a fix 7 | ### 8 | 9 | describe CheckSSLHSTSStatus do 10 | before(:all) do 11 | # Ensure the check isn't run when exiting (which is the default) 12 | CheckSSLHSTSStatus.class_variable_set(:@@autorun, nil) 13 | end 14 | 15 | let(:check) do 16 | CheckSSLHSTSStatus.new ['-d', 'hstspreload.org'] 17 | end 18 | 19 | it 'should pass check if the domain is preloaded' do 20 | expect(check).to receive(:ok).and_raise SystemExit 21 | expect { check.run }.to raise_error SystemExit 22 | end 23 | 24 | it 'should pass check if not preloaded' do 25 | check.config[:domain] = 'example.com' 26 | expect(check).to receive(:critical).and_raise SystemExit 27 | expect { check.run }.to raise_error SystemExit 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Sensu-Plugins 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | services: 3 | - docker 4 | cache: 5 | - bundler 6 | install: 7 | - bundle install 8 | rvm: 9 | - 2.4.1 10 | notifications: 11 | email: 12 | recipients: 13 | - sensu-plugin@sensu-plugins.io 14 | on_success: change 15 | on_failure: always 16 | script: 17 | - bundle exec rake default 18 | - gem build sensu-plugins-ssl.gemspec 19 | - gem install sensu-plugins-ssl-*.gem 20 | before_deploy: 21 | - bash -c "[ ! -d bonsai/ ] && git clone https://github.com/sensu/sensu-go-bonsai-asset.git bonsai || echo 'bonsai/ exists, skipping git clone'" 22 | deploy: 23 | - provider: script 24 | script: bonsai/ruby-runtime/travis-build-ruby-plugin-assets.sh sensu-plugins-ssl 25 | skip_cleanup: true 26 | on: 27 | tags: true 28 | all_branches: true 29 | rvm: 2.4.1 30 | - provider: rubygems 31 | api_key: 32 | secure: Kr0fckXoukenIVyWMFVYAi+llLNNKRtfnZatnGDgqQON+vRXuuLGnta2TyBEhmPdiRU8nax5VL3K5uNSkxWmPi7Vtzizz453KShtnFGXuq73H37WIvcJ4bLcvz5K/1RWNEcXMN7/6hgSeDBUzcliFyph4p00WhdhqgFttf0UI/Y= 33 | gem: sensu-plugins-ssl 34 | on: 35 | tags: true 36 | all_branches: true 37 | rvm: 2.4.1 38 | repo: sensu-plugins/sensu-plugins-ssl 39 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | require 'github/markup' 5 | require 'redcarpet' 6 | require 'rspec/core/rake_task' 7 | require 'rubocop/rake_task' 8 | require 'yard' 9 | require 'yard/rake/yardoc_task' 10 | 11 | ENV['TZ'] = 'CET' 12 | 13 | args = %i[spec make_bin_executable yard rubocop check_binstubs] 14 | 15 | YARD::Rake::YardocTask.new do |t| 16 | OTHER_PATHS = %w[].freeze 17 | t.files = ['lib/**/*.rb', 'bin/**/*.rb', OTHER_PATHS] 18 | t.options = %w[--markup-provider=redcarpet --markup=markdown --main=README.md --files CHANGELOG.md] 19 | end 20 | 21 | RuboCop::RakeTask.new 22 | 23 | RSpec::Core::RakeTask.new(:spec) do |r| 24 | r.pattern = FileList['**/**/*_spec.rb'] 25 | end 26 | 27 | desc 'Make all plugins executable' 28 | task :make_bin_executable do 29 | `chmod -R +x bin/*` 30 | end 31 | 32 | desc 'Test for binstubs' 33 | task :check_binstubs do 34 | bin_list = Gem::Specification.load('sensu-plugins-ssl.gemspec').executables 35 | bin_list.each do |b| 36 | `which #{b}` 37 | unless $CHILD_STATUS.success? 38 | puts "#{b} was not a binstub" 39 | exit 40 | end 41 | end 42 | end 43 | 44 | task default: args 45 | -------------------------------------------------------------------------------- /test/check-ssl-hsts-preloadable_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../bin/check-ssl-hsts-preloadable.rb' 4 | ### 5 | # If these randomly start failing then it's probably due to a change in the HSTS of the domain 6 | # feel free to raise an issue and mention @rwky on github for a fix 7 | ### 8 | 9 | describe CheckSSLHSTSPreloadable do 10 | before(:all) do 11 | # Ensure the check isn't run when exiting (which is the default) 12 | CheckSSLHSTSPreloadable.class_variable_set(:@@autorun, nil) 13 | end 14 | 15 | let(:check) do 16 | CheckSSLHSTSPreloadable.new ['-d', 'hstspreload.org'] 17 | end 18 | 19 | it 'should pass check if the domain is preloadedable and has no warnings' do 20 | expect(check).to receive(:ok).and_raise SystemExit 21 | expect { check.run }.to raise_error SystemExit 22 | end 23 | 24 | ## 25 | # Disabled 2020/06/24 JDS 26 | # Reason: the hsts-preloadable check depends on a domain lookup from https://hstspreload.org/ 27 | # There's no way to assure that an indexed domain at hstspreload.org will have a warning 28 | # The previously tested domain 'oskuro.net' no longer issues a warning 29 | # as its now incompliance with the hsts preload requirements. 30 | ## 31 | # it 'should pass check if the domain is preloadedable but has warnings' do 32 | # check.config[:domain] = 'oskuro.net' 33 | # expect(check).to receive(:warning).and_raise SystemExit 34 | # expect { check.run }.to raise_error SystemExit 35 | # end 36 | 37 | it 'should pass check if not preloadedable' do 38 | check.config[:domain] = 'example.com' 39 | expect(check).to receive(:critical).and_raise SystemExit 40 | expect { check.run }.to raise_error SystemExit 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /.bonsai.yml: -------------------------------------------------------------------------------- 1 | --- 2 | description: "#{repo}" 3 | builds: 4 | - platform: "alpine" 5 | arch: "amd64" 6 | asset_filename: "#{repo}_#{version}_alpine_linux_amd64.tar.gz" 7 | sha_filename: "#{repo}_#{version}_sha512-checksums.txt" 8 | filter: 9 | - "entity.system.os == 'linux'" 10 | - "entity.system.arch == 'amd64'" 11 | - "entity.system.platform == 'alpine'" 12 | - "entity.system.platform_version.split('.')[0] == '3'" 13 | - platform: "alpine3.8" 14 | arch: "amd64" 15 | asset_filename: "#{repo}_#{version}_alpine3.8_linux_amd64.tar.gz" 16 | sha_filename: "#{repo}_#{version}_sha512-checksums.txt" 17 | filter: 18 | - "entity.system.os == 'linux'" 19 | - "entity.system.arch == 'amd64'" 20 | - "entity.system.platform == 'alpine'" 21 | - platform: "centos6" 22 | arch: "amd64" 23 | asset_filename: "#{repo}_#{version}_centos6_linux_amd64.tar.gz" 24 | sha_filename: "#{repo}_#{version}_sha512-checksums.txt" 25 | filter: 26 | - "entity.system.os == 'linux'" 27 | - "entity.system.arch == 'amd64'" 28 | - "entity.system.platform_family == 'rhel'" 29 | - "entity.system.platform_version.split('.')[0] == '6'" 30 | - platform: "centos7" 31 | arch: "amd64" 32 | asset_filename: "#{repo}_#{version}_centos7_linux_amd64.tar.gz" 33 | sha_filename: "#{repo}_#{version}_sha512-checksums.txt" 34 | filter: 35 | - "entity.system.os == 'linux'" 36 | - "entity.system.arch == 'amd64'" 37 | - "entity.system.platform_family == 'rhel'" 38 | - "entity.system.platform_version.split('.')[0] == '7'" 39 | - platform: "debian" 40 | arch: "amd64" 41 | asset_filename: "#{repo}_#{version}_debian_linux_amd64.tar.gz" 42 | sha_filename: "#{repo}_#{version}_sha512-checksums.txt" 43 | filter: 44 | - "entity.system.os == 'linux'" 45 | - "entity.system.arch == 'amd64'" 46 | - "entity.system.platform_family == 'debian'" 47 | - platform: "debian9" 48 | arch: "amd64" 49 | asset_filename: "#{repo}_#{version}_debian9_linux_amd64.tar.gz" 50 | sha_filename: "#{repo}_#{version}_sha512-checksums.txt" 51 | filter: 52 | - "entity.system.os == 'linux'" 53 | - "entity.system.arch == 'amd64'" 54 | - "entity.system.platform_family == 'debian'" 55 | - "entity.system.platform_version.split('.')[0] == '9'" 56 | 57 | -------------------------------------------------------------------------------- /test/check-ssl-crl_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../bin/check-ssl-crl.rb' 4 | 5 | require 'timecop' 6 | 7 | # rubocop:disable Metrics/BlockLength 8 | describe CheckSSLCRL do 9 | before(:all) do 10 | # Ensure the check isn't run when exiting (which is the default) 11 | CheckSSLCRL.class_variable_set(:@@autorun, nil) 12 | end 13 | 14 | let(:check) do 15 | CheckSSLCRL.new ['--url', './test/fixtures/Class3SoftwarePublishers.crl', '--warning', '600', '--critical', '300'] 16 | end 17 | 18 | it 'should pass check if the CRL has not expired' do 19 | expect(check).to receive(:ok).and_raise SystemExit 20 | Timecop.freeze(Time.new(2017, 1, 14)) do 21 | expect { check.run }.to raise_error SystemExit 22 | end 23 | end 24 | 25 | it 'should return critical if the CRL has expired' do 26 | expect(check).to receive(:critical).with('./test/fixtures/Class3SoftwarePublishers.crl - Expired 1559 minutes ago').and_raise SystemExit 27 | Timecop.freeze(Time.new(2017, 2, 14)) do 28 | expect { check.run }.to raise_error SystemExit 29 | end 30 | end 31 | 32 | it 'should return critical if the CRL will expire in less than 300 minutes' do 33 | expect(check).to receive(:critical).with('./test/fixtures/Class3SoftwarePublishers.crl - 120 minutes left, next update at 2017-02-12 21:00:09 UTC').and_raise SystemExit 34 | Timecop.freeze(Time.new(2017, 2, 12, 20, 0, 9)) do 35 | expect { check.run }.to raise_error SystemExit 36 | end 37 | end 38 | 39 | it 'should return warning if the CRL will expire between 300 and 600 minutes from now' do 40 | expect(check).to receive(:warning).with('./test/fixtures/Class3SoftwarePublishers.crl - 420 minutes left, next update at 2017-02-12 21:00:09 UTC').and_raise SystemExit 41 | Timecop.freeze(Time.new(2017, 2, 12, 15, 0, 9)) do 42 | expect { check.run }.to raise_error SystemExit 43 | end 44 | end 45 | 46 | it 'should return unknown if warning threshold is less than critical threshold' do 47 | check.config[:warning] = 1 48 | 49 | expect(check).to receive(:unknown).with('warning cannot be less than critical').and_raise SystemExit 50 | 51 | expect { check.run }.to raise_error SystemExit 52 | end 53 | end 54 | # rubocop:enable Metrics/BlockLength 55 | -------------------------------------------------------------------------------- /bin/check-ssl-hsts-preloadable.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: false 3 | 4 | # check-ssl-hsts-preloadable.rb 5 | # 6 | # DESCRIPTION: 7 | # Checks a domain against the chromium HSTS API returning errors/warnings if the domain is preloadable 8 | # 9 | # OUTPUT: 10 | # plain text 11 | # 12 | # PLATFORMS: 13 | # Linux 14 | # 15 | # DEPENDENCIES: 16 | # gem: sensu-plugin 17 | # 18 | # USAGE: 19 | # # Basic usage 20 | # check-ssl-hsts-preloadable.rb -d 21 | # 22 | # LICENSE: 23 | # Copyright 2017 Rowan Wookey 24 | # Released under the same terms as Sensu (the MIT license); see LICENSE for 25 | # details. 26 | # 27 | # Inspired by https://github.com/sensu-plugins/sensu-plugins-ssl/blob/master/bin/check-ssl-qualys.rb Copyright 2015 William Cooke 28 | # 29 | 30 | require 'sensu-plugin/check/cli' 31 | require 'json' 32 | require 'net/http' 33 | 34 | class CheckSSLHSTSPreloadable < Sensu::Plugin::Check::CLI 35 | option :domain, 36 | description: 'The domain to run the test against', 37 | short: '-d DOMAIN', 38 | long: '--domain DOMAIN', 39 | required: true 40 | 41 | option :api_url, 42 | description: 'The URL of the API to run against', 43 | long: '--api-url URL', 44 | default: 'https://hstspreload.org/api/v2/preloadable' 45 | 46 | def fetch(uri, limit = 10) 47 | if limit == 0 # rubocop:disable Style/NumericPredicate 48 | return nil 49 | end 50 | 51 | response = Net::HTTP.get_response(uri) 52 | 53 | case response 54 | when Net::HTTPSuccess 55 | response 56 | when Net::HTTPRedirection 57 | location = URI(response['location']) 58 | fetch(location, limit - 1) 59 | end 60 | end 61 | 62 | def run 63 | uri = URI(config[:api_url]) 64 | uri.query = URI.encode_www_form(domain: config[:domain]) 65 | response = fetch(uri) 66 | if response.nil? 67 | return warning 'Bad response recieved from API' 68 | end 69 | 70 | body = JSON.parse(response.body) 71 | if !body['errors'].empty? 72 | critical body['errors'].map { |u| u['summary'] }.join(', ') 73 | elsif !body['warnings'].empty? 74 | warning body['warnings'].map { |u| u['summary'] }.join(', ') 75 | else 76 | ok 77 | end 78 | end 79 | end 80 | 81 | # vim: set tabstop=2 shiftwidth=2 expandtab: 82 | -------------------------------------------------------------------------------- /bin/check-ssl-crl.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # frozen_string_literal: false 3 | 4 | # 5 | # check-ssl-crl 6 | # 7 | # DESCRIPTION: 8 | # Check in minutes when a certificate revocation list will expire. 9 | # 10 | # OUTPUT: 11 | # plain text 12 | # 13 | # PLATFORMS: 14 | # Linux 15 | # 16 | # DEPENDENCIES: 17 | # gem: sensu-plugin 18 | # 19 | # USAGE: 20 | # ./check-ssl-crl -c 300 -w 600 -u /path/to/crl 21 | # ./check-ssl-crl -c 300 -w 600 -u http://www.website.com/file.crl 22 | # 23 | # LICENSE: 24 | # Stephen Hoekstra 25 | # 26 | # Released under the same terms as Sensu (the MIT license); see LICENSE 27 | # for details. 28 | # 29 | 30 | require 'open-uri' 31 | require 'openssl' 32 | require 'sensu-plugin/check/cli' 33 | require 'time' 34 | 35 | # 36 | # Check SSL Cert 37 | # 38 | class CheckSSLCRL < Sensu::Plugin::Check::CLI 39 | option :critical, 40 | description: 'Numbers of minutes left', 41 | short: '-c', 42 | long: '--critical MINUTES', 43 | proc: proc { |v| v.to_i }, 44 | required: true 45 | 46 | option :url, 47 | description: 'URL (or path) to CRL file', 48 | short: '-u', 49 | long: '--url URL', 50 | required: true 51 | 52 | option :warning, 53 | description: 'Numbers of minutes left', 54 | short: '-w', 55 | long: '--warning MINUTES', 56 | proc: proc { |v| v.to_i }, 57 | required: true 58 | 59 | def seconds_to_minutes(seconds) 60 | (seconds / 60).to_i 61 | end 62 | 63 | def validate_opts 64 | unknown 'warning cannot be less than critical' if config[:warning] < config[:critical] 65 | end 66 | 67 | def run 68 | validate_opts 69 | 70 | next_update = OpenSSL::X509::CRL.new(open(config[:url]).read).next_update # rubocop:disable Security/Open 71 | minutes_until = seconds_to_minutes(Time.parse(next_update.to_s) - Time.now) 72 | 73 | critical "#{config[:url]} - Expired #{minutes_until.abs} minutes ago" if minutes_until < 0 # rubocop:disable Style/NumericPredicate 74 | critical "#{config[:url]} - #{minutes_until} minutes left, next update at #{next_update}" if minutes_until < config[:critical].to_i 75 | warning "#{config[:url]} - #{minutes_until} minutes left, next update at #{next_update}" if minutes_until < config[:warning].to_i 76 | ok "#{config[:url]} - #{minutes_until} minutes left, next update at #{next_update}" 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /bin/check-java-keystore-cert.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: false 3 | 4 | # 5 | # check-java-keystore-cert 6 | # 7 | # DESCRIPTION: 8 | # Check when a certificate stored in a Java Keystore will expire 9 | # 10 | # OUTPUT: 11 | # plain text 12 | # 13 | # PLATFORMS: 14 | # Linux 15 | # 16 | # DEPENDENCIES: 17 | # gem: sensu-plugin 18 | # 19 | # USAGE: 20 | # example commands 21 | # 22 | # NOTES: 23 | # Does it behave differently on specific platforms, specific use cases, etc 24 | # 25 | 26 | require 'date' 27 | require 'shellwords' 28 | require 'sensu-plugin/check/cli' 29 | 30 | class CheckJavaKeystoreCert < Sensu::Plugin::Check::CLI 31 | option :path, 32 | long: '--path PATH', 33 | description: '', 34 | required: true 35 | 36 | option :alias, 37 | long: '--alias ALIAS', 38 | description: '', 39 | required: true 40 | 41 | option :password, 42 | long: '--password PASSWORD', 43 | description: '', 44 | required: true 45 | 46 | option :warning, 47 | long: '--warning DAYS', 48 | description: '', 49 | proc: proc { |v| v.to_i }, 50 | required: true 51 | 52 | option :critical, 53 | long: '--critical DAYS', 54 | description: '', 55 | proc: proc { |v| v.to_i }, 56 | required: true 57 | 58 | def certificate_expiration_date 59 | result = `keytool -keystore #{Shellwords.escape(config[:path])} \ 60 | -export -alias #{Shellwords.escape(config[:alias])} \ 61 | -storepass #{Shellwords.escape(config[:password])} -rfc 2>&1 | \ 62 | openssl x509 -enddate -noout 2>&1` 63 | 64 | # rubocop:disable Style/SpecialGlobalVars 65 | unknown 'could not get certificate from keystore' unless $?.success? 66 | # rubocop:enable Style/SpecialGlobalVars 67 | 68 | Date.parse(result.split('=').last) 69 | end 70 | 71 | def validate_opts 72 | unknown 'warning cannot be less than critical' if config[:warning] < config[:critical] 73 | end 74 | 75 | def run 76 | validate_opts 77 | 78 | days_until = (certificate_expiration_date - Date.today).to_i 79 | 80 | if days_until < 0 # rubocop: disable Style/NumericPredicate 81 | critical "Expired #{days_until.abs} days ago" 82 | elsif days_until < config[:critical] 83 | critical "#{days_until} days left" 84 | elsif days_until < config[:warning] 85 | warning "#{days_until} days left" 86 | end 87 | 88 | ok "#{days_until} days left" 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /sensu-plugins-ssl.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path('lib', __dir__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | 6 | require 'date' 7 | require_relative 'lib/sensu-plugins-ssl' 8 | 9 | Gem::Specification.new do |s| 10 | s.authors = ['Sensu-Plugins and contributors'] 11 | 12 | s.date = Date.today.to_s 13 | s.description = 'This plugin provides native SSL instrumentation 14 | for monitoring, including: hostname and chain 15 | verification, cert and crl expiry, and Qualys SSL Labs reporting' 16 | s.email = '' 17 | s.executables = Dir.glob('bin/**/*.rb').map { |file| File.basename(file) } 18 | s.files = Dir.glob('{bin,lib}/**/*') + %w[LICENSE README.md CHANGELOG.md] 19 | s.homepage = 'https://github.com/sensu-plugins/sensu-plugins-ssl' 20 | s.license = 'MIT' 21 | s.metadata = { 'maintainer' => 'sensu-plugin', 22 | 'development_status' => 'active', 23 | 'production_status' => 'unstable - testing recommended', 24 | 'release_draft' => 'false', 25 | 'release_prerelease' => 'false' } 26 | s.name = 'sensu-plugins-ssl' 27 | s.platform = Gem::Platform::RUBY 28 | s.post_install_message = 'You can use the embedded Ruby by setting EMBEDDED_RUBY=true in /etc/default/sensu' 29 | s.require_paths = ['lib'] 30 | s.required_ruby_version = '>= 2.4.0' 31 | 32 | s.summary = 'Sensu plugins for SSL' 33 | s.test_files = s.files.grep(%r{^(test|spec|features)/}) 34 | s.version = SensuPluginsSSL::Version::VER_STRING 35 | 36 | s.add_runtime_dependency 'sensu-plugin', '~> 4.0' 37 | 38 | s.add_development_dependency 'bundler', '~> 2.1' 39 | s.add_development_dependency 'codeclimate-test-reporter', '~> 0.4' 40 | s.add_development_dependency 'github-markup', '~> 4.0' 41 | s.add_development_dependency 'pry', '~> 0.10' 42 | s.add_development_dependency 'rake', '~> 13.0' 43 | s.add_development_dependency 'redcarpet', '~> 3.2' 44 | s.add_development_dependency 'rspec', '~> 3.1' 45 | s.add_development_dependency 'rubocop', '~> 0.89.1' 46 | s.add_development_dependency 'timecop', '~> 0.9.1' 47 | s.add_development_dependency 'yard', '~> 0.8' 48 | end 49 | -------------------------------------------------------------------------------- /test/check-java-keystore-cert_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../bin/check-java-keystore-cert.rb' 4 | 5 | require 'timecop' 6 | 7 | # rubocop:disable Metrics/BlockLength 8 | describe CheckJavaKeystoreCert do 9 | before(:all) do 10 | # Ensure the check isn't run when exiting (which is the default) 11 | CheckJavaKeystoreCert.class_variable_set(:@@autorun, nil) 12 | end 13 | 14 | let(:check) do 15 | CheckJavaKeystoreCert.new ['--path', './test/fixtures/test_store.jks', '--password', 'password', '--alias', 'certificate', '--warning', '15', '--critical', '5'] 16 | end 17 | 18 | it 'should pass check if the certificate has not expired' do 19 | expect(check).to receive(:ok).with('30 days left').and_raise SystemExit 20 | Timecop.freeze(Date.new(2016, 8, 11)) do 21 | expect { check.run }.to raise_error SystemExit 22 | end 23 | end 24 | 25 | it 'should return critical if the certificate has expired' do 26 | expect(check).to receive(:critical).with('Expired 81 days ago').and_raise SystemExit 27 | Timecop.freeze(Date.new(2016, 11, 30)) do 28 | expect { check.run }.to raise_error SystemExit 29 | end 30 | end 31 | 32 | it 'should return critical if the certificate will expire in less than 5 days' do 33 | expect(check).to receive(:critical).with('1 days left').and_raise SystemExit 34 | Timecop.freeze(Date.new(2016, 9, 9)) do 35 | expect { check.run }.to raise_error SystemExit 36 | end 37 | end 38 | 39 | it 'should return warning if the certificate will expire between 5 and 15 days from now' do 40 | expect(check).to receive(:warning).with('9 days left').and_raise SystemExit 41 | Timecop.freeze(Date.new(2016, 9, 1)) do 42 | expect { check.run }.to raise_error SystemExit 43 | end 44 | end 45 | 46 | it 'should return unknown if warning days are less than critical' do 47 | check.config[:warning] = 1 48 | 49 | expect(check).to receive(:unknown).with('warning cannot be less than critical').and_raise SystemExit 50 | 51 | expect { check.run }.to raise_error SystemExit 52 | end 53 | 54 | it 'should return unknown if keystore cannot be read' do 55 | check.config[:path] = 'nonexistent' 56 | 57 | expect(check).to receive(:unknown).with('could not get certificate from keystore').and_raise SystemExit 58 | 59 | expect { check.run }.to raise_error SystemExit 60 | end 61 | 62 | it 'should return unknown if keystore password wrong' do 63 | check.config[:password] = 'incorrect' 64 | 65 | expect(check).to receive(:unknown).with('could not get certificate from keystore').and_raise SystemExit 66 | 67 | expect { check.run }.to raise_error SystemExit 68 | end 69 | end 70 | # rubocop:enable Metrics/BlockLength 71 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | 2 | Metrics/MethodLength: 3 | Max: 200 4 | 5 | Layout/LineLength: 6 | Max: 172 7 | 8 | Metrics/AbcSize: 9 | Max: 100 10 | 11 | Naming/FileName: 12 | Enabled: false 13 | 14 | Metrics/PerceivedComplexity: 15 | Enabled: false 16 | 17 | Metrics/CyclomaticComplexity: 18 | Enabled: false 19 | 20 | Metrics/ClassLength: 21 | Enabled: false 22 | 23 | Style/IfUnlessModifier: 24 | Enabled: false 25 | 26 | Style/RegexpLiteral: 27 | Enabled: false 28 | 29 | Style/Documentation: 30 | Enabled: false 31 | 32 | Style/ClassVars: 33 | Enabled: false 34 | 35 | Layout/EmptyLinesAroundAttributeAccessor: 36 | Enabled: false 37 | 38 | Layout/SpaceAroundMethodCallOperator: 39 | Enabled: false 40 | 41 | Lint/BinaryOperatorWithIdenticalOperands: 42 | Enabled: false 43 | 44 | Lint/DeprecatedOpenSSLConstant: 45 | Enabled: false 46 | 47 | Lint/DuplicateElsifCondition: 48 | Enabled: false 49 | 50 | Lint/DuplicateRescueException: 51 | Enabled: false 52 | 53 | Lint/EmptyConditionalBody: 54 | Enabled: false 55 | 56 | Lint/FloatComparison: 57 | Enabled: false 58 | 59 | Lint/MissingSuper: 60 | Enabled: false 61 | 62 | Lint/MixedRegexpCaptureTypes: 63 | Enabled: false 64 | 65 | Lint/OutOfRangeRegexpRef: 66 | Enabled: false 67 | 68 | Lint/RaiseException: 69 | Enabled: false 70 | 71 | Lint/SelfAssignment: 72 | Enabled: false 73 | 74 | Lint/StructNewOverride: 75 | Enabled: false 76 | 77 | Lint/TopLevelReturnWithArgument: 78 | Enabled: false 79 | 80 | Lint/UnreachableLoop: 81 | Enabled: false 82 | 83 | Style/AccessorGrouping: 84 | Enabled: false 85 | 86 | Style/ArrayCoercion: 87 | Enabled: false 88 | 89 | Style/BisectedAttrAccessor: 90 | Enabled: false 91 | 92 | Style/CaseLikeIf: 93 | Enabled: false 94 | 95 | Style/ExplicitBlockArgument: 96 | Enabled: false 97 | 98 | Style/ExponentialNotation: 99 | Enabled: false 100 | 101 | Style/GlobalStdStream: 102 | Enabled: false 103 | 104 | Style/HashAsLastArrayItem: 105 | Enabled: false 106 | 107 | Style/HashEachMethods: 108 | Enabled: false 109 | 110 | Style/HashLikeCase: 111 | Enabled: false 112 | 113 | Style/HashTransformKeys: 114 | Enabled: false 115 | 116 | Style/HashTransformValues: 117 | Enabled: false 118 | 119 | Style/OptionalBooleanParameter: 120 | Enabled: false 121 | 122 | Style/RedundantAssignment: 123 | Enabled: false 124 | 125 | Style/RedundantFetchBlock: 126 | Enabled: false 127 | 128 | Style/RedundantFileExtensionInRequire: 129 | Enabled: false 130 | 131 | Style/RedundantRegexpCharacterClass: 132 | Enabled: false 133 | 134 | Style/RedundantRegexpEscape: 135 | Enabled: false 136 | 137 | Style/SingleArgumentDig: 138 | Enabled: false 139 | 140 | Style/SlicingWithRange: 141 | Enabled: false 142 | 143 | Style/StringConcatenation: 144 | Enabled: false 145 | 146 | 147 | -------------------------------------------------------------------------------- /bin/check-ssl-hsts-status.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: false 3 | 4 | # check-ssl-hsts-preload.rb 5 | # 6 | # DESCRIPTION: 7 | # Checks a domain against the chromium HSTS API reporting on the preload status of the domain 8 | # 9 | # OUTPUT: 10 | # plain text 11 | # 12 | # PLATFORMS: 13 | # Linux 14 | # 15 | # DEPENDENCIES: 16 | # gem: sensu-plugin 17 | # 18 | # USAGE: 19 | # # Basic usage 20 | # check-ssl-hsts-preload.rb -d 21 | # # Specify the CRITICAL and WARNING alerts to either unknown (not in the database), pending or preloaded 22 | # check-ssl-hsts-preload.rb -d -c -w 23 | # 24 | # LICENSE: 25 | # Copyright 2017 Rowan Wookey 26 | # Released under the same terms as Sensu (the MIT license); see LICENSE for 27 | # details. 28 | # 29 | # Inspired by https://github.com/sensu-plugins/sensu-plugins-ssl/blob/master/bin/check-ssl-qualys.rb Copyright 2015 William Cooke 30 | # 31 | 32 | require 'sensu-plugin/check/cli' 33 | require 'json' 34 | require 'net/http' 35 | 36 | class CheckSSLHSTSStatus < Sensu::Plugin::Check::CLI 37 | STATUSES = %w[unknown pending preloaded].freeze 38 | 39 | option :domain, 40 | description: 'The domain to run the test against', 41 | short: '-d DOMAIN', 42 | long: '--domain DOMAIN', 43 | required: true 44 | 45 | option :warn, 46 | short: '-w STATUS', 47 | long: '--warn STATUS', 48 | description: 'WARNING if this status or worse', 49 | in: STATUSES, 50 | default: 'pending' 51 | 52 | option :critical, 53 | short: '-c STATUS', 54 | long: '--critical STATUS', 55 | description: 'CRITICAL if this status or worse', 56 | in: STATUSES, 57 | default: 'unknown' 58 | 59 | option :api_url, 60 | description: 'The URL of the API to run against', 61 | long: '--api-url URL', 62 | default: 'https://hstspreload.org/api/v2/status' 63 | 64 | def fetch(uri, limit = 10) 65 | if limit == 0 # rubocop:disable Style/NumericPredicate 66 | return nil 67 | end 68 | 69 | response = Net::HTTP.get_response(uri) 70 | 71 | case response 72 | when Net::HTTPSuccess 73 | response 74 | when Net::HTTPRedirection 75 | location = URI(response['location']) 76 | fetch(location, limit - 1) 77 | end 78 | end 79 | 80 | def run 81 | uri = URI(config[:api_url]) 82 | uri.query = URI.encode_www_form(domain: config[:domain]) 83 | response = fetch(uri) 84 | if response.nil? 85 | return warning 'Bad response recieved from API' 86 | end 87 | 88 | body = JSON.parse(response.body) 89 | unless STATUSES.include? body['status'] 90 | warning 'Invalid status returned ' + body['status'] 91 | end 92 | 93 | if STATUSES.index(body['status']) <= STATUSES.index(config[:critical]) 94 | critical body['status'] 95 | elsif STATUSES.index(body['status']) <= STATUSES.index(config[:warn]) 96 | warning body['status'] 97 | else 98 | ok 99 | end 100 | end 101 | end 102 | 103 | # vim: set tabstop=2 shiftwidth=2 expandtab: 104 | -------------------------------------------------------------------------------- /bin/check-ssl-anchor.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # frozen_string_literal: false 3 | 4 | # 5 | # check-ssl-anchor 6 | # 7 | # DESCRIPTION: 8 | # Check that a certificate is chained to a specific root certificate 9 | # 10 | # OUTPUT: 11 | # plain text 12 | # 13 | # PLATFORMS: 14 | # Linux 15 | # 16 | # DEPENDENCIES: 17 | # gem: sensu-plugin 18 | # 19 | # USAGE: 20 | # 21 | # Check that a specific website is chained to a specific root certificate (Let's Encrypt for instance) 22 | # ./check-ssl-anchor.rb \ 23 | # -u example.com \ 24 | # -a "i:/O=Digital Signature Trust Co./CN=DST Root CA X3" 25 | # 26 | # NOTES: 27 | # This is basically a ruby wrapper around the following openssl command. 28 | # 29 | # openssl s_client -connect example.com:443 -servername example.com 30 | # 31 | # 32 | # 33 | # Use the -s flag if you need to override SNI (Server Name Indication). If you 34 | # are seeing discrepencies between `openssl s_client` and browser, that's a good 35 | # indication to use this flag. 36 | # 37 | # LICENSE: 38 | # Copyright 2017 Phil Porada 39 | # 40 | # Released under the same terms as Sensu (the MIT license); see LICENSE 41 | # for details. 42 | # 43 | 44 | require 'sensu-plugin/check/cli' 45 | 46 | # 47 | # Check certificate is anchored to a specific root 48 | # 49 | class CheckSSLAnchor < Sensu::Plugin::Check::CLI 50 | option :host, 51 | description: 'Host to check', 52 | short: '-h', 53 | long: '--host HOST', 54 | required: true 55 | 56 | option :anchor, 57 | description: 'An anchor looks something like /O=Digital Signature Trust Co./CN=DST Root CA X3', 58 | short: '-a', 59 | long: '--anchor ANCHOR_VAL', 60 | required: true 61 | 62 | option :regexp, 63 | description: 'Treat the anchor as a regexp', 64 | short: '-r', 65 | long: '--regexp', 66 | default: false, 67 | boolean: true, 68 | required: false 69 | 70 | option :servername, 71 | description: 'Set the TLS SNI (Server Name Indication) extension', 72 | short: '-s', 73 | long: '--servername SERVER' 74 | 75 | option :port, 76 | description: 'Port on server to check', 77 | short: '-p', 78 | long: '--port PORT', 79 | default: 443 80 | 81 | def validate_opts 82 | config[:servername] = config[:host] unless config[:servername] 83 | end 84 | 85 | # Do the actual work and massage some data 86 | def anchor_information 87 | data = `openssl s_client \ 88 | -connect #{config[:host]}:#{config[:port]} \ 89 | -servername #{config[:servername]} < /dev/null 2>&1`.match(/Certificate chain(.*)---\nServer certificate/m)[1].split(/$/).map(&:strip) 90 | data = data.reject(&:empty?) 91 | 92 | unless data[0] =~ /0 s:\/?CN ?=.*/m 93 | data = 'NOTOK' 94 | end 95 | data 96 | end 97 | 98 | def run 99 | validate_opts 100 | data = anchor_information 101 | if data == 'NOTOK' 102 | critical 'An error was encountered while trying to retrieve the certificate chain.' 103 | end 104 | puts config[:regexp] 105 | # rubocop:disable Style/IfInsideElse 106 | if config[:regexp] 107 | anchor_regexp = Regexp.new(config[:anchor].to_s) 108 | if data[-1] =~ anchor_regexp 109 | ok 'Root anchor has been found.' 110 | else 111 | critical 'Root anchor did not match regexp /' + config[:anchor].to_s + "/\nFound \"" + data[-1] + '" instead.' 112 | end 113 | else 114 | if data[-1] == config[:anchor].to_s 115 | ok 'Root anchor has been found.' 116 | else 117 | critical 'Root anchor did not match string "' + config[:anchor].to_s + "\"\nFound \"" + data[-1] + '" instead.' 118 | end 119 | end 120 | # rubocop:enable Style/IfInsideElse 121 | end 122 | end 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Sensu-Plugins-SSL 2 | 3 | [![Build Status](https://travis-ci.org/sensu-plugins/sensu-plugins-ssl.svg?branch=master)](https://travis-ci.org/sensu-plugins/sensu-plugins-ssl) 4 | [![Gem Version](https://badge.fury.io/rb/sensu-plugins-ssl.svg)](http://badge.fury.io/rb/sensu-plugins-ssl) 5 | [![Code Climate](https://codeclimate.com/github/sensu-plugins/sensu-plugins-ssl/badges/gpa.svg)](https://codeclimate.com/github/sensu-plugins/sensu-plugins-ssl) 6 | [![Test Coverage](https://codeclimate.com/github/sensu-plugins/sensu-plugins-ssl/badges/coverage.svg)](https://codeclimate.com/github/sensu-plugins/sensu-plugins-ssl) 7 | [![Dependency Status](https://gemnasium.com/sensu-plugins/sensu-plugins-ssl.svg)](https://gemnasium.com/sensu-plugins/sensu-plugins-ssl) 8 | [![Sensu Bonsai Asset](https://img.shields.io/badge/Bonsai-Download%20Me-brightgreen.svg?colorB=89C967&logo=sensu)](https://bonsai.sensu.io/assets/sensu-plugins/sensu-plugins-ssl) 9 | 10 | ## Sensu Asset 11 | The Sensu assets packaged from this repository are built against the Sensu Ruby runtime environment. When using these assets as part of a Sensu Go resource (check, mutator or handler), make sure you include the corresponding Sensu Ruby runtime asset in the list of assets needed by the resource. The current ruby-runtime assets can be found [here](https://bonsai.sensu.io/assets/sensu/sensu-ruby-runtime) in the [Bonsai Asset Index](bonsai.sensu.io). 12 | ## Functionality 13 | 14 | ## Files 15 | * bin/check-java-keystore-cert.rb 16 | * bin/check-ssl-anchor.rb 17 | * bin/check-ssl-crl.rb 18 | * bin/check-ssl-cert.rb 19 | * bin/check-ssl-host.rb 20 | * bin/check-ssl-hsts-preload.rb 21 | * bin/check-ssl-hsts-preloadable.rb 22 | * bin/check-ssl-qualys.rb 23 | * bin/check-ssl-root-issuer.rb 24 | 25 | ## Usage 26 | 27 | ### `bin/check-ssl-anchor.rb` 28 | 29 | Check that a specific website is chained to a specific root certificate (Let's Encrypt for instance). Requires the `openssl` commandline tool to be available on the system. 30 | 31 | ``` 32 | ./bin/check-ssl-anchor.rb -u example.com -a "i:/O=Digital Signature Trust Co./CN=DST Root CA X3" 33 | ``` 34 | 35 | ### `bin/check-ssl-crl.rb` 36 | 37 | Checks a CRL has not or is not expiring by inspecting it's next update value. 38 | 39 | You can check against a CRL file on disk: 40 | 41 | ``` 42 | ./bin/check-ssl-crl -c 300 -w 600 -u /path/to/crl 43 | ``` 44 | 45 | or an online CRL: 46 | 47 | ``` 48 | ./bin/check-ssl-crl -c 300 -w 600 -u http://www.website.com/file.crl 49 | ``` 50 | 51 | Critical and Warning thresholds are specified in minutes. 52 | 53 | ### `bin/check-ssl-qualys.rb` 54 | 55 | Checks the ssllabs qualysis api for grade of your server, this check can be quite long so it should not be scheduled with a low interval and will probably need to adjust the check `timeout` options per the [check attributes spec](https://docs.sensu.io/sensu-core/1.2/reference/checks/#check-attributes) based on my tests you should expect this to take around 3 minutes. 56 | ``` 57 | ./bin/check-ssl-qualys.rb -d google.com 58 | ``` 59 | 60 | ### `bin/check-ssl-root-issuer.rb` 61 | 62 | Check that a specific website is chained to a specific root certificate issuer. This is a pure Ruby implementation, does not require the openssl cmdline client tool to be installed. 63 | 64 | ``` 65 | ./bin/check-ssl-root-issuer.rb -u example.com -a "CN=DST Root CA X3,O=Digital Signature Trust Co." 66 | ``` 67 | 68 | ## Installation 69 | 70 | [Installation and Setup](http://sensu-plugins.io/docs/installation_instructions.html) 71 | 72 | ## Testing 73 | 74 | To run the testing suite, you'll need to have a working `ruby` environment, `gem`, and `bundler` installed. We use `rake` to run the `rspec` tests automatically. 75 | 76 | bundle install 77 | bundle update 78 | bundle exec rake 79 | 80 | ## Notes 81 | 82 | `bin/check-ssl-anchor.rb` and `bin/check-ssl-host.rb` would be good to run in combination with each other to test that the chain is anchored to a specific certificate and each certificate in the chain is correctly signed. 83 | -------------------------------------------------------------------------------- /bin/check-ssl-cert.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # frozen_string_literal: false 3 | 4 | # 5 | # check-ssl-cert 6 | # 7 | # DESCRIPTION: 8 | # Check when a SSL certificate will expire. 9 | # 10 | # OUTPUT: 11 | # plain text 12 | # 13 | # PLATFORMS: 14 | # Linux 15 | # 16 | # DEPENDENCIES: 17 | # gem: sensu-plugin 18 | # 19 | # USAGE: 20 | # example commands 21 | # 22 | # NOTES: 23 | # Does it behave differently on specific platforms, specific use cases, etc 24 | # 25 | # LICENSE: 26 | # Jean-Francois Theroux 27 | # Nathan Williams 28 | # Released under the same terms as Sensu (the MIT license); see LICENSE 29 | # for details. 30 | # 31 | 32 | require 'date' 33 | require 'openssl' 34 | require 'sensu-plugin/check/cli' 35 | 36 | # 37 | # Check SSL Cert 38 | # 39 | class CheckSSLCert < Sensu::Plugin::Check::CLI 40 | option :critical, 41 | description: 'Numbers of days left', 42 | short: '-c', 43 | long: '--critical DAYS', 44 | required: true 45 | 46 | option :warning, 47 | description: 'Numbers of days left', 48 | short: '-w', 49 | long: '--warning DAYS', 50 | required: true 51 | 52 | option :pem, 53 | description: 'Path to PEM file', 54 | short: '-P', 55 | long: '--pem PEM' 56 | 57 | option :host, 58 | description: 'Host to validate', 59 | short: '-h', 60 | long: '--host HOST' 61 | 62 | option :port, 63 | description: 'Port to validate', 64 | short: '-p', 65 | long: '--port PORT' 66 | 67 | option :servername, 68 | description: 'Set the TLS SNI (Server Name Indication) extension', 69 | short: '-s', 70 | long: '--servername SERVER' 71 | 72 | option :pkcs12, 73 | description: 'Path to PKCS#12 certificate', 74 | short: '-C', 75 | long: '--cert P12' 76 | 77 | option :pass, 78 | description: 'Pass phrase for the private key in PKCS#12 certificate', 79 | short: '-S', 80 | long: '--pass ' 81 | 82 | def ssl_cert_expiry 83 | `openssl s_client -servername #{config[:servername]} -connect #{config[:host]}:#{config[:port]} < /dev/null 2>&1 | openssl x509 -enddate -noout`.split('=').last 84 | end 85 | 86 | def ssl_pem_expiry 87 | OpenSSL::X509::Certificate.new(File.read config[:pem]).not_after # rubocop:disable Style/NestedParenthesizedCalls 88 | end 89 | 90 | def ssl_pkcs12_expiry 91 | `openssl pkcs12 -in #{config[:pkcs12]} -nokeys -nomacver -passin pass:"#{config[:pass]}" | openssl x509 -noout -enddate | grep -v MAC`.split('=').last 92 | end 93 | 94 | def validate_opts 95 | if !config[:pem] && !config[:pkcs12] 96 | unknown 'Host and port required' unless config[:host] && config[:port] 97 | elsif config[:pem] 98 | unknown 'No such cert' unless File.exist? config[:pem] 99 | elsif config[:pkcs12] 100 | if !config[:pass] 101 | unknown 'No pass phrase specified for PKCS#12 certificate' 102 | else 103 | unknown 'No such cert' unless File.exist? config[:pkcs12] 104 | end 105 | end 106 | config[:servername] = config[:host] unless config[:servername] 107 | end 108 | 109 | def run 110 | validate_opts 111 | 112 | expiry = if config[:pem] 113 | ssl_pem_expiry 114 | elsif config[:pkcs12] 115 | ssl_pkcs12_expiry 116 | else 117 | ssl_cert_expiry 118 | end 119 | 120 | days_until = (Date.parse(expiry.to_s) - Date.today).to_i 121 | 122 | if days_until < 0 # rubocop:disable Style/NumericPredicate 123 | critical "Expired #{days_until.abs} days ago" 124 | elsif days_until < config[:critical].to_i 125 | critical "#{days_until} days left" 126 | elsif days_until < config[:warning].to_i 127 | warning "#{days_until} days left" 128 | else 129 | ok "#{days_until} days left" 130 | end 131 | end 132 | end 133 | -------------------------------------------------------------------------------- /bin/check-ssl-root-issuer.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # frozen_string_literal: false 3 | 4 | # 5 | # check-ssl-root-issuer 6 | # 7 | # DESCRIPTION: 8 | # Check that a certificate is chained to a specific root certificate issuer 9 | # 10 | # OUTPUT: 11 | # plain text 12 | # 13 | # PLATFORMS: 14 | # Linux 15 | # 16 | # DEPENDENCIES: 17 | # gem: sensu-plugin 18 | # 19 | # USAGE: 20 | # 21 | # Check that a specific website is chained to a specific root certificate 22 | # ./check-ssl-root-issuer.rb \ 23 | # -u https://example.com \ 24 | # -i "CN=DST Root CA X3,O=Digital Signature Trust Co." 25 | # 26 | # LICENSE: 27 | # Copyright Jef Spaleta (jspaleta@gmail.com) 2020 28 | # Released under the same terms as Sensu (the MIT license); see LICENSE 29 | # for details. 30 | # 31 | 32 | require 'sensu-plugin/check/cli' 33 | require 'openssl' 34 | require 'uri' 35 | require 'net/http' 36 | require 'net/https' 37 | 38 | # 39 | # Check root certificate has specified issuer name 40 | # 41 | class CheckSSLRootIssuer < Sensu::Plugin::Check::CLI 42 | option :url, 43 | description: 'Url to check: Ex "https://google.com"', 44 | short: '-u', 45 | long: '--url URL', 46 | required: true 47 | 48 | option :issuer, 49 | description: 'An X509 certificate issuer name, RFC2253 format Ex: "CN=DST Root CA X3,O=Digital Signature Trust Co."', 50 | short: '-i', 51 | long: '--issuer ISSUER_NAME', 52 | required: true 53 | 54 | option :regexp, 55 | description: 'Treat the issuer name as a regexp', 56 | short: '-r', 57 | long: '--regexp', 58 | default: false, 59 | boolean: true, 60 | required: false 61 | 62 | option :format, 63 | description: 'optional issuer name format.', 64 | short: '-f', 65 | long: '--format FORMAT_VAL', 66 | default: 'RFC2253', 67 | in: %w[RFC2253 ONELINE COMPAT], 68 | required: false 69 | 70 | def cert_name_format 71 | # Note: because format argument is pre-validated by mixin 'in' logic eval is safe to use 72 | eval "OpenSSL::X509::Name::#{config[:format]}" # rubocop:disable Security/Eval, Style/EvalWithLocation 73 | end 74 | 75 | def validate_issuer(cert) 76 | issuer = cert.issuer.to_s(cert_name_format) 77 | if config[:regexp] 78 | issuer_regexp = Regexp.new(config[:issuer].to_s) 79 | issuer =~ issuer_regexp 80 | else 81 | issuer == config[:issuer].to_s 82 | end 83 | end 84 | 85 | def find_root_cert(uri) 86 | root_cert = nil 87 | http = Net::HTTP.new(uri.host, uri.port) 88 | http.open_timeout = 10 89 | http.read_timeout = 10 90 | http.use_ssl = true 91 | http.cert_store = OpenSSL::X509::Store.new 92 | http.cert_store.set_default_paths 93 | http.verify_mode = OpenSSL::SSL::VERIFY_PEER 94 | 95 | http.verify_callback = lambda { |verify_ok, store_context| 96 | root_cert = store_context.current_cert unless root_cert # rubocop:disable Style/OrAssignment 97 | unless verify_ok 98 | @failed_cert = store_context.current_cert 99 | @failed_cert_reason = [store_context.error, store_context.error_string] if store_context.error != 0 100 | end 101 | verify_ok 102 | } 103 | http.start {} 104 | root_cert 105 | end 106 | 107 | # Do the actual work and massage some data 108 | 109 | def run 110 | @fail_cert = nil 111 | @failed_cert_reason = 'Unknown' 112 | uri = URI.parse(config[:url]) 113 | critical "url protocol must be https, you specified #{url}" if uri.scheme != 'https' 114 | root_cert = find_root_cert(uri) 115 | if @failed_cert 116 | msg = "Certificate verification failed.\n Reason: #{@failed_cert_reason}" 117 | critical msg 118 | end 119 | 120 | if validate_issuer(root_cert) 121 | msg = 'Root certificate in chain has expected issuer name' 122 | ok msg 123 | else 124 | msg = "Root certificate issuer did not match expected name.\nFound: \"#{root_cert.issuer.to_s(config[:issuer_format])}\"" 125 | critical msg 126 | end 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /bin/check-ssl-qualys.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: false 3 | 4 | # check-ssl-qualys.rb 5 | # 6 | # DESCRIPTION: 7 | # Runs a report using the Qualys SSL Labs API and then alerts if a 8 | # domain does not meet the grade specified for *ALL* hosts that are 9 | # reachable from that domian. 10 | # 11 | # The checks that are performed are documented on 12 | # https://www.ssllabs.com/index.html as are the range of grades. 13 | # 14 | # OUTPUT: 15 | # plain text 16 | # 17 | # PLATFORMS: 18 | # Linux 19 | # 20 | # DEPENDENCIES: 21 | # gem: sensu-plugin 22 | # 23 | # USAGE: 24 | # # Basic usage 25 | # check-ssl-qualys.rb -d 26 | # # Specify the CRITICAL and WARNING grades to a specific grade 27 | # check-ssl-qualys.rb -d -c -w 28 | # # Use --api-url to specify an alternate api host 29 | # check-ssl-qualys.rb -d -api-url 30 | # 31 | # NOTE: This check takes a rather long time to run and will timeout if you're using 32 | # the default sensu check timeout. Make sure to set a longer timeout period in the 33 | # check definition. Two minutes or longer may be a good starting point as checks 34 | # regularly take 90+ seconds to run. 35 | # 36 | # LICENSE: 37 | # Copyright 2015 William Cooke 38 | # Released under the same terms as Sensu (the MIT license); see LICENSE for 39 | # details. 40 | # 41 | 42 | require 'sensu-plugin/check/cli' 43 | require 'json' 44 | require 'net/http' 45 | require 'timeout' 46 | 47 | # Checks a single DNS entry has a rating above a certain level 48 | class CheckSSLQualys < Sensu::Plugin::Check::CLI 49 | # Current grades that are avaialble from the API 50 | GRADE_OPTIONS = ['A+', 'A', 'A-', 'B', 'C', 'D', 'E', 'F', 'T', 'M'].freeze 51 | 52 | option :domain, 53 | description: 'The domain to run the test against', 54 | short: '-d DOMAIN', 55 | long: '--domain DOMAIN', 56 | required: true 57 | 58 | option :api_url, 59 | description: 'The URL of the API to run against', 60 | long: '--api-url URL', 61 | default: 'https://api.ssllabs.com/api/v3/' 62 | 63 | option :warn, 64 | short: '-w GRADE', 65 | long: '--warn GRADE', 66 | description: 'WARNING if below this grade', 67 | proc: proc { |g| GRADE_OPTIONS.index(g) }, 68 | default: 2 # 'A-' 69 | 70 | option :critical, 71 | short: '-c GRADE', 72 | long: '--critical GRADE', 73 | description: 'CRITICAL if below this grade', 74 | proc: proc { |g| GRADE_OPTIONS.index(g) }, 75 | default: 3 # 'B' 76 | 77 | option :debug, 78 | long: '--debug BOOL', 79 | description: 'toggles extra debug printing', 80 | boolean: true, 81 | default: false 82 | 83 | option :num_checks, 84 | short: '-n NUM_CHECKS', 85 | long: '--number-checks NUM_CHECKS', 86 | description: 'The number of checks to make before giving up (timeout of check)', 87 | proc: proc { |t| t.to_i }, 88 | default: 24 89 | 90 | option :between_checks, 91 | short: '-t SECONDS', 92 | long: '--time-between SECONDS', 93 | description: 'The fallback time between each poll of the API, when an ETA is given by the previous response and is higher than this value it is used', 94 | proc: proc { |t| t.to_i }, 95 | default: 10 96 | 97 | option :timeout, 98 | long: '--timeout SECONDS', 99 | descriptions: 'the amount of seconds that this is allowed to run for', 100 | proc: proc(&:to_i), 101 | default: 300 102 | 103 | def ssl_api_request(from_cache) 104 | params = { host: config[:domain] } 105 | params[:startNew] = if from_cache == true 106 | 'off' 107 | else 108 | 'on' 109 | end 110 | 111 | uri = URI("#{config[:api_url]}analyze") 112 | uri.query = URI.encode_www_form(params) 113 | begin 114 | response = Net::HTTP.get_response(uri) 115 | rescue StandardError => e 116 | warning e 117 | end 118 | 119 | warning 'Bad response recieved from API' unless response.is_a?(Net::HTTPSuccess) 120 | 121 | JSON.parse(response.body) 122 | end 123 | 124 | def ssl_check(from_cache) 125 | json = ssl_api_request(from_cache) 126 | warning "ERROR on #{config[:domain]} check" if json['status'] == 'ERROR' 127 | json 128 | end 129 | 130 | def ssl_recheck 131 | 1.upto(config[:num_checks]) do |step| 132 | p "step: #{step}" if config[:debug] 133 | start_time = Time.now 134 | p "start_time: #{start_time}" if config[:debug] 135 | json = if step == 1 136 | ssl_check(false) 137 | else 138 | ssl_check(true) 139 | end 140 | return json if json['status'] == 'READY' 141 | 142 | if json['endpoints'] && json['endpoints'].is_a?(Array) # rubocop:disable Style/SafeNavigation 143 | p "endpoints: #{json['endpoints']}" if config[:debug] 144 | # The api response sometimes has low eta (which seems unrealistic) from 145 | # my tests that can be 0 or low numbers which would imply it is done... 146 | # Basically we check if present and if its higher than the specified 147 | # time to wait between checks. If so we use the eta from the api get 148 | # response otherwise we use the time between check values. We have an 149 | # overall timeout that protects us from the api telling us to wait for 150 | # insanely long time periods. The highest I have seen the eta go was 151 | # around 250 seconds but put it in just in case as the api has very 152 | # erratic response times. 153 | if json['endpoints'].first.is_a?(Hash) && json['endpoints'].first.key?('eta') && json['endpoints'].first['eta'] > config[:between_checks] 154 | p "eta: #{json['endpoints'].first['eta']}" if config[:debug] 155 | sleep(json['endpoints'].first['eta']) 156 | else 157 | p "sleeping with default: #{config[:between_checks]}" if config[:debug] 158 | sleep(config[:between_checks]) 159 | end 160 | end 161 | p "elapsed: #{Time.now - start_time}" if config[:debug] 162 | warning 'Timeout waiting for check to finish' if step == config[:num_checks] 163 | end 164 | end 165 | 166 | def ssl_grades 167 | ssl_recheck['endpoints'].map do |endpoint| 168 | endpoint['grade'] 169 | end 170 | end 171 | 172 | def lowest_grade 173 | ssl_grades.sort_by! { |g| GRADE_OPTIONS.index(g) }.reverse![0] 174 | end 175 | 176 | def run 177 | Timeout.timeout(config[:timeout]) do 178 | grade = lowest_grade 179 | unless grade 180 | message "#{config[:domain]} not rated" 181 | critical 182 | end 183 | message "#{config[:domain]} rated #{grade}" 184 | grade_rank = GRADE_OPTIONS.index(grade) 185 | if grade_rank > config[:critical] 186 | critical 187 | elsif grade_rank > config[:warn] 188 | warning 189 | else 190 | ok 191 | end 192 | end 193 | end 194 | end 195 | -------------------------------------------------------------------------------- /bin/check-ssl-host.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: false 3 | 4 | # check-ssl-host.rb 5 | # 6 | # DESCRIPTION: 7 | # SSL certificate checker 8 | # Connects to a HTTPS (or other SSL) server and performs several checks on 9 | # the certificate: 10 | # - Is the hostname valid for the host we're requesting 11 | # - If any certificate chain is presented, is it valid (i.e. is each 12 | # certificate signed by the next) 13 | # - Is the certificate about to expire 14 | # Currently no checks are performed to make sure the certificate is signed 15 | # by a trusted authority. 16 | # 17 | # DEPENDENCIES: 18 | # gem: sensu-plugin 19 | # 20 | # USAGE: 21 | # # Basic usage 22 | # check-ssl-host.rb -h 23 | # # Specify specific days before cert expiry to alert on 24 | # check-ssl-host.rb -h -c -w 25 | # # Use -p to specify an alternate port 26 | # check-ssl-host.rb -h -p 8443 27 | # # Use --skip-hostname-verification and/or --skip-chain-verification to 28 | # # disable some of the checks made. 29 | # check-ssl-host.rb -h --skip-chain-verification 30 | # 31 | # LICENSE: 32 | # Copyright 2014 Chef Software, Inc. 33 | # Released under the same terms as Sensu (the MIT license); see LICENSE for 34 | # details. 35 | # 36 | 37 | require 'sensu-plugin/check/cli' 38 | require 'date' 39 | require 'openssl' 40 | require 'socket' 41 | 42 | # 43 | # Check SSL Host 44 | # 45 | class CheckSSLHost < Sensu::Plugin::Check::CLI 46 | STARTTLS_PROTOS = %w[smtp imap].freeze 47 | 48 | check_name 'check_ssl_host' 49 | 50 | option :critical, 51 | description: 'Return critical this many days before cert expiry', 52 | short: '-c', 53 | long: '--critical DAYS', 54 | proc: proc(&:to_i), 55 | default: 7 56 | 57 | option :warning, 58 | description: 'Return warning this many days before cert expiry', 59 | short: '-w', 60 | long: '--warning DAYS', 61 | required: true, 62 | proc: proc(&:to_i), 63 | default: 14 64 | 65 | option :host, 66 | description: 'Hostname of the server certificate to check, by default used as the server address if none ' \ 67 | 'is given', 68 | short: '-h', 69 | long: '--host HOST', 70 | required: true 71 | 72 | option :port, 73 | description: 'Port on server to check', 74 | short: '-p', 75 | long: '--port PORT', 76 | default: 443 77 | 78 | option :address, 79 | description: 'Address of server to check. This is used instead of the host argument for the TCP connection, ' \ 80 | 'however the server hostname is still used for the TLS/SSL context.', 81 | short: '-a', 82 | long: '--address ADDRESS' 83 | 84 | option :client_cert, 85 | description: 'Path to the client certificate in DER/PEM format', 86 | long: '--client-cert CERT' 87 | 88 | option :client_key, 89 | description: 'Path to the client RSA key in DER/PEM format', 90 | long: '--client-key KEY' 91 | 92 | option :skip_hostname_verification, 93 | description: 'Disables hostname verification', 94 | long: '--skip-hostname-verification', 95 | boolean: true 96 | 97 | option :skip_chain_verification, 98 | description: 'Disables certificate chain verification', 99 | long: '--skip-chain-verification', 100 | boolean: true 101 | 102 | option :starttls, 103 | description: 'use STARTTLS negotiation for the given protocol '\ 104 | "(#{STARTTLS_PROTOS.join(', ')})", 105 | long: '--starttls PROTO' 106 | 107 | def get_cert_chain(host, port, address, client_cert, client_key) 108 | tcp_client = TCPSocket.new(address ? address : host, port) # rubocop:disable Style/RedundantCondition 109 | handle_starttls(config[:starttls], tcp_client) if config[:starttls] 110 | ssl_context = OpenSSL::SSL::SSLContext.new 111 | ssl_context.cert = OpenSSL::X509::Certificate.new File.read(client_cert) if client_cert 112 | ssl_context.key = OpenSSL::PKey::RSA.new File.read(client_key) if client_key 113 | ssl_client = OpenSSL::SSL::SSLSocket.new(tcp_client, ssl_context) 114 | 115 | # If the OpenSSL version in use supports Server Name Indication (SNI, RFC 3546), then we set the hostname we 116 | # received to request that certificate. 117 | ssl_client.hostname = host if ssl_client.respond_to? :hostname= 118 | 119 | ssl_client.connect 120 | certs = ssl_client.peer_cert_chain 121 | ssl_client.close 122 | certs 123 | end 124 | 125 | def handle_starttls(proto, socket) 126 | if STARTTLS_PROTOS.include?(proto) # rubocop:disable Style/GuardClause 127 | send("starttls_#{proto}", socket) 128 | else 129 | raise ArgumentError, "STARTTLS supported only for #{STARTTLS_PROTOS.join(', ')}" 130 | end 131 | end 132 | 133 | def starttls_smtp(socket) 134 | status = socket.readline 135 | unless /^220 / =~ status 136 | critical "#{config[:host]} - did not receive initial SMTP 220" 137 | # no fall-through 138 | end 139 | socket.puts 'STARTTLS' 140 | 141 | status = socket.readline 142 | return if /^220 / =~ status 143 | 144 | critical "#{config[:host]} - did not receive SMTP 220 in response to STARTTLS" 145 | end 146 | 147 | def starttls_imap(socket) 148 | status = socket.readline 149 | unless /^* OK / =~ status 150 | critical "#{config[:host]} - did not receive initial * OK" 151 | end 152 | socket.puts 'a001 STARTTLS' 153 | 154 | status = socket.readline 155 | return if /^a001 OK Begin TLS negotiation now/ =~ status 156 | 157 | critical "#{config[:host]} - did not receive OK Begin TLS negotiation now" 158 | end 159 | 160 | def verify_expiry(cert) 161 | # Expiry check 162 | days = (cert.not_after.to_date - Date.today).to_i 163 | message = "#{config[:host]} - #{days} days until expiry" 164 | critical "#{config[:host]} - Expired #{days} days ago" if days < 0 # rubocop:disable Style/NumericPredicate 165 | critical message if days < config[:critical] 166 | warning message if days < config[:warning] 167 | ok message 168 | end 169 | 170 | def verify_certificate_chain(certs) 171 | # Validates that a chain of certs are each signed by the next 172 | # NOTE: doesn't validate that the top of the chain is signed by a trusted 173 | # CA. 174 | valid = true 175 | parent = nil 176 | certs.reverse_each do |c| 177 | if parent 178 | valid &= c.verify(parent.public_key) 179 | end 180 | parent = c 181 | end 182 | critical "#{config[:host]} - Invalid certificate chain" unless valid 183 | end 184 | 185 | def verify_hostname(cert) 186 | unless OpenSSL::SSL.verify_certificate_identity(cert, config[:host]) # rubocop:disable Style/GuardClause 187 | critical "#{config[:host]} hostname mismatch (#{cert.subject})" 188 | end 189 | end 190 | 191 | def run 192 | chain = get_cert_chain(config[:host], config[:port], config[:address], config[:client_cert], config[:client_key]) 193 | verify_hostname(chain[0]) unless config[:skip_hostname_verification] 194 | verify_certificate_chain(chain) unless config[:skip_chain_verification] 195 | verify_expiry(chain[0]) 196 | rescue Errno::ECONNRESET => e 197 | critical "#{e.class} - #{e.message}" 198 | end 199 | end 200 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | This project adheres to [Semantic Versioning](http://semver.org/). 3 | 4 | This CHANGELOG follows the format listed [here](https://github.com/sensu-plugins/community/blob/master/HOW_WE_CHANGELOG.md). 5 | 6 | ## [Unreleased] 7 | 8 | ### Changed 9 | - Removed centos build from .bonsai.yml 10 | 11 | ## [3.0.2] - 2020-08-27 12 | ### Changed 13 | - Fixed bonsai script in .travis.yml 14 | 15 | ## [3.0.1] - 2020-08-27 16 | ### Changed 17 | - Modified .travis.yml to re-order deploy steps 18 | 19 | ## [3.0.0] - 2020-08-27 20 | ### Breaking Changes 21 | - Bump `sensu-plugin` dependency from `~> 1.2` to `~> 4.0` you can read the changelog entries for [4.0](https://github.com/sensu-plugins/sensu-plugin/blob/master/CHANGELOG.md#400---2018-02-17), [3.0](https://github.com/sensu-plugins/sensu-plugin/blob/master/CHANGELOG.md#300---2018-12-04), and [2.0](https://github.com/sensu-plugins/sensu-plugin/blob/master/CHANGELOG.md#v200---2017-03-29) 22 | - Remove ruby-2.3.0. Upgrade bundler. Fix failing tests (@phumpal). 23 | 24 | ### Added 25 | - Travis build automation to generate Sensu Asset tarballs that can be used n conjunction with Sensu provided ruby runtime assets and the Bonsai Asset Index 26 | - Require latest sensu-plugin for [Sensu Go support](https://github.com/sensu-plugins/sensu-plugin#sensu-go-enablement) 27 | - New option to treat anchor argument as a regexp 28 | - New Check plugin `check-ssl-root-issuer.rb` with alternative logic for trust anchor verification. 29 | 30 | ### Changed 31 | - `check-ssl-anchor.rb` uses regexp to test for present of certificates in cert chain that works with both openssl 1.0 and 1.1 formatting 32 | - Upgrade rake and rubocop dependencies 33 | - Remediate rubocop issues 34 | 35 | ### Fixed 36 | - ssl-anchor test now uses regexp 37 | 38 | ## [2.0.1] - 2018-05-30 39 | ### Fixed 40 | - `check-ssl-qualys.rb`: Fixed typo and removed timeout `-t` short option replacing it with `--timeout` as per previous changelog. `-t` conflicts with the short option for `--time-between` 41 | - Fixed typo in changelog 42 | 43 | ## [2.0.0] - 2018-03-27 44 | ### Breaking Changes 45 | - `check-ssl-qualys.rb`: when you submit a request with caching enabled it will return back a response including an eta key. Rather than sleeping for some arbitrary number of time we now use this key when its greater than `--time-between` to wait before attempting the next attempt to query. If it is lower or not present we fall back to `--time-between` (@majormoses) 46 | - `check-ssl-qualys.rb`: new `--timeout` parameter to short circuit slow apis (@majormoses) 47 | 48 | ### Changed 49 | - `check-ssl-qualys.rb`: updated `--api-url` to default to `v3` but remains backwards compatible (@jhoblitt) (@majormoses) 50 | 51 | ### Added 52 | `check-ssl-qualys.rb`: option `--debug` to enable debug logging (@majormoses) 53 | 54 | ### Fixed 55 | - `check-ssl-hsts-preloadable.rb`: Fixed testing warnings for if a domain can be HSTS preloaded (@rwky) 56 | 57 | ## [1.5.0] - 2017-09-26 58 | ### Added 59 | - Ruby 2.4.1 testing 60 | - `check-ssl-hsts-preload.rb`: Added check for testing preload status of HSTS (@rwky) 61 | - `check-ssl-hsts-preloadable.rb`: Added check for testing if a domain can be HSTS preloaded (@rwky) 62 | 63 | ### Changed 64 | - updated CHANGELOG guidelines location (@majormoses) 65 | 66 | ### Fixed 67 | - `check-java-keystore-cert.rb`: Export cert in PEM format to fix tests that broke going from Precise to Trusty travis workers (@eheydrick) 68 | - fixed spelling in github pr template (@majormoses) 69 | 70 | ## [1.4.0] - 2017-06-20 71 | ### Added 72 | - `check-ssl-anchor.rb`: Add check for a specific root certificate signature. (@pgporada) 73 | - `check-ssl-anchor_spec.rb`: Tests for the `check-ssl-anchor.rb` script (@pgporada) 74 | 75 | ## [1.3.1] - 2017-05-30 76 | ### Fixed 77 | - `check-ssl-qualys.rb`: Fix missing `net/http` require that prevented the check from executing (@eheydrick) 78 | 79 | ## [1.3.0] 2017-05-18 80 | ### Changed 81 | - `check-java-keystore-cert.rb`: Escape variables sent to shell on calls to keytool. (@rs-mrichmond) 82 | 83 | ## [1.2.0] - 2017-05-17 84 | ### Changed 85 | - check-ssl-qualys.rb: removed dependency on rest-client so we don't need a c compiler (@baweaver) 86 | 87 | ## [1.1.0] - 2017-02-28 88 | ### Added 89 | - `check-ssl-host.rb`: Add optional `address` command line parameter for specifying the address of the server to 90 | connect to, to override the `hostname` parameter (which is still used for verification/SNI) (@advance512) 91 | - `check-ssl-host.rb`: Better error message when unable to connect to target host (@johntdyer) 92 | - `check-ssl-host.rb`: Add support for client certificates (@modax) 93 | - `check-ssl-host.rb`: Add basic IMAP STARTTLS negotiation (@lobeck) 94 | - `check-java-keystore-cert.rb`: Add new check to verify a certificate in a Java Keystore has not expired. (@joerayme) 95 | - `check-ssl-crl.rb`: Add check for expiring CRL (@shoekstra) 96 | 97 | ### Fixed 98 | - `check-ssl-qualys.rb`: Handle API errors with status unknown instead of unhandled "Check failed to run". (@11mariom) 99 | - `check-ssl-qualys.rb`: Handle nil grade_rank as critical not rated (@11mariom) 100 | 101 | ## [1.0.0] 102 | ### Changed 103 | - Updated Rubocop to 0.40, applied auto-correct 104 | - Loosened dependency on sensu-plugin from `= 1.2.0` to `~> 1.2` 105 | - Changed permissions on check-ssl-qualys.rb to ensure it is executable 106 | 107 | ### Added 108 | - check-ssl-cert.rb: Added optional `servername` configuration for specifying an SNI which may differ from the host 109 | 110 | ### Removed 111 | - Removed Ruby 1.9.3 support; add Ruby 2.3.0 support to testing matrix 112 | 113 | ## [0.0.6] - 2015-08-18 114 | ### Fixed 115 | - Added rest-client to the gemspec 116 | 117 | ## [0.0.5] - 2015-08-05 118 | ### Changed 119 | - updated sensu-plugin gem to 1.2.0 120 | 121 | ### Added 122 | - Basic support for STARTTLS negotiation (only SMTP to start with) 123 | 124 | ## [0.0.4] - 2015-07-14 125 | ### Changed 126 | - updated sensu-plugin gem to 1.2.0 127 | 128 | ## [0.0.3] - 2015-06-18 129 | ### Added 130 | - plugin to test SSL using the [Qualys SSL Test API](https://www.ssllabs.com/ssltest/) 131 | 132 | ## [0.0.2] - 2015-06-03 133 | ### Fixed 134 | - added binstubs 135 | 136 | ### Changed 137 | - removed cruft from /lib 138 | 139 | ## 0.0.1 - 2015-05-21 140 | ### Added 141 | - initial release 142 | 143 | [Unreleased]: https://github.com/sensu-plugins/sensu-plugins-ssl/compare/3.0.3...HEAD 144 | [3.0.3]: https://github.com/sensu-plugins/sensu-plugins-ssl/compare/3.0.2...3.0.3 145 | [3.0.2]: https://github.com/sensu-plugins/sensu-plugins-ssl/compare/3.0.1...3.0.2 146 | [3.0.1]: https://github.com/sensu-plugins/sensu-plugins-ssl/compare/3.0.0...3.0.1 147 | [3.0.0]: https://github.com/sensu-plugins/sensu-plugins-ssl/compare/2.0.1...3.0.0 148 | [2.0.1]: https://github.com/sensu-plugins/sensu-plugins-ssl/compare/2.0.0...2.0.1 149 | [2.0.0]: https://github.com/sensu-plugins/sensu-plugins-ssl/compare/1.5.0...2.0.0 150 | [1.5.0]: https://github.com/sensu-plugins/sensu-plugins-ssl/compare/1.4.0...1.5.0 151 | [1.4.0]: https://github.com/sensu-plugins/sensu-plugins-ssl/compare/1.3.1...1.4.0 152 | [1.3.1]: https://github.com/sensu-plugins/sensu-plugins-ssl/compare/1.3.0...1.3.1 153 | [1.3.0]: https://github.com/sensu-plugins/sensu-plugins-ssl/compare/1.2.0...1.3.0 154 | [1.2.0]: https://github.com/sensu-plugins/sensu-plugins-ssl/compare/1.1.0...1.2.0 155 | [1.1.0]: https://github.com/sensu-plugins/sensu-plugins-ssl/compare/1.0.0...1.1.0 156 | [1.0.0]: https://github.com/sensu-plugins/sensu-plugins-ssl/compare/0.0.6...1.0.0 157 | [0.0.6]: https://github.com/sensu-plugins/sensu-plugins-ssl/compare/0.0.5...0.0.6 158 | [0.0.5]: https://github.com/sensu-plugins/sensu-plugins-ssl/compare/0.0.4...0.0.5 159 | [0.0.4]: https://github.com/sensu-plugins/sensu-plugins-ssl/compare/0.0.3...0.0.4 160 | [0.0.3]: https://github.com/sensu-plugins/sensu-plugins-ssl/compare/0.0.2...0.0.3 161 | [0.0.2]: https://github.com/sensu-plugins/sensu-plugins-ssl/compare/0.0.1...0.0.2 162 | --------------------------------------------------------------------------------