diff --git a/CHANGELOG.md b/CHANGELOG.md index e818a0e..9c48952 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This CHANGELOG follows the format listed at [Keep A Changelog](http://keepachang ## [Unreleased] ### Added - Ruby 2.4.1 testing +- `check-ssl-hsts-preload.rb`: Added check for testing preload status of HSTS +- `check-ssl-hsts-preloadable.rb`: Added check for testing if a domain can be HSTS preloaded ## [1.4.0] - 2017-06-20 ### Added diff --git a/README.md b/README.md index ceaee7f..96c713b 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ * bin/check-ssl-crl.rb * bin/check-ssl-cert.rb * bin/check-ssl-host.rb + * bin/check-ssl-hsts-preload.rb + * bin/check-ssl-hsts-preloadable.rb * bin/check-ssl-qualys.rb ## Usage diff --git a/bin/check-ssl-hsts-preloadable.rb b/bin/check-ssl-hsts-preloadable.rb new file mode 100755 index 0000000..775394f --- /dev/null +++ b/bin/check-ssl-hsts-preloadable.rb @@ -0,0 +1,79 @@ +#!/usr/bin/env ruby +# encoding: UTF-8 +# check-ssl-hsts-preloadable.rb +# +# DESCRIPTION: +# Checks a domain against the chromium HSTS API returning errors/warnings if the domain is preloadable +# +# OUTPUT: +# plain text +# +# PLATFORMS: +# Linux +# +# DEPENDENCIES: +# gem: sensu-plugin +# +# USAGE: +# # Basic usage +# check-ssl-hsts-preloadable.rb -d +# +# LICENSE: +# Copyright 2017 Rowan Wookey +# Released under the same terms as Sensu (the MIT license); see LICENSE for +# details. +# +# Inspired by https://github.com/sensu-plugins/sensu-plugins-ssl/blob/master/bin/check-ssl-qualys.rb Copyright 2015 William Cooke +# + +require 'sensu-plugin/check/cli' +require 'json' +require 'net/http' + +class CheckSSLHSTSPreloadable < Sensu::Plugin::Check::CLI + option :domain, + description: 'The domain to run the test against', + short: '-d DOMAIN', + long: '--domain DOMAIN', + required: true + + option :api_url, + description: 'The URL of the API to run against', + long: '--api-url URL', + default: 'https://hstspreload.org/api/v2/preloadable' + + def fetch(uri, limit = 10) + if limit == 0 + return nil + end + + response = Net::HTTP.get_response(uri) + + case response + when Net::HTTPSuccess then + response + when Net::HTTPRedirection then + location = URI(response['location']) + fetch(location, limit - 1) + end + end + + def run + uri = URI(config[:api_url]) + uri.query = URI.encode_www_form(domain: config[:domain]) + response = fetch(uri) + if response.nil? + return warning 'Bad response recieved from API' + end + body = JSON.parse(response.body) + if !body['errors'].empty? + critical body['errors'].map { |u| u['summary'] }.join(', ') + elsif !body['warnings'].empty? + warning body['warnings'].map { |u| u['summary'] }.join(', ') + else + ok + end + end +end + +# vim: set tabstop=2 shiftwidth=2 expandtab: diff --git a/bin/check-ssl-hsts-status.rb b/bin/check-ssl-hsts-status.rb new file mode 100755 index 0000000..09e6712 --- /dev/null +++ b/bin/check-ssl-hsts-status.rb @@ -0,0 +1,101 @@ +#!/usr/bin/env ruby +# encoding: UTF-8 +# check-ssl-hsts-preload.rb +# +# DESCRIPTION: +# Checks a domain against the chromium HSTS API reporting on the preload status of the domain +# +# OUTPUT: +# plain text +# +# PLATFORMS: +# Linux +# +# DEPENDENCIES: +# gem: sensu-plugin +# +# USAGE: +# # Basic usage +# check-ssl-hsts-preload.rb -d +# # Specify the CRITICAL and WARNING alerts to either unknown (not in the database), pending or preloaded +# check-ssl-hsts-preload.rb -d -c -w +# +# LICENSE: +# Copyright 2017 Rowan Wookey +# Released under the same terms as Sensu (the MIT license); see LICENSE for +# details. +# +# Inspired by https://github.com/sensu-plugins/sensu-plugins-ssl/blob/master/bin/check-ssl-qualys.rb Copyright 2015 William Cooke +# + +require 'sensu-plugin/check/cli' +require 'json' +require 'net/http' + +class CheckSSLHSTSStatus < Sensu::Plugin::Check::CLI + STATUSES = %w(unknown pending preloaded).freeze + + option :domain, + description: 'The domain to run the test against', + short: '-d DOMAIN', + long: '--domain DOMAIN', + required: true + + option :warn, + short: '-w STATUS', + long: '--warn STATUS', + description: 'WARNING if this status or worse', + in: STATUSES, + default: 'pending' + + option :critical, + short: '-c STATUS', + long: '--critical STATUS', + description: 'CRITICAL if this status or worse', + in: STATUSES, + default: 'unknown' + + option :api_url, + description: 'The URL of the API to run against', + long: '--api-url URL', + default: 'https://hstspreload.org/api/v2/status' + + def fetch(uri, limit = 10) + if limit == 0 + return nil + end + + response = Net::HTTP.get_response(uri) + + case response + when Net::HTTPSuccess then + response + when Net::HTTPRedirection then + location = URI(response['location']) + fetch(location, limit - 1) + end + end + + def run + uri = URI(config[:api_url]) + uri.query = URI.encode_www_form(domain: config[:domain]) + response = fetch(uri) + if response.nil? + return warning 'Bad response recieved from API' + end + body = JSON.parse(response.body) + unless STATUSES.include? body['status'] + warning 'Invalid status returned ' + body['status'] + end + + if STATUSES.index(body['status']) <= STATUSES.index(config[:critical]) + critical body['status'] + elsif STATUSES.index(body['status']) <= STATUSES.index(config[:warn]) + warning body['status'] + else + ok + end + end +end + +# vim: set tabstop=2 shiftwidth=2 expandtab: diff --git a/test/check-ssl-hsts-preloadable_spec.rb b/test/check-ssl-hsts-preloadable_spec.rb new file mode 100644 index 0000000..e1fc6a4 --- /dev/null +++ b/test/check-ssl-hsts-preloadable_spec.rb @@ -0,0 +1,33 @@ +require_relative '../bin/check-ssl-hsts-preloadable.rb' +### +# If these randomly start failing then it's probably due to a change in the HSTS of the domain +# feel free to raise an issue and mention @rwky on github for a fix +### + +describe CheckSSLHSTSPreloadable do + before(:all) do + # Ensure the check isn't run when exiting (which is the default) + CheckSSLHSTSPreloadable.class_variable_set(:@@autorun, nil) + end + + let(:check) do + CheckSSLHSTSPreloadable.new ['-d', 'hstspreload.org'] + end + + it 'should pass check if the domain is preloadedable and has no warnings' do + expect(check).to receive(:ok).and_raise SystemExit + expect { check.run }.to raise_error SystemExit + end + + it 'should pass check if the domain is preloadedable but has warnings' do + check.config[:domain] = 'garron.net' + expect(check).to receive(:warning).and_raise SystemExit + expect { check.run }.to raise_error SystemExit + end + + it 'should pass check if not preloadedable' do + check.config[:domain] = 'example.com' + expect(check).to receive(:critical).and_raise SystemExit + expect { check.run }.to raise_error SystemExit + end +end diff --git a/test/check-ssl-hsts-status_spec.rb b/test/check-ssl-hsts-status_spec.rb new file mode 100644 index 0000000..13b73c9 --- /dev/null +++ b/test/check-ssl-hsts-status_spec.rb @@ -0,0 +1,27 @@ +require_relative '../bin/check-ssl-hsts-status.rb' +### +# If these randomly start failing then it's probably due to a change in the HSTS of the domain +# feel free to raise an issue and mention @rwky on github for a fix +### + +describe CheckSSLHSTSStatus do + before(:all) do + # Ensure the check isn't run when exiting (which is the default) + CheckSSLHSTSStatus.class_variable_set(:@@autorun, nil) + end + + let(:check) do + CheckSSLHSTSStatus.new ['-d', 'hstspreload.org'] + end + + it 'should pass check if the domain is preloaded' do + expect(check).to receive(:ok).and_raise SystemExit + expect { check.run }.to raise_error SystemExit + end + + it 'should pass check if not preloaded' do + check.config[:domain] = 'example.com' + expect(check).to receive(:critical).and_raise SystemExit + expect { check.run }.to raise_error SystemExit + end +end