riemann-tools 1.10.0 → 1.12.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 +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
|