log-courier 1.10.0 → 2.7.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,4 @@
1
- # encoding: utf-8
2
-
3
- # Copyright 2014-2019 Jason Woods and Contributors.
1
+ # Copyright 2014-2021 Jason Woods and Contributors.
4
2
  #
5
3
  # This file is a modification of code from Logstash Forwarder.
6
4
  # Copyright 2012-2013 Jordan Sissel and contributors.
@@ -19,7 +17,6 @@
19
17
 
20
18
  require 'openssl'
21
19
  require 'socket'
22
- require 'thread'
23
20
 
24
21
  module LogCourier
25
22
  # Wrap around TCPServer to grab last error for use in reporting which peer had an error
@@ -42,12 +39,12 @@ module LogCourier
42
39
  peer = sock.peeraddr
43
40
  end
44
41
  @peer = "#{peer[2]}:#{peer[1]}"
45
- return sock
42
+ sock
46
43
  end
47
44
 
48
45
  def reset_peer
49
46
  @peer = 'unknown'
50
- return
47
+ nil
51
48
  end
52
49
  end
53
50
 
@@ -58,29 +55,31 @@ module LogCourier
58
55
  # Create a new TLS transport endpoint
59
56
  def initialize(options = {})
60
57
  @options = {
61
- logger: nil,
62
- transport: 'tls',
63
- port: 0,
64
- address: '0.0.0.0',
65
- ssl_certificate: nil,
66
- ssl_key: nil,
67
- ssl_key_passphrase: nil,
68
- ssl_verify: false,
58
+ logger: nil,
59
+ transport: 'tls',
60
+ port: 0,
61
+ address: '0.0.0.0',
62
+ ssl_certificate: nil,
63
+ ssl_key: nil,
64
+ ssl_key_passphrase: nil,
65
+ ssl_verify: false,
69
66
  ssl_verify_default_ca: false,
70
- ssl_verify_ca: nil,
71
- max_packet_size: 10_485_760,
72
- add_peer_fields: false,
67
+ ssl_verify_ca: nil,
68
+ max_packet_size: 10_485_760,
69
+ add_peer_fields: false,
70
+ min_tls_version: 1.2,
71
+ disable_handshake: false,
73
72
  }.merge!(options)
74
73
 
75
74
  @logger = @options[:logger]
76
75
 
77
76
  if @options[:transport] == 'tls'
78
77
  [:ssl_certificate, :ssl_key].each do |k|
79
- fail "input/courier: '#{k}' is required" if @options[k].nil?
78
+ raise "input/courier: '#{k}' is required" if @options[k].nil?
80
79
  end
81
80
 
82
- if @options[:ssl_verify] and (!@options[:ssl_verify_default_ca] && @options[:ssl_verify_ca].nil?)
83
- fail 'input/courier: Either \'ssl_verify_default_ca\' or \'ssl_verify_ca\' must be specified when ssl_verify is true'
81
+ if @options[:ssl_verify] && (!@options[:ssl_verify_default_ca] && @options[:ssl_verify_ca].nil?)
82
+ raise 'input/courier: Either \'ssl_verify_default_ca\' or \'ssl_verify_ca\' must be specified when ssl_verify is true'
84
83
  end
85
84
  end
86
85
 
@@ -99,8 +98,15 @@ module LogCourier
99
98
  ssl.set_params
100
99
  # Modify the default options to ensure SSLv2 and SSLv3 is disabled
101
100
  # This retains any beneficial options set by default in the current Ruby implementation
102
- ssl.options |= OpenSSL::SSL::OP_NO_SSLv2 if defined?(OpenSSL::SSL::OP_NO_SSLv2)
103
- ssl.options |= OpenSSL::SSL::OP_NO_SSLv3 if defined?(OpenSSL::SSL::OP_NO_SSLv3)
101
+ # TODO: https://github.com/jruby/jruby-openssl/pull/215 is fixed in JRuby 9.3.0.0
102
+ # As of 7.15 Logstash, JRuby version is still 9.2
103
+ # Once 9.3 is in use we can switch to using min_version and max_version
104
+ ssl.options |= OpenSSL::SSL::OP_NO_SSLv2
105
+ ssl.options |= OpenSSL::SSL::OP_NO_SSLv3
106
+ ssl.options |= OpenSSL::SSL::OP_NO_TLSv1 if @options[:min_tls_version] > 1
107
+ ssl.options |= OpenSSL::SSL::OP_NO_TLSv1_1 if @options[:min_tls_version] > 1.1
108
+ ssl.options |= OpenSSL::SSL::OP_NO_TLSv1_2 if @options[:min_tls_version] > 1.2
109
+ raise 'Invalid min_tls_version - max is 1.3' if @options[:min_tls_version] > 1.3
104
110
 
