logstash-output-tcp 6.0.3 → 6.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5db164894be7c046e3dce9337af0ce5b684f19b1b35274a0d3b925e97fe402c8
4
- data.tar.gz: 63dbb52d49285af564f83de8218c3af098b1d0f62d9deee019c15eae5cbd98e2
3
+ metadata.gz: d30cf25cfbc7e1cc2e72d5e1eb8ab8179d42b31413a549801a012d5e1be7e640
4
+ data.tar.gz: 53632382865d4bade1e6d56e9872b1b5db690fd9b866cb4d0952dfa8d1156cee
5
5
  SHA512:
6
- metadata.gz: 10b22e722c22edce1fd3699e876ab898283d66ec0aa256f9d9c6159112bcf54f872b4e68d8bc7eb72b999c54442d5b65c69fa87194f6c6a58623f7c1939c9c09
7
- data.tar.gz: eb6477d5f227184f46bac1c01453215fbd2f2ebf3c0fbb819413d5555d2ab7472ab61b11d737016487b3d1255bcc65fce008f350683ad28e9468fbb1f55ded49
6
+ metadata.gz: e48f3511c25df04edb79dfd0425c6a84d18808bce5f8d4910cabe024955e6c16f8b856a8ac7fac01cb7721e7628934101e14e7180497a7c6cc1096169290963e
7
+ data.tar.gz: 94758e5adb6be529f4c70715aa0796d1a15bd8d17e91d35c63fdc1b29eccea58c0f36e4c29b0cf911a3691fade34c895922ca9b0384cd3df405f723a97ff642f
data/CHANGELOG.md CHANGED
@@ -1,8 +1,6 @@
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
1
+ ## 6.1.0
2
+ - Feat: ssl_supported_protocols (TLSv1.3) [#47](https://github.com/logstash-plugins/logstash-output-tcp/pull/47)
3
+ - Fix: close server and client sockets on plugin close
6
4
 
7
5
  ## 6.0.2
8
6
  - Fix: unable to start with password protected key [#45](https://github.com/logstash-plugins/logstash-output-tcp/pull/45)
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,30 +51,23 @@ 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
+ # NOTE: the default setting [] uses SSL engine defaults
55
+ config :ssl_supported_protocols, :validate => ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], :default => [], :list => true
56
+
57
57
  class Client
58
- def initialize(socket, logger_context)
58
+
59
+ def initialize(socket, logger)
59
60
  @socket = socket
60
- @peer_info = socket.peer
61
- @logger_context = logger_context
61
+ @logger = logger
62
62
  @queue = Queue.new
63
63
  end
64
64
 
65
65
  def run
66
66
  loop do
67
67
  begin
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
68
+ @socket.write(@queue.pop)
76
69
  rescue => e
77
- @logger_context.log_warn("tcp output exception: socket write failed", e, :socket => @peer_info)
70
+ log_warn 'socket write failed:', e, socket: (@socket ? @socket.to_s : nil)
78
71
  break
79
72
  end
80
73
  end
@@ -87,15 +80,14 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
87
80
  def close
88
81
  @socket.close
89
82
  rescue => e
90
- @logger_context.log_warn 'socket close failed:', e, socket: @socket&.to_s
83
+ log_warn 'socket close failed:', e, socket: (@socket ? @socket.to_s : nil)
91
84
  end
92
85
  end # class Client
93
86
 
94
- private
95
87
  def setup_ssl
96
88
  require "openssl"
97
89
 
98
- @ssl_context = OpenSSL::SSL::SSLContext.new
90
+ @ssl_context = new_ssl_context
99
91
  if @ssl_cert
100
92
  @ssl_context.cert = OpenSSL::X509::Certificate.new(File.read(@ssl_cert))
101
93
  if @ssl_key
@@ -117,44 +109,63 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
117
109
  @ssl_context.cert_store = @cert_store
118
110
  @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
119
111
  end
120
- end # def setup_ssl
121
112
 
122
- public
113
+ @ssl_context.min_version = :TLS1_1 # not strictly required - JVM should have disabled TLSv1
114
+ if ssl_supported_protocols.any?
115
+ disabled_protocols = ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'] - ssl_supported_protocols
116
+ unless OpenSSL::SSL.const_defined? :OP_NO_TLSv1_3 # work-around JRuby-OpenSSL bug - missing constant
117
+ @ssl_context.max_version = :TLS1_2 if disabled_protocols.delete('TLSv1.3')
118
+ end
119
+ # mapping 'TLSv1.2' -> OpenSSL::SSL::OP_NO_TLSv1_2
120
+ disabled_protocols.map! { |v| OpenSSL::SSL.const_get "OP_NO_#{v.sub('.', '_')}" }
121
+ @ssl_context.options = disabled_protocols.reduce(@ssl_context.options, :|)
122
+ end
123
+ @ssl_context
124
+ end
125
+ private :setup_ssl
126
+
127
+ # @note to be able to hook up into #ssl_context from tests
128
+ def new_ssl_context
129
+ OpenSSL::SSL::SSLContext.new
130
+ end
131
+ private :new_ssl_context
132
+
133
+ # @overload Base#register
123
134
  def register
124
135
  require "socket"
125
136
  require "stud/try"
126
- if @ssl_enable
127
- setup_ssl
128
- end # @ssl_enable
129
137
  @closed = Concurrent::AtomicBoolean.new(false)
130
- @thread_no = Concurrent::AtomicFixnum.new(0)
138
+ setup_ssl if @ssl_enable
131
139
 
132
140
  if server?
133
141
  @logger.info("Starting tcp output listener", :address => "#{@host}:#{@port}")
134
142
  begin
135
143
  @server_socket = TCPServer.new(@host, @port)
136
144
  rescue Errno::EADDRINUSE
137
- @logger.error("Could not start TCP server: Address in use",
138
- :host => @host, :port => @port)
145
+ @logger.error("Could not start tcp server: Address in use", host: @host, port: @port)
139
146
  raise
140
147
  end
141
148
  if @ssl_enable
142
149
  @server_socket = OpenSSL::SSL::SSLServer.new(@server_socket, @ssl_context)
143
150
  end # @ssl_enable
144
- @client_threads = []
151
+ @client_threads = Concurrent::Array.new
145
152
 
146
153
  @accept_thread = Thread.new(@server_socket) do |server_socket|
147
154
  LogStash::Util.set_thread_name("[#{pipeline_id}]|output|tcp|server_accept")
148
155
  loop do
149
156
  break if @closed.value
150
- Thread.start(server_socket.accept) do |client_socket|
157
+ client_socket = server_socket.accept_nonblock exception: false
158
+ if client_socket == :wait_readable
159
+ IO.select [ server_socket ]
160
+ next
161
+ end
162
+ Thread.start(client_socket) do |client_socket|
151
163
  # monkeypatch a 'peer' method onto the socket.
152
- client_socket.extend(::LogStash::Util::SocketPeer)
153
- @logger.debug("Accepted connection", :client => client_socket.peer,
154
- :server => "#{@host}:#{@port}")
155
- client = Client.new(client_socket, self)
164
+ client_socket.instance_eval { class << self; include ::LogStash::Util::SocketPeer end }
165
+ @logger.debug("accepted connection", client: client_socket.peer, server: "#{@host}:#{@port}")
166
+ client = Client.new(client_socket, @logger)
156
167
  Thread.current[:client] = client
157
- LogStash::Util.set_thread_name("[#{pipeline_id}]|output|tcp|client_socket-#{@thread_no.increment}")
168
+ LogStash::Util.set_thread_name("[#{pipeline_id}]|output|tcp|client_socket-#{@client_threads.size}")
158
169
  @client_threads << Thread.current
159
170
  client.run unless @closed.value
160
171
  end
@@ -168,62 +179,48 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
168
179
  end
169
180
  end
170
181
  else
171
- @client_socket = nil
172
- peer_info = nil
182
+ client_socket = nil
173
183
  @codec.on_event do |event, payload|
174
184
  begin
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
185
+ client_socket = connect unless client_socket
186
+ r,w,e = IO.select([client_socket], [client_socket], [client_socket], nil)
187
+ # don't expect any reads, but a readable socket might
188
+ # mean the remote end closed, so read it and throw it away.
189
+ # we'll get an EOFError if it happens.
190
+ client_socket.sysread(16384) if r.any?
190
191
 
191
192
  # Now send the payload
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
193
+ client_socket.syswrite(payload) if w.any?
198
194
  rescue => e
199
- log_warn "client socket failed:", e, host: @host, port: @port, socket: peer_info
200
- @client_socket.close rescue nil
201
- @client_socket = nil
195
+ log_warn "client socket failed:", e, host: @host, port: @port, socket: (client_socket ? client_socket.to_s : nil)
196
+ client_socket.close rescue nil
197
+ client_socket = nil
202
198
  sleep @reconnect_interval
203
199
  retry
204
200
  end
205
201
  end
206
202
  end
207
- end # def register
203
+ end
204
+
205
+ # @overload Base#receive
206
+ def receive(event)
207
+ @codec.encode(event)
208
+ end
208
209
 
209
210
  # @overload Base#close
210
211
  def close
211
- if server?
212
- # server-mode clean-up
213
- @closed.make_true
214
- @server_socket.shutdown rescue nil if @server_socket
212
+ @closed.make_true
213
+ @server_socket.close rescue nil if @server_socket
215
214
 
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
215
+ return unless @client_threads
216
+ @client_threads.each do |thread|
217
+ client = thread[:client]
218
+ client.close rescue nil if client
223
219
  end
224
220
  end
225
221
 
226
222
  private
223
+
227
224
  def connect
228
225
  begin
229
226
  client_socket = TCPSocket.new(@host, @port)
@@ -238,26 +235,20 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
238
235
  raise
239
236
  end
240
237
  end
241
- client_socket.extend(::LogStash::Util::SocketPeer)
242
- @logger.debug("Opened connection", :client => "#{client_socket.peer}")
238
+ client_socket.instance_eval { class << self; include ::LogStash::Util::SocketPeer end }
239
+ @logger.debug("opened connection", :client => client_socket.peer)
243
240
  return client_socket
244
- rescue StandardError => e
241
+ rescue => e
245
242
  log_error 'failed to connect:', e
246
243
  sleep @reconnect_interval
247
244
  retry
248
245
  end
249
246
  end # def connect
250
247
 
251
- private
252
248
  def server?
253
249
  @mode == "server"
254
250
  end # def server?
255
251
 
256
- public
257
- def receive(event)
258
- @codec.encode(event)
259
- end # def receive
260
-
261
252
  def pipeline_id
262
253
  execution_context.pipeline_id || 'main'
263
254
  end
@@ -273,4 +264,5 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
273
264
  details[:backtrace] = e.backtrace if backtrace
274
265
  @logger.error(msg, details)
275
266
  end
267
+
276
268
  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.3'
4
+ s.version = '6.1.0'
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
 
@@ -3,14 +3,113 @@ require "logstash/outputs/tcp"
3
3
  require "flores/pki"
4
4
 
5
5
  describe LogStash::Outputs::Tcp do
6
- subject(:instance) { described_class.new(config) }
7
- let(:config) { {
8
- "host" => "localhost",
9
- "port" => 2000 + rand(3000),
10
- } }
6
+ subject { described_class.new(config) }
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
@@ -51,160 +150,81 @@ describe LogStash::Outputs::Tcp do
51
150
  end
52
151
 
53
152
  end
54
- end
55
153
 
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
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)
63
163
  end
