logstash-input-tcp 4.2.2-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,55 @@
1
+ # encoding: utf-8
2
+ require 'java'
3
+
4
+ java_import org.logstash.tcp.Decoder
5
+
6
+ class DecoderImpl
7
+
8
+ include Decoder
9
+
10
+ def initialize(codec, tcp)
11
+ @tcp = tcp
12
+ @codec = codec
13
+ @first_read = true
14
+ end
15
+
16
+ def decode(channel_addr, data)
17
+ bytes = Java::byte[data.readableBytes].new
18
+ data.getBytes(0, bytes)
19
+ data.release
20
+ tbuf = String.from_java_bytes bytes, "ASCII-8BIT"
21
+ if @first_read
22
+ tbuf = init_first_read(channel_addr, tbuf)
23
+ end
24
+ @tcp.decode_buffer(@address, @port, @codec,
25
+ @proxy_address, @proxy_port, tbuf)
26
+ end
27
+
28
+ def copy()
29
+ DecoderImpl.new(@codec.clone, @tcp)
30
+ end
31
+
32
+ private
33
+ def init_first_read(channel_addr, received)
34
+ if @tcp.proxy_protocol
35
+ pp_hdr, filtered = received.split("\r\n", 2)
36
+ pp_info = pp_hdr.split(/\s/)
37
+ # PROXY proto clientip proxyip clientport proxyport
38
+ if pp_info[0] != "PROXY"
39
+ @tcp.logger.error("invalid proxy protocol header label", :hdr => pp_hdr)
40
+ raise IOError
41
+ else
42
+ @proxy_address = pp_info[3]
43
+ @proxy_port = pp_info[5]
44
+ @address = pp_info[2]
45
+ @port = pp_info[4]
46
+ end
47
+ else
48
+ filtered = received
49
+ @address = channel_addr.get_address.get_host_address
50
+ @port = channel_addr.get_port
51
+ end
52
+ @first_read = false
53
+ filtered
54
+ end
55
+ end
@@ -0,0 +1,36 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'logstash-input-tcp'
3
+ s.version = ::File.read('version').split("\n").first
4
+ s.licenses = ['Apache License (2.0)']
5
+ s.summary = "Read events over a TCP socket."
6
+ 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"
7
+ s.authors = ["Elastic"]
8
+ s.email = 'info@elastic.co'
9
+ s.homepage = "http://www.elastic.co/guide/en/logstash/current/index.html"
10
+ s.platform = "java"
11
+ s.require_paths = ["lib", "vendor/jar-dependencies"]
12
+
13
+ # Files
14
+ s.files = Dir["lib/**/*","spec/**/*","*.gemspec","*.md","CONTRIBUTORS","Gemfile","LICENSE","NOTICE.TXT", "vendor/jar-dependencies/**/*.jar", "vendor/jar-dependencies/**/*.rb", "version", "docs/**/*"]
15
+
16
+ # Tests
17
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
18
+
19
+ # Special flag to let us know this is actually a logstash plugin
20
+ s.metadata = { "logstash_plugin" => "true", "logstash_group" => "input" }
21
+
22
+ # Gem dependencies
23
+ s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
24
+
25
+ # line vs streaming codecs required for fix_streaming_codecs
26
+ # TODO: fix_streaming_codecs should be refactored to not
27
+ # require the codecs to be installed.
28
+ s.add_runtime_dependency 'logstash-codec-plain'
29
+ s.add_runtime_dependency 'logstash-codec-line'
30
+ s.add_runtime_dependency 'logstash-codec-json'
31
+ s.add_runtime_dependency 'logstash-codec-json_lines'
32
+
33
+ s.add_development_dependency 'logstash-devutils'
34
+ s.add_development_dependency 'flores', '~> 0.0.6'
35
+ s.add_development_dependency 'stud', '~> 0.0.22'
36
+ end
@@ -0,0 +1,421 @@
1
+ # encoding: utf-8
2
+ require "logstash/devutils/rspec/spec_helper"
3
+ require "socket"
4
+ require "timeout"
5
+ require "logstash/json"
6
+ require "logstash/inputs/tcp"
7
+ require "stud/try"
8
+ require "stud/task"
9
+ require "flores/pki"
10
+ require "openssl"
11
+
12
+ require_relative "../spec_helper"
13
+
14
+ #Cabin::Channel.get(LogStash).subscribe(STDOUT)
15
+ #Cabin::Channel.get(LogStash).level = :debug
16
+ describe LogStash::Inputs::Tcp do
17
+
18
+ context "codec (PR #1372)" do
19
+ it "switches from plain to line" do
20
+ require "logstash/codecs/plain"
21
+ require "logstash/codecs/line"
22
+ plugin = LogStash::Inputs::Tcp.new("codec" => LogStash::Codecs::Plain.new, "port" => 0)
23
+ plugin.register
24
+ insist { plugin.codec }.is_a?(LogStash::Codecs::Line)
25
+ plugin.close
26
+ end
27
+ it "switches from json to json_lines" do
28
+ require "logstash/codecs/json"
29
+ require "logstash/codecs/json_lines"
30
+ plugin = LogStash::Inputs::Tcp.new("codec" => LogStash::Codecs::JSON.new, "port" => 0)
31
+ plugin.register
32
+ insist { plugin.codec }.is_a?(LogStash::Codecs::JSONLines)
33
+ plugin.close
34
+ end
35
+ end
36
+
37
+ it "should read plain with unicode" do
38
+ event_count = 10
39
+ port = rand(1024..65535)
40
+ conf = <<-CONFIG
41
+ input {
42
+ tcp {
43
+ port => #{port}
44
+ }
45
+ }
46
+ CONFIG
47
+
48
+ host = '127.0.0.1'
49
+ events = input(conf) do |pipeline, queue|
50
+ socket = Stud::try(5.times) { TCPSocket.new(host, port) }
51
+ event_count.times do |i|
52
+ # unicode smiley for testing unicode support!
53
+ socket.puts("#{i} ☹")
54
+ socket.flush
55
+ end
56
+ socket.close
57
+
58
+ event_count.times.collect {queue.pop}
59
+ end
60
+
61
+ insist { events.length } == event_count
62
+ event_count.times do |i|
63
+ event = events[i]
64
+ insist { event.get("message") } == "#{i} ☹"
65
+ insist { event.get("host") } == host
66
+ end
67
+ end
68
+
69
+ it "should handle PROXY protocol v1 connections" do
70
+ event_count = 10
71
+ port = rand(1024..65535)
72
+ conf = <<-CONFIG
73
+ input {
74
+ tcp {
75
+ port => #{port}
76
+ proxy_protocol => true
77
+ }
78
+ }
79
+ CONFIG
80
+
81
+ events = input(conf) do |pipeline, queue|
82
+ socket = Stud::try(5.times) { TCPSocket.new("127.0.0.1", port) }
83
+ socket.puts("PROXY TCP4 1.2.3.4 5.6.7.8 1234 5678\r");
84
+ socket.flush
85
+ event_count.times do |i|
86
+ # unicode smiley for testing unicode support!
87
+ socket.puts("#{i} ☹")
88
+ socket.flush
89
+ end
90
+ socket.close
91
+
92
+ event_count.times.collect {queue.pop}
93
+ end
94
+
95
+ insist { events.length } == event_count
96
+ event_count.times do |i|
97
+ insist { events[i].get("message") } == "#{i} ☹"
98
+ insist { events[i].get("host") } == "1.2.3.4"
99
+ insist { events[i].get("port") } == "1234"
100
+ insist { events[i].get("proxy_host") } == "5.6.7.8"
101
+ insist { events[i].get("proxy_port") } == "5678"
102
+ end
103
+ end
104
+
105
+ it "should read events with plain codec and ISO-8859-1 charset" do
106
+ port = rand(1024..65535)
107
+ charset = "ISO-8859-1"
108
+ conf = <<-CONFIG
109
+ input {
110
+ tcp {
111
+ port => #{port}
112
+ codec => plain { charset => "#{charset}" }
113
+ }
114
+ }
115
+ CONFIG
116
+
117
+ event = input(conf) do |pipeline, queue|
118
+ socket = Stud::try(5.times) { TCPSocket.new("127.0.0.1", port) }
119
+ text = "\xA3" # the £ symbol in ISO-8859-1 aka Latin-1
120
+ text.force_encoding("ISO-8859-1")
121
+ socket.puts(text)
122
+ socket.close
123
+
124
+ queue.pop
125
+ end
126
+
127
+ # Make sure the 0xA3 latin-1 code converts correctly to UTF-8.
128
+ insist { event.get("message").size } == 1
129
+ insist { event.get("message").bytesize } == 2
130
+ insist { event.get("message") } == "£"
131
+ end
132
+
133
+ it "should read events with json codec" do
134
+ port = rand(1024..65535)
135
+ conf = <<-CONFIG
136
+ input {
137
+ tcp {
138
+ port => #{port}
139
+ codec => json
140
+ }
141
+ }
142
+ CONFIG
143
+
144
+ data = {
145
+ "hello" => "world",
146
+ "foo" => [1,2,3],
147
+ "baz" => { "1" => "2" },
148
+ "host" => "example host"
149
+ }
150
+
151
+ event = input(conf) do |pipeline, queue|
152
+ socket = Stud::try(5.times) { TCPSocket.new("127.0.0.1", port) }
153
+ socket.puts(LogStash::Json.dump(data))
154
+ socket.close
155
+
156
+ queue.pop
157
+ end
158
+
159
+ insist { event.get("hello") } == data["hello"]
160
+ insist { event.get("foo").to_a } == data["foo"] # to_a to cast Java ArrayList produced by JrJackson
161
+ insist { event.get("baz") } == data["baz"]
162
+
163
+ # Make sure the tcp input, w/ json codec, uses the event's 'host' value,
164
+ # if present, instead of providing its own
165
+ insist { event.get("host") } == data["host"]
166
+ end
167
+
168
+ it "should read events with json codec (testing 'host' handling)" do
169
+ port = rand(1024..65535)
170
+ conf = <<-CONFIG
171
+ input {
172
+ tcp {
173
+ port => #{port}
174
+ codec => json
175
+ }
176
+ }
177
+ CONFIG
178
+
179
+ data = {
180
+ "hello" => "world"
181
+ }
182
+
183
+ event = input(conf) do |pipeline, queue|
184
+ socket = Stud::try(5.times) { TCPSocket.new("127.0.0.1", port) }
185
+ socket.puts(LogStash::Json.dump(data))
186
+ socket.close
187
+
188
+ queue.pop
189
+ end
190
+
191
+ insist { event.get("hello") } == data["hello"]
192
+ insist { event }.include?("host")
193
+ end
194
+
195
+ it "should read events with json_lines codec" do
196
+ port = rand(1024..65535)
197
+ conf = <<-CONFIG
198
+ input {
199
+ tcp {
200
+ port => #{port}
201
+ codec => json_lines
202
+ }
203
+ }
204
+ CONFIG
205
+
206
+ data = {
207
+ "hello" => "world",
208
+ "foo" => [1,2,3],
209
+ "baz" => { "1" => "2" },
210
+ "idx" => 0
211
+ }
212
+ event_count = 5
213
+
214
+ events = input(conf) do |pipeline, queue|
215
+ socket = Stud::try(5.times) { TCPSocket.new("127.0.0.1", port) }
216
+ (1..event_count).each do |idx|
217
+ data["idx"] = idx
218
+ socket.puts(LogStash::Json.dump(data) + "\n")
219
+ end
220
+ socket.close
221
+
222
+ (1..event_count).map{queue.pop}
223
+ end
224
+
225
+ events.each_with_index do |event, idx|
226
+ insist { event.get("hello") } == data["hello"]
227
+ insist { event.get("foo").to_a } == data["foo"] # to_a to cast Java ArrayList produced by JrJackson
228
+ insist { event.get("baz") } == data["baz"]
229
+ insist { event.get("idx") } == idx + 1
230
+ end # do
231
+ end # describe
232
+
233
+ it "should one message per connection" do
234
+ event_count = 10
235
+ port = rand(1024..65535)
236
+ conf = <<-CONFIG
237
+ input {
238
+ tcp {
239
+ port => #{port}
240
+ }
241
+ }
242
+ CONFIG
243
+
244
+ events = input(conf) do |pipeline, queue|
245
+ event_count.times do |i|
246
+ socket = Stud::try(5.times) { TCPSocket.new("127.0.0.1", port) }
247
+ socket.puts("#{i}")
248
+ socket.flush
249
+ socket.close
250
+ end
251
+
252
+ # since each message is sent on its own tcp connection & thread, exact receiving order cannot be garanteed
253
+ event_count.times.collect{queue.pop}.sort_by{|event| event.get("message")}
254
+ end
255
+
256
+ event_count.times do |i|
257
+ insist { events[i].get("message") } == "#{i}"
258
+ end
259
+ end
260
+
261
+ # below are new specs added in the context of the shutdown semantic refactor.
262
+ # TODO:
263
+ # - refactor all specs using this new model
264
+ # - pipelineless_input has been basically copied from the udp input specs, it should be DRYied up
265
+ # - see if we should miminc the udp input UDPClient helper class instead of directly using TCPSocket
266
+
267
+ describe "LogStash::Inputs::Tcp new specs style" do
268
+
269
+ before do
270
+ srand(RSpec.configuration.seed)
271
+ end
272
+
273
+ let(:port) { rand(1024..65535) }
274
+ subject { LogStash::Plugin.lookup("input", "tcp").new({ "port" => port }) }
275
+ let!(:helper) { TcpHelpers.new }
276
+
277
+ after :each do
278
+ subject.close rescue nil
279
+ end
280
+
281
+ describe "#register" do
282
+ it "should register without errors" do
283
+ expect { subject.register }.to_not raise_error
284
+ end
285
+ end
286
+
287
+ describe "#receive" do
288
+ shared_examples "receiving events" do
289
+ # TODO(sissel): Implement normal event-receipt tests as as a shared example
290
+ end
291
+
292
+ context "when ssl_enable is true" do
293
+ let(:pki) { Flores::PKI.generate }
294
+ let(:certificate) { pki[0] }
295
+ let(:key) { pki[1] }
296
+ let(:certificate_file) { Stud::Temporary.file }
297
+ let(:key_file) { Stud::Temporary.file }
298
+ let(:queue) { Queue.new }
299
+
300
+ let(:config) do
301
+ {
302
+ "host" => "127.0.0.1",
303
+ "port" => port,
304
+ "ssl_enable" => true,
305
+ "ssl_cert" => certificate_file.path,
306
+ "ssl_key" => key_file.path,
307
+
308
+ # Trust our self-signed cert.
309
+ # TODO(sissel): Make this a separate certificate for the client
310
+ "ssl_extra_chain_certs" => certificate_file.path
311
+ }
312
+ end
313
+
314
+ subject(:input) { LogStash::Plugin.lookup("input", "tcp").new(config) }
315
+
316
+ before do
317
+ certificate_file.write(certificate)
318
+ key_file.write(key)
319
+
320
+ # Close to flush the file writes.
321
+ certificate_file.close
322
+ key_file.close
323
+ subject.register
324
+ end
325
+
326
+ after do
327
+ File.unlink(certificate_file.path)
328
+ File.unlink(key_file.path)
329
+ end
330
+
331
+ context "with a poorly-behaving client" do
332
+ let!(:input_task) { Stud::Task.new { input.run(queue) } }
333
+
334
+ after { input.close }
335
+
336
+ context "that disconnects before doing TLS handshake" do
337
+ before do
338
+ client = TCPSocket.new("127.0.0.1", port)
339
+ client.close
340
+ end
341
+
342
+ it "should not negatively impact the plugin" do
343
+ # TODO(sissel): Look for a better way to detect this failure
344
+ # besides a sleep/wait.
345
+ result = input_task.thread.join(0.5)
346
+ expect(result).to be_nil
347
+ end
348
+ end
349
+
350
+ context "that sends garbage instead of TLS handshake" do
351
+ let!(:input_task) { Stud::Task.new { input.run(queue) } }
352
+ let(:max_length) { 1000 }
353
+ let(:garbage) { Flores::Random.iterations(max_length).collect { Flores::Random.integer(1...255) }.pack("C*") }
354
+ before do
355
+ # Assertion to verify this test is actually sending something.
356
+ expect(garbage.length).to be > 0
357
+
358
+ client = TCPSocket.new("127.0.0.1", port)
359
+ client.write(garbage)
360
+ client.flush
361
+ Thread.new { sleep(1); client.close }
362
+ end
363
+ it "should not negatively impact the plugin" do
364
+ # TODO(sissel): Look for a better way to detect this failure besides a sleep/wait.
365
+ result = input_task.thread.join(0.5)
366
+ expect(result).to be_nil
367
+ end
368
+ end
369
+
370
+ context "connection was healthy but now has garbage or corruption" do
371
+ let!(:input_task) { Stud::Task.new { input.run(queue) } }
372
+ let(:tcp) { TCPSocket.new("127.0.0.1", port) }
373
+ let(:sslcontext) { OpenSSL::SSL::SSLContext.new }
374
+ let(:sslsocket) { OpenSSL::SSL::SSLSocket.new(tcp, sslcontext) }
375
+ let(:max_length) { 1000 }
376
+ let(:garbage) { Flores::Random.iterations(max_length).collect { Flores::Random.integer(1...255) }.pack("C*") }
377
+
378
+ before do
379
+ sslcontext.cert = certificate
380
+ sslcontext.key = key
381
+ sslcontext.verify_mode = OpenSSL::SSL::VERIFY_NONE
382
+
383
+ sslsocket.connect
384
+ sslsocket.write("Hello world\n")
385
+
386
+ # Assertion to verify this test is actually sending something.
387
+ expect(garbage.length).to be > 0
388
+ tcp.write(garbage)
389
+ tcp.flush
390
+ sslsocket.close
391
+ tcp.close
392
+ end
393
+
394
+ it "should not negatively impact the plugin" do
395
+ # TODO(sissel): Look for a better way to detect this failure besides a sleep/wait.
396
+ result = input_task.thread.join(0.5)
397
+ expect(result).to be_nil
398
+ end
399
+ end
400
+ end
401
+
402
+ # TODO(sissel): Spec multiple clients where only one is bad.
403
+
404
+ context "with client certificate problems" do
405
+ context "using an expired certificate"
406
+ context "using an untrusted certificate"
407
+ end
408
+
409
+ context "with a good connection" do
410
+ # TODO(sissel): use shared example
411
+ include_examples "receiving events"
412
+ end
413
+
414
+ end
415
+ end
416
+
417
+ it_behaves_like "an interruptible input plugin" do
418
+ let(:config) { { "port" => port } }
419
+ end
420
+ end
421
+ end