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,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: []