Skip to content

Commit

Permalink
Addressing multiple issues with timing out with check-ssl-qualys.rb:
Browse files Browse the repository at this point in the history
- 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 <[email protected]>
  • Loading branch information
majormoses committed Mar 20, 2018
1 parent e57724f commit 9544e31
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 21 deletions.
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@ RegexpLiteral:

Style/Documentation:
Enabled: false

# AllCops:
# TargetRubyVersion: 2.0
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ This CHANGELOG follows the format listed [here](https://github.com/sensu-plugins
### Fixed
- `check-ssl-hsts-preloadable.rb`: Fixed testing warnings for if a domain can be HSTS preloaded (@rwky)

### Breaking Changes
- `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)
- `check-ssl-qualys.rb`: new `--timeout` parameter to short circuit slow apis (@majormoses)

### Changed
- `check-ssl-qualys.rb`: updated `--api-url` to default to `v3` but remains backwards compatible (@jhoblitt) (@majormoses)

### Added
`check-ssl-qualys.rb`: option `--debug` to enable debug logging (@majormoses)

## [1.5.0] - 2017-09-26
### Added
- Ruby 2.4.1 testing
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ or an online CRL:

Critical and Warning thresholds are specified in minutes.

### `bin/check-ssl-qualysis.rb`

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.
```
./bin/check-ssl-qualys.rb -d google.com
```


## Installation

[Installation and Setup](http://sensu-plugins.io/docs/installation_instructions.html)
Expand Down
92 changes: 71 additions & 21 deletions bin/check-ssl-qualys.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env ruby
# encoding: UTF-8

# check-ssl-qualys.rb
#
# DESCRIPTION:
Expand Down Expand Up @@ -41,6 +42,7 @@
require 'sensu-plugin/check/cli'
require 'json'
require 'net/http'
require 'timeout'

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

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

option :debug,
long: '--debug BOOL',
description: 'toggles extra debug printing',
boolean: true,
default: false

option :num_checks,
short: '-n NUM_CHECKS',
long: '--number-checks NUM_CHECKS',
Expand All @@ -82,17 +90,31 @@ class CheckSSLQualys < Sensu::Plugin::Check::CLI
option :between_checks,
short: '-t SECONDS',
long: '--time-between SECONDS',
description: 'The time between each poll of the API',
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',
proc: proc { |t| t.to_i },
default: 10

option :timeout,
short: '-t SECONDS',
descriptions: 'the ammount of seconds that this is allowed to run for',
proc: proc(&:to_i),
default: 300

def ssl_api_request(from_cache)
params = { host: config[:domain] }
params[:startNew] = 'on' unless from_cache
params[:startNew] = if from_cache == true
'off'
else
'on'
end

uri = URI("#{config[:api_url]}analyze")
uri.query = URI.encode_www_form(params)
response = Net::HTTP.get_response(uri)
begin
response = Net::HTTP.get_response(uri)
rescue StandardError => e
warning e
end

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

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

def ssl_recheck
1.upto(config[:num_checks]) do |step|
json = ssl_check(step != 1)
p "step: #{step}" if config[:debug]
start_time = Time.now
p "start_time: #{start_time}" if config[:debug]
json = if step == 1
ssl_check(false)
else
ssl_check(true)
end
return json if json['status'] == 'READY'
sleep(config[:between_checks])
if json['endpoints'] && json['endpoints'].is_a?(Array)
p "endpoints: #{json['endpoints']}" if config[:debug]
# The api response sometimes has low eta (which seems unrealistic) from
# my tests that can be 0 or low numbers which would imply it is done...
# Basically we check if present and if its higher than the specified
# time to wait between checks. If so we use the eta from the api get
# response otherwise we use the time between check values. We have an
# overall timeout that protects us from the api telling us to wait for
# insanely long time periods. The highest I have seen the eta go was
# around 250 seconds but put it in just in case as the api has very
# erratic response times.
if json['endpoints'].first.is_a?(Hash) && json['endpoints'].first.key?('eta') && json['endpoints'].first['eta'] > config[:between_checks]
p "eta: #{json['endpoints'].first['eta']}" if config[:debug]
sleep(json['endpoints'].first['eta'])
else
p "sleeping with default: #{config[:between_checks]}" if config[:debug]
sleep(config[:between_checks])
end
end
p "elapsed: #{Time.now - start_time}" if config[:debug]
warning 'Timeout waiting for check to finish' if step == config[:num_checks]
end
warning 'Timeout waiting for check to finish'
end

def ssl_grades
Expand All @@ -121,23 +169,25 @@ def ssl_grades
end

def lowest_grade
ssl_grades.sort_by! { |g| GRADE_OPTIONS.index(g) } .reverse![0]
ssl_grades.sort_by! { |g| GRADE_OPTIONS.index(g) }.reverse![0]
end

def run
grade = lowest_grade
unless grade
message "#{config[:domain]} not rated"
critical
end
message "#{config[:domain]} rated #{grade}"
grade_rank = GRADE_OPTIONS.index(grade)
if grade_rank > config[:critical]
critical
elsif grade_rank > config[:warn]
warning
else
ok
Timeout.timeout(config[:timeout]) do
grade = lowest_grade
unless grade
message "#{config[:domain]} not rated"
critical
end
message "#{config[:domain]} rated #{grade}"
grade_rank = GRADE_OPTIONS.index(grade)
if grade_rank > config[:critical]
critical
elsif grade_rank > config[:warn]
warning
else
ok
end
end
end
end

0 comments on commit 9544e31

Please sign in to comment.