64
164
 
65
- context 'with valid password' do
66
-
67
- let(:config) { super().merge("ssl_key_passphrase" => '1234567890') }
165
+ let(:server_min_version) { nil }
166
+ let(:server_max_version) { nil }
167
+ let(:server_ssl_version) { nil }
68
168
 
69
- it "registers without error" do
70
- expect { subject.register }.to_not raise_error
71
- end
169
+ context 'with supported protocol' do
72
170
 
73
- end
74
- end
75
- end
171
+ let(:config) { super().merge("ssl_supported_protocols" => ['TLSv1.2']) }
76
172
 
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
173
+ let(:server_min_version) { 'TLS1_2' }
91
174
 
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
175
+ before { subject.register }
176
+ after { secure_server.close }
95
177
 
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) }
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
100
185
 
101
- let(:config) do
102
- { 'host' => server_host, 'port' => server_port, 'mode' => 'client' }
103
186
  end
104
187
 
105
- let(:event) { LogStash::Event.new({"hello" => "world"})}
188
+ context 'with unsupported protocol (on server)' do
106
189
 
107
- subject(:instance) { described_class.new(config) }
190
+ let(:config) { super().merge("ssl_supported_protocols" => ['TLSv1.1']) }
108
191
 
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
192
+ let(:server_min_version) { 'TLS1_2' }
117
193
 