105
111
  # Set the certificate file
106
112
  ssl.cert = OpenSSL::X509::Certificate.new(File.read(@options[:ssl_certificate]))
@@ -130,13 +136,11 @@ module LogCourier
130
136
  @server = @tcp_server
131
137
  end
132
138
 
133
- if @options[:port] == 0
134
- @logger.warn 'Ephemeral port allocated', :transport => @options[:transport], :port => @port unless @logger.nil?
135
- end
136
- rescue => e
139
+ @logger&.warn 'Ephemeral port allocated', transport: @options[:transport], port: @port if @options[:port].zero?
140
+ rescue StandardError => e
137
141
  raise "input/courier: Failed to initialise: #{e}"
138
142
  end
139
- end # def initialize
143
+ end
140
144
 
141
145
  def run(&block)
142
146
  client_threads = {}
@@ -148,14 +152,18 @@ module LogCourier
148
152
  client = nil
149
153
  begin
150
154
  client = @server.accept
151
- rescue EOFError, OpenSSL::SSL::SSLError, IOError => e
155
+ rescue OpenSSL::SSL::SSLError, IOError => e
152
156
  # Accept failure or other issue
153
- @logger.warn 'Connection failed to accept', :error => e.message, :peer => @tcp_server.peer unless @logger.nil?
154
- client.close rescue nil unless client.nil?
157
+ @logger&.warn 'Connection failed to accept', error: e.message, peer: @tcp_server.peer
158
+ begin
159
+ client&.close
160
+ rescue OpenSSL::SSL::SSLError, IOError
161
+ # Ignore IO error during close
162
+ end
155
163
  next
156
164
  end
157
165
 
158
- @logger.info 'New connection', :peer => @tcp_server.peer unless @logger.nil?
166
+ @logger&.info 'New connection', peer: @tcp_server.peer
159
167
 
160
168
  # Clear up finished threads
161
169
  client_threads.delete_if do |_, thr|
@@ -167,13 +175,13 @@ module LogCourier
167
175
  run_thread client_copy, peer_copy, &block
168
176
  end
169
177
  end
170
- return
178
+ nil
171
179
  rescue ShutdownSignal
172
- return
173
- rescue StandardError, NativeException => e
180
+ nil
181
+ rescue StandardError => e
174
182
  # Some other unknown problem
175
- @logger.warn e.message, :hint => 'Unknown error, shutting down' unless @logger.nil?
176
- return
183
+ @logger&.warn e.message, hint: 'Unknown error, shutting down'
184
+ nil
177
185
  ensure
178
186
  # Raise shutdown in all client threads and join then
179
187
  client_threads.each do |_, thr|
@@ -188,25 +196,28 @@ module LogCourier
188
196
  private
189
197
 
190
198
  def run_thread(client, peer, &block)
191
- begin
192
- # Perform the handshake inside the new thread so we don't block TCP accept
193
- if @options[:transport] == 'tls'
199
+ # Perform the handshake inside the new thread so we don't block TCP accept
200
+ if @options[:transport] == 'tls'
201
+ begin
202
+ client.accept
203
+ rescue OpenSSL::SSL::SSLError, IOError => e
204
+ # Handshake failure or other issue
205
+ @logger&.warn 'Connection failed to initialise', error: e.message, peer: peer
194
206
  begin
195
- client.accept
196
- rescue EOFError, OpenSSL::SSL::SSLError, IOError => e
197
- # Handshake failure or other issue
198
- @logger.warn 'Connection failed to initialise', :error => e.message, :peer => peer unless @logger.nil?
199
207
  client.close
200
- return
208
+ rescue OpenSSL::SSL::SSLError, IOError
209
+ # Ignore during close
201
210
  end
211
+ return
202
212
  end
203
213
 
204
- ConnectionTcp.new(@logger, client, peer, @options).run(&block)
205
- rescue ShutdownSignal
206
- # Shutting down
207
- @logger.info 'Server shutting down, connection closed', :peer => peer unless @logger.nil?
208
- return
214
+ @logger&.info 'Connection setup successfully', peer: peer, ssl_version: client.ssl_version
209
215
  end
