logstash-output-tcp 6.0.1 → 6.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1412e1fa9376c990f9983c62c84d5b40c7169df77ab387c4637851fdaf43deaf
4
- data.tar.gz: b7827ef1dc46b3bbeef55ad259250f868460685f7a2b16a0842e3309eda4f844
3
+ metadata.gz: 5db164894be7c046e3dce9337af0ce5b684f19b1b35274a0d3b925e97fe402c8
4
+ data.tar.gz: 63dbb52d49285af564f83de8218c3af098b1d0f62d9deee019c15eae5cbd98e2
5
5
  SHA512:
6
- metadata.gz: d34636d36f87a3c5d632263f603bf6816f88d3033d0394aba9f84438819b008bfc6cd66cf16baf2a1a2cff55e72e5f52b382c00422f2fcaaf5f5b883f7404ee8
7
- data.tar.gz: 50f4c2ef7feba191fd0d6cc815353ec1cafb09a7acfa74a328d9a3594c74bff96b8a61f61df11b1b44022fd6e3ce32f975d54c532cc70e6e1594dd8534a531b3
6
+ metadata.gz: 10b22e722c22edce1fd3699e876ab898283d66ec0aa256f9d9c6159112bcf54f872b4e68d8bc7eb72b999c54442d5b65c69fa87194f6c6a58623f7c1939c9c09
7
+ data.tar.gz: eb6477d5f227184f46bac1c01453215fbd2f2ebf3c0fbb819413d5555d2ab7472ab61b11d737016487b3d1255bcc65fce008f350683ad28e9468fbb1f55ded49
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 6.0.3
2
+ - Pulled applicable back-ports from 6.1.0 [#50](https://github.com/logstash-plugins/logstash-output-tcp/pull/50)
3
+ - Fix: Ensure sockets are closed when this plugin is closed
4
+ - Fix: Fixes an issue in client mode where payloads larger than a connection's current TCP window could be silently truncated
5
+ - Fix: Fixes an issue in server mode where payloads larger than a connection's current TCP window could be silently truncated
6
+
7
+ ## 6.0.2
8
+ - Fix: unable to start with password protected key [#45](https://github.com/logstash-plugins/logstash-output-tcp/pull/45)
9
+
1
10
  ## 6.0.1
2
11
  - Fixed logging fail retry to stdout [#43](https://github.com/logstash-plugins/logstash-output-tcp/pull/43)
3
12
  - Fixed to use `reconnect_interval` when establish a connection
@@ -51,31 +51,44 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
51
51
  # SSL key passphrase
52
52
  config :ssl_key_passphrase, :validate => :password, :default => nil
53
53
 
54
+ ##
55
+ # @param socket [Socket]
56
+ # @param logger_context [#log_warn&#log_error&#logger]
54
57
  class Client
55
- public
56
- def initialize(socket, logger)
58
+ def initialize(socket, logger_context)
57
59
  @socket = socket
58
- @logger = logger
60
+ @peer_info = socket.peer
61
+ @logger_context = logger_context
59
62
  @queue = Queue.new
60
63
  end
61
64
 
62
- public
63
65
  def run
64
66
  loop do
65
67
  begin
66
- @socket.write(@queue.pop)
68
+ payload = @queue.pop
69
+
70
+ @logger_context.logger.trace("transmitting #{payload.bytesize} bytes", socket: @peer_info) if @logger_context.logger.trace? && payload && !payload.empty?
71
+ while payload && !payload.empty?
72
+ written_bytes_size = @socket.write(payload)
73
+ payload = payload.byteslice(written_bytes_size..-1)
74
+ @logger_context.logger.log_trace(">transmitted #{written_bytes_size} bytes; #{payload.bytesize} bytes remain", socket: @peer_info) if @logger_context.logger.trace?
75
+ end
67
76
  rescue => e
68
- @logger.warn("tcp output exception", :socket => @socket,
69
- :exception => e)
77
+ @logger_context.log_warn("tcp output exception: socket write failed", e, :socket => @peer_info)
70
78
  break
71
79
  end
72
80
  end
73
81
  end # def run
74
82
 
75
- public
76
83
  def write(msg)
77
84
  @queue.push(msg)
78
85
  end # def write
86
+
87
+ def close
88
+ @socket.close
89
+ rescue => e
90
+ @logger_context.log_warn 'socket close failed:', e, socket: @socket&.to_s
91
+ end
79
92
  end # class Client
80
93
 
81
94
  private
@@ -86,7 +99,10 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
86
99
  if @ssl_cert
87
100
  @ssl_context.cert = OpenSSL::X509::Certificate.new(File.read(@ssl_cert))
88
101
  if @ssl_key
89
- @ssl_context.key = OpenSSL::PKey::RSA.new(File.read(@ssl_key),@ssl_key_passphrase)
102
+ # if we have an encrypted key and a password is not provided (nil) than OpenSSL::PKey::RSA
103
+ # prompts the user to enter a password interactively - we do not want to do that,
104
+ # for plain-text keys the default '' password argument gets simply ignored
105
+ @ssl_context.key = OpenSSL::PKey::RSA.new(File.read(@ssl_key), @ssl_key_passphrase.value || '')
90
106
  end
91
107
  end
92
108
  if @ssl_verify
@@ -110,6 +126,8 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
110
126
  if @ssl_enable
111
127
  setup_ssl
112
128
  end # @ssl_enable
129
+ @closed = Concurrent::AtomicBoolean.new(false)
130
+ @thread_no = Concurrent::AtomicFixnum.new(0)
113
131
 
114
132
  if server?
115
133
  @logger.info("Starting tcp output listener", :address => "#{@host}:#{@port}")
@@ -126,44 +144,61 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
126
144
  @client_threads = []
127
145
 
128
146
  @accept_thread = Thread.new(@server_socket) do |server_socket|
147
+ LogStash::Util.set_thread_name("[#{pipeline_id}]|output|tcp|server_accept")
129
148
  loop do
149
+ break if @closed.value
130
150
  Thread.start(server_socket.accept) do |client_socket|
131
151
  # monkeypatch a 'peer' method onto the socket.
132
- client_socket.instance_eval { class << self; include ::LogStash::Util::SocketPeer end }
152
+ client_socket.extend(::LogStash::Util::SocketPeer)
133
153
  @logger.debug("Accepted connection", :client => client_socket.peer,
134
154
  :server => "#{@host}:#{@port}")
135
- client = Client.new(client_socket, @logger)
155
+ client = Client.new(client_socket, self)
136
156
  Thread.current[:client] = client
157
+ LogStash::Util.set_thread_name("[#{pipeline_id}]|output|tcp|client_socket-#{@thread_no.increment}")
137
158
  @client_threads << Thread.current
138
- client.run
159
+ client.run unless @closed.value
139
160
  end
140
161
  end
141
162
  end
142
163
 
143
164
  @codec.on_event do |event, payload|
165
+ @client_threads.select!(&:alive?)
144
166
  @client_threads.each do |client_thread|
145
167
  client_thread[:client].write(payload)
146
168
  end
147
- @client_threads.reject! {|t| !t.alive? }
148
169
  end
149
170
  else
150
- client_socket = nil
171
+ @client_socket = nil
172
+ peer_info = nil
151
173
  @codec.on_event do |event, payload|
152
174
  begin
153
- client_socket = connect unless client_socket
154
- r,w,e = IO.select([client_socket], [client_socket], [client_socket], nil)
155
- # don't expect any reads, but a readable socket might
156
- # mean the remote end closed, so read it and throw it away.
157
- # we'll get an EOFError if it happens.
158
- client_socket.sysread(16384) if r.any?
175
+ # not threadsafe; this is why we require `concurrency: single`
176
+ unless @client_socket
177
+ @client_socket = connect
178
+ peer_info = @client_socket.peer
179
+ end
180
+
181
+ writable_io = nil
182
+ while writable_io.nil? || writable_io.any? == false
183
+ readable_io, writable_io, _ = IO.select([@client_socket],[@client_socket])
184
+
185
+ # don't expect any reads, but a readable socket might
186
+ # mean the remote end closed, so read it and throw it away.
187
+ # we'll get an EOFError if it happens.
188
+ readable_io.each { |readable| readable.sysread(16384) }
189
+ end
159
190
 
160
191
  # Now send the payload
161
- client_socket.syswrite(payload) if w.any?
192
+ @logger.trace("transmitting #{payload.bytesize} bytes", socket: peer_info) if @logger.trace? && payload && !payload.empty?
193
+ while payload && payload.bytesize > 0
194
+ written_bytes_size = @client_socket.syswrite(payload)
195
+ payload = payload.byteslice(written_bytes_size..-1)
196
+ @logger.trace(">transmitted #{written_bytes_size} bytes; #{payload.bytesize} bytes remain", socket: peer_info) if @logger.trace?
197
+ end
162
198
  rescue => e
163
- @logger.warn("tcp output exception", :host => @host, :port => @port,
164
- :exception => e, :backtrace => e.backtrace)
165
- client_socket.close rescue nil
166
- client_socket = nil
199
+ log_warn "client socket failed:", e, host: @host, port: @port, socket: peer_info
200
+ @client_socket.close rescue nil
201
+ @client_socket = nil
167
202
  sleep @reconnect_interval
168
203
  retry
169
204
  end
@@ -171,6 +206,23 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
171
206
  end
172
207
  end # def register
173
208
 
209
+ # @overload Base#close
210
+ def close
211
+ if server?
212
+ # server-mode clean-up
213
+ @closed.make_true
214
+ @server_socket.shutdown rescue nil if @server_socket
215
+
216
+ @client_threads&.each do |thread|
217
+ client = thread[:client]
218
+ client.close rescue nil if client
219
+ end
220
+ else
221
+ # client-mode clean-up
222
+ @client_socket&.close
223
+ end
224
+ end
225
+
174
226
  private
175
227
  def connect
176
228
  begin
@@ -180,17 +232,17 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
180
232
  begin
181
233
  client_socket.connect
182
234
  rescue OpenSSL::SSL::SSLError => ssle
183
- @logger.error("SSL Error", :exception => ssle, :backtrace => ssle.backtrace)
235
+ log_error 'connect ssl failure:', ssle, backtrace: false
184
236
  # NOTE(mrichar1): Hack to prevent hammering peer
185
237
  sleep(5)
186
238
  raise
187
239
  end
188
240
  end
189
- client_socket.instance_eval { class << self; include ::LogStash::Util::SocketPeer end }
241
+ client_socket.extend(::LogStash::Util::SocketPeer)
190
242
  @logger.debug("Opened connection", :client => "#{client_socket.peer}")
191
243
  return client_socket
192
244
  rescue StandardError => e
193
- @logger.error("Failed to connect: #{e.message}", :exception => e.class, :backtrace => e.backtrace)
245
+ log_error 'failed to connect:', e
194
246
  sleep @reconnect_interval
195
247
  retry
196
248
  end
@@ -205,4 +257,20 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
205
257
  def receive(event)
206
258
  @codec.encode(event)
207
259
  end # def receive
260
+
261
+ def pipeline_id
262
+ execution_context.pipeline_id || 'main'
263
+ end
264
+
265
+ def log_warn(msg, e, backtrace: @logger.debug?, **details)
266
+ details = details.merge message: e.message, exception: e.class
267
+ details[:backtrace] = e.backtrace if backtrace
268
+ @logger.warn(msg, details)
269
+ end
270
+
271
+ def log_error(msg, e, backtrace: @logger.info?, **details)
272
+ details = details.merge message: e.message, exception: e.class
273
+ details[:backtrace] = e.backtrace if backtrace
274
+ @logger.error(msg, details)
275
+ end
208
276
  end # class LogStash::Outputs::Tcp
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
 
3
3
  s.name = 'logstash-output-tcp'
4
- s.version = '6.0.1'
4
+ s.version = '6.0.3'
5
5
  s.licenses = ['Apache License (2.0)']
6
6
  s.summary = "Writes events over a TCP socket"
7
7
  s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
@@ -0,0 +1,19 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDIjCCAgqgAwIBAgIUTCjyocBgCYSWU/GYI58F0IBXLHswDQYJKoZIhvcNAQEL
3
+ BQAwNDEyMDAGA1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5l
4
+ cmF0ZWQgQ0EwHhcNMjIwMzIxMDc1NTU2WhcNMjUwMzIwMDc1NTU2WjATMREwDwYD
5
+ VQQDEwhpbnN0YW5jZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJKT
6
+ f0FF/R9WBC7lwN0IJdOn+jPJJ+4M3LY/sfFuTaAUgPSaiRLLzCjox1p1Xy9z+xyk
7
+ C2Dkhb4FdOZBNqS/w0Z7lkrha8VZg2UKW4+i1lq4ANPl9sP9YCbh1R+SRdpHnZX7
8
+ APG798+0+NC4GmEeOIFfWsEE4MiFXAPunhAlAbxMl6e5htX4cgYWMbH67Ur1/uQB
9
+ 26Y6PBahKP7dbxaL/ugPE6MDmmA+cNRWMa21Ay2h94IesRXwNRFn1gQ1SqKU0hBJ
10
+ KX7u0IoYyS6nbDlF6E+npLLWzDMCHsykrnadWV5kDSNv1t7Wvk0noNStrryOXgtc
11
+ 7iCCOX1oqsP/yDNl9OECAwEAAaNNMEswHQYDVR0OBBYEFBtzuCJPEhHW3ZD1bReq
12
+ S6K3P09OMB8GA1UdIwQYMBaAFJcC1Mw29qzRV0dPCCc6GyB8LXx3MAkGA1UdEwQC
13
+ MAAwDQYJKoZIhvcNAQELBQADggEBAHvXsfMgY+PTBCnW3A6QCGZ3jTSRqqXRFYB8
14
+ b/unfhD4iUFlgO4a/UCL2MTsFYXkzvlzwQyH6ll9/rTslBvJLiqi3+IZMfGNoaNI
15
+ LhTLy0NF9scPju7Q6MxcPAceI4gmxKyBWJxm4XYJ1VwmPKJNzKqFEwfPWMakxUaT
16
+ wiuhVodqZweWi7S2keYiduBLEbqWBJPKAnb8e3PIILBqL0nfhmVw0NSAD19d9oLR
17
+ 89AaY8zQ4YL4Txs4FSrK0VQl3L6NAtAaq9JvXqzXD/E0EO1YL8ykM3Zash/GG9iN
18
+ B9Momegqn8/q7pfoWdkE2oL9KUKispUT9Fq3mY6KHZL1T4SbxL8=
19
+ -----END CERTIFICATE-----
@@ -0,0 +1,30 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ Proc-Type: 4,ENCRYPTED
3
+ DEK-Info: AES-128-CBC,635374A0A566A0189BE4366E77881457
4
+
5
+ LbCmLGC8BAWKYUt+fYIIxUC5rh3D7CFN8LXjjGIYMVRs2seMqhCNJjtdLQ6poin5
6
+ H6W/c5+VXzzsgczFgZnQecoqWgqg4FoTZSacVVx/waekkdua9zjPReastV+7AcBG
7
+ MJJi8lYEIv+qacqxEzDJSis7XO0YXp2OBqUtfcaT5PunJ1bj5SQnqf7k1hb193bZ
8
+ C03MhZ6HdblY6IKvslqV5VWU38gPN0F6JN5/7vLgZyExYjTA1wkzdJXCzKAEwlfU
9
+ vF5/AKDGFpQAYMTnPwszpeUj+wyuAwaxt8uNkVFrfVIf+eaPqX1OStEAIiRNZerl
10
+ dTsKxLdYBBJz6G9wZLz+bWNpFsvS9gG1LlvMsycnwrfmwWTidmS8nDdLVfja2/XH
11
+ LXvwSnI+NHtnQ/R+duMZpJrqQljBZAZsyzW8Eyjaqi1ybwi50J/pwsX/J0jVQly/
12
+ Gr8A/SDfr1qJ82SQUfi0ZGoKVXaSJ31pPKBy9LpDGFZS7rFJsL/Aysd2lhyF/Tj1
13
+ kG60MAxFjfoRsNhBWj7pm/13hVlidwNrmhbzrnsFPuWDVWFYq71oljJ+YLlbljLB
14
+ L6rnQXr2iy+y3W03lryAsxtCrcJ6Sa+9EUNp55loGhK3EXCXnhNr3B1SfxGCXbAv
15
+ GI2h1RRbn/Pnht17pkiXsajE8vESWLqzZj6gtmYmMHQ+GY5l8SHTTMhV2DpYZFfE
16
+ nnUxyPOlXnm2uegQRf/ee8kKeUQZl9Bs2ZyniE0uzRGZqhQLlJCwYhOmEYFsv5kk
17
+ vD1Go4N2NqbMOipCFHbnm3IvG1rOBYtqHzHLwycsJw+2f1Dyu5T9GlEcJL7GeCWO
18
+ muY4Gjx7p84N1wwJ/IN4BRjkMD0qAXbKg9cnh/nDSaFgMez6jiaR+lJGV/srfpwB
19
+ 6bDOpRN/RarjRcQGVEfkP/UqJVWU8ZwnNqar5202WGSXI9HUYTQapVCh2NvBdYOS
20
+ VV+Ah2qE48s3qI95h80Ix0G7Or7ALJbSn6d190frUnXOUOye4+4MB3w+G0kGTQq8
21
+ w+pjqrSAGLA3yobrPki374X3ZMdYbdggDHn3pl/NvQVHZ4FRmYXyUVZfckV/7w0C
22
+ t1wBqh83ZM3XusivAy0/g6/124Xe2jkl6B0aNlAtxj3VTMBJNjLRIpoX5hR6TZq8
23
+ cHRvWaIOif8oOn6Q6PIGtpsAjejZ0jUPuvPHUwuYSYFUk/WH09k8tXD/ZZnoKZWV
24
+ xqrx3cBCKjoBu9HnPsPoQ6sedaM/L/i3osW7GBSMt2GApOqDoKQ70Ya6TSmbnDYv
25
+ roYy/R5vDqzTGK0gUyLNNCNhpuzEXjjaCwXegJ/GYl3mwcB8o5y1insyqfc0JPy1
26
+ oF0tYxw62Ovx9Udg9Ib94q0BQDhi5L55QZYKQAAz4+CW71aG30ETw32EtnUMnB4f
27
+ 2cswibLj7JhER/4pjOoKHF47wLJtlWlaceFvGWzno8uSY74qaRQHslwNHwx61I2q
28
+ rhsbD1wsvyEkxlIdSIlyUf8yj5LXWNMoixFppqh4VY4OAINxkNBEaJRzU6aMKn1n
29
+ Btvg2OePOvebCvJRvWousIgB2brZ5ioU336Tjb0uDeHIPF51+o78wtaS+jK084Bu
30
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,19 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDIjCCAgqgAwIBAgIURM555Ac4/c8UOP5fB12+1bo+b7owDQYJKoZIhvcNAQEL
3
+ BQAwNDEyMDAGA1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5l
4
+ cmF0ZWQgQ0EwHhcNMjIwMzIxMDc1NTA4WhcNMjUwMzIwMDc1NTA4WjATMREwDwYD
5
+ VQQDEwhpbnN0YW5jZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAI0s
6
+ sYPGWeca0YMIVATxnEA/+WFL/eWWcbaX5JeaJA+g6WxMs2B6FEMfaSxauGrHZJWt
7
+ YNP1s0pptzJEPAZyx3Tkv8oNtSai3rpOocY3ebE/SWXlQd7xHWvy+w5ls1LRHINU
8
+ pEnMZhjdHqXFjIipmsAXihzU/BrHkWGfddco88YuaMgtWbLIqQr/oHyd2F6KgQby
9
+ HPSD35hCYvLQiHEY7vIntA6A9mYSdbZg0ZvI+QGEvZmhYXZjKOy7Vfg8p1zmfbIn
10
+ XdddCOkBp5gXDu8aRxigaQAi3Y+ESnVPxDr62+VEzCdmd/PCW8blHfvE9bTgqjYm
11
+ gH01PAoqQewkuxclufECAwEAAaNNMEswHQYDVR0OBBYEFNIW9layQi7V2kbYjwIu
12
+ NWT0QXOdMB8GA1UdIwQYMBaAFEyJvLXizLXiGu8sBnIcIS001ArxMAkGA1UdEwQC
13
+ MAAwDQYJKoZIhvcNAQELBQADggEBAAOnGIKiJxEc684Y6w1WpRftiD8VnkzE+A8n
14
+ uVXJibZzEKqxMWZ83A4lTugG+vKpOj1+8hcYUdXhIo2Qt92ArIOr2S7awdkMeoRq
15
+ eIKPElC9/8mm3572kdlqBuiT12bA2oD4zrI2hS0HV21DMLAxoorzLqn0TjMnnpm3
16
+ J1KLJzzMSRtiUtwiLXG4tx2ThXfTwcJmsRMQqZW/mFajEVs0jafUAeBH7hZoZX8o
17
+ 7e0O3TCQ5JWW3/xeGK/JnAZ1IyA6jQU/J5LbGgPVu0FBGmVn3PhtwYASrdXWzK4s
18
+ ncofcBHflmi8dkiKHVWNnOTdZdzkApYo59/eq4iDESR8YNgOCKA=
19
+ -----END CERTIFICATE-----
@@ -0,0 +1,27 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIIEpAIBAAKCAQEAjSyxg8ZZ5xrRgwhUBPGcQD/5YUv95ZZxtpfkl5okD6DpbEyz
3
+ YHoUQx9pLFq4asdkla1g0/WzSmm3MkQ8BnLHdOS/yg21JqLeuk6hxjd5sT9JZeVB
4
+ 3vEda/L7DmWzUtEcg1SkScxmGN0epcWMiKmawBeKHNT8GseRYZ911yjzxi5oyC1Z
5
+ ssipCv+gfJ3YXoqBBvIc9IPfmEJi8tCIcRju8ie0DoD2ZhJ1tmDRm8j5AYS9maFh
6
+ dmMo7LtV+DynXOZ9sidd110I6QGnmBcO7xpHGKBpACLdj4RKdU/EOvrb5UTMJ2Z3
7
+ 88JbxuUd+8T1tOCqNiaAfTU8CipB7CS7FyW58QIDAQABAoIBAD10dD4J7X72JLgm
8
+ uvR//OXXM4cQXpFAAXZb/s2j8wi+on5bkUZxPjrOBKmjQF5zOC0UEW+TqJ2/EVmX
9
+ bI3eD0eqgHbDqtUL12tA6Zlw8s+e3iO2Pgt/6K/iUTm+OebWUtQ012Osz9EJCNte
10
+ +MNRGaV/WccdTDWYJIhbsx+bmyrsxxW49rwvgxI26tpJYp0QEmwEiDqfc8kRFWgy
11
+ xVSt8Rjwk418YdsYUIGrO6RsGkwsCOTJWUJpBOzLBKwj4do5l5fC2g1/MSlj7Odj
12
+ 0HH/riBmzBj/YcyMlkhfGU0Zj/OlDnz8FP5HhfpYsGxvrjOYJ49Cl4cu8r47wnFP
13
+ nx4RMmUCgYEA4U9hOtp8oJ+Ztio/1I6bAb/hKLJ3LNeV46697/lTy6tSVmYJ1DGJ
14
+ YU3P5wmOpjZC2xs22VyX/vgC1v5RT0Jg8CGgXukmMV1gQQch6gAQci9+ZJ4LRz9U
15
+ ++dXyKuUbqncx9ezFwddBr+jQuu6Wn2AHljEp7wZGaijxD2lsp4B8vcCgYEAoGd8
16
+ w+hre2pGy5PKJPSoPp+9tNVJasXe3DM6ZYEZkd+96gStA+8UlSYe9hP+Taik7oUJ
17
+ SYzELfyU8s+9XWozaa1T5PeyY8jIbEnUHWy8Uag4dHY7ooR+7b3iAD4RLfx3o2GP
18
+ vJwP3i2FwklDbbyugVXZBfkSf2NJ4QidYIWzGFcCgYAD4MHjqW8LtLOIlyGSHwI7
19
+ /Xl6ode7RdqmmJNcVgZDMyevpQH2TQP4UMaLS3bRFY4BB27iPt2+3bXuzWHI43OX
20
+ rnx8JbcqkljdxamnxWiDDp42TSIUj9p+m3S/V3Suku3h4qyKcO4A97tvo28Jr69M
21
+ 1mpMGMi10FlBP25irKWL8QKBgQCZqKlnjrWwA24QROJnpoupeiMkIRH0m9rS/Kwb
22
+ YqHZEQoALTyEwTnpaxxLxXlecYiWCZGNCLFCEG2rcQBJhZv8xxLQC8yzNDtzKQJu
23
+ saRxYQG75ytXky94lebzLoIMmIcPVz13g9TblKZHKSHT9OUCdvewdhqXN8klLrh8
24
+ J3gafwKBgQDcn/8lzfmbsKO/V9fzEbqUQJOJkwyf2adLFD+KlHM3+VAghnj70j05
25
+ 1XDkQ3LOgKrqPdC/HIGjjlkTn6vaieXltHguMg99nPpzj+LXrtN22ru2eTjjUNsD
26
+ +uB9N4kgUAR00xu60xkkOSK1TcudeL5uwejO2ul0tkDu1YGDvhXymA==
27
+ -----END RSA PRIVATE KEY-----
@@ -3,7 +3,7 @@ require "logstash/outputs/tcp"
3
3
  require "flores/pki"
4
4
 
5
5
  describe LogStash::Outputs::Tcp do
6
- subject { described_class.new(config) }
6
+ subject(:instance) { described_class.new(config) }
7
7
  let(:config) { {
8
8
  "host" => "localhost",
9
9
  "port" => 2000 + rand(3000),
@@ -16,6 +16,7 @@ describe LogStash::Outputs::Tcp do
16
16
  expect { subject.register }.to_not raise_error
17
17
  end
18
18
  end
19
+
19
20
  context "and providing a certificate/key pair" do
20
21
  let(:cert_key_pair) { Flores::PKI.generate }
21
22
  let(:certificate) { cert_key_pair.first }
@@ -24,10 +25,187 @@ describe LogStash::Outputs::Tcp do
24
25
  IO.write(path, certificate.to_s)
25
26
  path
26
27
  end
27
- let(:config) { super().merge("ssl_cert" => true, "ssl_cert" => cert_file) }
28
+ let(:config) { super().merge("ssl_cert" => cert_file) }
28
29
  it "registers without error" do
29
30
  expect { subject.register }.to_not raise_error
30
31
  end
31
32
  end
33
+
34
+ FIXTURES_PATH = File.expand_path('../fixtures', File.dirname(__FILE__))
35
+
36
+ context "ES generated plain-text certificate/key" do
37
+ let(:key_file) { File.join(FIXTURES_PATH, 'plaintext/instance.key') }
38
+ let(:crt_file) { File.join(FIXTURES_PATH, 'plaintext/instance.crt') }
39
+ let(:config) { super().merge("ssl_cert" => crt_file, "ssl_key" => key_file) }
40
+
41
+ it "registers without error" do
42
+ expect { subject.register }.to_not raise_error
43
+ end
44
+
45
+ context 'with password set' do
46
+
47
+ let(:config) { super().merge("ssl_key_passphrase" => 'ignored') }
48
+
49
+ it "registers without error" do # password simply ignored
50
+ expect { subject.register }.to_not raise_error
51
+ end
52
+
53
+ end
54
+ end
55
+
56
+ context "encrypted key using PKCS#1" do
57
+ let(:key_file) { File.join(FIXTURES_PATH, 'encrypted/instance.key') }
58
+ let(:crt_file) { File.join(FIXTURES_PATH, 'encrypted/instance.crt') }
59
+ let(:config) { super().merge("ssl_cert" => crt_file, "ssl_key" => key_file) }
60
+
61
+ it "registers with error (due missing password)" do
62
+ expect { subject.register }.to raise_error(OpenSSL::PKey::RSAError) # TODO need a better error
63
+ end
64
+
65
+ context 'with valid password' do
66
+
67
+ let(:config) { super().merge("ssl_key_passphrase" => '1234567890') }
68
+
69
+ it "registers without error" do
70
+ expect { subject.register }.to_not raise_error
71
+ end
72
+
73
+ end
74
+ end
75
+ end
76
+
77
+ ##
78
+ # Reads `in_io` until EOF and writes the bytes
79
+ # it receives to `out_io`, tolerating partial writes.
80
+ def siphon_until_eof(in_io, out_io)
81
+ buffer = ""
82
+ while (retval = in_io.read_nonblock(32*1024, buffer, exception:false)) do
83
+ (IO.select([in_io], nil, nil, 5); next) if retval == :wait_readable
84
+
85
+ while (buffer && !buffer.empty?) do
86
+ bytes_written = out_io.write(buffer)
87
+ buffer.replace buffer.byteslice(bytes_written..-1)
88
+ end
89
+ end
90
+ end
91
+
92
+ context 'client mode' do
93
+ context 'transmitting data' do
94
+ let!(:io) { StringIO.new } # somewhere for our server to stash the data it receives
95
+
96
+ let(:server_host) { 'localhost' }
97
+ let(:server_port) { server.addr[1] } # get actual since we bind to port 0
98
+
99
+ let!(:server) { TCPServer.new(server_host, 0) }
100
+
101
+ let(:config) do
102
+ { 'host' => server_host, 'port' => server_port, 'mode' => 'client' }
103
+ end
104
+
105
+ let(:event) { LogStash::Event.new({"hello" => "world"})}
106
+
107
+ subject(:instance) { described_class.new(config) }
108
+
109
+ before(:each) do
110
+ # accepts ONE connection
111
+ @server_socket_thread = Thread.start do
112
+ client = server.accept
113
+ siphon_until_eof(client, io)
114
+ end
115
+ instance.register
116
+ end
117
+
118
+ after(:each) do
119
+ @server_socket_thread&.join
120
+ end
121
+
122
+ it 'encodes and transmits data' do
123
+ instance.receive(event)
124
+ sleep 1
125
+ instance.close # release the connection
126
+ @server_socket_thread.join(30) || fail('server failed to join')
127
+ expect(io.string).to include('"hello"','"world"')
128
+ end
129
+
130
+ context 'when payload is very large' do
131
+ let(:one_hundred_megabyte_message) { "a" * 1024 * 1024 * 100 }
132
+ let(:event) { LogStash::Event.new("message" => one_hundred_megabyte_message) }
133
+
134
+
135
+ it 'encodes and transmits data' do
136
+ instance.receive(event)
137
+ sleep 1
138
+ instance.close # release the connection
139
+ @server_socket_thread.join(30) || fail('server failed to join')
140
+ expect(io.string).to include('"message"',%Q("#{one_hundred_megabyte_message}"))
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ context 'server mode' do
147
+
148
+ def wait_for_condition(total_time_in_seconds, &block)
149
+ deadline = Time.now + total_time_in_seconds
150
+ until Time.now > deadline
151
+ return if yield
152
+ sleep(1)
153
+ end
154
+ fail('condition not met!')
155
+ end
156
+
157
+ context 'transmitting data' do
158
+ let(:server_host) { 'localhost' }
159
+ let(:server_port) { Random.rand(1024...5000) }
160
+
161
+ let(:config) do
162
+ { 'host' => server_host, 'port' => server_port, 'mode' => 'server' }
163
+ end
164
+
165
+ subject(:instance) { described_class.new(config) }
166
+
167
+ before(:each) { instance.register } # start listener
168
+ after(:each) { instance.close }
169
+
170
+ let(:event) { LogStash::Event.new({"hello" => "world"})}
171
+
172
+ context 'when one client is connected' do
173
+ let(:io) { StringIO.new }
174
+ let(:client_socket) { TCPSocket.new(server_host, server_port) }
175
+
176
+ before(:each) do
177
+ @client_socket_thread = Thread.start { siphon_until_eof(client_socket, io) }
178
+ sleep 1 # wait for it to actually connect
179
+ end
180
+
181
+ it 'encodes and transmits data' do
182
+ sleep 1
183
+ instance.receive(event)
184
+
185
+ wait_for_condition(30) { !io.size.zero? }
186
+ sleep 1 # wait for the event to get sent...
187
+ instance.close # release the connection
188
+
189
+ @client_socket_thread.join(30) || fail('client failed to join')
190
+ expect(io.string).to include('"hello"','"world"')
191
+ end
192
+
193
+ context 'when payload is very large' do
194
+ let(:one_hundred_megabyte_message) { "a" * 1024 * 1024 * 100 }
195
+ let(:event) { LogStash::Event.new("message" => one_hundred_megabyte_message) }
196
+
197
+ it 'encodes and transmits data' do
198
+ instance.receive(event)
199
+
200
+ wait_for_condition(30) { io.size >= one_hundred_megabyte_message.size }
201
+ sleep 1 # wait for the event to get sent...
202
+ instance.close # release the connection
203
+
204
+ @client_socket_thread.join(30) || fail('client failed to join')
205
+ expect(io.string).to include('"message"',%Q("#{one_hundred_megabyte_message}"))
206
+ end
207
+ end
208
+ end
209
+ end
32
210
  end
33
211
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-output-tcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.0.1
4
+ version: 6.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-07 00:00:00.000000000 Z
11
+ date: 2022-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -103,6 +103,10 @@ files:
103
103
  - docs/index.asciidoc
104
104
  - lib/logstash/outputs/tcp.rb
105
105
  - logstash-output-tcp.gemspec
106
+ - spec/fixtures/encrypted/instance.crt
107
+ - spec/fixtures/encrypted/instance.key
108
+ - spec/fixtures/plaintext/instance.crt
109
+ - spec/fixtures/plaintext/instance.key
106
110
  - spec/outputs/tcp_spec.rb
107
111
  homepage: http://www.elastic.co/guide/en/logstash/current/index.html
108
112
  licenses:
@@ -130,4 +134,8 @@ signing_key:
130
134
  specification_version: 4
131
135
  summary: Writes events over a TCP socket
132
136
  test_files:
137
+ - spec/fixtures/encrypted/instance.crt
138
+ - spec/fixtures/encrypted/instance.key
139
+ - spec/fixtures/plaintext/instance.crt
140
+ - spec/fixtures/plaintext/instance.key
133
141
  - spec/outputs/tcp_spec.rb