Skip to content

Commit 4276db0

Browse files
committed
Addressing multiple issues with timing out with check-ssl-qualys.rb:
- added an overall timeout parameter to short circuit slow api - when you call the api beyond the initial attempt it needs to wait until the report is done. Previously it would sleep for a specified arbitrary value and would often timeout. Now we look at the api response and if it has an ETA and the ETA is larger than the value of `--between-checks` we use it. We check if its greater than the specified as in many cases this has been found to be unreliable when seeing small numbers but seems legit when the api is slower to complete the report. In some cases we see a 0 eta which one might assume is complete or near complete but often saw several 0's in a row leading me to put in that stipulation on using it. - adding documentation about how long it takes and that you probably need to configure your sensu check to have a longer timeout or sensu will kill the process. - moved to v3 of the api (backwards compatible) I saw little to no difference in speed but the docs indicate that v2 is deprecated Signed-off-by: Ben Abrams <me@benabrams.it>
1 parent e57724f commit 4276db0

File tree

4 files changed

+92
-21
lines changed

4 files changed

+92
-21
lines changed

.rubocop.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,6 @@ RegexpLiteral:
2828

2929
Style/Documentation:
3030
Enabled: false
31+
32+
# AllCops:
33+
# TargetRubyVersion: 2.0

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ This CHANGELOG follows the format listed [here](https://github.com/sensu-plugins
77
### Fixed
88
- `check-ssl-hsts-preloadable.rb`: Fixed testing warnings for if a domain can be HSTS preloaded (@rwky)
99

10+
### Breaking Changes
11+
- `check-ssl-qualys.rb`: when you submit a request with caching enabled it will return back a response including an eta key. Rather than sleeping for some arbitrary number of time we now use this key when its greater than `--between-checks` to wait before attempting the next attempt to query. If it is lower or not present we fall back to `--between-checks` (@majormoses)
12+
- `check-ssl-qualys.rb`: new `--timeout` parameter to short circuit slow apis (@majormoses)
13+
14+
### Changed
15+
- `check-ssl-qualys.rb`: updated `--api-url` to default to `v3` but remains backwards compatible (@jhoblitt) (@majormoses)
16+
17+
### Added
18+
`check-ssl-qualys.rb`: option `--debug` to enable debug logging (@majormoses)
19+
1020
## [1.5.0] - 2017-09-26
1121
### Added
1222
- Ruby 2.4.1 testing

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ or an online CRL:
4646

4747
Critical and Warning thresholds are specified in minutes.
4848

49+
### `bin/check-ssl-qualys.rb`
50+
51+
Checks the ssllabs qualysis api for grade of your server, this check can be quite long so it should not be scheduled with a low interval and will probably need to adjust the check `timeout` options per the [check attributes spec](https://docs.sensu.io/sensu-core/1.2/reference/checks/#check-attributes) based on my tests you should expect this to take around 3 minutes.
52+
```
53+
./bin/check-ssl-qualys.rb -d google.com
54+
```
55+
56+
4957
## Installation
5058

5159
[Installation and Setup](http://sensu-plugins.io/docs/installation_instructions.html)

bin/check-ssl-qualys.rb

Lines changed: 71 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#!/usr/bin/env ruby
22
# encoding: UTF-8
3+
34
# check-ssl-qualys.rb
45
#
56
# DESCRIPTION:
@@ -41,6 +42,7 @@
4142
require 'sensu-plugin/check/cli'
4243
require 'json'
4344
require 'net/http'
45+
require 'timeout'
4446

4547
# Checks a single DNS entry has a rating above a certain level
4648
class CheckSSLQualys < Sensu::Plugin::Check::CLI
@@ -56,7 +58,7 @@ class CheckSSLQualys < Sensu::Plugin::Check::CLI
5658
option :api_url,
5759
description: 'The URL of the API to run against',
5860
long: '--api-url URL',
59-
default: 'https://api.ssllabs.com/api/v2/'
61+
default: 'https://api.ssllabs.com/api/v3/'
6062

6163
option :warn,
6264
short: '-w GRADE',
@@ -72,6 +74,12 @@ class CheckSSLQualys < Sensu::Plugin::Check::CLI
7274
proc: proc { |g| GRADE_OPTIONS.index(g) },
7375
default: 3 # 'B'
7476

77+
option :debug,
78+
long: '--debug BOOL',
79+
description: 'toggles extra debug printing',
80+
boolean: true,
81+
default: false
82+
7583
option :num_checks,
7684
short: '-n NUM_CHECKS',
7785
long: '--number-checks NUM_CHECKS',
@@ -82,17 +90,31 @@ class CheckSSLQualys < Sensu::Plugin::Check::CLI
8290
option :between_checks,
8391
short: '-t SECONDS',
8492
long: '--time-between SECONDS',
85-
description: 'The time between each poll of the API',
93+
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',
8694
proc: proc { |t| t.to_i },
8795
default: 10
8896

97+
option :timeout,
98+
short: '-t SECONDS',
99+
descriptions: 'the ammount of seconds that this is allowed to run for',
100+
proc: proc(&:to_i),
101+
default: 300
102+
89103
def ssl_api_request(from_cache)
90104
params = { host: config[:domain] }
91-
params[:startNew] = 'on' unless from_cache
105+
params[:startNew] = if from_cache == true
106+
'off'
107+
else
108+
'on'
109+
end
92110

93111
uri = URI("#{config[:api_url]}analyze")
94112
uri.query = URI.encode_www_form(params)
95-
response = Net::HTTP.get_response(uri)
113+
begin
114+
response = Net::HTTP.get_response(uri)
115+
rescue StandardError => e
116+
warning e
117+
end
96118

97119
warning 'Bad response recieved from API' unless response.is_a?(Net::HTTPSuccess)
98120

@@ -107,11 +129,37 @@ def ssl_check(from_cache)
107129

108130
def ssl_recheck
109131
1.upto(config[:num_checks]) do |step|
110-
json = ssl_check(step != 1)
132+
p "step: #{step}" if config[:debug]
133+
start_time = Time.now
134+
p "start_time: #{start_time}" if config[:debug]
135+
json = if step == 1
136+
ssl_check(false)
137+
else
138+
ssl_check(true)
139+
end
111140
return json if json['status'] == 'READY'
112-
sleep(config[:between_checks])
141+
if json['endpoints'] && 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]
113162
end
114-
warning 'Timeout waiting for check to finish'
115163
end
116164

117165
def ssl_grades
@@ -121,23 +169,25 @@ def ssl_grades
121169
end
122170

123171
def lowest_grade
124-
ssl_grades.sort_by! { |g| GRADE_OPTIONS.index(g) } .reverse![0]
172+
ssl_grades.sort_by! { |g| GRADE_OPTIONS.index(g) }.reverse![0]
125173
end
126174

127175
def run
128-
grade = lowest_grade
129-
unless grade
130-
message "#{config[:domain]} not rated"
131-
critical
132-
end
133-
message "#{config[:domain]} rated #{grade}"
134-
grade_rank = GRADE_OPTIONS.index(grade)
135-
if grade_rank > config[:critical]
136-
critical
137-
elsif grade_rank > config[:warn]
138-
warning
139-
else
140-
ok
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
141191
end
142192
end
143193
end

0 commit comments

Comments
 (0)