216
+
217
+ ConnectionTcp.new(@logger, client, peer, @options).run(&block)
218
+ rescue ShutdownSignal
219
+ # Shutting down
220
+ @logger&.info 'Server shutting down, connection closed', peer: peer
210
221
  end
211
222
  end
212
223
 
@@ -214,93 +225,105 @@ module LogCourier
214
225
  class ConnectionTcp
215
226
  attr_accessor :peer
216
227
 
217
- def initialize(logger, fd, peer, options)
228
+ def initialize(logger, sfd, peer, options)
218
229
  @logger = logger
219
- @fd = fd
230
+ @fd = sfd
220
231
  @peer = peer
221
232
  @peer_fields = {}
222
233
  @in_progress = false
223
234
  @options = options
235
+ @client = 'Unknown'
236
+ @major_version = 0
237
+ @minor_version = 0
238
+ @patch_version = 0
239
+ @version = '0.0.0'
240
+ @client_version = 'Unknown'
224
241
 
225
- if @options[:add_peer_fields]
226
- @peer_fields['peer'] = peer
227
- if @options[:transport] == 'tls' && !@fd.peer_cert.nil?
228
- @peer_fields['peer_ssl_cn'] = get_cn(@fd.peer_cert)
229
- end
230
- end
242
+ return unless @options[:add_peer_fields]
243
+
244
+ @peer_fields['peer'] = peer
245
+ return unless @options[:transport] == 'tls' && !@fd.peer_cert.nil?
246
+
247
+ @peer_fields['peer_ssl_cn'] = get_cn(@fd.peer_cert)
231
248
  end
232
249
 
233
250
  def add_fields(event)
234
- event.merge! @peer_fields if @peer_fields.length != 0
251
+ event.merge! @peer_fields unless @peer_fields.empty?
235
252
  end
236
253
 
237
254
  def run(&block)
238
- process_messages &block
239
- rescue ShutdownSignal
240
- # Shutting down
241
- @logger.info 'Server shutting down, closing connection', :peer => @peer unless @logger.nil?
242
- return
243
- rescue StandardError, NativeException => e
244
- # Some other unknown problem
245
- @logger.warn e.message, :hint => 'Unknown error, connection aborted', :peer => @peer unless @logger.nil?
246
- return
247
- end
255
+ handshake(&block)
248
256
 
249
- def process_messages
250
257
  loop do
251
- # Read messages
252
- # Each message begins with a header
253
- # 4 byte signature
254
- # 4 byte length
255
- # Normally we would not parse this inside transport, but for TLS we have to in order to locate frame boundaries
256
- signature, length = recv(8).unpack('A4N')
257
-
258
- # Sanity
259
- if length > @options[:max_packet_size]
260
- fail ProtocolError, "packet too large (#{length} > #{@options[:max_packet_size]})"
261
- end
262
-
263
- # While we're processing, EOF is bad as it may occur during send
264
- @in_progress = true
265
-
266
- # Read the message
267
- if length == 0
268
- data = ''
269
- else
270
- data = recv(length)
271
- end
258
+ signature, data = receive
272
259
 
273
260
  # Send for processing
274
261
  yield signature, data, self
275
-
276
- # If we EOF next it's a graceful close
277
- @in_progress = false
278
262
  end
279
263
  rescue TimeoutError
280
264
  # Timeout of the connection, we were idle too long without a ping/pong
281
- @logger.warn 'Connection timed out', :peer => @peer unless @logger.nil?
282
- return
265
+ @logger&.warn 'Connection timed out', peer: @peer
266
+ nil
283
267
  rescue EOFError
284
268
  if @in_progress
285
- @logger.warn 'Unexpected EOF', :peer => @peer unless @logger.nil?
269
+ @logger&.warn 'Unexpected EOF', peer: @peer
286
270
  else
287
- @logger.info 'Connection closed', :peer => @peer unless @logger.nil?
271
+ @logger&.info 'Connection closed', peer: @peer
288
272
  end
289
- return
273
+ nil
290
274
  rescue OpenSSL::SSL::SSLError => e
291
275
  # Read errors, only action is to shutdown which we'll do in ensure
292
- @logger.warn 'SSL error, connection aborted', :error => e.message, :peer => @peer unless @logger.nil?
293
- return
294
- rescue IOError, Errno::ECONNRESET => e
276
+ @logger&.warn 'SSL error, connection aborted', error: e.message, peer: @peer
277
+ nil
278
+ rescue IOError, SystemCallError => e
295
279
  # Read errors, only action is to shutdown which we'll do in ensure
