logstash-input-relp 0.1.5 → 2.0.1

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
  SHA1:
3
- metadata.gz: 07d2a8b3a1b966051c8b74a3a94db1c77ea76ef1
4
- data.tar.gz: d5938b79c90b7760de5e937a4963c302000b244c
3
+ metadata.gz: bf41bf3e9f571773cd7317ccaa639812b8af4b75
4
+ data.tar.gz: 5b7b8ade54236ee71f4dd1540a81a831b245c02b
5
5
  SHA512:
6
- metadata.gz: cb273ffba74d11178774f298d9f32a32ba501dfd82a81f1291cf1e01d5723f8927c01586fab31a0f3bd0d47c23f441b6877140b893fabad3e31010e9e4a99086
7
- data.tar.gz: 42354917795eb38ae5b6b640290d02d10baa18478c07b6f14b9c130c03a850481a77e9a0ed0243a84b7b5256396c56a07187e382f9b81fa79fb6791a85707efa
6
+ metadata.gz: c6724f73a866b45ec55f446c732b1fae0c027fda14bb5cf7516be7fd669a078c82d227db9442fd90467b5ad78d3552873df1ba823573facd37f0807ca17ee548
7
+ data.tar.gz: e616c70e39a338199012442b962b2b3766417b028d5b3ab10f0d0aefcd9bdf26e3e970714eed99973865ac286ec02b78504b93b6cbdbf620e1ecc5aba662f27f
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ ## 2.0.0
2
+ - Plugins were updated to follow the new shutdown semantic, this mainly allows Logstash to instruct input plugins to terminate gracefully,
3
+ instead of using Thread.raise on the plugins' threads. Ref: https://github.com/elastic/logstash/pull/3895
4
+ - Dependency on logstash-core update to 2.0
5
+
6
+ # 0.2.0
7
+ - Add support for using RELP over SSL sockets
data/NOTICE.TXT ADDED
@@ -0,0 +1,5 @@
1
+ Elasticsearch
2
+ Copyright 2012-2015 Elasticsearch
3
+
4
+ This product includes software developed by The Apache Software
5
+ Foundation (http://www.apache.org/).
data/README.md CHANGED
@@ -1,19 +1,19 @@
1
1
  # Logstash Plugin
2
2
 
3
- This is a plugin for [Logstash](https://github.com/elasticsearch/logstash).
3
+ This is a plugin for [Logstash](https://github.com/elastic/logstash).
4
4
 
5
5
  It is fully free and fully open source. The license is Apache 2.0, meaning you are pretty much free to use it however you want in whatever way.
6
6
 
7
7
  ## Documentation
8
8
 
9
- Logstash provides infrastructure to automatically generate documentation for this plugin. We use the asciidoc format to write documentation so any comments in the source code will be first converted into asciidoc and then into html. All plugin documentation are placed under one [central location](http://www.elasticsearch.org/guide/en/logstash/current/).
9
+ Logstash provides infrastructure to automatically generate documentation for this plugin. We use the asciidoc format to write documentation so any comments in the source code will be first converted into asciidoc and then into html. All plugin documentation are placed under one [central location](http://www.elastic.co/guide/en/logstash/current/).
10
10
 
11
11
  - For formatting code or config example, you can use the asciidoc `[source,ruby]` directive
12
- - For more asciidoc formatting tips, see the excellent reference here https://github.com/elasticsearch/docs#asciidoc-guide
12
+ - For more asciidoc formatting tips, see the excellent reference here https://github.com/elastic/docs#asciidoc-guide
13
13
 
14
14
  ## Need Help?
15
15
 
16
- Need help? Try #logstash on freenode IRC or the logstash-users@googlegroups.com mailing list.
16
+ Need help? Try #logstash on freenode IRC or the https://discuss.elastic.co/c/logstash discussion forum.
17
17
 
18
18
  ## Developing
19
19
 
@@ -83,4 +83,4 @@ Programming is not a required skill. Whatever you've seen about open source and
83
83
 
84
84
  It is more important to the community that you are able to contribute.
85
85
 
86
- For more information about contributing, see the [CONTRIBUTING](https://github.com/elasticsearch/logstash/blob/master/CONTRIBUTING.md) file.
86
+ For more information about contributing, see the [CONTRIBUTING](https://github.com/elastic/logstash/blob/master/CONTRIBUTING.md) file.
@@ -4,8 +4,6 @@ require "logstash/namespace"
4
4
  require "logstash/util/relp"
5
5
  require "logstash/util/socket_peer"
6
6
 
7
-
8
-
9
7
  # Read RELP events over a TCP socket.
10
8
  #
11
9
  # For more information about RELP, see
@@ -27,25 +25,77 @@ class LogStash::Inputs::Relp < LogStash::Inputs::Base
27
25
  # The port to listen on.
28
26
  config :port, :validate => :number, :required => true
29
27
 
28
+ # Enable SSL (must be set for other `ssl_` options to take effect).
29
+ config :ssl_enable, :validate => :boolean, :default => false
30
+
31
+ # Verify the identity of the other end of the SSL connection against the CA.
32
+ # For input, sets the field `sslsubject` to that of the client certificate.
33
+ config :ssl_verify, :validate => :boolean, :default => true
34
+
35
+ # The SSL CA certificate, chainfile or CA path. The system CA path is automatically included.
36
+ config :ssl_cacert, :validate => :path
37
+
38
+ # SSL certificate path
39
+ config :ssl_cert, :validate => :path
40
+
41
+ # SSL key path
42
+ config :ssl_key, :validate => :path
43
+
44
+ # SSL key passphrase
45
+ config :ssl_key_passphrase, :validate => :password, :default => nil
46
+
30
47
  def initialize(*args)
31
48
  super(*args)
32
- @interrupted = false
33
49
  @relp_server = nil
34
50
  end # def initialize
35
51
 
36
52
  public
37
53
  def register
38
54
  @logger.info("Starting relp input listener", :address => "#{@host}:#{@port}")
39
- @relp_server = RelpServer.new(@host, @port,['syslog'])
55
+ if @ssl_enable
56
+ initialize_ssl_context
57
+ if @ssl_verify == false
58
+ @logger.warn [
59
+ "** WARNING ** Detected UNSAFE options in relp input configuration!",
60
+ "** WARNING ** You have enabled encryption but DISABLED certificate verification.",
61
+ "** WARNING ** To make sure your data is secure change :ssl_verify to true"
62
+ ].join("\n")
63
+ end
64
+ end
65
+ @relp_server = RelpServer.new(@host, @port,['syslog'], @ssl_context)
40
66
  end # def register
41
67
 
68
+ private
69
+ def initialize_ssl_context
70
+ require "openssl"
71
+
72
+ @ssl_context = OpenSSL::SSL::SSLContext.new
73
+ @ssl_context.cert = OpenSSL::X509::Certificate.new(File.read(@ssl_cert))
74
+ @ssl_context.key = OpenSSL::PKey::RSA.new(File.read(@ssl_key),@ssl_key_passphrase.value)
75
+ if @ssl_verify
76
+ @cert_store = OpenSSL::X509::Store.new
77
+ # Load the system default certificate path to the store
78
+ @cert_store.set_default_paths
79
+ if !@ssl_cacert.nil?
80
+ if File.directory?(@ssl_cacert)
81
+ @cert_store.add_path(@ssl_cacert)
82
+ else
83
+ @cert_store.add_file(@ssl_cacert)
84
+ end
85
+ end
86
+ @ssl_context.cert_store = @cert_store
87
+ @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
88
+ end
89
+ end
90
+
42
91
  private
43
92
  def relp_stream(relpserver,socket,output_queue,client_address)
44
- loop do
93
+ while !stop?
45
94
  frame = relpserver.syslog_read(socket)
46
95
  @codec.decode(frame["message"]) do |event|
47
96
  decorate(event)
48
97
  event["host"] = client_address
98
+ event["sslsubject"] ||= socket.peer_cert.subject if @ssl_enable && @ssl_verify
49
99
  output_queue << event
50
100
  end
51
101
 
@@ -58,8 +108,7 @@ class LogStash::Inputs::Relp < LogStash::Inputs::Base
58
108
 
59
109
  public
60
110
  def run(output_queue)
61
- @thread = Thread.current
62
- while !@interrupted
111
+ while !stop?
63
112
  begin
64
113
  # Start a new thread for each connection.
65
114
  Thread.start(@relp_server.accept) do |client|
@@ -87,18 +136,17 @@ class LogStash::Inputs::Relp < LogStash::Inputs::Base
87
136
  @logger.warn('Relp client trying to open connection with something other than open:'+e.message)
88
137
  rescue Relp::InsufficientCommands
89
138
  @logger.warn('Relp client incapable of syslog')
90
- rescue IOError
91
- break if @interrupted
92
- raise
93
- rescue LogStash::ShutdownSignal
94
- @interrupted = true
95
- break
96
- end
139
+ rescue Relp::ConnectionClosed
140
+ @logger.debug('Relp Connection closed')
141
+ rescue OpenSSL::SSL::SSLError => ssle
142
+ # NOTE(mrichar1): This doesn't return a useful error message for some reason
143
+ @logger.error("SSL Error", :exception => ssle,
144
+ :backtrace => ssle.backtrace)
145
+ end
97
146
  end # loop
98
147
  end # def run
99
148
 
100
- def teardown
101
- @interrupted = true
149
+ def stop
102
150
  if @relp_server
103
151
  @relp_server.shutdown rescue nil
104
152
  @relp_server = nil
@@ -100,7 +100,7 @@ end
100
100
 
101
101
  class RelpServer < Relp
102
102
 
103
- def initialize(host,port,required_commands=[])
103
+ def initialize(host,port,required_commands=[],ssl_context=nil)
104
104
  @logger = Cabin::Channel.get(LogStash)
105
105
 
106
106
  @server=true
@@ -118,7 +118,10 @@ class RelpServer < Relp
118
118
  :host => host, :port => port)
119
119
  raise
120
120
  end
121
- @logger.info? and @logger.info("Started RELP Server", :host => host, :port => port)
121
+ if ssl_context
122
+ @server = OpenSSL::SSL::SSLServer.new(@server, ssl_context)
123
+ end
124
+ @logger.info("Started #{ssl_context ? 'SSL-enabled ' : ''}RELP Server", :host => host, :port => port)
122
125
  end
123
126
 
124
127
  def accept
@@ -213,121 +216,3 @@ class RelpServer < Relp
213
216
  end
214
217
 
215
218
  end
216
-
217
- #This is only used by the tests; any problems here are not as important as elsewhere
218
- class RelpClient < Relp
219
-
220
- def initialize(host,port,required_commands = [],buffer_size = 128,
221
- retransmission_timeout=10)
222
- @logger = Cabin::Channel.get(LogStash)
223
- @logger.info? and @logger.info("Starting RELP client", :host => host, :port => port)
224
- @server = false
225
- @buffer = Hash.new
226
-
227
- @buffer_size = buffer_size
228
- @retransmission_timeout = retransmission_timeout
229
-
230
- #These are things that are part of the basic protocol, but only valid in one direction (rsp, close etc.)
231
- @basic_relp_commands = ['serverclose','rsp']#TODO: check for others
232
-
233
- #These are extra commands that we require, otherwise refuse the connection
234
- @required_relp_commands = required_commands
235
-
236
- @socket=TCPSocket.new(host,port)
237
-
238
- #This'll start the automatic frame numbering
239
- @lasttxnr = 0
240
-
241
- offer=Hash.new
242
- offer['command'] = 'open'
243
- offer['message'] = 'relp_version=' + RelpVersion + "\n"
244
- offer['message'] += 'relp_software=' + RelpSoftware + "\n"
245
- offer['message'] += 'commands=' + @required_relp_commands.join(',')#TODO: add optional ones
246
- self.frame_write(@socket, offer)
247
- response_frame = self.frame_read(@socket)
248
- if response_frame['message'][0,3] != '200'
249
- raise RelpError,response_frame['message']
250
- end
251
-
252
- response=Hash[*response_frame['message'][7..-1].scan(/^(.*)=(.*)$/).flatten]
253
- if response['relp_version'].nil?
254
- #if no version specified, relp spec says we must close connection
255
- self.close()
256
- raise RelpError, 'No relp_version specified; offer: '
257
- + response_frame['message'][6..-1].scan(/^(.*)=(.*)$/).flatten
258
-
259
- #subtracting one array from the other checks to see if all elements in @required_relp_commands are present in the offer
260
- elsif ! (@required_relp_commands - response['commands'].split(',')).empty?
261
- #if it can't receive syslog it's useless to us; close the connection
262
- self.close()
263
- raise InsufficientCommands, response['commands'] + ' offered, require '
264
- + @required_relp_commands.join(',')
265
- end
266
- #If we've got this far with no problems, we're good to go
267
- @logger.info? and @logger.info("Connection establish with server")
268
-
269
- #This thread deals with responses that come back
270
- reader = Thread.start do
271
- loop do
272
- f = self.frame_read(@socket)
273
- if f['command'] == 'rsp' && f['message'] == '200 OK'
274
- @buffer.delete(f['txnr'])
275
- elsif f['command'] == 'rsp' && f['message'][0,1] == '5'
276
- #TODO: What if we get an error for something we're already retransmitted due to timeout?
277
- new_txnr = self.frame_write(@socket, @buffer[f['txnr']])
278
- @buffer[new_txnr] = @buffer[f['txnr']]
279
- @buffer.delete(f['txnr'])
280
- elsif f['command'] == 'serverclose' || f['txnr'] == @close_txnr
281
- break
282
- else
283
- #Don't know what's going on if we get here, but it can't be good
284
- raise RelpError#TODO: raising errors like this makes no sense
285
- end
286
- end
287
- end
288
-
289
- #While this one deals with frames for which we get no reply
290
- Thread.start do
291
- old_buffer = Hash.new
292
- loop do
293
- #This returns old txnrs that are still present
294
- (@buffer.keys & old_buffer.keys).each do |txnr|
295
- new_txnr = self.frame_write(@socket, @buffer[txnr])
296
- @buffer[new_txnr] = @buffer[txnr]
297
- @buffer.delete(txnr)
298
- end
299
- old_buffer = @buffer
300
- sleep @retransmission_timeout
301
- end
302
- end
303
- end
304
-
305
- #TODO: have a way to get back unacked messages on close
306
- def close
307
- frame = Hash.new
308
- frame['command'] = 'close'
309
- @close_txnr=self.frame_write(@socket, frame)
310
- #TODO: ought to properly wait for a reply etc. The serverclose will make it work though
311
- sleep @retransmission_timeout
312
- @socket.close#TODO: shutdown?
313
- return @buffer
314
- end
315
-
316
- def syslog_write(logline)
317
-
318
- #If the buffer is already full, wait until a gap opens up
319
- sleep 0.1 until @buffer.length<@buffer_size
320
-
321
- frame = Hash.new
322
- frame['command'] = 'syslog'
323
- frame['message'] = logline
324
-
325
- txnr = self.frame_write(@socket, frame)
326
- @buffer[txnr] = frame
327
- end
328
-
329
- def nexttxnr
330
- @lasttxnr += 1
331
- end
332
-
333
- end
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
 
3
3
  s.name = 'logstash-input-relp'
4
- s.version = '0.1.5'
4
+ s.version = '2.0.1'
5
5
  s.licenses = ['Apache License (2.0)']
6
6
  s.summary = "Read RELP 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/plugin install gemname. This gem is not a stand-alone program"
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
11
11
  s.require_paths = ["lib"]
12
12
 
13
13
  # Files
14
- s.files = `git ls-files`.split($\)+::Dir.glob('vendor/*')
14
+ s.files = Dir['lib/**/*','spec/**/*','vendor/**/*','*.gemspec','*.md','CONTRIBUTORS','Gemfile','LICENSE','NOTICE.TXT']
15
15
 
16
16
  # Tests
17
17
  s.test_files = s.files.grep(%r{^(test|spec|features)/})
@@ -20,7 +20,7 @@ Gem::Specification.new do |s|
20
20
  s.metadata = { "logstash_plugin" => "true", "logstash_group" => "input" }
21
21
 
22
22
  # Gem dependencies
23
- s.add_runtime_dependency "logstash-core", '>= 1.4.0', '< 2.0.0'
23
+ s.add_runtime_dependency "logstash-core", ">= 2.0.0.snapshot", "< 3.0.0"
24
24
 
25
25
  s.add_runtime_dependency 'logstash-codec-plain'
26
26
  s.add_development_dependency 'logstash-devutils'
@@ -1,60 +1,113 @@
1
- # coding: utf-8
2
- require "logstash/devutils/rspec/spec_helper"
3
- require "socket"
4
- require "logstash/util/relp"
5
-
6
- describe "inputs/relp" do
7
-
8
- it "should do single client connection" do
9
- event_count = 10
10
- port = 5511
11
- conf = <<-CONFIG
12
- input {
13
- relp {
14
- type => "blah"
15
- port => #{port}
16
- }
17
- }
18
- CONFIG
19
-
20
- events = input(conf) do |pipeline, queue|
21
- client = RelpClient.new("0.0.0.0", port, ["syslog"])
22
- event_count.times do |value|
23
- client.syslog_write("Hello #{value}")
1
+ # encoding: utf-8
2
+ require_relative "../spec_helper"
3
+ require_relative "../support/ssl"
4
+
5
+ describe LogStash::Inputs::Relp do
6
+
7
+ before do
8
+ srand(RSpec.configuration.seed)
9
+ end
10
+
11
+ describe "registration and close" do
12
+
13
+ it "should register without errors" do
14
+ input = LogStash::Plugin.lookup("input", "relp").new("port" => 1234)
15
+ expect {input.register}.to_not raise_error
16
+ end
17
+
18
+ end
19
+
20
+ describe "when interrupting the plugin" do
21
+
22
+ let(:port) { rand(1024..65532) }
23
+
24
+ it_behaves_like "an interruptible input plugin" do
25
+ let(:config) { { "port" => port } }
26
+ end
27
+ end
28
+
29
+ describe "multiple client connections" do
30
+
31
+ let(:nclients) { rand(200) }
32
+ let(:nevents) { 100 }
33
+ let(:port) { 5512 }
34
+
35
+ let(:conf) do
36
+ <<-CONFIG
37
+ input {
38
+ relp {
39
+ type => "blah"
40
+ port => #{port}
41
+ }
42
+ }
43
+ CONFIG
44
+ end
45
+
46
+ let(:clients) { setup_clients(nclients, port) }
47
+
48
+ let(:events) do
49
+ input(conf, (nevents*nclients)) do
50
+ nevents.times do |value|
51
+ clients.each_with_index do |client, index|
52
+ client.syslog_write("Hello from client#{index}")
53
+ end
54
+ end
24
55
  end
25
- event_count.times.collect { queue.pop }
26
56
  end
27
57
 
28
- event_count.times do |i|
29
- insist { events[i]["message"] } == "Hello #{i}"
58
+ it "should do multiple connections" do
59
+ nclients.times do |client_id|
60
+ expect(events).to have(nevents).with("Hello from client#{client_id}")
61
+ end
30
62
  end
31
63
  end
32
64
 
33
- it "should do two client connection" do
34
- event_count = 100
35
- port = 5512
36
- conf = <<-CONFIG
37
- input {
38
- relp {
39
- type => "blah"
40
- port => #{port}
41
- }
42
- }
43
- CONFIG
44
-
45
- events = input(conf) do |pipeline, queue|
46
- client = RelpClient.new("0.0.0.0", port, ["syslog"])
47
- client2 = RelpClient.new("0.0.0.0", port, ["syslog"])
48
-
49
- event_count.times do
50
- client.syslog_write("Hello from client")
51
- client2.syslog_write("Hello from client 2")
65
+ describe "SSL support" do
66
+
67
+ let(:nevents) { 100 }
68
+ let(:certificate) { RelpTest.certificate }
69
+ let(:port) { 5513 }
70
+
71
+ let(:conf) do
72
+ <<-CONFIG
73
+ input {
74
+ relp {
75
+ type => "blah"
76
+ port => #{port}
77
+ ssl_enable => true
78
+ ssl_verify => false
79
+ ssl_cert => "#{certificate.ssl_cert}"
80
+ ssl_key => "#{certificate.ssl_key}"
81
+ }
82
+ }
83
+ CONFIG
84
+ end
85
+
86
+ let(:client) { RelpClient.new("0.0.0.0", port, ["syslog"], {:ssl => true}) }
87
+
88
+ let(:events) do
89
+ input(conf, nevents) do
90
+ nevents.times do
91
+ client.syslog_write("Hello from client")
92
+ end
93
+ end
94
+ end
95
+
96
+ context "registration and close" do
97
+
98
+ it "should register without errors" do
99
+ input = LogStash::Plugin.lookup("input", "relp").new("port" => 1235, "ssl_enable" => true,
100
+ "ssl_cert" => certificate.ssl_cert,
101
+ "ssl_key" => certificate.ssl_key)
102
+ expect {input.register}.to_not raise_error
52
103
  end
53
104
 
54
- (event_count * 2).times.map{queue.pop}
55
105
  end
56
106
 
57
- insist { events.select{|event| event["message"] == "Hello from client" }.size } == event_count
58
- insist { events.select{|event| event["message"] == "Hello from client 2" }.size } == event_count
107
+ it "should generated the events as expected" do
108
+ expect(events).to have(nevents).with("Hello from client")
109
+ end
110
+
59
111
  end
112
+
60
113
  end
@@ -0,0 +1,66 @@
1
+ # encoding: utf-8
2
+ require "logstash/devutils/rspec/spec_helper"
3
+ require "logstash/inputs/relp"
4
+ require "logstash/util/relp"
5
+ require "socket"
6
+ require "support/client"
7
+
8
+ module RelpHelpers
9
+
10
+ def setup_clients(number_of_clients, port)
11
+ number_of_clients.times.inject([]) do |clients|
12
+ clients << RelpClient.new("0.0.0.0", port, ["syslog"])
13
+ end
14
+ end
15
+
16
+ def filter(events, message)
17
+ events.select{|event| event["message"] == message }
18
+ end
19
+
20
+ def input(config, size, &block)
21
+ pipeline = LogStash::Pipeline.new(config)
22
+ queue = Queue.new
23
+
24
+ pipeline.instance_eval do
25
+ # create closure to capture queue
26
+ @output_func = lambda { |event| queue << event }
27
+
28
+ # output_func is now a method, call closure
29
+ def output_func(event)
30
+ @output_func.call(event)
31
+ end
32
+ end
33
+
34
+ pipeline_thread = Thread.new { pipeline.run }
35
+ sleep 0.1 while !pipeline.ready?
36
+
37
+ block.call
38
+ sleep 0.1 while queue.size != size
39
+
40
+ result = size.times.inject([]) do |acc|
41
+ acc << queue.pop
42
+ end
43
+
44
+ pipeline.shutdown
45
+ pipeline_thread.join
46
+
47
+ result
48
+ end # def input
49
+
50
+ end
51
+
52
+ RSpec.configure do |c|
53
+ c.include RelpHelpers
54
+ end
55
+
56
+ RSpec::Matchers.define :have do |nevents|
57
+
58
+ match do |events|
59
+ filter(events, @pattern).size == nevents
60
+ end
61
+
62
+ chain :with do |pattern|
63
+ @pattern = pattern
64
+ end
65
+
66
+ end
@@ -0,0 +1,131 @@
1
+ # encoding: utf-8
2
+ require "socket"
3
+
4
+ #This is only used by the tests; any problems here are not as important as elsewhere
5
+ class RelpClient < Relp
6
+
7
+ def initialize(host,port,required_commands = [], options={})
8
+ buffer_size = options[:buffer_size] || 128
9
+ retransmission_timeout = options[:retransmission_timeout] || 10
10
+ ssl = options[:ssl] || false
11
+ @logger = Cabin::Channel.get(LogStash)
12
+ @logger.info? and @logger.info("Starting RELP client", :host => host, :port => port)
13
+ @server = false
14
+ @buffer = Hash.new
15
+
16
+ @buffer_size = buffer_size
17
+ @retransmission_timeout = retransmission_timeout
18
+
19
+ #These are things that are part of the basic protocol, but only valid in one direction (rsp, close etc.)
20
+ @basic_relp_commands = ['serverclose','rsp']#TODO: check for others
21
+
22
+ #These are extra commands that we require, otherwise refuse the connection
23
+ @required_relp_commands = required_commands
24
+
25
+ @socket=TCPSocket.new(host,port)
26
+
27
+ if ssl
28
+ ctx = OpenSSL::SSL::SSLContext.new
29
+ ctx.set_params(verify_mode: OpenSSL::SSL::VERIFY_NONE)
30
+ @socket = OpenSSL::SSL::SSLSocket.new(@socket, ctx).tap do |socket|
31
+ socket.sync_close = true
32
+ socket.connect
33
+ end
34
+ end
35
+
36
+ #This'll start the automatic frame numbering
37
+ @lasttxnr = 0
38
+
39
+ offer=Hash.new
40
+ offer['command'] = 'open'
41
+ offer['message'] = 'relp_version=' + RelpVersion + "\n"
42
+ offer['message'] += 'relp_software=' + RelpSoftware + "\n"
43
+ offer['message'] += 'commands=' + @required_relp_commands.join(',')#TODO: add optional ones
44
+ self.frame_write(@socket, offer)
45
+ response_frame = self.frame_read(@socket)
46
+ if response_frame['message'][0,3] != '200'
47
+ raise RelpError,response_frame['message']
48
+ end
49
+
50
+ response=Hash[*response_frame['message'][7..-1].scan(/^(.*)=(.*)$/).flatten]
51
+ if response['relp_version'].nil?
52
+ #if no version specified, relp spec says we must close connection
53
+ self.close()
54
+ raise RelpError, 'No relp_version specified; offer: '
55
+ + response_frame['message'][6..-1].scan(/^(.*)=(.*)$/).flatten
56
+
57
+ #subtracting one array from the other checks to see if all elements in @required_relp_commands are present in the offer
58
+ elsif ! (@required_relp_commands - response['commands'].split(',')).empty?
59
+ #if it can't receive syslog it's useless to us; close the connection
60
+ self.close()
61
+ raise InsufficientCommands, response['commands'] + ' offered, require '
62
+ + @required_relp_commands.join(',')
63
+ end
64
+ #If we've got this far with no problems, we're good to go
65
+ @logger.info? and @logger.info("Connection establish with server")
66
+
67
+ #This thread deals with responses that come back
68
+ reader = Thread.start do
69
+ loop do
70
+ f = self.frame_read(@socket)
71
+ if f['command'] == 'rsp' && f['message'] == '200 OK'
72
+ @buffer.delete(f['txnr'])
73
+ elsif f['command'] == 'rsp' && f['message'][0,1] == '5'
74
+ #TODO: What if we get an error for something we're already retransmitted due to timeout?
75
+ new_txnr = self.frame_write(@socket, @buffer[f['txnr']])
76
+ @buffer[new_txnr] = @buffer[f['txnr']]
77
+ @buffer.delete(f['txnr'])
78
+ elsif f['command'] == 'serverclose' || f['txnr'] == @close_txnr
79
+ break
80
+ else
81
+ #Don't know what's going on if we get here, but it can't be good
82
+ raise RelpError#TODO: raising errors like this makes no sense
83
+ end
84
+ end
85
+ end
86
+
87
+ #While this one deals with frames for which we get no reply
88
+ Thread.start do
89
+ old_buffer = Hash.new
90
+ loop do
91
+ #This returns old txnrs that are still present
92
+ (@buffer.keys & old_buffer.keys).each do |txnr|
93
+ new_txnr = self.frame_write(@socket, @buffer[txnr])
94
+ @buffer[new_txnr] = @buffer[txnr]
95
+ @buffer.delete(txnr)
96
+ end
97
+ old_buffer = @buffer
98
+ sleep @retransmission_timeout
99
+ end
100
+ end
101
+ end
102
+
103
+ #TODO: have a way to get back unacked messages on close
104
+ def close
105
+ frame = Hash.new
106
+ frame['command'] = 'close'
107
+ @close_txnr=self.frame_write(@socket, frame)
108
+ #TODO: ought to properly wait for a reply etc. The serverclose will make it work though
109
+ sleep @retransmission_timeout
110
+ @socket.close#TODO: shutdown?
111
+ return @buffer
112
+ end
113
+
114
+ def syslog_write(logline)
115
+
116
+ #If the buffer is already full, wait until a gap opens up
117
+ sleep 0.1 until @buffer.length<@buffer_size
118
+
119
+ frame = Hash.new
120
+ frame['command'] = 'syslog'
121
+ frame['message'] = logline
122
+
123
+ txnr = self.frame_write(@socket, frame)
124
+ @buffer[txnr] = frame
125
+ end
126
+
127
+ def nexttxnr
128
+ @lasttxnr += 1
129
+ end
130
+
131
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+ require "stud/temporary"
3
+
4
+ module RelpTest
5
+
6
+ class Certicate
7
+ attr_reader :ssl_key, :ssl_cert
8
+
9
+ def initialize
10
+ @ssl_cert = Stud::Temporary.pathname("ssl_certificate")
11
+ @ssl_key = Stud::Temporary.pathname("ssl_key")
12
+
13
+ system("openssl req -x509 -batch -nodes -newkey rsa:2048 -keyout #{ssl_key} -out #{ssl_cert} -subj /CN=localhost > /dev/null 2>&1")
14
+ end
15
+ end
16
+
17
+ class << self
18
+ def certificate
19
+ Certicate.new
20
+ end
21
+
22
+ def random_port
23
+ rand(2000..10000)
24
+ end
25
+ end
26
+
27
+ end
metadata CHANGED
@@ -1,24 +1,24 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-input-relp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-20 00:00:00.000000000 Z
11
+ date: 2015-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
16
  - - '>='
17
17
  - !ruby/object:Gem::Version
18
- version: 1.4.0
18
+ version: 2.0.0.snapshot
19
19
  - - <
20
20
  - !ruby/object:Gem::Version
21
- version: 2.0.0
21
+ version: 3.0.0
22
22
  name: logstash-core
23
23
  prerelease: false
24
24
  type: :runtime
@@ -26,10 +26,10 @@ dependencies:
26
26
  requirements:
27
27
  - - '>='
28
28
  - !ruby/object:Gem::Version
29
- version: 1.4.0
29
+ version: 2.0.0.snapshot
30
30
  - - <
31
31
  - !ruby/object:Gem::Version
32
- version: 2.0.0
32
+ version: 3.0.0
33
33
  - !ruby/object:Gem::Dependency
34
34
  requirement: !ruby/object:Gem::Requirement
35
35
  requirements:
@@ -64,16 +64,19 @@ executables: []
64
64
  extensions: []
65
65
  extra_rdoc_files: []
66
66
  files:
67
- - .gitignore
67
+ - CHANGELOG.md
68
68
  - CONTRIBUTORS
69
69
  - Gemfile
70
70
  - LICENSE
71
+ - NOTICE.TXT
71
72
  - README.md
72
- - Rakefile
73
73
  - lib/logstash/inputs/relp.rb
74
74
  - lib/logstash/util/relp.rb
75
75
  - logstash-input-relp.gemspec
76
76
  - spec/inputs/relp_spec.rb
77
+ - spec/spec_helper.rb
78
+ - spec/support/client.rb
79
+ - spec/support/ssl.rb
77
80
  homepage: http://www.elastic.co/guide/en/logstash/current/index.html
78
81
  licenses:
79
82
  - Apache License (2.0)
@@ -96,9 +99,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
96
99
  version: '0'
97
100
  requirements: []
98
101
  rubyforge_project:
99
- rubygems_version: 2.1.9
102
+ rubygems_version: 2.4.8
100
103
  signing_key:
101
104
  specification_version: 4
102
105
  summary: Read RELP events over a TCP socket.
103
106
  test_files:
104
107
  - spec/inputs/relp_spec.rb
108
+ - spec/spec_helper.rb
109
+ - spec/support/client.rb
110
+ - spec/support/ssl.rb
data/.gitignore DELETED
@@ -1,4 +0,0 @@
1
- *.gem
2
- Gemfile.lock
3
- .bundle
4
- vendor
data/Rakefile DELETED
@@ -1,7 +0,0 @@
1
- @files=[]
2
-
3
- task :default do
4
- system("rake -T")
5
- end
6
-
7
- require "logstash/devutils/rake"