log-courier 1.10.0 → 2.7.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.
@@ -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, NativeException => e # Until Jruby updated we need NativeException
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, NativeException => e # Can remove NativeException after 9.2.14.0 JRuby
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
276
+ @logger&.warn 'SSL error, connection aborted', error: e.message, peer: @peer
277
+ nil
294
278
  rescue IOError, Errno::ECONNRESET => 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, NativeException => e # Can remove NativeException after 9.2.14.0 JRuby
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, NativeException
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 1 byte - 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 1 byte
376
+ # Minor Version 1 byte
377
+ # Patch Version 1 byte
378
+ # Client String 4 bytes
379
+ data = [1, 2, 7, 0, 'RYLC'].pack('CCCCA4')
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.0
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-20 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
@@ -64,13 +50,11 @@ files:
64
50
  - lib/log-courier/event_queue.rb
65
51
  - lib/log-courier/server.rb
66
52
  - lib/log-courier/server_tcp.rb
67
- - lib/log-courier/server_zmq.rb
68
- - lib/log-courier/zmq_qpoll.rb
69
53
  homepage: https://github.com/driskell/ruby-log-courier
70
54
  licenses:
71
- - Apache
55
+ - Apache-2.0
72
56
  metadata: {}
73
- post_install_message:
57
+ post_install_message:
74
58
  rdoc_options: []
75
59
  require_paths:
76
60
  - lib
@@ -85,9 +69,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
85
69
  - !ruby/object:Gem::Version
86
70
  version: '0'
87
71
  requirements: []
88
- rubyforge_project: nowarning
89
- rubygems_version: 2.7.7
90
- signing_key:
72
+ rubygems_version: 3.0.6
73
+ signing_key:
91
74
  specification_version: 4
92
75
  summary: Ruby implementation of the Courier protocol
93
76
  test_files: []