sensu-plugins-ssl-boutetnico 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1 -0
- data/LICENSE +22 -0
- data/README.md +78 -0
- data/bin/check-java-keystore-cert.rb +88 -0
- data/bin/check-ssl-anchor.rb +120 -0
- data/bin/check-ssl-cert.rb +130 -0
- data/bin/check-ssl-crl.rb +76 -0
- data/bin/check-ssl-host.rb +198 -0
- data/bin/check-ssl-hsts-preloadable.rb +79 -0
- data/bin/check-ssl-hsts-status.rb +101 -0
- data/bin/check-ssl-qualys.rb +193 -0
- data/bin/check-ssl-root-issuer.rb +126 -0
- data/lib/sensu-plugins-ssl.rb +1 -0
- data/lib/sensu-plugins-ssl/version.rb +9 -0
- metadata +229 -0
@@ -0,0 +1,76 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# check-ssl-crl
|
4
|
+
#
|
5
|
+
# DESCRIPTION:
|
6
|
+
# Check in minutes when a certificate revocation list will expire.
|
7
|
+
#
|
8
|
+
# OUTPUT:
|
9
|
+
# plain text
|
10
|
+
#
|
11
|
+
# PLATFORMS:
|
12
|
+
# Linux
|
13
|
+
#
|
14
|
+
# DEPENDENCIES:
|
15
|
+
# gem: sensu-plugin
|
16
|
+
#
|
17
|
+
# USAGE:
|
18
|
+
# ./check-ssl-crl -c 300 -w 600 -u /path/to/crl
|
19
|
+
# ./check-ssl-crl -c 300 -w 600 -u http://www.website.com/file.crl
|
20
|
+
#
|
21
|
+
# LICENSE:
|
22
|
+
# Stephen Hoekstra <shoekstra@schubergphilis.com>
|
23
|
+
#
|
24
|
+
# Released under the same terms as Sensu (the MIT license); see LICENSE
|
25
|
+
# for details.
|
26
|
+
#
|
27
|
+
|
28
|
+
require 'open-uri'
|
29
|
+
require 'openssl'
|
30
|
+
require 'sensu-plugin/check/cli'
|
31
|
+
require 'time'
|
32
|
+
|
33
|
+
#
|
34
|
+
# Check SSL Cert
|
35
|
+
#
|
36
|
+
class CheckSSLCRL < Sensu::Plugin::Check::CLI
|
37
|
+
option :critical,
|
38
|
+
description: 'Numbers of minutes left',
|
39
|
+
short: '-c',
|
40
|
+
long: '--critical MINUTES',
|
41
|
+
proc: proc { |v| v.to_i },
|
42
|
+
required: true
|
43
|
+
|
44
|
+
option :url,
|
45
|
+
description: 'URL (or path) to CRL file',
|
46
|
+
short: '-u',
|
47
|
+
long: '--url URL',
|
48
|
+
required: true
|
49
|
+
|
50
|
+
option :warning,
|
51
|
+
description: 'Numbers of minutes left',
|
52
|
+
short: '-w',
|
53
|
+
long: '--warning MINUTES',
|
54
|
+
proc: proc { |v| v.to_i },
|
55
|
+
required: true
|
56
|
+
|
57
|
+
def seconds_to_minutes(seconds)
|
58
|
+
(seconds / 60).to_i
|
59
|
+
end
|
60
|
+
|
61
|
+
def validate_opts
|
62
|
+
unknown 'warning cannot be less than critical' if config[:warning] < config[:critical]
|
63
|
+
end
|
64
|
+
|
65
|
+
def run
|
66
|
+
validate_opts
|
67
|
+
|
68
|
+
next_update = OpenSSL::X509::CRL.new(open(config[:url]).read).next_update
|
69
|
+
minutes_until = seconds_to_minutes(Time.parse(next_update.to_s) - Time.now)
|
70
|
+
|
71
|
+
critical "#{config[:url]} - Expired #{minutes_until.abs} minutes ago" if minutes_until.negative?
|
72
|
+
critical "#{config[:url]} - #{minutes_until} minutes left, next update at #{next_update}" if minutes_until < config[:critical].to_i
|
73
|
+
warning "#{config[:url]} - #{minutes_until} minutes left, next update at #{next_update}" if minutes_until < config[:warning].to_i
|
74
|
+
ok "#{config[:url]} - #{minutes_until} minutes left, next update at #{next_update}"
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# check-ssl-host.rb
|
4
|
+
#
|
5
|
+
# DESCRIPTION:
|
6
|
+
# SSL certificate checker
|
7
|
+
# Connects to a HTTPS (or other SSL) server and performs several checks on
|
8
|
+
# the certificate:
|
9
|
+
# - Is the hostname valid for the host we're requesting
|
10
|
+
# - If any certificate chain is presented, is it valid (i.e. is each
|
11
|
+
# certificate signed by the next)
|
12
|
+
# - Is the certificate about to expire
|
13
|
+
# Currently no checks are performed to make sure the certificate is signed
|
14
|
+
# by a trusted authority.
|
15
|
+
#
|
16
|
+
# DEPENDENCIES:
|
17
|
+
# gem: sensu-plugin
|
18
|
+
#
|
19
|
+
# USAGE:
|
20
|
+
# # Basic usage
|
21
|
+
# check-ssl-host.rb -h <hostname>
|
22
|
+
# # Specify specific days before cert expiry to alert on
|
23
|
+
# check-ssl-host.rb -h <hostmame> -c <critical_days> -w <warning_days>
|
24
|
+
# # Use -p to specify an alternate port
|
25
|
+
# check-ssl-host.rb -h <hostname> -p 8443
|
26
|
+
# # Use --skip-hostname-verification and/or --skip-chain-verification to
|
27
|
+
# # disable some of the checks made.
|
28
|
+
# check-ssl-host.rb -h <hostname> --skip-chain-verification
|
29
|
+
#
|
30
|
+
# LICENSE:
|
31
|
+
# Copyright 2014 Chef Software, Inc.
|
32
|
+
# Released under the same terms as Sensu (the MIT license); see LICENSE for
|
33
|
+
# details.
|
34
|
+
#
|
35
|
+
|
36
|
+
require 'sensu-plugin/check/cli'
|
37
|
+
require 'date'
|
38
|
+
require 'openssl'
|
39
|
+
require 'socket'
|
40
|
+
|
41
|
+
#
|
42
|
+
# Check SSL Host
|
43
|
+
#
|
44
|
+
class CheckSSLHost < Sensu::Plugin::Check::CLI
|
45
|
+
STARTTLS_PROTOS = %w[smtp imap].freeze
|
46
|
+
|
47
|
+
check_name 'check_ssl_host'
|
48
|
+
|
49
|
+
option :critical,
|
50
|
+
description: 'Return critical this many days before cert expiry',
|
51
|
+
short: '-c',
|
52
|
+
long: '--critical DAYS',
|
53
|
+
proc: proc(&:to_i),
|
54
|
+
default: 7
|
55
|
+
|
56
|
+
option :warning,
|
57
|
+
description: 'Return warning this many days before cert expiry',
|
58
|
+
short: '-w',
|
59
|
+
long: '--warning DAYS',
|
60
|
+
required: true,
|
61
|
+
proc: proc(&:to_i),
|
62
|
+
default: 14
|
63
|
+
|
64
|
+
option :host,
|
65
|
+
description: 'Hostname of the server certificate to check, by default used as the server address if none ' \
|
66
|
+
'is given',
|
67
|
+
short: '-h',
|
68
|
+
long: '--host HOST',
|
69
|
+
required: true
|
70
|
+
|
71
|
+
option :port,
|
72
|
+
description: 'Port on server to check',
|
73
|
+
short: '-p',
|
74
|
+
long: '--port PORT',
|
75
|
+
default: 443
|
76
|
+
|
77
|
+
option :address,
|
78
|
+
description: 'Address of server to check. This is used instead of the host argument for the TCP connection, ' \
|
79
|
+
'however the server hostname is still used for the TLS/SSL context.',
|
80
|
+
short: '-a',
|
81
|
+
long: '--address ADDRESS'
|
82
|
+
|
83
|
+
option :client_cert,
|
84
|
+
description: 'Path to the client certificate in DER/PEM format',
|
85
|
+
long: '--client-cert CERT'
|
86
|
+
|
87
|
+
option :client_key,
|
88
|
+
description: 'Path to the client RSA key in DER/PEM format',
|
89
|
+
long: '--client-key KEY'
|
90
|
+
|
91
|
+
option :skip_hostname_verification,
|
92
|
+
description: 'Disables hostname verification',
|
93
|
+
long: '--skip-hostname-verification',
|
94
|
+
boolean: true
|
95
|
+
|
96
|
+
option :skip_chain_verification,
|
97
|
+
description: 'Disables certificate chain verification',
|
98
|
+
long: '--skip-chain-verification',
|
99
|
+
boolean: true
|
100
|
+
|
101
|
+
option :starttls,
|
102
|
+
description: 'use STARTTLS negotiation for the given protocol '\
|
103
|
+
"(#{STARTTLS_PROTOS.join(', ')})",
|
104
|
+
long: '--starttls PROTO'
|
105
|
+
|
106
|
+
def get_cert_chain(host, port, address, client_cert, client_key)
|
107
|
+
tcp_client = TCPSocket.new(address || host, port)
|
108
|
+
handle_starttls(config[:starttls], tcp_client) if config[:starttls]
|
109
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
110
|
+
ssl_context.cert = OpenSSL::X509::Certificate.new File.read(client_cert) if client_cert
|
111
|
+
ssl_context.key = OpenSSL::PKey::RSA.new File.read(client_key) if client_key
|
112
|
+
ssl_client = OpenSSL::SSL::SSLSocket.new(tcp_client, ssl_context)
|
113
|
+
|
114
|
+
# If the OpenSSL version in use supports Server Name Indication (SNI, RFC 3546), then we set the hostname we
|
115
|
+
# received to request that certificate.
|
116
|
+
ssl_client.hostname = host if ssl_client.respond_to? :hostname=
|
117
|
+
|
118
|
+
ssl_client.connect
|
119
|
+
certs = ssl_client.peer_cert_chain
|
120
|
+
ssl_client.close
|
121
|
+
certs
|
122
|
+
end
|
123
|
+
|
124
|
+
def handle_starttls(proto, socket)
|
125
|
+
if STARTTLS_PROTOS.include?(proto) # rubocop:disable Style/GuardClause
|
126
|
+
send("starttls_#{proto}", socket)
|
127
|
+
else
|
128
|
+
raise ArgumentError, "STARTTLS supported only for #{STARTTLS_PROTOS.join(', ')}"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def starttls_smtp(socket)
|
133
|
+
status = socket.readline
|
134
|
+
unless /^220 / =~ status
|
135
|
+
critical "#{config[:host]} - did not receive initial SMTP 220"
|
136
|
+
# no fall-through
|
137
|
+
end
|
138
|
+
socket.puts 'STARTTLS'
|
139
|
+
|
140
|
+
status = socket.readline
|
141
|
+
return if /^220 / =~ status
|
142
|
+
|
143
|
+
critical "#{config[:host]} - did not receive SMTP 220 in response to STARTTLS"
|
144
|
+
end
|
145
|
+
|
146
|
+
def starttls_imap(socket)
|
147
|
+
status = socket.readline
|
148
|
+
unless /^* OK / =~ status
|
149
|
+
critical "#{config[:host]} - did not receive initial * OK"
|
150
|
+
end
|
151
|
+
socket.puts 'a001 STARTTLS'
|
152
|
+
|
153
|
+
status = socket.readline
|
154
|
+
return if /^a001 OK Begin TLS negotiation now/ =~ status
|
155
|
+
|
156
|
+
critical "#{config[:host]} - did not receive OK Begin TLS negotiation now"
|
157
|
+
end
|
158
|
+
|
159
|
+
def verify_expiry(cert)
|
160
|
+
# Expiry check
|
161
|
+
days = (cert.not_after.to_date - Date.today).to_i
|
162
|
+
message = "#{config[:host]} - #{days} days until expiry"
|
163
|
+
critical "#{config[:host]} - Expired #{days} days ago" if days.negative?
|
164
|
+
critical message if days < config[:critical]
|
165
|
+
warning message if days < config[:warning]
|
166
|
+
ok message
|
167
|
+
end
|
168
|
+
|
169
|
+
def verify_certificate_chain(certs)
|
170
|
+
# Validates that a chain of certs are each signed by the next
|
171
|
+
# NOTE: doesn't validate that the top of the chain is signed by a trusted
|
172
|
+
# CA.
|
173
|
+
valid = true
|
174
|
+
parent = nil
|
175
|
+
certs.reverse_each do |c|
|
176
|
+
if parent
|
177
|
+
valid &= c.verify(parent.public_key)
|
178
|
+
end
|
179
|
+
parent = c
|
180
|
+
end
|
181
|
+
critical "#{config[:host]} - Invalid certificate chain" unless valid
|
182
|
+
end
|
183
|
+
|
184
|
+
def verify_hostname(cert)
|
185
|
+
unless OpenSSL::SSL.verify_certificate_identity(cert, config[:host]) # rubocop:disable Style/GuardClause
|
186
|
+
critical "#{config[:host]} hostname mismatch (#{cert.subject})"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def run
|
191
|
+
chain = get_cert_chain(config[:host], config[:port], config[:address], config[:client_cert], config[:client_key])
|
192
|
+
verify_hostname(chain[0]) unless config[:skip_hostname_verification]
|
193
|
+
verify_certificate_chain(chain) unless config[:skip_chain_verification]
|
194
|
+
verify_expiry(chain[0])
|
195
|
+
rescue Errno::ECONNRESET => e
|
196
|
+
critical "#{e.class} - #{e.message}"
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# check-ssl-hsts-preloadable.rb
|
4
|
+
#
|
5
|
+
# DESCRIPTION:
|
6
|
+
# Checks a domain against the chromium HSTS API returning errors/warnings if the domain is preloadable
|
7
|
+
#
|
8
|
+
# OUTPUT:
|
9
|
+
# plain text
|
10
|
+
#
|
11
|
+
# PLATFORMS:
|
12
|
+
# Linux
|
13
|
+
#
|
14
|
+
# DEPENDENCIES:
|
15
|
+
# gem: sensu-plugin
|
16
|
+
#
|
17
|
+
# USAGE:
|
18
|
+
# # Basic usage
|
19
|
+
# check-ssl-hsts-preloadable.rb -d <domain_name>
|
20
|
+
#
|
21
|
+
# LICENSE:
|
22
|
+
# Copyright 2017 Rowan Wookey <admin@rwky.net>
|
23
|
+
# Released under the same terms as Sensu (the MIT license); see LICENSE for
|
24
|
+
# details.
|
25
|
+
#
|
26
|
+
# Inspired by https://github.com/sensu-plugins/sensu-plugins-ssl/blob/master/bin/check-ssl-qualys.rb Copyright 2015 William Cooke <will@bruisyard.eu>
|
27
|
+
#
|
28
|
+
|
29
|
+
require 'sensu-plugin/check/cli'
|
30
|
+
require 'json'
|
31
|
+
require 'net/http'
|
32
|
+
|
33
|
+
class CheckSSLHSTSPreloadable < Sensu::Plugin::Check::CLI
|
34
|
+
option :domain,
|
35
|
+
description: 'The domain to run the test against',
|
36
|
+
short: '-d DOMAIN',
|
37
|
+
long: '--domain DOMAIN',
|
38
|
+
required: true
|
39
|
+
|
40
|
+
option :api_url,
|
41
|
+
description: 'The URL of the API to run against',
|
42
|
+
long: '--api-url URL',
|
43
|
+
default: 'https://hstspreload.org/api/v2/preloadable'
|
44
|
+
|
45
|
+
def fetch(uri, limit = 10)
|
46
|
+
if limit.zero?
|
47
|
+
return nil
|
48
|
+
end
|
49
|
+
|
50
|
+
response = Net::HTTP.get_response(uri)
|
51
|
+
|
52
|
+
case response
|
53
|
+
when Net::HTTPSuccess then response
|
54
|
+
when Net::HTTPRedirection then
|
55
|
+
location = URI(response['location'])
|
56
|
+
fetch(location, limit - 1)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def run
|
61
|
+
uri = URI(config[:api_url])
|
62
|
+
uri.query = URI.encode_www_form(domain: config[:domain])
|
63
|
+
response = fetch(uri)
|
64
|
+
if response.nil?
|
65
|
+
return warning 'Bad response recieved from API'
|
66
|
+
end
|
67
|
+
|
68
|
+
body = JSON.parse(response.body)
|
69
|
+
if !body['errors'].empty?
|
70
|
+
critical body['errors'].map { |u| u['summary'] }.join(', ')
|
71
|
+
elsif !body['warnings'].empty?
|
72
|
+
warning body['warnings'].map { |u| u['summary'] }.join(', ')
|
73
|
+
else
|
74
|
+
ok
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# vim: set tabstop=2 shiftwidth=2 expandtab:
|
@@ -0,0 +1,101 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# check-ssl-hsts-preload.rb
|
4
|
+
#
|
5
|
+
# DESCRIPTION:
|
6
|
+
# Checks a domain against the chromium HSTS API reporting on the preload status of the domain
|
7
|
+
#
|
8
|
+
# OUTPUT:
|
9
|
+
# plain text
|
10
|
+
#
|
11
|
+
# PLATFORMS:
|
12
|
+
# Linux
|
13
|
+
#
|
14
|
+
# DEPENDENCIES:
|
15
|
+
# gem: sensu-plugin
|
16
|
+
#
|
17
|
+
# USAGE:
|
18
|
+
# # Basic usage
|
19
|
+
# check-ssl-hsts-preload.rb -d <domain_name>
|
20
|
+
# # Specify the CRITICAL and WARNING alerts to either unknown (not in the database), pending or preloaded
|
21
|
+
# check-ssl-hsts-preload.rb -d <domain_name> -c <critical_alert> -w <warning_alert>
|
22
|
+
#
|
23
|
+
# LICENSE:
|
24
|
+
# Copyright 2017 Rowan Wookey <admin@rwky.net>
|
25
|
+
# Released under the same terms as Sensu (the MIT license); see LICENSE for
|
26
|
+
# details.
|
27
|
+
#
|
28
|
+
# Inspired by https://github.com/sensu-plugins/sensu-plugins-ssl/blob/master/bin/check-ssl-qualys.rb Copyright 2015 William Cooke <will@bruisyard.eu>
|
29
|
+
#
|
30
|
+
|
31
|
+
require 'sensu-plugin/check/cli'
|
32
|
+
require 'json'
|
33
|
+
require 'net/http'
|
34
|
+
|
35
|
+
class CheckSSLHSTSStatus < Sensu::Plugin::Check::CLI
|
36
|
+
STATUSES = %w[unknown pending preloaded].freeze
|
37
|
+
|
38
|
+
option :domain,
|
39
|
+
description: 'The domain to run the test against',
|
40
|
+
short: '-d DOMAIN',
|
41
|
+
long: '--domain DOMAIN',
|
42
|
+
required: true
|
43
|
+
|
44
|
+
option :warn,
|
45
|
+
short: '-w STATUS',
|
46
|
+
long: '--warn STATUS',
|
47
|
+
description: 'WARNING if this status or worse',
|
48
|
+
in: STATUSES,
|
49
|
+
default: 'pending'
|
50
|
+
|
51
|
+
option :critical,
|
52
|
+
short: '-c STATUS',
|
53
|
+
long: '--critical STATUS',
|
54
|
+
description: 'CRITICAL if this status or worse',
|
55
|
+
in: STATUSES,
|
56
|
+
default: 'unknown'
|
57
|
+
|
58
|
+
option :api_url,
|
59
|
+
description: 'The URL of the API to run against',
|
60
|
+
long: '--api-url URL',
|
61
|
+
default: 'https://hstspreload.org/api/v2/status'
|
62
|
+
|
63
|
+
def fetch(uri, limit = 10)
|
64
|
+
if limit.zero?
|
65
|
+
return nil
|
66
|
+
end
|
67
|
+
|
68
|
+
response = Net::HTTP.get_response(uri)
|
69
|
+
|
70
|
+
case response
|
71
|
+
when Net::HTTPSuccess then response
|
72
|
+
when Net::HTTPRedirection then
|
73
|
+
location = URI(response['location'])
|
74
|
+
fetch(location, limit - 1)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def run
|
79
|
+
uri = URI(config[:api_url])
|
80
|
+
uri.query = URI.encode_www_form(domain: config[:domain])
|
81
|
+
response = fetch(uri)
|
82
|
+
if response.nil?
|
83
|
+
return warning 'Bad response recieved from API'
|
84
|
+
end
|
85
|
+
|
86
|
+
body = JSON.parse(response.body)
|
87
|
+
unless STATUSES.include? body['status']
|
88
|
+
warning 'Invalid status returned ' + body['status']
|
89
|
+
end
|
90
|
+
|
91
|
+
if STATUSES.index(body['status']) <= STATUSES.index(config[:critical])
|
92
|
+
critical body['status']
|
93
|
+
elsif STATUSES.index(body['status']) <= STATUSES.index(config[:warn])
|
94
|
+
warning body['status']
|
95
|
+
else
|
96
|
+
ok
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# vim: set tabstop=2 shiftwidth=2 expandtab:
|