296
- @logger.warn 'Connection aborted', :error => e.message, :peer => @peer unless @logger.nil?
297
- return
280
+ @logger&.warn 'Connection aborted', error: e.message, peer: @peer
281
+ nil
298
282
  rescue ProtocolError => e
299
283
  # Connection abort request due to a protocol error
300
- @logger.warn 'Protocol error, connection aborted', :error => e.message, :peer => @peer unless @logger.nil?
301
- return
284
+ @logger&.warn 'Protocol error, connection aborted', error: e.message, peer: @peer
285
+ nil
286
+ rescue ShutdownSignal
287
+ # Shutting down
288
+ @logger&.info 'Server shutting down, closing connection', peer: @peer
289
+ nil
290
+ rescue StandardError => e
291
+ # Some other unknown problem
292
+ @logger&.warn e.message, hint: 'Unknown error, connection aborted', peer: @peer
293
+ nil
302
294
  ensure
303
- @fd.close rescue nil
295
+ begin
296
+ @fd.close
297
+ rescue OpenSSL::SSL::SSLError, IOError
298
+ # Ignore during close
299
+ end
300
+ end
301
+
302
+ def receive
303
+ # Read message
304
+ # Each message begins with a header
305
+ # 4 byte signature
306
+ # 4 byte length
307
+ # Normally we would not parse this inside transport, but for TLS we have to in order to locate frame boundaries
308
+ signature, length = recv(8).unpack('A4N')
309
+
310
+ # Sanity
311
+ raise ProtocolError, "packet too large (#{length} > #{@options[:max_packet_size]})" if length > @options[:max_packet_size]
312
+
313
+ # While we're processing, EOF is bad as it may occur during send
314
+ @in_progress = true
315
+
316
+ # Read the message
317
+ data = if length.zero?
318
+ ''
319
+ else
320
+ recv(length)
321
+ end
322
+
323
+ # If we EOF next it's a graceful close
324
+ @in_progress = false
325
+
326
+ [signature, data]
304
327
  end
305
328
 
306
329
  def send(signature, message)
@@ -311,24 +334,55 @@ module LogCourier
311
334
  begin
312
335
  written = @fd.write_nonblock(data[done...data.length])
313
336
  rescue IO::WaitReadable
314
- fail TimeoutError if IO.select([@fd], nil, [@fd], @timeout - Time.now.to_i).nil?
337
+ raise TimeoutError if IO.select([@fd], nil, [@fd], @timeout - Time.now.to_i).nil?
338
+
315
339
  retry
316
340
  rescue IO::WaitWritable
317
- fail TimeoutError if IO.select(nil, [@fd], [@fd], @timeout - Time.now.to_i).nil?
341
+ raise TimeoutError if IO.select(nil, [@fd], [@fd], @timeout - Time.now.to_i).nil?
342
+
318
343
  retry
319
344
  end
320
- fail ProtocolError, "write failure (#{done}/#{data.length})" if written == 0
345
+ raise ProtocolError, "write failure (#{done}/#{data.length})" if written.zero?
346
+
321
347
  done += written
322
348
  break if done >= data.length
323
349
  end
324
- return
350
+ nil
325
351
  end
326
352
 
327
353
  private
328
354
 
355
+ def handshake
356
+ return if @options[:disable_handshake]
357
+
358
+ signature, data = receive
359
+ if signature == 'JDAT'
360
+ @helo = Protocol.parse_helo_vers('')
361
+ @logger&.info 'Remote does not support protocol handshake', peer: @peer
362
+ yield signature, data, self
363
+ return
364
+ elsif signature != 'HELO'
365
+ raise ProtocolError, "unexpected #{signature} message"
366
+ end
367
+
368
+ @helo = Protocol.parse_helo_vers(data)
369
+ @logger&.info 'Remote identified', peer: @peer, client_version: @helo[:client_version]
370
+
371
+ # Flags 4 bytes - EVNT flag = 0
372
+ # (Significant rewrite would be required to support streaming messages as currently we read
373
+ # first and then yield for processing. To support EVNT we have to move protocol parsing to
374
+ # the connection layer here so we can keep reading until we reach the end of the stream)
375
+ # Major Version 4 bytes
376
+ # Minor Version 4 bytes
377
+ # Patch Version 4 bytes
378
+ # Client String 4 bytes
379
+ data = [0, MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION, 'RYLC'].pack('NNNNA4')
380
+ send 'VERS', data
381
+ end
382
+
329
383
  def get_cn(cert)