118
- after(:each) do
119
- @server_socket_thread&.join
120
- end
194
+ before { subject.register }
195
+ after { secure_server.close }
121
196
 
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
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
129
202
 
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}"))
203
+ Thread.start { secure_server.accept rescue nil }
204
+ expect { subject.receive event }.to throw_symbol(:TEST_DONE)
141
205
  end
142
- end
143
- end
144
- end
145
206
 
146
- context 'server mode' do
207
+ end if LOGSTASH_VERSION > '7.0'
147
208
 
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
209
  end
156
210
 
157
- context 'transmitting data' do
158
- let(:server_host) { 'localhost' }
159
- let(:server_port) { Random.rand(1024...5000) }
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) }
160
215
 
161
- let(:config) do
162
- { 'host' => server_host, 'port' => server_port, 'mode' => 'server' }
216
+ it "registers with error (due missing password)" do
217
+ expect { subject.register }.to raise_error(OpenSSL::PKey::RSAError) # TODO need a better error
163
218
  end
164
219
 
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)
220
+ context 'with valid password' do
184
221
 
185
- wait_for_condition(30) { !io.size.zero? }
186
- sleep 1 # wait for the event to get sent...
187
- instance.close # release the connection
222
+ let(:config) { super().merge("ssl_key_passphrase" => '1234567890') }
188
223
 
189
- @client_socket_thread.join(30) || fail('client failed to join')
190
- expect(io.string).to include('"hello"','"world"')
224
+ it "registers without error" do
225
+ expect { subject.register }.to_not raise_error
191
226
  end
192
227
 
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
228
  end
209
229
  end
210
230
  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.3
4
+ version: 6.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-04 00:00:00.000000000 Z
11
+ date: 2022-06-06 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: