riemann-tools 1.10.0 → 1.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +8 -6
- data/.gitignore +1 -0
- data/.rubocop.yml +18 -0
- data/CHANGELOG.md +35 -0
- data/Gemfile +22 -0
- data/Rakefile +1 -1
- data/bin/riemann-hwmon +8 -0
- data/bin/riemann-tls-check +8 -0
- data/lib/riemann/tools/apache_status.rb +2 -0
- data/lib/riemann/tools/bench.rb +9 -7
- data/lib/riemann/tools/consul_health.rb +2 -0
- data/lib/riemann/tools/dir_files_count.rb +2 -0
- data/lib/riemann/tools/dir_space.rb +2 -0
- data/lib/riemann/tools/diskstats.rb +6 -4
- data/lib/riemann/tools/fd.rb +2 -0
- data/lib/riemann/tools/freeswitch.rb +2 -0
- data/lib/riemann/tools/haproxy.rb +2 -0
- data/lib/riemann/tools/health.rb +79 -11
- data/lib/riemann/tools/http_check.rb +56 -17
- data/lib/riemann/tools/hwmon.rb +111 -0
- data/lib/riemann/tools/mdstat_parser.tab.rb +4 -2
- data/lib/riemann/tools/net.rb +3 -7
- data/lib/riemann/tools/nginx_status.rb +8 -4
- data/lib/riemann/tools/ntp.rb +2 -0
- data/lib/riemann/tools/portcheck.rb +2 -0
- data/lib/riemann/tools/proc.rb +2 -0
- data/lib/riemann/tools/tls_check.rb +604 -0
- data/lib/riemann/tools/utils.rb +39 -0
- data/lib/riemann/tools/varnish.rb +2 -0
- data/lib/riemann/tools/version.rb +1 -1
- data/lib/riemann/tools.rb +26 -9
- data/riemann-tools.gemspec +6 -14
- data/tools/riemann-aws/lib/riemann/tools/aws/billing.rb +3 -1
- data/tools/riemann-aws/lib/riemann/tools/aws/rds_status.rb +2 -0
- data/tools/riemann-aws/lib/riemann/tools/aws/s3_status.rb +1 -1
- data/tools/riemann-aws/lib/riemann/tools/aws/sqs_status.rb +2 -0
- data/tools/riemann-aws/lib/riemann/tools/aws/status.rb +11 -9
- data/tools/riemann-chronos/lib/riemann/tools/chronos.rb +2 -0
- data/tools/riemann-docker/lib/riemann/tools/docker.rb +5 -5
- data/tools/riemann-marathon/lib/riemann/tools/marathon.rb +2 -0
- data/tools/riemann-munin/lib/riemann/tools/munin.rb +2 -0
- data/tools/riemann-rabbitmq/lib/riemann/tools/rabbitmq.rb +7 -7
- data/tools/riemann-riak/lib/riemann/tools/riak.rb +4 -2
- metadata +24 -132
@@ -0,0 +1,604 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'resolv'
|
5
|
+
|
6
|
+
require 'riemann/tools'
|
7
|
+
require 'riemann/tools/utils'
|
8
|
+
|
9
|
+
module URI
|
10
|
+
{
|
11
|
+
'IMAP' => 143,
|
12
|
+
'IMAPS' => 993,
|
13
|
+
'MYSQL' => 3306,
|
14
|
+
'POSTGRES' => 5432,
|
15
|
+
}.each do |scheme, port|
|
16
|
+
klass = Class.new(Generic)
|
17
|
+
klass.const_set('DEFAULT_PORT', port)
|
18
|
+
|
19
|
+
if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('3.1.0')
|
20
|
+
scheme_list[scheme] = klass
|
21
|
+
else
|
22
|
+
register_scheme(scheme, klass)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module Riemann
|
28
|
+
module Tools
|
29
|
+
class TLSCheck
|
30
|
+
include Riemann::Tools
|
31
|
+
|
32
|
+
# Ruby OpenSSL does not expose ERR_error_string(3), and depending on the
|
33
|
+
# version of OpenSSL the available values change. Build a local list of
|
34
|
+
# mappings from include/openssl/x509_vfy.h.in and crypto/x509/x509_txt.c
|
35
|
+
# for lookups.
|
36
|
+
OPENSSL_ERROR_STRINGS = [
|
37
|
+
'ok',
|
38
|
+
'unspecified certificate verification error',
|
39
|
+
'unable to get issuer certificate',
|
40
|
+
'unable to get certificate CRL',
|
41
|
+
"unable to decrypt certificate's signature",
|
42
|
+
"unable to decrypt CRL's signature",
|
43
|
+
'unable to decode issuer public key',
|
44
|
+
'certificate signature failure',
|
45
|
+
'CRL signature failure',
|
46
|
+
'certificate is not yet valid',
|
47
|
+
'certificate has expired',
|
48
|
+
'CRL is not yet valid',
|
49
|
+
'CRL has expired',
|
50
|
+
"format error in certificate's notBefore field",
|
51
|
+
"format error in certificate's notAfter field",
|
52
|
+
"format error in CRL's lastUpdate field",
|
53
|
+
"format error in CRL's nextUpdate field",
|
54
|
+
'out of memory',
|
55
|
+
'self-signed certificate',
|
56
|
+
'self-signed certificate in certificate chain',
|
57
|
+
'unable to get local issuer certificate',
|
58
|
+
'unable to verify the first certificate',
|
59
|
+
'certificate chain too long',
|
60
|
+
'certificate revoked',
|
61
|
+
"issuer certificate doesn't have a public key",
|
62
|
+
'path length constraint exceeded',
|
63
|
+
'unsuitable certificate purpose',
|
64
|
+
'certificate not trusted',
|
65
|
+
'certificate rejected',
|
66
|
+
].freeze
|
67
|
+
|
68
|
+
class TLSCheckResult
|
69
|
+
include Riemann::Tools::Utils
|
70
|
+
|
71
|
+
attr_reader :uri, :address, :tls_socket
|
72
|
+
|
73
|
+
def initialize(uri, address, tls_socket, checker)
|
74
|
+
@uri = uri
|
75
|
+
@address = address
|
76
|
+
@tls_socket = tls_socket
|
77
|
+
@checker = checker
|
78
|
+
end
|
79
|
+
|
80
|
+
def peer_cert
|
81
|
+
tls_socket.peer_cert
|
82
|
+
end
|
83
|
+
|
84
|
+
def peer_cert_chain
|
85
|
+
tls_socket.peer_cert_chain
|
86
|
+
end
|
87
|
+
|
88
|
+
def exception
|
89
|
+
tls_socket.exception if tls_socket.respond_to?(:exception)
|
90
|
+
end
|
91
|
+
|
92
|
+
def valid_identity?
|
93
|
+
OpenSSL::SSL.verify_certificate_identity(peer_cert, uri.host)
|
94
|
+
end
|
95
|
+
|
96
|
+
def acceptable_identities
|
97
|
+
res = []
|
98
|
+
|
99
|
+
peer_cert.extensions.each do |ext|
|
100
|
+
next unless ext.oid == 'subjectAltName'
|
101
|
+
|
102
|
+
ostr = OpenSSL::ASN1.decode(ext.to_der).value.last
|
103
|
+
sequence = OpenSSL::ASN1.decode(ostr.value)
|
104
|
+
res = sequence.value.map(&:value)
|
105
|
+
end
|
106
|
+
|
107
|
+
res << peer_cert.subject.to_s unless res.any?
|
108
|
+
|
109
|
+
res
|
110
|
+
end
|
111
|
+
|
112
|
+
def not_valid_yet?
|
113
|
+
utcnow < not_before
|
114
|
+
end
|
115
|
+
|
116
|
+
def not_after
|
117
|
+
peer_cert.not_after
|
118
|
+
end
|
119
|
+
|
120
|
+
def not_after_ago
|
121
|
+
not_after - utcnow
|
122
|
+
end
|
123
|
+
|
124
|
+
def not_after_ago_in_words
|
125
|
+
when_from_utcnow(not_after)
|
126
|
+
end
|
127
|
+
|
128
|
+
def not_before
|
129
|
+
peer_cert.not_before
|
130
|
+
end
|
131
|
+
|
132
|
+
def not_before_away
|
133
|
+
utcnow - not_before
|
134
|
+
end
|
135
|
+
|
136
|
+
def not_before_away_in_words
|
137
|
+
when_from_utcnow(not_before)
|
138
|
+
end
|
139
|
+
|
140
|
+
def validity_duration
|
141
|
+
not_after - not_before
|
142
|
+
end
|
143
|
+
|
144
|
+
def renewal_duration
|
145
|
+
[validity_duration * @checker.opts[:renewal_duration_ratio], @checker.opts[:renewal_duration_days] * 3600 * 24].min
|
146
|
+
end
|
147
|
+
|
148
|
+
def expired_or_expire_soon?
|
149
|
+
utcnow + (renewal_duration / 3) > not_after
|
150
|
+
end
|
151
|
+
|
152
|
+
def expire_soonish?
|
153
|
+
utcnow + (2 * renewal_duration / 3) > not_after
|
154
|
+
end
|
155
|
+
|
156
|
+
def expired?
|
157
|
+
utcnow > not_after
|
158
|
+
end
|
159
|
+
|
160
|
+
def verify_result
|
161
|
+
tls_socket.verify_result
|
162
|
+
end
|
163
|
+
|
164
|
+
def trusted?
|
165
|
+
verify_result == OpenSSL::X509::V_OK
|
166
|
+
end
|
167
|
+
|
168
|
+
def ocsp_status
|
169
|
+
@ocsp_status ||= check_ocsp_status
|
170
|
+
end
|
171
|
+
|
172
|
+
def ocsp?
|
173
|
+
!ocsp_status.empty?
|
174
|
+
end
|
175
|
+
|
176
|
+
def valid_ocsp?
|
177
|
+
ocsp_status == 'successful'
|
178
|
+
end
|
179
|
+
|
180
|
+
def check_ocsp_status
|
181
|
+
subject = peer_cert
|
182
|
+
issuer = peer_cert_chain[1]
|
183
|
+
|
184
|
+
return '' unless issuer
|
185
|
+
|
186
|
+
digest = OpenSSL::Digest.new('SHA1')
|
187
|
+
certificate_id = OpenSSL::OCSP::CertificateId.new(subject, issuer, digest)
|
188
|
+
|
189
|
+
request = OpenSSL::OCSP::Request.new
|
190
|
+
request.add_certid(certificate_id)
|
191
|
+
|
192
|
+
request.add_nonce
|
193
|
+
|
194
|
+
authority_info_access = subject.extensions.find do |extension|
|
195
|
+
extension.oid == 'authorityInfoAccess'
|
196
|
+
end
|
197
|
+
|
198
|
+
return '' unless authority_info_access
|
199
|
+
|
200
|
+
descriptions = authority_info_access.value.split("\n")
|
201
|
+
ocsp = descriptions.find do |description|
|
202
|
+
description.start_with? 'OCSP'
|
203
|
+
end
|
204
|
+
|
205
|
+
ocsp_uri = URI(ocsp[/URI:(.*)/, 1])
|
206
|
+
|
207
|
+
http_response = ::Net::HTTP.start(ocsp_uri.hostname, ocsp_uri.port) do |http|
|
208
|
+
ocsp_uri.path = '/' if ocsp_uri.path.empty?
|
209
|
+
http.post(ocsp_uri.path, request.to_der, 'content-type' => 'application/ocsp-request')
|
210
|
+
end
|
211
|
+
|
212
|
+
response = OpenSSL::OCSP::Response.new http_response.body
|
213
|
+
response_basic = response.basic
|
214
|
+
|
215
|
+
return '' unless response_basic&.verify([issuer], @checker.store)
|
216
|
+
|
217
|
+
response.status_string
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
class UnexpectedMessage < StandardError
|
222
|
+
def initialize(msg)
|
223
|
+
super(%(Unexpected message: "#{msg.chomp}"))
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
opt :uri, 'URI to check', short: :none, type: :strings
|
228
|
+
opt :checks, 'A list of checks to run.', short: :none, type: :strings, default: %w[identity not-after not-before ocsp trust]
|
229
|
+
|
230
|
+
opt :renewal_duration_days, 'Number of days before certificate expiration it is considered renewalable', short: :none, type: :integer, default: 90
|
231
|
+
opt :renewal_duration_ratio, 'Portion of the certificate lifespan it is considered renewalable', short: :none, type: :float, default: 1.0 / 3
|
232
|
+
|
233
|
+
opt :trust, 'Additionnal CA to trust', short: :none, type: :strings, default: []
|
234
|
+
|
235
|
+
opt :resolvers, 'Run this number of resolver threads', short: :none, type: :integer, default: 5
|
236
|
+
opt :workers, 'Run this number of worker threads', short: :none, type: :integer, default: 20
|
237
|
+
opt :connect_timeout, 'Timeout to espablish a connection (default to half the interval caped to 10s)', short: :none, type: :integer
|
238
|
+
|
239
|
+
def initialize
|
240
|
+
super
|
241
|
+
|
242
|
+
opts[:connect_timeout] ||= [10, opts[:interval] / 2].min
|
243
|
+
|
244
|
+
@resolve_queue = Queue.new
|
245
|
+
@work_queue = Queue.new
|
246
|
+
|
247
|
+
opts[:resolvers].times do
|
248
|
+
Thread.new do
|
249
|
+
loop do
|
250
|
+
uri = @resolve_queue.pop
|
251
|
+
host = uri.host
|
252
|
+
|
253
|
+
addresses = if host == 'localhost'
|
254
|
+
Socket.ip_address_list.select { |address| address.ipv6_loopback? || address.ipv4_loopback? }.map(&:ip_address)
|
255
|
+
else
|
256
|
+
Resolv::DNS.new.getaddresses(host)
|
257
|
+
end
|
258
|
+
if addresses.empty?
|
259
|
+
host = host[1...-1] if host[0] == '[' && host[-1] == ']'
|
260
|
+
begin
|
261
|
+
addresses << IPAddr.new(host)
|
262
|
+
rescue IPAddr::InvalidAddressError
|
263
|
+
# Ignore
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
@work_queue.push([uri, addresses])
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
opts[:workers].times do
|
273
|
+
Thread.new do
|
274
|
+
loop do
|
275
|
+
uri, addresses = @work_queue.pop
|
276
|
+
test_uri_addresses(uri, addresses)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def tick
|
283
|
+
report(
|
284
|
+
service: 'riemann tls-check resolvers utilization',
|
285
|
+
metric: (opts[:resolvers].to_f - @resolve_queue.num_waiting) / opts[:resolvers],
|
286
|
+
state: @resolve_queue.num_waiting.positive? ? 'ok' : 'critical',
|
287
|
+
tags: %w[riemann],
|
288
|
+
)
|
289
|
+
report(
|
290
|
+
service: 'riemann tls-check resolvers saturation',
|
291
|
+
metric: @resolve_queue.length,
|
292
|
+
state: @resolve_queue.empty? ? 'ok' : 'critical',
|
293
|
+
tags: %w[riemann],
|
294
|
+
)
|
295
|
+
report(
|
296
|
+
service: 'riemann tls-check workers utilization',
|
297
|
+
metric: (opts[:workers].to_f - @work_queue.num_waiting) / opts[:workers],
|
298
|
+
state: @work_queue.num_waiting.positive? ? 'ok' : 'critical',
|
299
|
+
tags: %w[riemann],
|
300
|
+
)
|
301
|
+
report(
|
302
|
+
service: 'riemann tls-check workers saturation',
|
303
|
+
metric: @work_queue.length,
|
304
|
+
state: @work_queue.empty? ? 'ok' : 'critical',
|
305
|
+
tags: %w[riemann],
|
306
|
+
)
|
307
|
+
|
308
|
+
opts[:uri].each do |uri|
|
309
|
+
@resolve_queue.push(URI(uri))
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
def test_uri_addresses(uri, addresses)
|
314
|
+
addresses.each do |address|
|
315
|
+
test_uri_address(uri, address.to_s)
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def test_uri_address(uri, address)
|
320
|
+
socket = tls_socket(uri, address)
|
321
|
+
tls_check_result = TLSCheckResult.new(uri, address, socket, self)
|
322
|
+
report_availability(tls_check_result)
|
323
|
+
return unless socket.peer_cert
|
324
|
+
|
325
|
+
report_not_before(tls_check_result) if opts[:checks].include?('not-before')
|
326
|
+
report_not_after(tls_check_result) if opts[:checks].include?('not-after')
|
327
|
+
report_identity(tls_check_result) if opts[:checks].include?('identity')
|
328
|
+
report_trust(tls_check_result) if opts[:checks].include?('trust')
|
329
|
+
report_ocsp(tls_check_result) if opts[:checks].include?('ocsp')
|
330
|
+
rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, UnexpectedMessage => e
|
331
|
+
report_unavailability(uri, address, e)
|
332
|
+
end
|
333
|
+
|
334
|
+
def report_availability(tls_check_result)
|
335
|
+
if tls_check_result.exception
|
336
|
+
report(
|
337
|
+
service: "#{tls_endpoint_name(tls_check_result)} availability",
|
338
|
+
state: 'critical',
|
339
|
+
description: tls_check_result.exception.message,
|
340
|
+
)
|
341
|
+
else
|
342
|
+
issues = []
|
343
|
+
|
344
|
+
issues << 'Certificate is not valid yet' if tls_check_result.not_valid_yet?
|
345
|
+
issues << 'Certificate has expired' if tls_check_result.expired?
|
346
|
+
issues << 'Certificate identity could not be verified' unless tls_check_result.valid_identity?
|
347
|
+
issues << 'Certificate is not trusted' unless tls_check_result.trusted?
|
348
|
+
issues << 'Certificate OCSP verification failed' if tls_check_result.ocsp? && !tls_check_result.valid_ocsp?
|
349
|
+
|
350
|
+
report(
|
351
|
+
service: "#{tls_endpoint_name(tls_check_result)} availability",
|
352
|
+
state: issues.empty? ? 'ok' : 'critical',
|
353
|
+
description: issues.join("\n"),
|
354
|
+
)
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
def report_unavailability(uri, address, exception)
|
359
|
+
report(
|
360
|
+
service: "#{tls_endpoint_name2(uri, address)} availability",
|
361
|
+
state: 'critical',
|
362
|
+
description: exception.message,
|
363
|
+
)
|
364
|
+
end
|
365
|
+
|
366
|
+
def report_not_after(tls_check_result)
|
367
|
+
report(
|
368
|
+
service: "#{tls_endpoint_name(tls_check_result)} not after",
|
369
|
+
state: not_after_state(tls_check_result),
|
370
|
+
metric: tls_check_result.not_after_ago,
|
371
|
+
description: tls_check_result.not_after_ago_in_words,
|
372
|
+
)
|
373
|
+
end
|
374
|
+
|
375
|
+
def report_not_before(tls_check_result)
|
376
|
+
report(
|
377
|
+
service: "#{tls_endpoint_name(tls_check_result)} not before",
|
378
|
+
state: not_before_state(tls_check_result),
|
379
|
+
metric: tls_check_result.not_before_away,
|
380
|
+
description: tls_check_result.not_before_away_in_words,
|
381
|
+
)
|
382
|
+
end
|
383
|
+
|
384
|
+
def report_identity(tls_check_result)
|
385
|
+
report(
|
386
|
+
service: "#{tls_endpoint_name(tls_check_result)} identity",
|
387
|
+
state: tls_check_result.valid_identity? ? 'ok' : 'critical',
|
388
|
+
description: "Valid for:\n#{tls_check_result.acceptable_identities.join("\n")}",
|
389
|
+
)
|
390
|
+
end
|
391
|
+
|
392
|
+
def report_trust(tls_check_result)
|
393
|
+
commont_attrs = {
|
394
|
+
service: "#{tls_endpoint_name(tls_check_result)} trust",
|
395
|
+
}
|
396
|
+
extra_attrs = if tls_check_result.exception
|
397
|
+
{
|
398
|
+
state: 'critical',
|
399
|
+
description: tls_check_result.exception.message,
|
400
|
+
}
|
401
|
+
else
|
402
|
+
{
|
403
|
+
state: tls_check_result.trusted? ? 'ok' : 'critical',
|
404
|
+
description: if OPENSSL_ERROR_STRINGS[tls_check_result.verify_result]
|
405
|
+
format('%<code>d - %<msg>s', code: tls_check_result.verify_result, msg: OPENSSL_ERROR_STRINGS[tls_check_result.verify_result])
|
406
|
+
else
|
407
|
+
tls_check_result.verify_result.to_s
|
408
|
+
end,
|
409
|
+
}
|
410
|
+
end
|
411
|
+
report(commont_attrs.merge(extra_attrs))
|
412
|
+
end
|
413
|
+
|
414
|
+
def report_ocsp(tls_check_result)
|
415
|
+
return unless tls_check_result.ocsp?
|
416
|
+
|
417
|
+
report(
|
418
|
+
service: "#{tls_endpoint_name(tls_check_result)} OCSP status",
|
419
|
+
state: tls_check_result.valid_ocsp? ? 'ok' : 'critical',
|
420
|
+
description: tls_check_result.ocsp_status,
|
421
|
+
)
|
422
|
+
end
|
423
|
+
|
424
|
+
# not_before not_after
|
425
|
+
# |<----------------------------->| validity_duration
|
426
|
+
# …ccccccccoooooooooooooooooooooooooooooooooooooo… not_before_state
|
427
|
+
#
|
428
|
+
# time --->>>>
|
429
|
+
def not_before_state(tls_check_result)
|
430
|
+
tls_check_result.not_valid_yet? ? 'critical' : 'ok'
|
431
|
+
end
|
432
|
+
|
433
|
+
# not_before not_after
|
434
|
+
# |<----------------------------->| validity_duration
|
435
|
+
# |<--------->| renewal_duration
|
436
|
+
# | ⅓ | ⅓ | ⅓ |
|
437
|
+
# …oooooooooooooooooooooooooooooooowwwwcccccccccc… not_after_state
|
438
|
+
#
|
439
|
+
# time --->>>>
|
440
|
+
def not_after_state(tls_check_result)
|
441
|
+
if tls_check_result.expired_or_expire_soon?
|
442
|
+
'critical'
|
443
|
+
elsif tls_check_result.expire_soonish?
|
444
|
+
'warning'
|
445
|
+
else
|
446
|
+
'ok'
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
def tcp_socket(host, port)
|
451
|
+
Socket.tcp(host, port, connect_timeout: opts[:connect_timeout])
|
452
|
+
end
|
453
|
+
|
454
|
+
def tls_socket(uri, address)
|
455
|
+
case uri.scheme
|
456
|
+
when 'smtp'
|
457
|
+
smtp_tls_socket(uri, address)
|
458
|
+
when 'imap'
|
459
|
+
imap_tls_socket(uri, address)
|
460
|
+
when 'ldap'
|
461
|
+
ldap_tls_socket(uri, address)
|
462
|
+
when 'mysql'
|
463
|
+
mysql_tls_socket(uri, address)
|
464
|
+
when 'postgres'
|
465
|
+
postgres_tls_socket(uri, address)
|
466
|
+
else
|
467
|
+
raw_tls_socket(uri, address)
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
def mysql_tls_socket(uri, address)
|
472
|
+
socket = tcp_socket(address, uri.port)
|
473
|
+
length = "#{socket.read(3)}\0".unpack1('L*')
|
474
|
+
_sequence = socket.read(1)
|
475
|
+
body = socket.read(length)
|
476
|
+
initial_handshake_packet = body.unpack('cZ*La8aScSS')
|
477
|
+
|
478
|
+
capabilities = initial_handshake_packet[5] | (initial_handshake_packet[8] << 16)
|
479
|
+
|
480
|
+
ssl_flag = 1 << 11
|
481
|
+
raise 'No TLS support' if capabilities.nobits?(ssl_flag)
|
482
|
+
|
483
|
+
socket.write(['2000000185ae7f0000000001210000000000000000000000000000000000000000000000'].pack('H*'))
|
484
|
+
tls_handshake(socket, uri.host)
|
485
|
+
end
|
486
|
+
|
487
|
+
def postgres_tls_socket(uri, address)
|
488
|
+
socket = tcp_socket(address, uri.port)
|
489
|
+
socket.write(['0000000804d2162f'].pack('H*'))
|
490
|
+
raise 'Unexpected reply' unless socket.read(1) == 'S'
|
491
|
+
|
492
|
+
tls_handshake(socket, uri.host)
|
493
|
+
end
|
494
|
+
|
495
|
+
def smtp_tls_socket(uri, address)
|
496
|
+
socket = tcp_socket(address, uri.port)
|
497
|
+
read_socket_lines_until_prefix_matched(socket, '220 ', also_accept_prefixes: ['220-'])
|
498
|
+
socket.send("EHLO #{my_hostname}\r\n", 0)
|
499
|
+
read_socket_lines_until_prefix_matched(socket, '250 ', also_accept_prefixes: ['250-'])
|
500
|
+
socket.send("STARTTLS\r\n", 0)
|
501
|
+
socket.gets
|
502
|
+
|
503
|
+
tls_handshake(socket, uri.host)
|
504
|
+
end
|
505
|
+
|
506
|
+
def my_hostname
|
507
|
+
Addrinfo.tcp(Socket.gethostname, 8023).getnameinfo.first
|
508
|
+
rescue SocketError
|
509
|
+
Socket.gethostname
|
510
|
+
end
|
511
|
+
|
512
|
+
def read_socket_lines_until_prefix_matched(socket, prefix, also_accept_prefixes: [])
|
513
|
+
loop do
|
514
|
+
line = socket.gets
|
515
|
+
break if line.start_with?(prefix)
|
516
|
+
next if also_accept_prefixes.map { |accepted_prefix| line.start_with?(accepted_prefix) }.any?
|
517
|
+
|
518
|
+
raise UnexpectedMessage, line
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
def imap_tls_socket(uri, address)
|
523
|
+
socket = tcp_socket(address, uri.port)
|
524
|
+
read_socket_lines_until_prefix_matched(socket, '* OK')
|
525
|
+
socket.send(". CAPABILITY\r\n", 0)
|
526
|
+
read_socket_lines_until_prefix_matched(socket, '. OK', also_accept_prefixes: ['* CAPABILITY'])
|
527
|
+
socket.send(". STARTTLS\r\n", 0)
|
528
|
+
read_socket_lines_until_prefix_matched(socket, '. OK')
|
529
|
+
|
530
|
+
tls_handshake(socket, uri.host)
|
531
|
+
end
|
532
|
+
|
533
|
+
def ldap_tls_socket(uri, address)
|
534
|
+
socket = tcp_socket(address, uri.port)
|
535
|
+
socket.write(['301d02010177188016312e332e362e312e342e312e313436362e3230303337'].pack('H*'))
|
536
|
+
expected_res = ['300c02010178070a010004000400'].pack('H*')
|
537
|
+
res = socket.read(expected_res.length)
|
538
|
+
|
539
|
+
return nil unless res == expected_res
|
540
|
+
|
541
|
+
tls_handshake(socket, uri.host)
|
542
|
+
end
|
543
|
+
|
544
|
+
def raw_tls_socket(uri, address)
|
545
|
+
raise "No default port for #{uri.scheme} scheme" unless uri.port
|
546
|
+
|
547
|
+
socket = tcp_socket(address, uri.port)
|
548
|
+
tls_handshake(socket, uri.host)
|
549
|
+
end
|
550
|
+
|
551
|
+
def tls_handshake(raw_socket, hostname)
|
552
|
+
tls_socket = OpenSSL::SSL::SSLSocket.new(raw_socket, ssl_context)
|
553
|
+
tls_socket.hostname = hostname
|
554
|
+
begin
|
555
|
+
tls_socket.connect
|
556
|
+
rescue OpenSSL::SSL::SSLError => e
|
557
|
+
# This may fail for example if a client certificate is required but
|
558
|
+
# not provided. In this case, the remote certificate is available and
|
559
|
+
# we can ignore this issue. In other cases, the remote certificate is
|
560
|
+
# not available, in this case we want to stop and report the issue
|
561
|
+
# (e.g. connecting to a host with a SNI for a name not handled by
|
562
|
+
# that host).
|
563
|
+
tls_socket.define_singleton_method(:exception) do
|
564
|
+
e
|
565
|
+
end
|
566
|
+
end
|
567
|
+
tls_socket
|
568
|
+
end
|
569
|
+
|
570
|
+
def ssl_context
|
571
|
+
@ssl_context ||= begin
|
572
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
573
|
+
ctx.cert_store = store
|
574
|
+
ctx.verify_hostname = false
|
575
|
+
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
576
|
+
ctx
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
580
|
+
def store
|
581
|
+
@store ||= begin
|
582
|
+
store = OpenSSL::X509::Store.new
|
583
|
+
store.set_default_paths
|
584
|
+
opts[:trust].each do |path|
|
585
|
+
if File.directory?(path)
|
586
|
+
store.add_path(path)
|
587
|
+
else
|
588
|
+
store.add_file(path)
|
589
|
+
end
|
590
|
+
end
|
591
|
+
store
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
def tls_endpoint_name(tls_check_result)
|
596
|
+
tls_endpoint_name2(tls_check_result.uri, tls_check_result.address)
|
597
|
+
end
|
598
|
+
|
599
|
+
def tls_endpoint_name2(uri, address)
|
600
|
+
"TLS certificate #{uri} #{endpoint_name(IPAddr.new(address), uri.port)}"
|
601
|
+
end
|
602
|
+
end
|
603
|
+
end
|
604
|
+
end
|
data/lib/riemann/tools/utils.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'socket'
|
4
|
+
|
3
5
|
module Riemann
|
4
6
|
module Tools
|
5
7
|
module Utils # :nodoc:
|
@@ -60,6 +62,43 @@ module Riemann
|
|
60
62
|
|
61
63
|
(header + lines[0, count]).join("\n")
|
62
64
|
end
|
65
|
+
|
66
|
+
def when_from_utcnow(date)
|
67
|
+
if date > utcnow
|
68
|
+
"in #{distance_of_time_in_words_to_utcnow(date)}"
|
69
|
+
else
|
70
|
+
"#{distance_of_time_in_words_to_utcnow(date)} ago"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Stolen from ActionView, to avoid pulling a lot of dependencies
|
75
|
+
def distance_of_time_in_words_to_utcnow(to_time)
|
76
|
+
distance_in_seconds = (to_time - utcnow).round.abs
|
77
|
+
distance_in_minutes = distance_in_seconds / 60
|
78
|
+
|
79
|
+
case distance_in_minutes
|
80
|
+
when 0 then 'less than 1 minute'
|
81
|
+
when 1...45 then pluralize_string('%d minute', distance_in_minutes)
|
82
|
+
when 45...1440 then pluralize_string('about %d hour', (distance_in_minutes.to_f / 60.0).round)
|
83
|
+
# 24 hours up to 30 days
|
84
|
+
when 1440...43_200 then pluralize_string('%d day', (distance_in_minutes.to_f / 1440.0).round)
|
85
|
+
# 30 days up to 60 days
|
86
|
+
when 43_200...86_400 then pluralize_string('about %d month', (distance_in_minutes.to_f / 43_200.0).round)
|
87
|
+
# 60 days up to 365 days
|
88
|
+
when 86_400...525_600 then pluralize_string('%d month', (distance_in_minutes.to_f / 43_200.0).round)
|
89
|
+
else
|
90
|
+
pluralize_string('about %d year', (distance_in_minutes.to_f / 525_600.0).round)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def pluralize_string(string, number)
|
95
|
+
format(string, number) + (number == 1 ? '' : 's')
|
96
|
+
end
|
97
|
+
|
98
|
+
# The current date and time, but in UTC
|
99
|
+
def utcnow
|
100
|
+
Time.at(Time.now, in: '+00:00')
|
101
|
+
end
|
63
102
|
end
|
64
103
|
end
|
65
104
|
end
|
@@ -12,6 +12,8 @@ module Riemann
|
|
12
12
|
opt :varnish_host, 'Varnish hostname', default: `hostname`.chomp
|
13
13
|
|
14
14
|
def initialize
|
15
|
+
super
|
16
|
+
|
15
17
|
cmd = 'varnishstat -V'
|
16
18
|
Open3.popen3(cmd) do |_stdin, _stdout, stderr, _wait_thr|
|
17
19
|
@ver = /varnishstat \(varnish-(\d+)/.match(stderr.read)[1].to_i
|