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,193 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # check-ssl-qualys.rb
4
+ #
5
+ # DESCRIPTION:
6
+ # Runs a report using the Qualys SSL Labs API and then alerts if a
7
+ # domain does not meet the grade specified for *ALL* hosts that are
8
+ # reachable from that domian.
9
+ #
10
+ # The checks that are performed are documented on
11
+ # https://www.ssllabs.com/index.html as are the range of grades.
12
+ #
13
+ # OUTPUT:
14
+ # plain text
15
+ #
16
+ # PLATFORMS:
17
+ # Linux
18
+ #
19
+ # DEPENDENCIES:
20
+ # gem: sensu-plugin
21
+ #
22
+ # USAGE:
23
+ # # Basic usage
24
+ # check-ssl-qualys.rb -d <domain_name>
25
+ # # Specify the CRITICAL and WARNING grades to a specific grade
26
+ # check-ssl-qualys.rb -d <domain_name> -c <critical_grade> -w <warning_grade>
27
+ # # Use --api-url to specify an alternate api host
28
+ # check-ssl-qualys.rb -d <domain_name> -api-url <alternate_host>
29
+ #
30
+ # NOTE: This check takes a rather long time to run and will timeout if you're using
31
+ # the default sensu check timeout. Make sure to set a longer timeout period in the
32
+ # check definition. Two minutes or longer may be a good starting point as checks
33
+ # regularly take 90+ seconds to run.
34
+ #
35
+ # LICENSE:
36
+ # Copyright 2015 William Cooke <will@bruisyard.eu>
37
+ # Released under the same terms as Sensu (the MIT license); see LICENSE for
38
+ # details.
39
+ #
40
+
41
+ require 'sensu-plugin/check/cli'
42
+ require 'json'
43
+ require 'net/http'
44
+ require 'timeout'
45
+
46
+ # Checks a single DNS entry has a rating above a certain level
47
+ class CheckSSLQualys < Sensu::Plugin::Check::CLI
48
+ # Current grades that are avaialble from the API
49
+ GRADE_OPTIONS = ['A+', 'A', 'A-', 'B', 'C', 'D', 'E', 'F', 'T', 'M'].freeze
50
+
51
+ option :domain,
52
+ description: 'The domain to run the test against',
53
+ short: '-d DOMAIN',
54
+ long: '--domain DOMAIN',
55
+ required: true
56
+
57
+ option :api_url,
58
+ description: 'The URL of the API to run against',
59
+ long: '--api-url URL',
60
+ default: 'https://api.ssllabs.com/api/v3/'
61
+
62
+ option :warn,
63
+ short: '-w GRADE',
64
+ long: '--warn GRADE',
65
+ description: 'WARNING if below this grade',
66
+ proc: proc { |g| GRADE_OPTIONS.index(g) },
67
+ default: 2 # 'A-'
68
+
69
+ option :critical,
70
+ short: '-c GRADE',
71
+ long: '--critical GRADE',
72
+ description: 'CRITICAL if below this grade',
73
+ proc: proc { |g| GRADE_OPTIONS.index(g) },
74
+ default: 3 # 'B'
75
+
76
+ option :debug,
77
+ long: '--debug BOOL',
78
+ description: 'toggles extra debug printing',
79
+ boolean: true,
80
+ default: false
81
+
82
+ option :num_checks,
83
+ short: '-n NUM_CHECKS',
84
+ long: '--number-checks NUM_CHECKS',
85
+ description: 'The number of checks to make before giving up (timeout of check)',
86
+ proc: proc { |t| t.to_i },
87
+ default: 24
88
+
89
+ option :between_checks,
90
+ short: '-t SECONDS',
91
+ long: '--time-between SECONDS',
92
+ 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',
93
+ proc: proc { |t| t.to_i },
94
+ default: 10
95
+
96
+ option :timeout,
97
+ long: '--timeout SECONDS',
98
+ descriptions: 'the amount of seconds that this is allowed to run for',
99
+ proc: proc(&:to_i),
100
+ default: 300
101
+
102
+ def ssl_api_request(from_cache)
103
+ params = { host: config[:domain] }
104
+ params[:startNew] = if from_cache == true
105
+ 'off'
106
+ else
107
+ 'on'
108
+ end
109
+
110
+ uri = URI("#{config[:api_url]}analyze")
111
+ uri.query = URI.encode_www_form(params)
112
+ begin
113
+ response = Net::HTTP.get_response(uri)
114
+ rescue StandardError => e
115
+ warning e
116
+ end
117
+
118
+ warning 'Bad response recieved from API' unless response.is_a?(Net::HTTPSuccess)
119
+
120
+ JSON.parse(response.body)
121
+ end
122
+
123
+ def ssl_check(from_cache)
124
+ json = ssl_api_request(from_cache)
125
+ warning "ERROR on #{config[:domain]} check" if json['status'] == 'ERROR'
126
+ json
127
+ end
128
+
129
+ def ssl_recheck
130
+ 1.upto(config[:num_checks]) do |step|
131
+ p "step: #{step}" if config[:debug]
132
+ start_time = Time.now
133
+ p "start_time: #{start_time}" if config[:debug]
134
+ json = if step == 1
135
+ ssl_check(false)
136
+ else
137
+ ssl_check(true)
138
+ end
139
+ return json if json['status'] == 'READY'
140
+
141
+ if json['endpoints']&.is_a?(Array)
142
+ p "endpoints: #{json['endpoints']}" if config[:debug]
143
+ # The api response sometimes has low eta (which seems unrealistic) from
144
+ # my tests that can be 0 or low numbers which would imply it is done...
145
+ # Basically we check if present and if its higher than the specified
146
+ # time to wait between checks. If so we use the eta from the api get
147
+ # response otherwise we use the time between check values. We have an
148
+ # overall timeout that protects us from the api telling us to wait for
149
+ # insanely long time periods. The highest I have seen the eta go was
150
+ # around 250 seconds but put it in just in case as the api has very
151
+ # erratic response times.
152
+ if json['endpoints'].first.is_a?(Hash) && json['endpoints'].first.key?('eta') && json['endpoints'].first['eta'] > config[:between_checks]
153
+ p "eta: #{json['endpoints'].first['eta']}" if config[:debug]
154
+ sleep(json['endpoints'].first['eta'])
155
+ else
156
+ p "sleeping with default: #{config[:between_checks]}" if config[:debug]
157
+ sleep(config[:between_checks])
158
+ end
159
+ end
160
+ p "elapsed: #{Time.now - start_time}" if config[:debug]
161
+ warning 'Timeout waiting for check to finish' if step == config[:num_checks]
162
+ end
163
+ end
164
+
165
+ def ssl_grades
166
+ ssl_recheck['endpoints'].map do |endpoint|
167
+ endpoint['grade']
168
+ end
169
+ end
170
+
171
+ def lowest_grade
172
+ ssl_grades.sort_by! { |g| GRADE_OPTIONS.index(g) }.reverse![0]
173
+ end
174
+
175
+ def run
176
+ Timeout.timeout(config[:timeout]) do
177
+ grade = lowest_grade
178
+ unless grade
179
+ message "#{config[:domain]} not rated"
180
+ critical
181
+ end
182
+ message "#{config[:domain]} rated #{grade}"
183
+ grade_rank = GRADE_OPTIONS.index(grade)
184
+ if grade_rank > config[:critical]
185
+ critical
186
+ elsif grade_rank > config[:warn]
187
+ warning
188
+ else
189
+ ok
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,126 @@
1
+ #! /usr/bin/env ruby
2
+ #
3
+ # check-ssl-root-issuer
4
+ #
5
+ # DESCRIPTION:
6
+ # Check that a certificate is chained to a specific root certificate issuer
7
+ #
8
+ # OUTPUT:
9
+ # plain text
10
+ #
11
+ # PLATFORMS:
12
+ # Linux
13
+ #
14
+ # DEPENDENCIES:
15
+ # gem: sensu-plugin
16
+ #
17
+ # USAGE:
18
+ #
19
+ # Check that a specific website is chained to a specific root certificate
20
+ # ./check-ssl-root-issuer.rb \
21
+ # -u https://example.com \
22
+ # -i "CN=DST Root CA X3,O=Digital Signature Trust Co."
23
+ #
24
+ # LICENSE:
25
+ # Copyright Jef Spaleta (jspaleta@gmail.com) 2020
26
+ # Released under the same terms as Sensu (the MIT license); see LICENSE
27
+ # for details.
28
+ #
29
+
30
+ require 'sensu-plugin/check/cli'
31
+ require 'openssl'
32
+ require 'uri'
33
+ require 'net/http'
34
+ require 'net/https'
35
+
36
+ #
37
+ # Check root certificate has specified issuer name
38
+ #
39
+ class CheckSSLRootIssuer < Sensu::Plugin::Check::CLI
40
+ option :url,
41
+ description: 'Url to check: Ex "https://google.com"',
42
+ short: '-u',
43
+ long: '--url URL',
44
+ required: true
45
+
46
+ option :issuer,
47
+ description: 'An X509 certificate issuer name, RFC2253 format Ex: "CN=DST Root CA X3,O=Digital Signature Trust Co."',
48
+ short: '-i',
49
+ long: '--issuer ISSUER_NAME',
50
+ required: true
51
+
52
+ option :regexp,
53
+ description: 'Treat the issuer name as a regexp',
54
+ short: '-r',
55
+ long: '--regexp',
56
+ default: false,
57
+ boolean: true,
58
+ required: false
59
+
60
+ option :format,
61
+ description: 'optional issuer name format.',
62
+ short: '-f',
63
+ long: '--format FORMAT_VAL',
64
+ default: 'RFC2253',
65
+ in: %w[RFC2253 ONELINE COMPAT],
66
+ required: false
67
+
68
+ def cert_name_format
69
+ # Note: because format argument is pre-validated by mixin 'in' logic eval is safe to use
70
+ eval "OpenSSL::X509::Name::#{config[:format]}" # rubocop:disable Lint/Eval
71
+ end
72
+
73
+ def validate_issuer(cert)
74
+ issuer = cert.issuer.to_s(cert_name_format)
75
+ if config[:regexp]
76
+ issuer_regexp = Regexp.new(config[:issuer].to_s)
77
+ issuer =~ issuer_regexp
78
+ else
79
+ issuer == config[:issuer].to_s
80
+ end
81
+ end
82
+
83
+ def find_root_cert(uri)
84
+ root_cert = nil
85
+ http = Net::HTTP.new(uri.host, uri.port)
86
+ http.open_timeout = 10
87
+ http.read_timeout = 10
88
+ http.use_ssl = true
89
+ http.cert_store = OpenSSL::X509::Store.new
90
+ http.cert_store.set_default_paths
91
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
92
+
93
+ http.verify_callback = lambda { |verify_ok, store_context|
94
+ root_cert ||= store_context.current_cert
95
+ unless verify_ok
96
+ @failed_cert = store_context.current_cert
97
+ @failed_cert_reason = [store_context.error, store_context.error_string] if store_context.error != 0
98
+ end
99
+ verify_ok
100
+ }
101
+ http.start {}
102
+ root_cert
103
+ end
104
+
105
+ # Do the actual work and massage some data
106
+
107
+ def run
108
+ @fail_cert = nil
109
+ @failed_cert_reason = 'Unknown'
110
+ uri = URI.parse(config[:url])
111
+ critical "url protocol must be https, you specified #{url}" if uri.scheme != 'https'
112
+ root_cert = find_root_cert(uri)
113
+ if @failed_cert
114
+ msg = "Certificate verification failed.\n Reason: #{@failed_cert_reason}"
115
+ critical msg
116
+ end
117
+
118
+ if validate_issuer(root_cert)
119
+ msg = 'Root certificate in chain has expected issuer name'
120
+ ok msg
121
+ else
122
+ msg = "Root certificate issuer did not match expected name.\nFound: \"#{root_cert.issuer.to_s(config[:issuer_format])}\""
123
+ critical msg
124
+ end
125
+ end
126
+ end
@@ -0,0 +1 @@
1
+ require 'sensu-plugins-ssl/version'
@@ -0,0 +1,9 @@
1
+ module SensuPluginsSSL
2
+ module Version
3
+ MAJOR = 1
4
+ MINOR = 0
5
+ PATCH = 0
6
+
7
+ VER_STRING = [MAJOR, MINOR, PATCH].compact.join('.')
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,229 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sensu-plugins-ssl-boutetnico
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Sensu-Plugins and contributors
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-06-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sensu-plugin
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.1'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: codeclimate-test-reporter
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: github-markup
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.13'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.13'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '13.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '13.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: redcarpet
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.5'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.5'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.9'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.9'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rubocop
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 0.85.0
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 0.85.0
139
+ - !ruby/object:Gem::Dependency
140
+ name: timecop
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: 0.9.1
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: 0.9.1
153
+ - !ruby/object:Gem::Dependency
154
+ name: yard
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: 0.9.25
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: 0.9.25
167
+ description: |-
168
+ This plugin provides native SSL instrumentation
169
+ for monitoring, including: hostname and chain
170
+ verification, cert and crl expiry, and Qualys SSL Labs reporting
171
+ email: "<sensu-users@googlegroups.com>"
172
+ executables:
173
+ - check-ssl-anchor.rb
174
+ - check-java-keystore-cert.rb
175
+ - check-ssl-cert.rb
176
+ - check-ssl-crl.rb
177
+ - check-ssl-qualys.rb
178
+ - check-ssl-host.rb
179
+ - check-ssl-root-issuer.rb
180
+ - check-ssl-hsts-preloadable.rb
181
+ - check-ssl-hsts-status.rb
182
+ extensions: []
183
+ extra_rdoc_files: []
184
+ files:
185
+ - CHANGELOG.md
186
+ - LICENSE
187
+ - README.md
188
+ - bin/check-java-keystore-cert.rb
189
+ - bin/check-ssl-anchor.rb
190
+ - bin/check-ssl-cert.rb
191
+ - bin/check-ssl-crl.rb
192
+ - bin/check-ssl-host.rb
193
+ - bin/check-ssl-hsts-preloadable.rb
194
+ - bin/check-ssl-hsts-status.rb
195
+ - bin/check-ssl-qualys.rb
196
+ - bin/check-ssl-root-issuer.rb
197
+ - lib/sensu-plugins-ssl.rb
198
+ - lib/sensu-plugins-ssl/version.rb
199
+ homepage: https://github.com/boutetnico/sensu-plugins-ssl
200
+ licenses:
201
+ - MIT
202
+ metadata:
203
+ maintainer: sensu-plugin
204
+ development_status: active
205
+ production_status: unstable - testing recommended
206
+ release_draft: 'false'
207
+ release_prerelease: 'false'
208
+ post_install_message: You can use the embedded Ruby by setting EMBEDDED_RUBY=true
209
+ in /etc/default/sensu
210
+ rdoc_options: []
211
+ require_paths:
212
+ - lib
213
+ required_ruby_version: !ruby/object:Gem::Requirement
214
+ requirements:
215
+ - - ">="
216
+ - !ruby/object:Gem::Version
217
+ version: 2.4.0
218
+ required_rubygems_version: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - ">="
221
+ - !ruby/object:Gem::Version
222
+ version: '0'
223
+ requirements: []
224
+ rubyforge_project:
225
+ rubygems_version: 2.6.14.4
226
+ signing_key:
227
+ specification_version: 4
228
+ summary: Sensu plugins for SSL
229
+ test_files: []