sensu-plugins-http-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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1 -0
- data/LICENSE +22 -0
- data/README.md +211 -0
- data/bin/check-head-redirect.rb +151 -0
- data/bin/check-http-cors.rb +145 -0
- data/bin/check-http-json.rb +232 -0
- data/bin/check-http.rb +461 -0
- data/bin/check-https-cert.rb +122 -0
- data/bin/check-last-modified.rb +178 -0
- data/bin/metrics-curl.rb +86 -0
- data/bin/metrics-http-json-deep.rb +133 -0
- data/bin/metrics-http-json.rb +137 -0
- data/bin/metrics-libcurl.rb +167 -0
- data/lib/sensu-plugins-http.rb +5 -0
- data/lib/sensu-plugins-http/aws-v4.rb +40 -0
- data/lib/sensu-plugins-http/common.rb +35 -0
- data/lib/sensu-plugins-http/version.rb +11 -0
- metadata +290 -0
@@ -0,0 +1,232 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: false
|
3
|
+
|
4
|
+
#
|
5
|
+
# check-http-json
|
6
|
+
#
|
7
|
+
# DESCRIPTION:
|
8
|
+
# Takes either a URL or a combination of host/path/query/port/ssl, and checks
|
9
|
+
# for valid JSON output in the response. Can also optionally validate simple
|
10
|
+
# string key/value pairs, and optionally check if a specified value is within
|
11
|
+
# bounds.
|
12
|
+
#
|
13
|
+
# OUTPUT:
|
14
|
+
# plain text
|
15
|
+
#
|
16
|
+
# PLATFORMS:
|
17
|
+
# Linux
|
18
|
+
#
|
19
|
+
# DEPENDENCIES:
|
20
|
+
# gem: sensu-plugin
|
21
|
+
# gem: json
|
22
|
+
#
|
23
|
+
# USAGE:
|
24
|
+
# Check that will verify http status and JSON validity
|
25
|
+
# ./check-http-json.rb -u http://my.site.com/health.json
|
26
|
+
#
|
27
|
+
# Check that will verify http status, JSON validity, and that page.totalElements value is
|
28
|
+
# greater than 10
|
29
|
+
# ./check-http-json.rb -u http://my.site.com/metric.json --key page.totalElements --value-greater-than 10
|
30
|
+
#
|
31
|
+
# Check that will POST json
|
32
|
+
# ./check-http-json.rb -u http://my.site.com/metric.json -m POST --header 'Content-type: application/json' --post-body '{"serverId": "myserver"}'
|
33
|
+
#
|
34
|
+
# NOTES:
|
35
|
+
# Based on Check HTTP by Sonian Inc.
|
36
|
+
#
|
37
|
+
# LICENSE:
|
38
|
+
# Copyright 2013 Matt Revell <nightowlmatt@gmail.com>
|
39
|
+
# Released under the same terms as Sensu (the MIT license); see LICENSE
|
40
|
+
# for details.
|
41
|
+
#
|
42
|
+
|
43
|
+
require 'sensu-plugin/check/cli'
|
44
|
+
require 'json'
|
45
|
+
require 'net/http'
|
46
|
+
require 'net/https'
|
47
|
+
|
48
|
+
#
|
49
|
+
# Check JSON
|
50
|
+
#
|
51
|
+
class CheckJson < Sensu::Plugin::Check::CLI
|
52
|
+
option :url, short: '-u URL'
|
53
|
+
option :host, short: '-h HOST'
|
54
|
+
option :path, short: '-p PATH'
|
55
|
+
option :query, short: '-q QUERY'
|
56
|
+
option :port, short: '-P PORT', proc: proc(&:to_i)
|
57
|
+
option :method, short: '-m GET|POST'
|
58
|
+
option :postbody, short: '-b /file/with/post/body'
|
59
|
+
option :post_body, long: '--post-body VALUE'
|
60
|
+
option :header, short: '-H HEADER', long: '--header HEADER'
|
61
|
+
option :ssl, short: '-s', boolean: true, default: false
|
62
|
+
option :insecure, short: '-k', boolean: true, default: false
|
63
|
+
option :user, short: '-U', long: '--username USER'
|
64
|
+
option :password, short: '-a', long: '--password PASS'
|
65
|
+
option :cert, short: '-c FILE', long: '--cert FILE'
|
66
|
+
option :certkey, long: '--cert-key FILE'
|
67
|
+
option :cacert, short: '-C FILE', long: '--cacert FILE'
|
68
|
+
option :timeout, short: '-t SECS', proc: proc(&:to_i), default: 15
|
69
|
+
option :key, short: '-K KEY', long: '--key KEY'
|
70
|
+
option :value, short: '-v VALUE', long: '--value VALUE'
|
71
|
+
option :valueGt, long: '--value-greater-than VALUE'
|
72
|
+
option :valueLt, long: '--value-less-than VALUE'
|
73
|
+
option :whole_response, short: '-w', long: '--whole-response', boolean: true, default: false
|
74
|
+
option :dump_json, short: '-d', long: '--dump-json', boolean: true, default: false
|
75
|
+
option :pretty, long: '--pretty', boolean: true, default: false
|
76
|
+
|
77
|
+
option :response_code,
|
78
|
+
long: '--response-code REGEX',
|
79
|
+
description: 'Critical if HTTP response code does not match REGEX',
|
80
|
+
default: '^2([0-9]{2})$'
|
81
|
+
|
82
|
+
def run
|
83
|
+
if config[:url]
|
84
|
+
uri = URI.parse(config[:url])
|
85
|
+
config[:host] = uri.host
|
86
|
+
config[:path] = uri.path
|
87
|
+
config[:query] = uri.query
|
88
|
+
config[:port] = uri.port
|
89
|
+
config[:ssl] = uri.scheme == 'https'
|
90
|
+
else
|
91
|
+
# #YELLOW
|
92
|
+
unless config[:host] && config[:path]
|
93
|
+
unknown 'No URL specified'
|
94
|
+
end
|
95
|
+
config[:port] ||= config[:ssl] ? 443 : 80
|
96
|
+
end
|
97
|
+
|
98
|
+
begin
|
99
|
+
Timeout.timeout(config[:timeout]) do
|
100
|
+
acquire_resource
|
101
|
+
end
|
102
|
+
rescue Timeout::Error
|
103
|
+
critical 'Connection timed out'
|
104
|
+
rescue StandardError => e
|
105
|
+
critical "Connection error: #{e.message}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def deep_value(data, desired_key, parent = '')
|
110
|
+
case data
|
111
|
+
when Array
|
112
|
+
data.each_with_index do |value, index|
|
113
|
+
arr_key = parent + '[' + index.to_s + ']'
|
114
|
+
|
115
|
+
if arr_key == desired_key
|
116
|
+
return value.nil? ? 'null' : value
|
117
|
+
end
|
118
|
+
|
119
|
+
if desired_key.include? arr_key
|
120
|
+
search = deep_value(value, desired_key, arr_key)
|
121
|
+
|
122
|
+
return search unless search.nil?
|
123
|
+
end
|
124
|
+
end
|
125
|
+
when Hash
|
126
|
+
data.each do |key, value|
|
127
|
+
key_prefix = parent.empty? ? '' : '.'
|
128
|
+
hash_key = parent + key_prefix + key
|
129
|
+
|
130
|
+
if hash_key == desired_key
|
131
|
+
return value.nil? ? 'null' : value
|
132
|
+
end
|
133
|
+
|
134
|
+
if desired_key.include?(hash_key + '.') || desired_key.include?(hash_key + '[')
|
135
|
+
search = deep_value(value, desired_key, hash_key)
|
136
|
+
|
137
|
+
return search unless search.nil?
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def json_valid?(str)
|
144
|
+
::JSON.parse(str)
|
145
|
+
true
|
146
|
+
rescue ::JSON::ParserError
|
147
|
+
false
|
148
|
+
end
|
149
|
+
|
150
|
+
def acquire_resource
|
151
|
+
http = Net::HTTP.new(config[:host], config[:port])
|
152
|
+
|
153
|
+
if config[:ssl]
|
154
|
+
http.use_ssl = true
|
155
|
+
if config[:cert]
|
156
|
+
cert_data = File.read(config[:cert])
|
157
|
+
http.cert = OpenSSL::X509::Certificate.new(cert_data)
|
158
|
+
if config[:certkey]
|
159
|
+
cert_data = File.read(config[:certkey])
|
160
|
+
end
|
161
|
+
http.key = OpenSSL::PKey::RSA.new(cert_data, nil)
|
162
|
+
end
|
163
|
+
http.ca_file = config[:cacert] if config[:cacert]
|
164
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if config[:insecure]
|
165
|
+
end
|
166
|
+
|
167
|
+
req = if config[:method] == 'POST'
|
168
|
+
Net::HTTP::Post.new([config[:path], config[:query]].compact.join('?'))
|
169
|
+
else
|
170
|
+
Net::HTTP::Get.new([config[:path], config[:query]].compact.join('?'))
|
171
|
+
end
|
172
|
+
if config[:postbody]
|
173
|
+
post_body = IO.readlines(config[:postbody])
|
174
|
+
req.body = post_body.join
|
175
|
+
end
|
176
|
+
if config[:post_body]
|
177
|
+
req.body = config[:post_body]
|
178
|
+
end
|
179
|
+
unless config[:user].nil? && config[:password].nil?
|
180
|
+
req.basic_auth config[:user], config[:password]
|
181
|
+
end
|
182
|
+
if config[:header]
|
183
|
+
config[:header].split(',').each do |header|
|
184
|
+
h, v = header.split(':', 2)
|
185
|
+
req[h] = v.strip
|
186
|
+
end
|
187
|
+
end
|
188
|
+
res = http.request(req)
|
189
|
+
|
190
|
+
if res.code !~ /#{config[:response_code]}/
|
191
|
+
critical "http code: #{res.code}: body: #{res.body}" if config[:whole_response]
|
192
|
+
critical res.code
|
193
|
+
end
|
194
|
+
critical 'invalid JSON from request' unless json_valid?(res.body)
|
195
|
+
ok 'valid JSON returned' if config[:key].nil? && config[:value].nil?
|
196
|
+
|
197
|
+
json = ::JSON.parse(res.body)
|
198
|
+
|
199
|
+
begin
|
200
|
+
leaf = deep_value(json, config[:key])
|
201
|
+
|
202
|
+
raise "could not find key: #{config[:key]}" if leaf.nil?
|
203
|
+
|
204
|
+
message = "key has expected value: '#{config[:key]}' "
|
205
|
+
if config[:value]
|
206
|
+
raise "unexpected value for key: '#{leaf}' != '#{config[:value]}'" unless leaf.to_s == config[:value].to_s
|
207
|
+
|
208
|
+
message += "equals '#{config[:value]}'"
|
209
|
+
end
|
210
|
+
if config[:valueGt]
|
211
|
+
raise "unexpected value for key: '#{leaf}' not > '#{config[:valueGt]}'" unless leaf.to_f > config[:valueGt].to_f
|
212
|
+
|
213
|
+
message += "greater than '#{config[:valueGt]}'"
|
214
|
+
end
|
215
|
+
if config[:valueLt]
|
216
|
+
raise "unexpected value for key: '#{leaf}' not < '#{config[:valueLt]}'" unless leaf.to_f < config[:valueLt].to_f
|
217
|
+
|
218
|
+
message += "less than '#{config[:valueLt]}'"
|
219
|
+
end
|
220
|
+
|
221
|
+
ok message
|
222
|
+
rescue StandardError => e
|
223
|
+
if config[:dump_json]
|
224
|
+
json_response = config[:pretty] ? ::JSON.pretty_generate(json) : json
|
225
|
+
message = "key check failed: #{e}. Response: #{json_response}"
|
226
|
+
else
|
227
|
+
message = "key check failed: #{e}"
|
228
|
+
end
|
229
|
+
critical message
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
data/bin/check-http.rb
ADDED
@@ -0,0 +1,461 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: false
|
3
|
+
|
4
|
+
#
|
5
|
+
# check-http
|
6
|
+
#
|
7
|
+
# DESCRIPTION:
|
8
|
+
# Takes either a URL or a combination of host/path/port/ssl, and checks for
|
9
|
+
# a 200 response (that matches a pattern, if given). Can use client certs.
|
10
|
+
#
|
11
|
+
# OUTPUT:
|
12
|
+
# plain text
|
13
|
+
#
|
14
|
+
# PLATFORMS:
|
15
|
+
# Linux
|
16
|
+
#
|
17
|
+
# DEPENDENCIES:
|
18
|
+
# gem: sensu-plugin
|
19
|
+
#
|
20
|
+
# USAGE:
|
21
|
+
# Basic HTTP check - expect a 200 response
|
22
|
+
# check-http.rb -u http://my.site.com
|
23
|
+
#
|
24
|
+
# Pattern check - expect a 200 response and the string 'OK' in the body
|
25
|
+
# check-http.rb -u http://my.site.com/health -q 'OK'
|
26
|
+
#
|
27
|
+
# Check if a response is greater than the specified minimum value
|
28
|
+
# check-http.rb -u https://my.site.com/redirect --min-bytes 10
|
29
|
+
#
|
30
|
+
# Check response code - expect a 301 response
|
31
|
+
# check-http.rb -u https://my.site.com/redirect --response-code 301 -r
|
32
|
+
#
|
33
|
+
# Use a proxy to check a URL
|
34
|
+
# check-http.rb -u https://www.google.com --proxy-url http://my.proxy.com:3128
|
35
|
+
#
|
36
|
+
# Use a proxy with username and password to check a URL
|
37
|
+
# NOTE: Use 'check token substition' to avoid credentials leakage!
|
38
|
+
# check-http.rb -u https://www.google.com --proxy-url http://a_user:a_pass@my.proxy.com:3128
|
39
|
+
#
|
40
|
+
# Check something with needing to set multiple headers
|
41
|
+
# check-http.rb -u https://www.google.com --header 'Origin: ma.local.box, SomeRandomHeader: foo'
|
42
|
+
#
|
43
|
+
# Check something that requires a POST with json data
|
44
|
+
# check-http.rb -u https://httpbin.org/post --method POST --header 'Content-type: application/json' --body '{"foo": "bar"}'
|
45
|
+
# NOTES:
|
46
|
+
#
|
47
|
+
# LICENSE:
|
48
|
+
# Copyright 2011 Sonian, Inc <chefs@sonian.net>
|
49
|
+
# Updated by Lewis Preson 2012 to accept basic auth credentials
|
50
|
+
# Updated by SweetSpot 2012 to require specified redirect
|
51
|
+
# Updated by Chris Armstrong 2013 to accept multiple headers
|
52
|
+
# Updated by Mark Clarkson 2018 to accept proxy auth credentials
|
53
|
+
# Released under the same terms as Sensu (the MIT license); see LICENSE
|
54
|
+
# for details.
|
55
|
+
#
|
56
|
+
|
57
|
+
require 'sensu-plugins-http'
|
58
|
+
require 'sensu-plugin/check/cli'
|
59
|
+
require 'net/http'
|
60
|
+
require 'net/https'
|
61
|
+
require 'digest'
|
62
|
+
require 'resolv-replace'
|
63
|
+
|
64
|
+
#
|
65
|
+
# Check HTTP
|
66
|
+
#
|
67
|
+
class CheckHttp < Sensu::Plugin::Check::CLI
|
68
|
+
option :ua,
|
69
|
+
short: '-x USER-AGENT',
|
70
|
+
long: '--user-agent USER-AGENT',
|
71
|
+
description: 'Specify a USER-AGENT',
|
72
|
+
default: 'Sensu-HTTP-Check'
|
73
|
+
|
74
|
+
option :url,
|
75
|
+
short: '-u URL',
|
76
|
+
long: '--url URL',
|
77
|
+
description: 'A URL to connect to'
|
78
|
+
|
79
|
+
option :host,
|
80
|
+
short: '-h HOST',
|
81
|
+
long: '--hostname HOSTNAME',
|
82
|
+
description: 'A HOSTNAME to connect to'
|
83
|
+
|
84
|
+
option :port,
|
85
|
+
short: '-P PORT',
|
86
|
+
long: '--port PORT',
|
87
|
+
proc: proc(&:to_i),
|
88
|
+
description: 'Select another port'
|
89
|
+
|
90
|
+
option :request_uri,
|
91
|
+
short: '-p PATH',
|
92
|
+
long: '--request-uri PATH',
|
93
|
+
description: 'Specify a uri path'
|
94
|
+
|
95
|
+
option :method,
|
96
|
+
short: '-m GET|HEAD|POST|PUT',
|
97
|
+
long: '--method GET|HEAD|POST|PUT',
|
98
|
+
description: 'Specify a GET, HEAD, POST, or PUT operation; defaults to GET',
|
99
|
+
in: %w[GET HEAD POST PUT],
|
100
|
+
default: 'GET'
|
101
|
+
|
102
|
+
option :header,
|
103
|
+
short: '-H HEADER',
|
104
|
+
long: '--header HEADER',
|
105
|
+
description: 'Send one or more comma-separated headers with the request'
|
106
|
+
|
107
|
+
option :headerfile,
|
108
|
+
long: '--headerfile FILE',
|
109
|
+
description: 'Send headers with the request, read from FILE, separated by newline'
|
110
|
+
|
111
|
+
option :body,
|
112
|
+
short: '-d BODY',
|
113
|
+
long: '--body BODY',
|
114
|
+
description: 'Send a data body string with the request'
|
115
|
+
|
116
|
+
option :ssl,
|
117
|
+
short: '-s',
|
118
|
+
boolean: true,
|
119
|
+
description: 'Enabling SSL connections',
|
120
|
+
default: false
|
121
|
+
|
122
|
+
option :insecure,
|
123
|
+
short: '-k',
|
124
|
+
boolean: true,
|
125
|
+
description: 'Enabling insecure connections',
|
126
|
+
default: false
|
127
|
+
|
128
|
+
option :user,
|
129
|
+
short: '-U',
|
130
|
+
long: '--username USER',
|
131
|
+
description: 'A username to connect as'
|
132
|
+
|
133
|
+
option :password,
|
134
|
+
short: '-a',
|
135
|
+
long: '--password PASS',
|
136
|
+
description: 'A password to use for the username'
|
137
|
+
|
138
|
+
option :cert,
|
139
|
+
short: '-c FILE',
|
140
|
+
long: '--cert FILE',
|
141
|
+
description: 'Cert to use'
|
142
|
+
|
143
|
+
option :cacert,
|
144
|
+
short: '-C FILE',
|
145
|
+
long: '--cacert FILE',
|
146
|
+
description: 'A CA Cert to use'
|
147
|
+
|
148
|
+
option :expiry,
|
149
|
+
short: '-e EXPIRY',
|
150
|
+
long: '--expiry EXPIRY',
|
151
|
+
proc: proc(&:to_i),
|
152
|
+
description: 'Warn EXPIRE days before cert expires'
|
153
|
+
|
154
|
+
option :pattern,
|
155
|
+
short: '-q PAT',
|
156
|
+
long: '--query PAT',
|
157
|
+
description: 'Query for a specific pattern that must exist'
|
158
|
+
|
159
|
+
option :negpattern,
|
160
|
+
short: '-n PAT',
|
161
|
+
long: '--negquery PAT',
|
162
|
+
description: 'Query for a specific pattern that must be absent'
|
163
|
+
|
164
|
+
option :sha256checksum,
|
165
|
+
short: '-S CHECKSUM',
|
166
|
+
long: '--checksum CHECKSUM',
|
167
|
+
description: 'SHA-256 checksum'
|
168
|
+
|
169
|
+
option :timeout,
|
170
|
+
short: '-t SECS',
|
171
|
+
long: '--timeout SECS',
|
172
|
+
proc: proc(&:to_i),
|
173
|
+
description: 'Set the total execution timeout in seconds',
|
174
|
+
default: 15
|
175
|
+
|
176
|
+
option :open_timeout,
|
177
|
+
long: '--open-timeout SECS',
|
178
|
+
proc: proc(&:to_i),
|
179
|
+
description: 'Number of seconds to wait for the connection to open',
|
180
|
+
default: 15
|
181
|
+
|
182
|
+
option :read_timeout,
|
183
|
+
long: '--read-timeout SECS',
|
184
|
+
proc: proc(&:to_i),
|
185
|
+
description: 'Number of seconds to wait for one block to be read',
|
186
|
+
default: 15
|
187
|
+
|
188
|
+
option :dns_timeout,
|
189
|
+
long: '--dns-timeout SECS',
|
190
|
+
proc: proc(&:to_f),
|
191
|
+
description: 'Number of seconds to allow for DNS resolution. Accepts decimal number.',
|
192
|
+
default: 0.8
|
193
|
+
|
194
|
+
option :redirectok,
|
195
|
+
short: '-r',
|
196
|
+
boolean: true,
|
197
|
+
description: 'Check if a redirect is ok',
|
198
|
+
default: false
|
199
|
+
|
200
|
+
option :redirectto,
|
201
|
+
short: '-R URL',
|
202
|
+
long: '--redirect-to URL',
|
203
|
+
description: 'Redirect to another page'
|
204
|
+
|
205
|
+
option :whole_response,
|
206
|
+
short: '-w',
|
207
|
+
long: '--whole-response',
|
208
|
+
boolean: true,
|
209
|
+
default: false,
|
210
|
+
description: 'Print whole output when check fails'
|
211
|
+
|
212
|
+
option :response_bytes,
|
213
|
+
short: '-b BYTES',
|
214
|
+
long: '--response-bytes BYTES',
|
215
|
+
description: 'Print BYTES of the output',
|
216
|
+
proc: proc(&:to_i)
|
217
|
+
|
218
|
+
option :require_bytes,
|
219
|
+
short: '-B BYTES',
|
220
|
+
long: '--require-bytes BYTES',
|
221
|
+
description: 'Check the response contains exactly BYTES bytes',
|
222
|
+
proc: proc(&:to_i)
|
223
|
+
|
224
|
+
option :min_bytes,
|
225
|
+
short: '-g BYTES',
|
226
|
+
long: '--min-bytes BYTES',
|
227
|
+
description: 'Check the response contains at least BYTES bytes',
|
228
|
+
proc: proc(&:to_i)
|
229
|
+
|
230
|
+
option :response_code,
|
231
|
+
long: '--response-code REGEX',
|
232
|
+
description: 'Critical if HTTP response code does not match REGEX'
|
233
|
+
|
234
|
+
option :proxy_url,
|
235
|
+
long: '--proxy-url PROXY_URL',
|
236
|
+
description: 'Use a proxy server to connect'
|
237
|
+
|
238
|
+
option :no_proxy,
|
239
|
+
long: '--noproxy',
|
240
|
+
boolean: true,
|
241
|
+
description: 'Do not use proxy server even from environment http_proxy setting',
|
242
|
+
default: false
|
243
|
+
|
244
|
+
option :aws_v4,
|
245
|
+
long: '--aws-v4',
|
246
|
+
boolean: true,
|
247
|
+
description: 'Sign http request with AWS v4 signature',
|
248
|
+
default: false
|
249
|
+
|
250
|
+
option :aws_v4_region,
|
251
|
+
long: '--aws-v4-region REGION',
|
252
|
+
description: 'Region to use for AWS v4 signing. Defaults to AWS_REGION or AWS_DEFAULT_REGION'
|
253
|
+
|
254
|
+
option :aws_v4_service,
|
255
|
+
long: '--aws-v4-service SERVICE',
|
256
|
+
description: 'Service name to use when building the v4 signature',
|
257
|
+
default: 'execute-api'
|
258
|
+
|
259
|
+
include SensuPluginsHttp::AwsV4
|
260
|
+
|
261
|
+
def run
|
262
|
+
if config[:url]
|
263
|
+
uri = URI.parse(config[:url])
|
264
|
+
config[:host] = uri.host
|
265
|
+
config[:port] = uri.port
|
266
|
+
config[:request_uri] = uri.request_uri
|
267
|
+
config[:ssl] = uri.scheme == 'https'
|
268
|
+
else
|
269
|
+
# #YELLOW
|
270
|
+
unless config[:host] && config[:request_uri]
|
271
|
+
unknown 'No URL specified'
|
272
|
+
end
|
273
|
+
config[:port] ||= config[:ssl] ? 443 : 80
|
274
|
+
end
|
275
|
+
|
276
|
+
# Use Ruby DNS Resolver and set DNS resolution timeout to dns_timeout value
|
277
|
+
hosts_resolver = Resolv::Hosts.new
|
278
|
+
dns_resolver = Resolv::DNS.new
|
279
|
+
dns_resolver.timeouts = config[:dns_timeout]
|
280
|
+
Resolv::DefaultResolver.replace_resolvers([hosts_resolver, dns_resolver])
|
281
|
+
|
282
|
+
begin
|
283
|
+
Timeout.timeout(config[:timeout]) do
|
284
|
+
acquire_resource
|
285
|
+
end
|
286
|
+
rescue Net::OpenTimeout
|
287
|
+
critical 'Request timed out opening connection'
|
288
|
+
rescue Net::ReadTimeout
|
289
|
+
critical 'Request timed out reading data'
|
290
|
+
rescue Timeout::Error
|
291
|
+
critical 'Request timed out'
|
292
|
+
rescue StandardError => e
|
293
|
+
critical "Request error: #{e.message}"
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def acquire_resource
|
298
|
+
http = nil
|
299
|
+
|
300
|
+
if config[:no_proxy]
|
301
|
+
http = Net::HTTP.new(config[:host], config[:port], nil, nil)
|
302
|
+
elsif config[:proxy_url]
|
303
|
+
proxy_uri = URI.parse(config[:proxy_url])
|
304
|
+
if proxy_uri.host.nil?
|
305
|
+
unknown 'Invalid proxy url specified'
|
306
|
+
end
|
307
|
+
http = if proxy_uri.user && proxy_uri.password
|
308
|
+
Net::HTTP.new(config[:host], config[:port], proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password)
|
309
|
+
else
|
310
|
+
Net::HTTP.new(config[:host], config[:port], proxy_uri.host, proxy_uri.port)
|
311
|
+
end
|
312
|
+
else
|
313
|
+
http = Net::HTTP.new(config[:host], config[:port])
|
314
|
+
end
|
315
|
+
http.read_timeout = config[:read_timeout]
|
316
|
+
http.open_timeout = config[:open_timeout]
|
317
|
+
http.ssl_timeout = config[:timeout]
|
318
|
+
http.continue_timeout = config[:timeout]
|
319
|
+
http.keep_alive_timeout = config[:timeout]
|
320
|
+
|
321
|
+
warn_cert_expire = nil
|
322
|
+
if config[:ssl]
|
323
|
+
http.use_ssl = true
|
324
|
+
if config[:cert]
|
325
|
+
cert_data = File.read(config[:cert])
|
326
|
+
http.cert = OpenSSL::X509::Certificate.new(cert_data)
|
327
|
+
http.key = OpenSSL::PKey::RSA.new(cert_data, nil)
|
328
|
+
end
|
329
|
+
http.ca_file = config[:cacert] if config[:cacert]
|
330
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if config[:insecure]
|
331
|
+
|
332
|
+
unless config[:expiry].nil?
|
333
|
+
expire_warn_date = Time.now + (config[:expiry] * 60 * 60 * 24)
|
334
|
+
# We can't raise inside the callback, have to check when we finish.
|
335
|
+
http.verify_callback = proc do |preverify_ok, ssl_context|
|
336
|
+
if ssl_context.current_cert.not_after <= expire_warn_date
|
337
|
+
warn_cert_expire = ssl_context.current_cert.not_after
|
338
|
+
end
|
339
|
+
|
340
|
+
preverify_ok
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
req = case config[:method]
|
346
|
+
when 'GET'
|
347
|
+
Net::HTTP::Get.new(config[:request_uri], 'User-Agent' => config[:ua])
|
348
|
+
when 'HEAD'
|
349
|
+
Net::HTTP::Head.new(config[:request_uri], 'User-Agent' => config[:ua])
|
350
|
+
when 'POST'
|
351
|
+
Net::HTTP::Post.new(config[:request_uri], 'User-Agent' => config[:ua])
|
352
|
+
when 'PUT'
|
353
|
+
Net::HTTP::Put.new(config[:request_uri], 'User-Agent' => config[:ua])
|
354
|
+
end
|
355
|
+
|
356
|
+
unless config[:user].nil? && config[:password].nil?
|
357
|
+
req.basic_auth config[:user], config[:password]
|
358
|
+
end
|
359
|
+
if config[:header]
|
360
|
+
config[:header].split(',').each do |header|
|
361
|
+
h, v = header.split(':', 2)
|
362
|
+
req[h.strip] = v.strip
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
if config[:headerfile]
|
367
|
+
File.readlines(config[:headerfile]).each do |line|
|
368
|
+
h, v = line.split(':', 2)
|
369
|
+
req[h.strip] = v.strip
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
req.body = config[:body] if config[:body]
|
374
|
+
|
375
|
+
req = apply_v4_signature(http, req, config) if config[:aws_v4]
|
376
|
+
|
377
|
+
res = http.request(req)
|
378
|
+
|
379
|
+
body = if config[:whole_response]
|
380
|
+
"\n" + res.body.to_s
|
381
|
+
else
|
382
|
+
body = if config[:response_bytes] # rubocop:disable Lint/UselessAssignment
|
383
|
+
"\n" + res.body[0..config[:response_bytes]]
|
384
|
+
else
|
385
|
+
''
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
if config[:require_bytes] && res.body.length != config[:require_bytes]
|
390
|
+
critical "Response was #{res.body.length} bytes instead of #{config[:require_bytes]}" + body
|
391
|
+
end
|
392
|
+
|
393
|
+
if config[:min_bytes] && res.body.length < config[:min_bytes]
|
394
|
+
critical "Response was #{res.body.length} bytes instead of the indicated minimum #{config[:min_bytes]}" + body
|
395
|
+
end
|
396
|
+
|
397
|
+
unless warn_cert_expire.nil?
|
398
|
+
warning "Certificate will expire #{warn_cert_expire}"
|
399
|
+
end
|
400
|
+
|
401
|
+
size = res.body.nil? ? '0' : res.body.size
|
402
|
+
|
403
|
+
handle_response(res, size, body)
|
404
|
+
end
|
405
|
+
|
406
|
+
def handle_response(res, size, body)
|
407
|
+
case res.code
|
408
|
+
when /^2/
|
409
|
+
if config[:redirectto]
|
410
|
+
critical "Expected redirect to #{config[:redirectto]} but got #{res.code}" + body
|
411
|
+
elsif config[:pattern]
|
412
|
+
if res.body =~ /#{config[:pattern]}/
|
413
|
+
ok "#{res.code}, found /#{config[:pattern]}/ in #{size} bytes" + body
|
414
|
+
else
|
415
|
+
critical "#{res.code}, did not find /#{config[:pattern]}/ in #{size} bytes: #{res.body[0...200]}..."
|
416
|
+
end
|
417
|
+
elsif config[:negpattern]
|
418
|
+
if res.body =~ /#{config[:negpattern]}/
|
419
|
+
critical "#{res.code}, found /#{config[:negpattern]}/ in #{size} bytes: #{res.body[0...200]}..."
|
420
|
+
else
|
421
|
+
ok "#{res.code}, did not find /#{config[:negpattern]}/ in #{size} bytes" + body
|
422
|
+
end
|
423
|
+
elsif config[:sha256checksum]
|
424
|
+
if Digest::SHA256.hexdigest(res.body).eql? config[:sha256checksum]
|
425
|
+
ok "#{res.code}, checksum match #{config[:sha256checksum]} in #{size} bytes" + body
|
426
|
+
else
|
427
|
+
critical "#{res.code}, checksum did not match #{config[:sha256checksum]} in #{size} bytes: #{res.body[0...200]}..."
|
428
|
+
end
|
429
|
+
else
|
430
|
+
ok("#{res.code}, #{size} bytes" + body) unless config[:response_code]
|
431
|
+
end
|
432
|
+
when /^3/
|
433
|
+
if config[:redirectok] || config[:redirectto]
|
434
|
+
if config[:redirectok]
|
435
|
+
# #YELLOW
|
436
|
+
ok("#{res.code}, #{size} bytes" + body) unless config[:response_code] # rubocop:disable Metrics/BlockNesting
|
437
|
+
elsif config[:redirectto]
|
438
|
+
# #YELLOW
|
439
|
+
if config[:redirectto] == res['Location'] # rubocop:disable Metrics/BlockNesting
|
440
|
+
ok "#{res.code} found redirect to #{res['Location']}" + body
|
441
|
+
else
|
442
|
+
critical "Expected redirect to #{config[:redirectto]} instead redirected to #{res['Location']}" + body
|
443
|
+
end
|
444
|
+
end
|
445
|
+
else
|
446
|
+
warning res.code + body
|
447
|
+
end
|
448
|
+
when /^4/, /^5/
|
449
|
+
critical(res.code + body) unless config[:response_code]
|
450
|
+
else
|
451
|
+
warning(res.code + body) unless config[:response_code]
|
452
|
+
end
|
453
|
+
|
454
|
+
if config[:response_code] && res.code =~ /#{config[:response_code]}/
|
455
|
+
ok "#{res.code}, #{size} bytes" + body
|
456
|
+
|
457
|
+
else
|
458
|
+
critical res.code + body
|
459
|
+
end
|
460
|
+
end
|
461
|
+
end
|