330
384
  cert.subject.to_a.find do |oid, value|
331
- return value if oid == "CN"
385
+ return value if oid == 'CN'
332
386
  end
333
387
  nil
334
388
  end
@@ -338,20 +392,20 @@ module LogCourier
338
392
  have = ''
339
393
  loop do
340
394
  begin
341
- buffer = @fd.read_nonblock need - have.length
395
+ buffer = @fd.read_nonblock need - have.length
342
396
  rescue IO::WaitReadable
343
- fail TimeoutError if IO.select([@fd], nil, [@fd], @timeout - Time.now.to_i).nil?
397
+ raise TimeoutError if IO.select([@fd], nil, [@fd], @timeout - Time.now.to_i).nil?
398
+
344
399
  retry
345
400
  rescue IO::WaitWritable
346
- fail TimeoutError if IO.select(nil, [@fd], [@fd], @timeout - Time.now.to_i).nil?
401
+ raise TimeoutError if IO.select(nil, [@fd], [@fd], @timeout - Time.now.to_i).nil?
402
+
347
403
  retry
348
404
  end
349
- if buffer.nil?
350
- fail EOFError
351
- elsif buffer.length == 0
352
- fail ProtocolError, "read failure (#{have.length}/#{need})"
353
- end
354
- if have.length == 0
405
+ raise EOFError if buffer.nil?
406
+ raise ProtocolError, "read failure (#{have.length}/#{need})" if buffer.length.zero?
407
+
408
+ if have.length.zero?
355
409
  have = buffer
356
410
  else
357
411
  have << buffer
@@ -364,7 +418,7 @@ module LogCourier
364
418
  def reset_timeout
365
419
  # TODO: Make configurable
366
420
  @timeout = Time.now.to_i + 1_800
367
- return
421
+ nil
368
422
  end
369
423
  end
370
424
  end
metadata CHANGED
@@ -1,22 +1,22 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: log-courier
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.10.0
4
+ version: 2.7.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Woods
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-21 00:00:00.000000000 Z
11
+ date: 2021-10-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: cabin
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
16
  - - "~>"
18
17
  - !ruby/object:Gem::Version
19
18
  version: '0.6'
19
+ name: cabin
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
@@ -25,26 +25,12 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0.6'
27
27
  - !ruby/object:Gem::Dependency
28
- name: ffi-rzmq
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '2.0'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '2.0'
41
- - !ruby/object:Gem::Dependency
42
- name: multi_json
43
28
  requirement: !ruby/object:Gem::Requirement
44
29
  requirements:
45
30
  - - "~>"
46
31
  - !ruby/object:Gem::Version
47
32
  version: '1.10'
33
+ name: multi_json
48
34
  type: :runtime
49
35
  prerelease: false
50
36
  version_requirements: !ruby/object:Gem::Requirement
@@ -62,15 +48,15 @@ files:
62
48
  - lib/log-courier/client.rb
63
49
  - lib/log-courier/client_tcp.rb
64
50
  - lib/log-courier/event_queue.rb
51
+ - lib/log-courier/protocol.rb
52
+ - lib/log-courier/rspec/spec_helper.rb
65
53
  - lib/log-courier/server.rb
66
54
  - lib/log-courier/server_tcp.rb
67
- - lib/log-courier/server_zmq.rb
68
- - lib/log-courier/zmq_qpoll.rb
69
- homepage: https://github.com/driskell/ruby-log-courier
55
+ homepage: https://github.com/driskell/log-courier
70
56
  licenses:
71
- - Apache
57
+ - Apache-2.0
72
58
  metadata: {}
73
- post_install_message:
59
+ post_install_message:
74
60
  rdoc_options: []
75
61
  require_paths:
76
62
  - lib
@@ -85,9 +71,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
85
71
  - !ruby/object:Gem::Version
86
72
  version: '0'
87
73
  requirements: []
88
- rubyforge_project: nowarning
89
- rubygems_version: 2.7.7
90
- signing_key:
74
+ rubygems_version: 3.0.6
75
+ signing_key:
91
76
  specification_version: 4
92
77
  summary: Ruby implementation of the Courier protocol
93
78
  test_files: []