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 +4 -4
- data/CHANGELOG.md +3 -5
- data/docs/index.asciidoc +15 -0
- data/lib/logstash/outputs/tcp.rb +72 -80
- data/logstash-output-tcp.gemspec +5 -2
- data/spec/outputs/tcp_spec.rb +152 -132
- metadata +44 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d30cf25cfbc7e1cc2e72d5e1eb8ab8179d42b31413a549801a012d5e1be7e640
|
4
|
+
data.tar.gz: 53632382865d4bade1e6d56e9872b1b5db690fd9b866cb4d0952dfa8d1156cee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e48f3511c25df04edb79dfd0425c6a84d18808bce5f8d4910cabe024955e6c16f8b856a8ac7fac01cb7721e7628934101e14e7180497a7c6cc1096169290963e
|
7
|
+
data.tar.gz: 94758e5adb6be529f4c70715aa0796d1a15bd8d17e91d35c63fdc1b29eccea58c0f36e4c29b0cf911a3691fade34c895922ca9b0384cd3df405f723a97ff642f
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,6 @@
|
|
1
|
-
## 6.0
|
2
|
-
-
|
3
|
-
|
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
|
|
data/lib/logstash/outputs/tcp.rb
CHANGED
@@ -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
|
-
|
56
|
-
|
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
|
-
|
58
|
+
|
59
|
+
def initialize(socket, logger)
|
59
60
|
@socket = socket
|
60
|
-
@
|
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
|
-
|
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
|
-
|
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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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.
|
153
|
-
@logger.debug("
|
154
|
-
|
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-#{@
|
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
|
-
|
172
|
-
peer_info = nil
|
182
|
+
client_socket = nil
|
173
183
|
@codec.on_event do |event, payload|
|
174
184
|
begin
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
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
|
-
|
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:
|
200
|
-
|
201
|
-
|
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
|
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
|
-
|
212
|
-
|
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
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
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.
|
242
|
-
@logger.debug("
|
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
|
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
|
data/logstash-output-tcp.gemspec
CHANGED
@@ -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
|
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
|
|
data/spec/outputs/tcp_spec.rb
CHANGED
@@ -3,14 +3,113 @@ require "logstash/outputs/tcp"
|
|
3
3
|
require "flores/pki"
|
4
4
|
|
5
5
|
describe LogStash::Outputs::Tcp do
|
6
|
-
subject
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
66
|
-
|
67
|
-
|
165
|
+
let(:server_min_version) { nil }
|
166
|
+
let(:server_max_version) { nil }
|
167
|
+
let(:server_ssl_version) { nil }
|
68
168
|
|
69
|
-
|
70
|
-
expect { subject.register }.to_not raise_error
|
71
|
-
end
|
169
|
+
context 'with supported protocol' do
|
72
170
|
|
73
|
-
|
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
|
-
|
93
|
-
|
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
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
-
|
188
|
+
context 'with unsupported protocol (on server)' do
|
106
189
|
|
107
|
-
|
190
|
+
let(:config) { super().merge("ssl_supported_protocols" => ['TLSv1.1']) }
|
108
191
|
|
109
|
-
|
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
|
-
|
119
|
-
|
120
|
-
end
|
194
|
+
before { subject.register }
|
195
|
+
after { secure_server.close }
|
121
196
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
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
|
-
|
131
|
-
|
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
|
-
|
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
|
158
|
-
let(:
|
159
|
-
let(:
|
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
|
-
|
162
|
-
{
|
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
|
-
|
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
|
-
|
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
|
-
|
190
|
-
expect
|
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
|
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
|
+
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:
|