sensu-plugins-ssl-boutetnico 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: