sensu-plugins-ssl-boutetnico 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: