logstash-output-tcp 6.0.3 → 6.1.0

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