logstash-output-tcp 6.0.1 → 6.0.3

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 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