logstash-output-tcp 6.0.1 → 6.1.1

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: c907232f3196c96261615180d8c1fc85c7973dc94c31fbd5161bf657f9c32adb
4
+ data.tar.gz: 2787199dc0aa9904ae4387a97d7f8add642b8cbd5af37929a263604c0d15c1d1
5
5
  SHA512:
6
- metadata.gz: d34636d36f87a3c5d632263f603bf6816f88d3033d0394aba9f84438819b008bfc6cd66cf16baf2a1a2cff55e72e5f52b382c00422f2fcaaf5f5b883f7404ee8
7
- data.tar.gz: 50f4c2ef7feba191fd0d6cc815353ec1cafb09a7acfa74a328d9a3594c74bff96b8a61f61df11b1b44022fd6e3ce32f975d54c532cc70e6e1594dd8534a531b3
6
+ metadata.gz: e0a8adce0cb539e83d1305c5d52931710041d7f4d7b8e42098f141bfca50980f80c39bef88116de710463044b6cfb5a14e0a32d3e32c3058d76990bb5bbd5d3b
7
+ data.tar.gz: 754a94a8b98ed57b77390091ed6dc05538d28d6aea75e4e5c30a438c34696fe80f9cb56503ed1a81ee2832fccee347122fbb07afb755ea01d2070c2ce4384b21
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## 6.1.1
2
+ - Fixes an issue where payloads larger than a connection's current TCP window could be silently truncated [#49](https://github.com/logstash-plugins/logstash-output-tcp/pull/49)
3
+
4
+ ## 6.1.0
5
+ - Feat: ssl_supported_protocols (TLSv1.3) [#47](https://github.com/logstash-plugins/logstash-output-tcp/pull/47)
6
+ - Fix: close server and client sockets on plugin close
7
+
8
+ ## 6.0.2
9
+ - Fix: unable to start with password protected key [#45](https://github.com/logstash-plugins/logstash-output-tcp/pull/45)
10
+
1
11
  ## 6.0.1
2
12
  - Fixed logging fail retry to stdout [#43](https://github.com/logstash-plugins/logstash-output-tcp/pull/43)
3
13
  - Fixed to use `reconnect_interval` when establish a connection
data/docs/index.asciidoc CHANGED
@@ -45,6 +45,7 @@ This plugin supports the following configuration options plus the <<plugins-{typ
45
45
  | <<plugins-{type}s-{plugin}-ssl_enable>> |<<boolean,boolean>>|No
46
46
  | <<plugins-{type}s-{plugin}-ssl_key>> |a valid filesystem path|No
47
47
  | <<plugins-{type}s-{plugin}-ssl_key_passphrase>> |<<password,password>>|No
48
+ | <<plugins-{type}s-{plugin}-ssl_supported_protocols>> |<<string,string>>|No
48
49
  | <<plugins-{type}s-{plugin}-ssl_verify>> |<<boolean,boolean>>|No
49
50
  |=======================================================================
50
51
 
@@ -130,6 +131,20 @@ SSL key path
130
131
 
131
132
  SSL key passphrase
132
133
 
134
+ [id="plugins-{type}s-{plugin}-ssl_supported_protocols"]
135
+ ===== `ssl_supported_protocols`
136
+
137
+ * Value type is <<string,string>>
138
+ * Allowed values are: `'TLSv1.1'`, `'TLSv1.2'`, `'TLSv1.3'`
139
+ * Default depends on the JDK being used. With up-to-date Logstash, the default is `['TLSv1.2', 'TLSv1.3']`.
140
+ `'TLSv1.1'` is not considered secure and is only provided for legacy applications.
141
+
142
+ List of allowed SSL/TLS versions to use when establishing a secure connection.
143
+
144
+ NOTE: If you configure the plugin to use `'TLSv1.1'` on any recent JVM, such as the one packaged with Logstash,
145
+ the protocol is disabled by default and needs to be enabled manually by changing `jdk.tls.disabledAlgorithms` in
146
+ the *$JDK_HOME/conf/security/java.security* configuration file. That is, `TLSv1.1` needs to be removed from the list.
147
+
133
148
  [id="plugins-{type}s-{plugin}-ssl_verify"]
134
149
  ===== `ssl_verify`
135
150
 
@@ -51,42 +51,57 @@ 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
+ # NOTE: the default setting [] uses SSL engine defaults
55
+ config :ssl_supported_protocols, :validate => ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], :default => [], :list => true
56
+
54
57
  class Client
55
- public
56
- def initialize(socket, logger)
58
+
59
+ ##
60
+ # @param socket [Socket]
61
+ # @param logger_context [#log_warn&#log_error]
62
+ def initialize(socket, logger_context)
57
63
  @socket = socket
58
- @logger = logger
64
+ @logger_context = logger_context
59
65
  @queue = Queue.new
60
66
  end
61
67
 
62
- public
63
68
  def run
64
69
  loop do
65
70
  begin
66
- @socket.write(@queue.pop)
71
+ remaining_payload = @queue.pop
72
+ while remaining_payload && remaining_payload.bytesize > 0
73
+ written_bytes_size = @socket.write(remaining_payload)
74
+ remaining_payload = remaining_payload.byteslice(written_bytes_size..-1)
75
+ end
67
76
  rescue => e
68
- @logger.warn("tcp output exception", :socket => @socket,
69
- :exception => e)
77
+ @logger_context.log_warn 'socket write failed:', e, socket: (@socket ? @socket.to_s : nil)
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 ? @socket.to_s : nil)
91
+ end
79
92
  end # class Client
80
93
 
81
- private
82
94
  def setup_ssl
83
95
  require "openssl"
84
96
 
85
- @ssl_context = OpenSSL::SSL::SSLContext.new
97
+ @ssl_context = new_ssl_context
86
98
  if @ssl_cert
87
99
  @ssl_context.cert = OpenSSL::X509::Certificate.new(File.read(@ssl_cert))
88
100
  if @ssl_key
89
- @ssl_context.key = OpenSSL::PKey::RSA.new(File.read(@ssl_key),@ssl_key_passphrase)
101
+ # if we have an encrypted key and a password is not provided (nil) than OpenSSL::PKey::RSA
102
+ # prompts the user to enter a password interactively - we do not want to do that,
103
+ # for plain-text keys the default '' password argument gets simply ignored
104
+ @ssl_context.key = OpenSSL::PKey::RSA.new(File.read(@ssl_key), @ssl_key_passphrase.value || '')
90
105
  end
91
106
  end
92
107
  if @ssl_verify
@@ -101,77 +116,146 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
101
116
  @ssl_context.cert_store = @cert_store
102
117
  @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
103
118
  end
104
- end # def setup_ssl
105
119
 
106
- public
120
+ @ssl_context.min_version = :TLS1_1 # not strictly required - JVM should have disabled TLSv1
121
+ if ssl_supported_protocols.any?
122
+ disabled_protocols = ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'] - ssl_supported_protocols
123
+ unless OpenSSL::SSL.const_defined? :OP_NO_TLSv1_3 # work-around JRuby-OpenSSL bug - missing constant
124
+ @ssl_context.max_version = :TLS1_2 if disabled_protocols.delete('TLSv1.3')
125
+ end
126
+ # mapping 'TLSv1.2' -> OpenSSL::SSL::OP_NO_TLSv1_2
127
+ disabled_protocols.map! { |v| OpenSSL::SSL.const_get "OP_NO_#{v.sub('.', '_')}" }
128
+ @ssl_context.options = disabled_protocols.reduce(@ssl_context.options, :|)
129
+ end
130
+ @ssl_context
131
+ end
132
+ private :setup_ssl
133
+
134
+ # @note to be able to hook up into #ssl_context from tests
135
+ def new_ssl_context
136
+ OpenSSL::SSL::SSLContext.new
137
+ end
138
+ private :new_ssl_context
139
+
140
+ # @overload Base#register
107
141
  def register
108
142
  require "socket"
109
143
  require "stud/try"
144
+ @closed = Concurrent::AtomicBoolean.new(false)
145
+ @thread_no = Concurrent::AtomicFixnum.new(0)
146
+ setup_ssl if @ssl_enable
147
+
148
+ if server?
149
+ run_as_server
150
+ else
151
+ run_as_client
152
+ end
153
+ end
154
+
155
+ def run_as_server
156
+ @logger.info("Starting tcp output listener", :address => "#{@host}:#{@port}")
157
+ begin
158
+ @server_socket = TCPServer.new(@host, @port)
159
+ rescue Errno::EADDRINUSE
160
+ @logger.error("Could not start tcp server: Address in use", host: @host, port: @port)
161
+ raise
162
+ end
110
163
  if @ssl_enable
111
- setup_ssl
164
+ @server_socket = OpenSSL::SSL::SSLServer.new(@server_socket, @ssl_context)
112
165
  end # @ssl_enable
166
+ @client_threads = Concurrent::Array.new
113
167
 
114
- if server?
115
- @logger.info("Starting tcp output listener", :address => "#{@host}:#{@port}")
116
- begin
117
- @server_socket = TCPServer.new(@host, @port)
118
- rescue Errno::EADDRINUSE
119
- @logger.error("Could not start TCP server: Address in use",
120
- :host => @host, :port => @port)
121
- raise
122
- end
123
- if @ssl_enable
124
- @server_socket = OpenSSL::SSL::SSLServer.new(@server_socket, @ssl_context)
125
- end # @ssl_enable
126
- @client_threads = []
127
-
128
- @accept_thread = Thread.new(@server_socket) do |server_socket|
129
- loop do
130
- Thread.start(server_socket.accept) do |client_socket|
131
- # monkeypatch a 'peer' method onto the socket.
132
- client_socket.instance_eval { class << self; include ::LogStash::Util::SocketPeer end }
133
- @logger.debug("Accepted connection", :client => client_socket.peer,
134
- :server => "#{@host}:#{@port}")
135
- client = Client.new(client_socket, @logger)
136
- Thread.current[:client] = client
137
- @client_threads << Thread.current
138
- client.run
139
- end
168
+ @accept_thread = Thread.new(@server_socket) do |server_socket|
169
+ LogStash::Util.set_thread_name("[#{pipeline_id}]|output|tcp|server_accept")
170
+ loop do
171
+ break if @closed.value
172
+ client_socket = server_socket.accept_nonblock exception: false
173
+ if client_socket == :wait_readable
174
+ IO.select [ server_socket ]
175
+ next
176
+ end
177
+ Thread.start(client_socket) do |client_socket|
178
+ # monkeypatch a 'peer' method onto the socket.
179
+ client_socket.extend(::LogStash::Util::SocketPeer)
180
+ @logger.debug("accepted connection", client: client_socket.peer, server: "#{@host}:#{@port}")
181
+ client = Client.new(client_socket, self)
182
+ Thread.current[:client] = client
183
+ LogStash::Util.set_thread_name("[#{pipeline_id}]|output|tcp|client_socket-#{@thread_no.increment}")
184
+ @client_threads << Thread.current
185
+ client.run unless @closed.value
140
186
  end
141
187
  end
188
+ end
142
189
 
143
- @codec.on_event do |event, payload|
144
- @client_threads.each do |client_thread|
145
- client_thread[:client].write(payload)
146
- end
147
- @client_threads.reject! {|t| !t.alive? }
190
+ @codec.on_event do |event, payload|
191
+ @client_threads.select!(&:alive?)
192
+ @client_threads.each do |client_thread|
193
+ client_thread[:client].write(payload)
148
194
  end
149
- else
150
- client_socket = nil
151
- @codec.on_event do |event, payload|
152
- begin
153
- client_socket = connect unless client_socket
154
- r,w,e = IO.select([client_socket], [client_socket], [client_socket], nil)
195
+ end
196
+ end
197
+
198
+ def run_as_client
199
+ client_socket = nil
200
+ @codec.on_event do |event, payload|
201
+ begin
202
+ client_socket = connect unless client_socket
203
+
204
+ writable_io = nil
205
+ while writable_io.nil? || writable_io.any? == false
206
+ readable_io, writable_io, _ = IO.select([client_socket],[client_socket])
207
+
155
208
  # don't expect any reads, but a readable socket might
156
209
  # mean the remote end closed, so read it and throw it away.
157
210
  # we'll get an EOFError if it happens.
158
- client_socket.sysread(16384) if r.any?
211
+ readable_io.each { |readable| readable.sysread(16384) }
212
+ end
159
213
 
160
- # Now send the payload
161
- client_socket.syswrite(payload) if w.any?
162
- 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
167
- sleep @reconnect_interval
168
- retry
214
+ while payload && payload.bytesize > 0
215
+ written_bytes_size = client_socket.syswrite(payload)
216
+ payload = payload.byteslice(written_bytes_size..-1)
169
217
  end
218
+ rescue => e
219
+ log_warn "client socket failed:", e, host: @host, port: @port, socket: (client_socket ? client_socket.to_s : nil)
220
+ client_socket.close rescue nil
221
+ client_socket = nil
222
+ sleep @reconnect_interval
223
+ retry
170
224
  end
171
225
  end
172
- end # def register
226
+ end
227
+
228
+ # @overload Base#receive
229
+ def receive(event)
230
+ @codec.encode(event)
231
+ end
232
+
233
+ # @overload Base#close
234
+ def close
235
+ @closed.make_true
236
+ @server_socket.close rescue nil if @server_socket
237
+
238
+ return unless @client_threads
239
+ @client_threads.each do |thread|
240
+ client = thread[:client]
241
+ client.close rescue nil if client
242
+ end
243
+ end
244
+
245
+ def log_warn(msg, e, backtrace: @logger.debug?, **details)
246
+ details = details.merge message: e.message, exception: e.class
247
+ details[:backtrace] = e.backtrace if backtrace
248
+ @logger.warn(msg, details)
249
+ end
250
+
251
+ def log_error(msg, e, backtrace: @logger.info?, **details)
252
+ details = details.merge message: e.message, exception: e.class
253
+ details[:backtrace] = e.backtrace if backtrace
254
+ @logger.error(msg, details)
255
+ end
173
256
 
174
257
  private
258
+
175
259
  def connect
176
260
  begin
177
261
  client_socket = TCPSocket.new(@host, @port)
@@ -180,29 +264,28 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
180
264
  begin
181
265
  client_socket.connect
182
266
  rescue OpenSSL::SSL::SSLError => ssle
183
- @logger.error("SSL Error", :exception => ssle, :backtrace => ssle.backtrace)
267
+ log_error 'connect ssl failure:', ssle, backtrace: false
184
268
  # NOTE(mrichar1): Hack to prevent hammering peer
185
269
  sleep(5)
186
270
  raise
187
271
  end
188
272
  end
189
- client_socket.instance_eval { class << self; include ::LogStash::Util::SocketPeer end }
190
- @logger.debug("Opened connection", :client => "#{client_socket.peer}")
273
+ client_socket.extend(::LogStash::Util::SocketPeer)
274
+ @logger.debug("opened connection", :client => client_socket.peer)
191
275
  return client_socket
192
- rescue StandardError => e
193
- @logger.error("Failed to connect: #{e.message}", :exception => e.class, :backtrace => e.backtrace)
276
+ rescue => e
277
+ log_error 'failed to connect:', e
194
278
  sleep @reconnect_interval
195
279
  retry
196
280
  end
197
281
  end # def connect
198
282
 
199
- private
200
283
  def server?
201
284
  @mode == "server"
202
285
  end # def server?
203
286
 
204
- public
205
- def receive(event)
206
- @codec.encode(event)
207
- end # def receive
287
+ def pipeline_id
288
+ execution_context.pipeline_id || 'main'
289
+ end
290
+
208
291
  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.1.1'
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"
@@ -21,11 +21,14 @@ Gem::Specification.new do |s|
21
21
 
22
22
  # Gem dependencies
23
23
  s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
24
-
24
+ s.add_runtime_dependency 'logstash-core', '>= 8.1.0'
25
25
  s.add_runtime_dependency 'logstash-codec-json'
26
26
  s.add_runtime_dependency 'stud'
27
27
 
28
+ s.add_runtime_dependency 'jruby-openssl', '>= 0.12.2' # 0.12 supports TLSv1.3
29
+
28
30
  s.add_development_dependency 'logstash-devutils'
31
+ s.add_development_dependency 'logstash-codec-plain'
29
32
  s.add_development_dependency 'flores'
30
33
  end
31
34
 
@@ -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-----
@@ -4,18 +4,118 @@ require "flores/pki"
4
4
 
5
5
  describe LogStash::Outputs::Tcp do
6
6
  subject { described_class.new(config) }
7
- let(:config) { {
8
- "host" => "localhost",
9
- "port" => 2000 + rand(3000),
10
- } }
7
+
8
+ let(:port) do
9
+ begin
10
+ # Start high to better avoid common services
11
+ port = rand(10000..65535)
12
+ s = TCPServer.new("127.0.0.1", port)
13
+ s.close
14
+
15
+ port
16
+ rescue Errno::EADDRINUSE
17
+ retry
18
+ end
19
+ end
20
+
21
+ let(:server) { TCPServer.new("127.0.0.1", port) }
22
+
23
+ let(:config) { { "host" => "localhost", "port" => port } }
24
+
25
+ let(:event) { LogStash::Event.new('message' => 'foo bar') }
26
+
27
+ context 'failing to connect' do
28
+
29
+ before { subject.register }
30
+
31
+ let(:config) { super().merge 'port' => 1000 }
32
+
33
+ it 'fails to connect' do
34
+ expect( subject ).to receive(:log_error).and_call_original
35
+ Thread.start { subject.receive(event) }
36
+ sleep 1.0
37
+ end
38
+
39
+ end
40
+
41
+ context 'server mode' do
42
+
43
+ before { subject.register }
44
+
45
+ let(:config) { super().merge 'mode' => 'server' }
46
+
47
+ let(:client) do
48
+ Stud::try(3.times) { TCPSocket.new("127.0.0.1", port) }
49
+ end
50
+
51
+ after { subject.close }
52
+
53
+ it 'receives serialized data' do; require 'json'
54
+ client # connect
55
+ Thread.start { sleep 0.5; subject.receive event }
56
+
57
+ read = client.recv(1000)
58
+ expect( read.size ).to be > 0
59
+ expect( JSON.parse(read)['message'] ).to eql 'foo bar'
60
+ end
61
+
62
+ end
63
+
64
+ context "with forced protocol" do
65
+ let(:config) do
66
+ super().merge 'ssl_supported_protocols' => [ 'TLSv1.1' ]
67
+ end
68
+
69
+ it "limits protocol selection" do
70
+ if OpenSSL::SSL.const_defined? :OP_NO_TLSv1_3
71
+ ssl_context = subject.send :setup_ssl
72
+ expect(ssl_context.options & OpenSSL::SSL::OP_NO_TLSv1_3).to_not eql 0
73
+ expect(ssl_context.options & OpenSSL::SSL::OP_NO_TLSv1_2).to_not eql 0
74
+ expect(ssl_context.options & OpenSSL::SSL::OP_NO_TLSv1_1).to eql 0
75
+ else
76
+ ssl_context = OpenSSL::SSL::SSLContext.new
77
+ allow(subject).to receive(:new_ssl_context).and_return(ssl_context)
78
+ expect(ssl_context).to receive(:max_version=).with(:'TLS1_2').and_call_original
79
+ ssl_context = subject.send :setup_ssl
80
+ expect(ssl_context.options & OpenSSL::SSL::OP_NO_TLSv1_2).to_not eql 0
81
+ expect(ssl_context.options & OpenSSL::SSL::OP_NO_TLSv1_1).to eql 0
82
+ end
83
+ end
84
+ end
85
+
86
+ context "with protocol range" do
87
+ let(:config) do
88
+ super().merge 'ssl_supported_protocols' => [ 'TLSv1.3', 'TLSv1.1', 'TLSv1.2' ]
89
+ end
90
+
91
+ it "does not limit protocol selection (except min_version)" do
92
+ ssl_context = OpenSSL::SSL::SSLContext.new
93
+ allow(subject).to receive(:new_ssl_context).and_return(ssl_context)
94
+ expect(ssl_context).to receive(:min_version=).with(:'TLS1_1').at_least(1).and_call_original
95
+
96
+ if OpenSSL::SSL.const_defined? :OP_NO_TLSv1_3
97
+ subject.send :setup_ssl
98
+ expect(ssl_context.options & OpenSSL::SSL::OP_NO_TLSv1_3).to eql 0
99
+ expect(ssl_context.options & OpenSSL::SSL::OP_NO_TLSv1_2).to eql 0
100
+ expect(ssl_context.options & OpenSSL::SSL::OP_NO_TLSv1_1).to eql 0
101
+ else
102
+ subject.send :setup_ssl
103
+ expect(ssl_context.options & OpenSSL::SSL::OP_NO_TLSv1_2).to eql 0
104
+ expect(ssl_context.options & OpenSSL::SSL::OP_NO_TLSv1_1).to eql 0
105
+ end
106
+
107
+ subject.send :setup_ssl
108
+ end
109
+ end
11
110
 
12
111
  context "when enabling SSL" do
13
- let(:config) { super().merge("ssl_enable" => true) }
112
+ let(:config) { super().merge("ssl_enable" => true, 'codec' => 'plain') }
14
113
  context "and not providing a certificate/key pair" do
15
114
  it "registers without error" do
16
115
  expect { subject.register }.to_not raise_error
17
116
  end
18
117
  end
118
+
19
119
  context "and providing a certificate/key pair" do
20
120
  let(:cert_key_pair) { Flores::PKI.generate }
21
121
  let(:certificate) { cert_key_pair.first }
@@ -24,10 +124,108 @@ describe LogStash::Outputs::Tcp do
24
124
  IO.write(path, certificate.to_s)
25
125
  path
26
126
  end
27
- let(:config) { super().merge("ssl_cert" => true, "ssl_cert" => cert_file) }
127
+ let(:config) { super().merge("ssl_cert" => cert_file) }
28
128
  it "registers without error" do
29
129
  expect { subject.register }.to_not raise_error
30
130
  end
31
131
  end
132
+
133
+ FIXTURES_PATH = File.expand_path('../fixtures', File.dirname(__FILE__))
134
+
135
+ context "ES generated plain-text certificate/key" do
136
+ let(:key_file) { File.join(FIXTURES_PATH, 'plaintext/instance.key') }
137
+ let(:crt_file) { File.join(FIXTURES_PATH, 'plaintext/instance.crt') }
138
+ let(:config) { super().merge("ssl_cert" => crt_file, "ssl_key" => key_file) }
139
+
140
+ it "registers without error" do
141
+ expect { subject.register }.to_not raise_error
142
+ end
143
+
144
+ context 'with password set' do
145
+
146
+ let(:config) { super().merge("ssl_key_passphrase" => 'ignored') }
147
+
148
+ it "registers without error" do # password simply ignored
149
+ expect { subject.register }.to_not raise_error
150
+ end
151
+
152
+ end
153
+
154
+ let(:secure_server) do
155
+ ssl_context = OpenSSL::SSL::SSLContext.new
156
+ ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
157
+ ssl_context.cert = OpenSSL::X509::Certificate.new(File.read(crt_file))
158
+ ssl_context.key = OpenSSL::PKey::RSA.new(File.read(key_file), nil)
159
+ ssl_context.ssl_version = server_ssl_version if server_ssl_version
160
+ ssl_context.min_version = server_min_version if server_min_version
161
+ ssl_context.max_version = server_max_version if server_max_version
162
+ OpenSSL::SSL::SSLServer.new(server, ssl_context)
163
+ end
164
+
165
+ let(:server_min_version) { nil }
166
+ let(:server_max_version) { nil }
167
+ let(:server_ssl_version) { nil }
168
+
169
+ context 'with supported protocol' do
170
+
171
+ let(:config) { super().merge("ssl_supported_protocols" => ['TLSv1.2']) }
172
+
173
+ let(:server_min_version) { 'TLS1_2' }
174
+
175
+ before { subject.register }
176
+ after { secure_server.close }
177
+
178
+ it 'reads plain data' do
179
+ Thread.start { sleep 0.25; subject.receive event }
180
+ socket = secure_server.accept
181
+ read = socket.sysread(100)
182
+ expect( read.size ).to be > 0
183
+ expect( read ).to end_with 'foo bar'
184
+ end
185
+
186
+ end
187
+
188
+ context 'with unsupported protocol (on server)' do
189
+
190
+ let(:config) { super().merge("ssl_supported_protocols" => ['TLSv1.1']) }
191
+
192
+ let(:server_min_version) { 'TLS1_2' }
193
+
194
+ before { subject.register }
195
+ after { secure_server.close }
196
+
197
+ it 'fails (and loops retrying)' do
198
+ expect(subject.logger).to receive(:error).with(/connect ssl failure/i, hash_including(message: /No appropriate protocol/i)).and_call_original
199
+ expect(subject.logger).to receive(:error).with(/failed to connect/i, hash_including(exception: OpenSSL::SSL::SSLError)).and_call_original
200
+ expect(subject).to receive(:sleep).once.and_call_original
201
+ expect(subject).to receive(:sleep).once.and_throw :TEST_DONE # to be able to abort the retry loop
202
+
203
+ Thread.start { secure_server.accept rescue nil }
204
+ expect { subject.receive event }.to throw_symbol(:TEST_DONE)
205
+ end
206
+
207
+ end if LOGSTASH_VERSION > '7.0'
208
+
209
+ end
210
+
211
+ context "encrypted key using PKCS#1" do
212
+ let(:key_file) { File.join(FIXTURES_PATH, 'encrypted/instance.key') }
213
+ let(:crt_file) { File.join(FIXTURES_PATH, 'encrypted/instance.crt') }
214
+ let(:config) { super().merge("ssl_cert" => crt_file, "ssl_key" => key_file) }
215
+
216
+ it "registers with error (due missing password)" do
217
+ expect { subject.register }.to raise_error(OpenSSL::PKey::RSAError) # TODO need a better error
218
+ end
219
+
220
+ context 'with valid password' do
221
+
222
+ let(:config) { super().merge("ssl_key_passphrase" => '1234567890') }
223
+
224
+ it "registers without error" do
225
+ expect { subject.register }.to_not raise_error
226
+ end
227
+
228
+ end
229
+ end
32
230
  end
33
231
  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.1.1
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-08-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -30,6 +30,20 @@ dependencies:
30
30
  - - "<="
31
31
  - !ruby/object:Gem::Version
32
32
  version: '2.99'
33
+ - !ruby/object:Gem::Dependency
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: 8.1.0
39
+ name: logstash-core
40
+ prerelease: false
41
+ type: :runtime
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 8.1.0
33
47
  - !ruby/object:Gem::Dependency
34
48
  requirement: !ruby/object:Gem::Requirement
35
49
  requirements:
@@ -58,6 +72,20 @@ dependencies:
58
72
  - - ">="
59
73
  - !ruby/object:Gem::Version
60
74
  version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ requirement: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: 0.12.2
81
+ name: jruby-openssl
82
+ prerelease: false
83
+ type: :runtime
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: 0.12.2
61
89
  - !ruby/object:Gem::Dependency
62
90
  requirement: !ruby/object:Gem::Requirement
63
91
  requirements:
@@ -72,6 +100,20 @@ dependencies:
72
100
  - - ">="
73
101
  - !ruby/object:Gem::Version
74
102
  version: '0'
103
+ - !ruby/object:Gem::Dependency
104
+ requirement: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ name: logstash-codec-plain
110
+ prerelease: false
111
+ type: :development
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
75
117
  - !ruby/object:Gem::Dependency
76
118
  requirement: !ruby/object:Gem::Requirement
77
119
  requirements:
@@ -103,6 +145,10 @@ files:
103
145
  - docs/index.asciidoc
104
146
  - lib/logstash/outputs/tcp.rb
105
147
  - logstash-output-tcp.gemspec
148
+ - spec/fixtures/encrypted/instance.crt
149
+ - spec/fixtures/encrypted/instance.key
150
+ - spec/fixtures/plaintext/instance.crt
151
+ - spec/fixtures/plaintext/instance.key
106
152
  - spec/outputs/tcp_spec.rb
107
153
  homepage: http://www.elastic.co/guide/en/logstash/current/index.html
108
154
  licenses:
@@ -130,4 +176,8 @@ signing_key:
130
176
  specification_version: 4
131
177
  summary: Writes events over a TCP socket
132
178
  test_files:
179
+ - spec/fixtures/encrypted/instance.crt
180
+ - spec/fixtures/encrypted/instance.key
181
+ - spec/fixtures/plaintext/instance.crt
182
+ - spec/fixtures/plaintext/instance.key
133
183
  - spec/outputs/tcp_spec.rb