logstash-output-tcp 6.0.1 → 6.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/lib/logstash/outputs/tcp.rb +96 -28
- data/logstash-output-tcp.gemspec +1 -1
- data/spec/fixtures/encrypted/instance.crt +19 -0
- data/spec/fixtures/encrypted/instance.key +30 -0
- data/spec/fixtures/plaintext/instance.crt +19 -0
- data/spec/fixtures/plaintext/instance.key +27 -0
- data/spec/outputs/tcp_spec.rb +180 -2
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5db164894be7c046e3dce9337af0ce5b684f19b1b35274a0d3b925e97fe402c8
|
4
|
+
data.tar.gz: 63dbb52d49285af564f83de8218c3af098b1d0f62d9deee019c15eae5cbd98e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 10b22e722c22edce1fd3699e876ab898283d66ec0aa256f9d9c6159112bcf54f872b4e68d8bc7eb72b999c54442d5b65c69fa87194f6c6a58623f7c1939c9c09
|
7
|
+
data.tar.gz: eb6477d5f227184f46bac1c01453215fbd2f2ebf3c0fbb819413d5555d2ab7472ab61b11d737016487b3d1255bcc65fce008f350683ad28e9468fbb1f55ded49
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
## 6.0.3
|
2
|
+
- Pulled applicable back-ports from 6.1.0 [#50](https://github.com/logstash-plugins/logstash-output-tcp/pull/50)
|
3
|
+
- Fix: Ensure sockets are closed when this plugin is closed
|
4
|
+
- Fix: Fixes an issue in client mode where payloads larger than a connection's current TCP window could be silently truncated
|
5
|
+
- Fix: Fixes an issue in server mode where payloads larger than a connection's current TCP window could be silently truncated
|
6
|
+
|
7
|
+
## 6.0.2
|
8
|
+
- Fix: unable to start with password protected key [#45](https://github.com/logstash-plugins/logstash-output-tcp/pull/45)
|
9
|
+
|
1
10
|
## 6.0.1
|
2
11
|
- Fixed logging fail retry to stdout [#43](https://github.com/logstash-plugins/logstash-output-tcp/pull/43)
|
3
12
|
- Fixed to use `reconnect_interval` when establish a connection
|
data/lib/logstash/outputs/tcp.rb
CHANGED
@@ -51,31 +51,44 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
|
|
51
51
|
# SSL key passphrase
|
52
52
|
config :ssl_key_passphrase, :validate => :password, :default => nil
|
53
53
|
|
54
|
+
##
|
55
|
+
# @param socket [Socket]
|
56
|
+
# @param logger_context [#log_warn&#log_error&#logger]
|
54
57
|
class Client
|
55
|
-
|
56
|
-
def initialize(socket, logger)
|
58
|
+
def initialize(socket, logger_context)
|
57
59
|
@socket = socket
|
58
|
-
@
|
60
|
+
@peer_info = socket.peer
|
61
|
+
@logger_context = logger_context
|
59
62
|
@queue = Queue.new
|
60
63
|
end
|
61
64
|
|
62
|
-
public
|
63
65
|
def run
|
64
66
|
loop do
|
65
67
|
begin
|
66
|
-
@
|
68
|
+
payload = @queue.pop
|
69
|
+
|
70
|
+
@logger_context.logger.trace("transmitting #{payload.bytesize} bytes", socket: @peer_info) if @logger_context.logger.trace? && payload && !payload.empty?
|
71
|
+
while payload && !payload.empty?
|
72
|
+
written_bytes_size = @socket.write(payload)
|
73
|
+
payload = payload.byteslice(written_bytes_size..-1)
|
74
|
+
@logger_context.logger.log_trace(">transmitted #{written_bytes_size} bytes; #{payload.bytesize} bytes remain", socket: @peer_info) if @logger_context.logger.trace?
|
75
|
+
end
|
67
76
|
rescue => e
|
68
|
-
@
|
69
|
-
:exception => e)
|
77
|
+
@logger_context.log_warn("tcp output exception: socket write failed", e, :socket => @peer_info)
|
70
78
|
break
|
71
79
|
end
|
72
80
|
end
|
73
81
|
end # def run
|
74
82
|
|
75
|
-
public
|
76
83
|
def write(msg)
|
77
84
|
@queue.push(msg)
|
78
85
|
end # def write
|
86
|
+
|
87
|
+
def close
|
88
|
+
@socket.close
|
89
|
+
rescue => e
|
90
|
+
@logger_context.log_warn 'socket close failed:', e, socket: @socket&.to_s
|
91
|
+
end
|
79
92
|
end # class Client
|
80
93
|
|
81
94
|
private
|
@@ -86,7 +99,10 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
|
|
86
99
|
if @ssl_cert
|
87
100
|
@ssl_context.cert = OpenSSL::X509::Certificate.new(File.read(@ssl_cert))
|
88
101
|
if @ssl_key
|
89
|
-
|
102
|
+
# if we have an encrypted key and a password is not provided (nil) than OpenSSL::PKey::RSA
|
103
|
+
# prompts the user to enter a password interactively - we do not want to do that,
|
104
|
+
# for plain-text keys the default '' password argument gets simply ignored
|
105
|
+
@ssl_context.key = OpenSSL::PKey::RSA.new(File.read(@ssl_key), @ssl_key_passphrase.value || '')
|
90
106
|
end
|
91
107
|
end
|
92
108
|
if @ssl_verify
|
@@ -110,6 +126,8 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
|
|
110
126
|
if @ssl_enable
|
111
127
|
setup_ssl
|
112
128
|
end # @ssl_enable
|
129
|
+
@closed = Concurrent::AtomicBoolean.new(false)
|
130
|
+
@thread_no = Concurrent::AtomicFixnum.new(0)
|
113
131
|
|
114
132
|
if server?
|
115
133
|
@logger.info("Starting tcp output listener", :address => "#{@host}:#{@port}")
|
@@ -126,44 +144,61 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
|
|
126
144
|
@client_threads = []
|
127
145
|
|
128
146
|
@accept_thread = Thread.new(@server_socket) do |server_socket|
|
147
|
+
LogStash::Util.set_thread_name("[#{pipeline_id}]|output|tcp|server_accept")
|
129
148
|
loop do
|
149
|
+
break if @closed.value
|
130
150
|
Thread.start(server_socket.accept) do |client_socket|
|
131
151
|
# monkeypatch a 'peer' method onto the socket.
|
132
|
-
client_socket.
|
152
|
+
client_socket.extend(::LogStash::Util::SocketPeer)
|
133
153
|
@logger.debug("Accepted connection", :client => client_socket.peer,
|
134
154
|
:server => "#{@host}:#{@port}")
|
135
|
-
client = Client.new(client_socket,
|
155
|
+
client = Client.new(client_socket, self)
|
136
156
|
Thread.current[:client] = client
|
157
|
+
LogStash::Util.set_thread_name("[#{pipeline_id}]|output|tcp|client_socket-#{@thread_no.increment}")
|
137
158
|
@client_threads << Thread.current
|
138
|
-
client.run
|
159
|
+
client.run unless @closed.value
|
139
160
|
end
|
140
161
|
end
|
141
162
|
end
|
142
163
|
|
143
164
|
@codec.on_event do |event, payload|
|
165
|
+
@client_threads.select!(&:alive?)
|
144
166
|
@client_threads.each do |client_thread|
|
145
167
|
client_thread[:client].write(payload)
|
146
168
|
end
|
147
|
-
@client_threads.reject! {|t| !t.alive? }
|
148
169
|
end
|
149
170
|
else
|
150
|
-
client_socket = nil
|
171
|
+
@client_socket = nil
|
172
|
+
peer_info = nil
|
151
173
|
@codec.on_event do |event, payload|
|
152
174
|
begin
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
175
|
+
# not threadsafe; this is why we require `concurrency: single`
|
176
|
+
unless @client_socket
|
177
|
+
@client_socket = connect
|
178
|
+
peer_info = @client_socket.peer
|
179
|
+
end
|
180
|
+
|
181
|
+
writable_io = nil
|
182
|
+
while writable_io.nil? || writable_io.any? == false
|
183
|
+
readable_io, writable_io, _ = IO.select([@client_socket],[@client_socket])
|
184
|
+
|
185
|
+
# don't expect any reads, but a readable socket might
|
186
|
+
# mean the remote end closed, so read it and throw it away.
|
187
|
+
# we'll get an EOFError if it happens.
|
188
|
+
readable_io.each { |readable| readable.sysread(16384) }
|
189
|
+
end
|
159
190
|
|
160
191
|
# Now send the payload
|
161
|
-
|
192
|
+
@logger.trace("transmitting #{payload.bytesize} bytes", socket: peer_info) if @logger.trace? && payload && !payload.empty?
|
193
|
+
while payload && payload.bytesize > 0
|
194
|
+
written_bytes_size = @client_socket.syswrite(payload)
|
195
|
+
payload = payload.byteslice(written_bytes_size..-1)
|
196
|
+
@logger.trace(">transmitted #{written_bytes_size} bytes; #{payload.bytesize} bytes remain", socket: peer_info) if @logger.trace?
|
197
|
+
end
|
162
198
|
rescue => e
|
163
|
-
|
164
|
-
|
165
|
-
client_socket
|
166
|
-
client_socket = nil
|
199
|
+
log_warn "client socket failed:", e, host: @host, port: @port, socket: peer_info
|
200
|
+
@client_socket.close rescue nil
|
201
|
+
@client_socket = nil
|
167
202
|
sleep @reconnect_interval
|
168
203
|
retry
|
169
204
|
end
|
@@ -171,6 +206,23 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
|
|
171
206
|
end
|
172
207
|
end # def register
|
173
208
|
|
209
|
+
# @overload Base#close
|
210
|
+
def close
|
211
|
+
if server?
|
212
|
+
# server-mode clean-up
|
213
|
+
@closed.make_true
|
214
|
+
@server_socket.shutdown rescue nil if @server_socket
|
215
|
+
|
216
|
+
@client_threads&.each do |thread|
|
217
|
+
client = thread[:client]
|
218
|
+
client.close rescue nil if client
|
219
|
+
end
|
220
|
+
else
|
221
|
+
# client-mode clean-up
|
222
|
+
@client_socket&.close
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
174
226
|
private
|
175
227
|
def connect
|
176
228
|
begin
|
@@ -180,17 +232,17 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
|
|
180
232
|
begin
|
181
233
|
client_socket.connect
|
182
234
|
rescue OpenSSL::SSL::SSLError => ssle
|
183
|
-
|
235
|
+
log_error 'connect ssl failure:', ssle, backtrace: false
|
184
236
|
# NOTE(mrichar1): Hack to prevent hammering peer
|
185
237
|
sleep(5)
|
186
238
|
raise
|
187
239
|
end
|
188
240
|
end
|
189
|
-
client_socket.
|
241
|
+
client_socket.extend(::LogStash::Util::SocketPeer)
|
190
242
|
@logger.debug("Opened connection", :client => "#{client_socket.peer}")
|
191
243
|
return client_socket
|
192
244
|
rescue StandardError => e
|
193
|
-
|
245
|
+
log_error 'failed to connect:', e
|
194
246
|
sleep @reconnect_interval
|
195
247
|
retry
|
196
248
|
end
|
@@ -205,4 +257,20 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
|
|
205
257
|
def receive(event)
|
206
258
|
@codec.encode(event)
|
207
259
|
end # def receive
|
260
|
+
|
261
|
+
def pipeline_id
|
262
|
+
execution_context.pipeline_id || 'main'
|
263
|
+
end
|
264
|
+
|
265
|
+
def log_warn(msg, e, backtrace: @logger.debug?, **details)
|
266
|
+
details = details.merge message: e.message, exception: e.class
|
267
|
+
details[:backtrace] = e.backtrace if backtrace
|
268
|
+
@logger.warn(msg, details)
|
269
|
+
end
|
270
|
+
|
271
|
+
def log_error(msg, e, backtrace: @logger.info?, **details)
|
272
|
+
details = details.merge message: e.message, exception: e.class
|
273
|
+
details[:backtrace] = e.backtrace if backtrace
|
274
|
+
@logger.error(msg, details)
|
275
|
+
end
|
208
276
|
end # class LogStash::Outputs::Tcp
|
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.0.3'
|
5
5
|
s.licenses = ['Apache License (2.0)']
|
6
6
|
s.summary = "Writes events over a TCP socket"
|
7
7
|
s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIDIjCCAgqgAwIBAgIUTCjyocBgCYSWU/GYI58F0IBXLHswDQYJKoZIhvcNAQEL
|
3
|
+
BQAwNDEyMDAGA1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5l
|
4
|
+
cmF0ZWQgQ0EwHhcNMjIwMzIxMDc1NTU2WhcNMjUwMzIwMDc1NTU2WjATMREwDwYD
|
5
|
+
VQQDEwhpbnN0YW5jZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJKT
|
6
|
+
f0FF/R9WBC7lwN0IJdOn+jPJJ+4M3LY/sfFuTaAUgPSaiRLLzCjox1p1Xy9z+xyk
|
7
|
+
C2Dkhb4FdOZBNqS/w0Z7lkrha8VZg2UKW4+i1lq4ANPl9sP9YCbh1R+SRdpHnZX7
|
8
|
+
APG798+0+NC4GmEeOIFfWsEE4MiFXAPunhAlAbxMl6e5htX4cgYWMbH67Ur1/uQB
|
9
|
+
26Y6PBahKP7dbxaL/ugPE6MDmmA+cNRWMa21Ay2h94IesRXwNRFn1gQ1SqKU0hBJ
|
10
|
+
KX7u0IoYyS6nbDlF6E+npLLWzDMCHsykrnadWV5kDSNv1t7Wvk0noNStrryOXgtc
|
11
|
+
7iCCOX1oqsP/yDNl9OECAwEAAaNNMEswHQYDVR0OBBYEFBtzuCJPEhHW3ZD1bReq
|
12
|
+
S6K3P09OMB8GA1UdIwQYMBaAFJcC1Mw29qzRV0dPCCc6GyB8LXx3MAkGA1UdEwQC
|
13
|
+
MAAwDQYJKoZIhvcNAQELBQADggEBAHvXsfMgY+PTBCnW3A6QCGZ3jTSRqqXRFYB8
|
14
|
+
b/unfhD4iUFlgO4a/UCL2MTsFYXkzvlzwQyH6ll9/rTslBvJLiqi3+IZMfGNoaNI
|
15
|
+
LhTLy0NF9scPju7Q6MxcPAceI4gmxKyBWJxm4XYJ1VwmPKJNzKqFEwfPWMakxUaT
|
16
|
+
wiuhVodqZweWi7S2keYiduBLEbqWBJPKAnb8e3PIILBqL0nfhmVw0NSAD19d9oLR
|
17
|
+
89AaY8zQ4YL4Txs4FSrK0VQl3L6NAtAaq9JvXqzXD/E0EO1YL8ykM3Zash/GG9iN
|
18
|
+
B9Momegqn8/q7pfoWdkE2oL9KUKispUT9Fq3mY6KHZL1T4SbxL8=
|
19
|
+
-----END CERTIFICATE-----
|
@@ -0,0 +1,30 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
Proc-Type: 4,ENCRYPTED
|
3
|
+
DEK-Info: AES-128-CBC,635374A0A566A0189BE4366E77881457
|
4
|
+
|
5
|
+
LbCmLGC8BAWKYUt+fYIIxUC5rh3D7CFN8LXjjGIYMVRs2seMqhCNJjtdLQ6poin5
|
6
|
+
H6W/c5+VXzzsgczFgZnQecoqWgqg4FoTZSacVVx/waekkdua9zjPReastV+7AcBG
|
7
|
+
MJJi8lYEIv+qacqxEzDJSis7XO0YXp2OBqUtfcaT5PunJ1bj5SQnqf7k1hb193bZ
|
8
|
+
C03MhZ6HdblY6IKvslqV5VWU38gPN0F6JN5/7vLgZyExYjTA1wkzdJXCzKAEwlfU
|
9
|
+
vF5/AKDGFpQAYMTnPwszpeUj+wyuAwaxt8uNkVFrfVIf+eaPqX1OStEAIiRNZerl
|
10
|
+
dTsKxLdYBBJz6G9wZLz+bWNpFsvS9gG1LlvMsycnwrfmwWTidmS8nDdLVfja2/XH
|
11
|
+
LXvwSnI+NHtnQ/R+duMZpJrqQljBZAZsyzW8Eyjaqi1ybwi50J/pwsX/J0jVQly/
|
12
|
+
Gr8A/SDfr1qJ82SQUfi0ZGoKVXaSJ31pPKBy9LpDGFZS7rFJsL/Aysd2lhyF/Tj1
|
13
|
+
kG60MAxFjfoRsNhBWj7pm/13hVlidwNrmhbzrnsFPuWDVWFYq71oljJ+YLlbljLB
|
14
|
+
L6rnQXr2iy+y3W03lryAsxtCrcJ6Sa+9EUNp55loGhK3EXCXnhNr3B1SfxGCXbAv
|
15
|
+
GI2h1RRbn/Pnht17pkiXsajE8vESWLqzZj6gtmYmMHQ+GY5l8SHTTMhV2DpYZFfE
|
16
|
+
nnUxyPOlXnm2uegQRf/ee8kKeUQZl9Bs2ZyniE0uzRGZqhQLlJCwYhOmEYFsv5kk
|
17
|
+
vD1Go4N2NqbMOipCFHbnm3IvG1rOBYtqHzHLwycsJw+2f1Dyu5T9GlEcJL7GeCWO
|
18
|
+
muY4Gjx7p84N1wwJ/IN4BRjkMD0qAXbKg9cnh/nDSaFgMez6jiaR+lJGV/srfpwB
|
19
|
+
6bDOpRN/RarjRcQGVEfkP/UqJVWU8ZwnNqar5202WGSXI9HUYTQapVCh2NvBdYOS
|
20
|
+
VV+Ah2qE48s3qI95h80Ix0G7Or7ALJbSn6d190frUnXOUOye4+4MB3w+G0kGTQq8
|
21
|
+
w+pjqrSAGLA3yobrPki374X3ZMdYbdggDHn3pl/NvQVHZ4FRmYXyUVZfckV/7w0C
|
22
|
+
t1wBqh83ZM3XusivAy0/g6/124Xe2jkl6B0aNlAtxj3VTMBJNjLRIpoX5hR6TZq8
|
23
|
+
cHRvWaIOif8oOn6Q6PIGtpsAjejZ0jUPuvPHUwuYSYFUk/WH09k8tXD/ZZnoKZWV
|
24
|
+
xqrx3cBCKjoBu9HnPsPoQ6sedaM/L/i3osW7GBSMt2GApOqDoKQ70Ya6TSmbnDYv
|
25
|
+
roYy/R5vDqzTGK0gUyLNNCNhpuzEXjjaCwXegJ/GYl3mwcB8o5y1insyqfc0JPy1
|
26
|
+
oF0tYxw62Ovx9Udg9Ib94q0BQDhi5L55QZYKQAAz4+CW71aG30ETw32EtnUMnB4f
|
27
|
+
2cswibLj7JhER/4pjOoKHF47wLJtlWlaceFvGWzno8uSY74qaRQHslwNHwx61I2q
|
28
|
+
rhsbD1wsvyEkxlIdSIlyUf8yj5LXWNMoixFppqh4VY4OAINxkNBEaJRzU6aMKn1n
|
29
|
+
Btvg2OePOvebCvJRvWousIgB2brZ5ioU336Tjb0uDeHIPF51+o78wtaS+jK084Bu
|
30
|
+
-----END RSA PRIVATE KEY-----
|
@@ -0,0 +1,19 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIDIjCCAgqgAwIBAgIURM555Ac4/c8UOP5fB12+1bo+b7owDQYJKoZIhvcNAQEL
|
3
|
+
BQAwNDEyMDAGA1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5l
|
4
|
+
cmF0ZWQgQ0EwHhcNMjIwMzIxMDc1NTA4WhcNMjUwMzIwMDc1NTA4WjATMREwDwYD
|
5
|
+
VQQDEwhpbnN0YW5jZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAI0s
|
6
|
+
sYPGWeca0YMIVATxnEA/+WFL/eWWcbaX5JeaJA+g6WxMs2B6FEMfaSxauGrHZJWt
|
7
|
+
YNP1s0pptzJEPAZyx3Tkv8oNtSai3rpOocY3ebE/SWXlQd7xHWvy+w5ls1LRHINU
|
8
|
+
pEnMZhjdHqXFjIipmsAXihzU/BrHkWGfddco88YuaMgtWbLIqQr/oHyd2F6KgQby
|
9
|
+
HPSD35hCYvLQiHEY7vIntA6A9mYSdbZg0ZvI+QGEvZmhYXZjKOy7Vfg8p1zmfbIn
|
10
|
+
XdddCOkBp5gXDu8aRxigaQAi3Y+ESnVPxDr62+VEzCdmd/PCW8blHfvE9bTgqjYm
|
11
|
+
gH01PAoqQewkuxclufECAwEAAaNNMEswHQYDVR0OBBYEFNIW9layQi7V2kbYjwIu
|
12
|
+
NWT0QXOdMB8GA1UdIwQYMBaAFEyJvLXizLXiGu8sBnIcIS001ArxMAkGA1UdEwQC
|
13
|
+
MAAwDQYJKoZIhvcNAQELBQADggEBAAOnGIKiJxEc684Y6w1WpRftiD8VnkzE+A8n
|
14
|
+
uVXJibZzEKqxMWZ83A4lTugG+vKpOj1+8hcYUdXhIo2Qt92ArIOr2S7awdkMeoRq
|
15
|
+
eIKPElC9/8mm3572kdlqBuiT12bA2oD4zrI2hS0HV21DMLAxoorzLqn0TjMnnpm3
|
16
|
+
J1KLJzzMSRtiUtwiLXG4tx2ThXfTwcJmsRMQqZW/mFajEVs0jafUAeBH7hZoZX8o
|
17
|
+
7e0O3TCQ5JWW3/xeGK/JnAZ1IyA6jQU/J5LbGgPVu0FBGmVn3PhtwYASrdXWzK4s
|
18
|
+
ncofcBHflmi8dkiKHVWNnOTdZdzkApYo59/eq4iDESR8YNgOCKA=
|
19
|
+
-----END CERTIFICATE-----
|
@@ -0,0 +1,27 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
MIIEpAIBAAKCAQEAjSyxg8ZZ5xrRgwhUBPGcQD/5YUv95ZZxtpfkl5okD6DpbEyz
|
3
|
+
YHoUQx9pLFq4asdkla1g0/WzSmm3MkQ8BnLHdOS/yg21JqLeuk6hxjd5sT9JZeVB
|
4
|
+
3vEda/L7DmWzUtEcg1SkScxmGN0epcWMiKmawBeKHNT8GseRYZ911yjzxi5oyC1Z
|
5
|
+
ssipCv+gfJ3YXoqBBvIc9IPfmEJi8tCIcRju8ie0DoD2ZhJ1tmDRm8j5AYS9maFh
|
6
|
+
dmMo7LtV+DynXOZ9sidd110I6QGnmBcO7xpHGKBpACLdj4RKdU/EOvrb5UTMJ2Z3
|
7
|
+
88JbxuUd+8T1tOCqNiaAfTU8CipB7CS7FyW58QIDAQABAoIBAD10dD4J7X72JLgm
|
8
|
+
uvR//OXXM4cQXpFAAXZb/s2j8wi+on5bkUZxPjrOBKmjQF5zOC0UEW+TqJ2/EVmX
|
9
|
+
bI3eD0eqgHbDqtUL12tA6Zlw8s+e3iO2Pgt/6K/iUTm+OebWUtQ012Osz9EJCNte
|
10
|
+
+MNRGaV/WccdTDWYJIhbsx+bmyrsxxW49rwvgxI26tpJYp0QEmwEiDqfc8kRFWgy
|
11
|
+
xVSt8Rjwk418YdsYUIGrO6RsGkwsCOTJWUJpBOzLBKwj4do5l5fC2g1/MSlj7Odj
|
12
|
+
0HH/riBmzBj/YcyMlkhfGU0Zj/OlDnz8FP5HhfpYsGxvrjOYJ49Cl4cu8r47wnFP
|
13
|
+
nx4RMmUCgYEA4U9hOtp8oJ+Ztio/1I6bAb/hKLJ3LNeV46697/lTy6tSVmYJ1DGJ
|
14
|
+
YU3P5wmOpjZC2xs22VyX/vgC1v5RT0Jg8CGgXukmMV1gQQch6gAQci9+ZJ4LRz9U
|
15
|
+
++dXyKuUbqncx9ezFwddBr+jQuu6Wn2AHljEp7wZGaijxD2lsp4B8vcCgYEAoGd8
|
16
|
+
w+hre2pGy5PKJPSoPp+9tNVJasXe3DM6ZYEZkd+96gStA+8UlSYe9hP+Taik7oUJ
|
17
|
+
SYzELfyU8s+9XWozaa1T5PeyY8jIbEnUHWy8Uag4dHY7ooR+7b3iAD4RLfx3o2GP
|
18
|
+
vJwP3i2FwklDbbyugVXZBfkSf2NJ4QidYIWzGFcCgYAD4MHjqW8LtLOIlyGSHwI7
|
19
|
+
/Xl6ode7RdqmmJNcVgZDMyevpQH2TQP4UMaLS3bRFY4BB27iPt2+3bXuzWHI43OX
|
20
|
+
rnx8JbcqkljdxamnxWiDDp42TSIUj9p+m3S/V3Suku3h4qyKcO4A97tvo28Jr69M
|
21
|
+
1mpMGMi10FlBP25irKWL8QKBgQCZqKlnjrWwA24QROJnpoupeiMkIRH0m9rS/Kwb
|
22
|
+
YqHZEQoALTyEwTnpaxxLxXlecYiWCZGNCLFCEG2rcQBJhZv8xxLQC8yzNDtzKQJu
|
23
|
+
saRxYQG75ytXky94lebzLoIMmIcPVz13g9TblKZHKSHT9OUCdvewdhqXN8klLrh8
|
24
|
+
J3gafwKBgQDcn/8lzfmbsKO/V9fzEbqUQJOJkwyf2adLFD+KlHM3+VAghnj70j05
|
25
|
+
1XDkQ3LOgKrqPdC/HIGjjlkTn6vaieXltHguMg99nPpzj+LXrtN22ru2eTjjUNsD
|
26
|
+
+uB9N4kgUAR00xu60xkkOSK1TcudeL5uwejO2ul0tkDu1YGDvhXymA==
|
27
|
+
-----END RSA PRIVATE KEY-----
|
data/spec/outputs/tcp_spec.rb
CHANGED
@@ -3,7 +3,7 @@ require "logstash/outputs/tcp"
|
|
3
3
|
require "flores/pki"
|
4
4
|
|
5
5
|
describe LogStash::Outputs::Tcp do
|
6
|
-
subject { described_class.new(config) }
|
6
|
+
subject(:instance) { described_class.new(config) }
|
7
7
|
let(:config) { {
|
8
8
|
"host" => "localhost",
|
9
9
|
"port" => 2000 + rand(3000),
|
@@ -16,6 +16,7 @@ describe LogStash::Outputs::Tcp do
|
|
16
16
|
expect { subject.register }.to_not raise_error
|
17
17
|
end
|
18
18
|
end
|
19
|
+
|
19
20
|
context "and providing a certificate/key pair" do
|
20
21
|
let(:cert_key_pair) { Flores::PKI.generate }
|
21
22
|
let(:certificate) { cert_key_pair.first }
|
@@ -24,10 +25,187 @@ describe LogStash::Outputs::Tcp do
|
|
24
25
|
IO.write(path, certificate.to_s)
|
25
26
|
path
|
26
27
|
end
|
27
|
-
let(:config) { super().merge("ssl_cert" =>
|
28
|
+
let(:config) { super().merge("ssl_cert" => cert_file) }
|
28
29
|
it "registers without error" do
|
29
30
|
expect { subject.register }.to_not raise_error
|
30
31
|
end
|
31
32
|
end
|
33
|
+
|
34
|
+
FIXTURES_PATH = File.expand_path('../fixtures', File.dirname(__FILE__))
|
35
|
+
|
36
|
+
context "ES generated plain-text certificate/key" do
|
37
|
+
let(:key_file) { File.join(FIXTURES_PATH, 'plaintext/instance.key') }
|
38
|
+
let(:crt_file) { File.join(FIXTURES_PATH, 'plaintext/instance.crt') }
|
39
|
+
let(:config) { super().merge("ssl_cert" => crt_file, "ssl_key" => key_file) }
|
40
|
+
|
41
|
+
it "registers without error" do
|
42
|
+
expect { subject.register }.to_not raise_error
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'with password set' do
|
46
|
+
|
47
|
+
let(:config) { super().merge("ssl_key_passphrase" => 'ignored') }
|
48
|
+
|
49
|
+
it "registers without error" do # password simply ignored
|
50
|
+
expect { subject.register }.to_not raise_error
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "encrypted key using PKCS#1" do
|
57
|
+
let(:key_file) { File.join(FIXTURES_PATH, 'encrypted/instance.key') }
|
58
|
+
let(:crt_file) { File.join(FIXTURES_PATH, 'encrypted/instance.crt') }
|
59
|
+
let(:config) { super().merge("ssl_cert" => crt_file, "ssl_key" => key_file) }
|
60
|
+
|
61
|
+
it "registers with error (due missing password)" do
|
62
|
+
expect { subject.register }.to raise_error(OpenSSL::PKey::RSAError) # TODO need a better error
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'with valid password' do
|
66
|
+
|
67
|
+
let(:config) { super().merge("ssl_key_passphrase" => '1234567890') }
|
68
|
+
|
69
|
+
it "registers without error" do
|
70
|
+
expect { subject.register }.to_not raise_error
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Reads `in_io` until EOF and writes the bytes
|
79
|
+
# it receives to `out_io`, tolerating partial writes.
|
80
|
+
def siphon_until_eof(in_io, out_io)
|
81
|
+
buffer = ""
|
82
|
+
while (retval = in_io.read_nonblock(32*1024, buffer, exception:false)) do
|
83
|
+
(IO.select([in_io], nil, nil, 5); next) if retval == :wait_readable
|
84
|
+
|
85
|
+
while (buffer && !buffer.empty?) do
|
86
|
+
bytes_written = out_io.write(buffer)
|
87
|
+
buffer.replace buffer.byteslice(bytes_written..-1)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'client mode' do
|
93
|
+
context 'transmitting data' do
|
94
|
+
let!(:io) { StringIO.new } # somewhere for our server to stash the data it receives
|
95
|
+
|
96
|
+
let(:server_host) { 'localhost' }
|
97
|
+
let(:server_port) { server.addr[1] } # get actual since we bind to port 0
|
98
|
+
|
99
|
+
let!(:server) { TCPServer.new(server_host, 0) }
|
100
|
+
|
101
|
+
let(:config) do
|
102
|
+
{ 'host' => server_host, 'port' => server_port, 'mode' => 'client' }
|
103
|
+
end
|
104
|
+
|
105
|
+
let(:event) { LogStash::Event.new({"hello" => "world"})}
|
106
|
+
|
107
|
+
subject(:instance) { described_class.new(config) }
|
108
|
+
|
109
|
+
before(:each) do
|
110
|
+
# accepts ONE connection
|
111
|
+
@server_socket_thread = Thread.start do
|
112
|
+
client = server.accept
|
113
|
+
siphon_until_eof(client, io)
|
114
|
+
end
|
115
|
+
instance.register
|
116
|
+
end
|
117
|
+
|
118
|
+
after(:each) do
|
119
|
+
@server_socket_thread&.join
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'encodes and transmits data' do
|
123
|
+
instance.receive(event)
|
124
|
+
sleep 1
|
125
|
+
instance.close # release the connection
|
126
|
+
@server_socket_thread.join(30) || fail('server failed to join')
|
127
|
+
expect(io.string).to include('"hello"','"world"')
|
128
|
+
end
|
129
|
+
|
130
|
+
context 'when payload is very large' do
|
131
|
+
let(:one_hundred_megabyte_message) { "a" * 1024 * 1024 * 100 }
|
132
|
+
let(:event) { LogStash::Event.new("message" => one_hundred_megabyte_message) }
|
133
|
+
|
134
|
+
|
135
|
+
it 'encodes and transmits data' do
|
136
|
+
instance.receive(event)
|
137
|
+
sleep 1
|
138
|
+
instance.close # release the connection
|
139
|
+
@server_socket_thread.join(30) || fail('server failed to join')
|
140
|
+
expect(io.string).to include('"message"',%Q("#{one_hundred_megabyte_message}"))
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
context 'server mode' do
|
147
|
+
|
148
|
+
def wait_for_condition(total_time_in_seconds, &block)
|
149
|
+
deadline = Time.now + total_time_in_seconds
|
150
|
+
until Time.now > deadline
|
151
|
+
return if yield
|
152
|
+
sleep(1)
|
153
|
+
end
|
154
|
+
fail('condition not met!')
|
155
|
+
end
|
156
|
+
|
157
|
+
context 'transmitting data' do
|
158
|
+
let(:server_host) { 'localhost' }
|
159
|
+
let(:server_port) { Random.rand(1024...5000) }
|
160
|
+
|
161
|
+
let(:config) do
|
162
|
+
{ 'host' => server_host, 'port' => server_port, 'mode' => 'server' }
|
163
|
+
end
|
164
|
+
|
165
|
+
subject(:instance) { described_class.new(config) }
|
166
|
+
|
167
|
+
before(:each) { instance.register } # start listener
|
168
|
+
after(:each) { instance.close }
|
169
|
+
|
170
|
+
let(:event) { LogStash::Event.new({"hello" => "world"})}
|
171
|
+
|
172
|
+
context 'when one client is connected' do
|
173
|
+
let(:io) { StringIO.new }
|
174
|
+
let(:client_socket) { TCPSocket.new(server_host, server_port) }
|
175
|
+
|
176
|
+
before(:each) do
|
177
|
+
@client_socket_thread = Thread.start { siphon_until_eof(client_socket, io) }
|
178
|
+
sleep 1 # wait for it to actually connect
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'encodes and transmits data' do
|
182
|
+
sleep 1
|
183
|
+
instance.receive(event)
|
184
|
+
|
185
|
+
wait_for_condition(30) { !io.size.zero? }
|
186
|
+
sleep 1 # wait for the event to get sent...
|
187
|
+
instance.close # release the connection
|
188
|
+
|
189
|
+
@client_socket_thread.join(30) || fail('client failed to join')
|
190
|
+
expect(io.string).to include('"hello"','"world"')
|
191
|
+
end
|
192
|
+
|
193
|
+
context 'when payload is very large' do
|
194
|
+
let(:one_hundred_megabyte_message) { "a" * 1024 * 1024 * 100 }
|
195
|
+
let(:event) { LogStash::Event.new("message" => one_hundred_megabyte_message) }
|
196
|
+
|
197
|
+
it 'encodes and transmits data' do
|
198
|
+
instance.receive(event)
|
199
|
+
|
200
|
+
wait_for_condition(30) { io.size >= one_hundred_megabyte_message.size }
|
201
|
+
sleep 1 # wait for the event to get sent...
|
202
|
+
instance.close # release the connection
|
203
|
+
|
204
|
+
@client_socket_thread.join(30) || fail('client failed to join')
|
205
|
+
expect(io.string).to include('"message"',%Q("#{one_hundred_megabyte_message}"))
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
32
210
|
end
|
33
211
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: logstash-output-tcp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.0.
|
4
|
+
version: 6.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Elastic
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-11-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
@@ -103,6 +103,10 @@ files:
|
|
103
103
|
- docs/index.asciidoc
|
104
104
|
- lib/logstash/outputs/tcp.rb
|
105
105
|
- logstash-output-tcp.gemspec
|
106
|
+
- spec/fixtures/encrypted/instance.crt
|
107
|
+
- spec/fixtures/encrypted/instance.key
|
108
|
+
- spec/fixtures/plaintext/instance.crt
|
109
|
+
- spec/fixtures/plaintext/instance.key
|
106
110
|
- spec/outputs/tcp_spec.rb
|
107
111
|
homepage: http://www.elastic.co/guide/en/logstash/current/index.html
|
108
112
|
licenses:
|
@@ -130,4 +134,8 @@ signing_key:
|
|
130
134
|
specification_version: 4
|
131
135
|
summary: Writes events over a TCP socket
|
132
136
|
test_files:
|
137
|
+
- spec/fixtures/encrypted/instance.crt
|
138
|
+
- spec/fixtures/encrypted/instance.key
|
139
|
+
- spec/fixtures/plaintext/instance.crt
|
140
|
+
- spec/fixtures/plaintext/instance.key
|
133
141
|
- spec/outputs/tcp_spec.rb
|