logstash-output-tcp 6.0.2 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f565b112e5a38f65bebf7c13061e0b7de65a460952afdc93bcf145998bd6ae30
4
- data.tar.gz: 97b30dfceff9b0d2187c408e3ac5fe4abc8def98e9fdd4e59580fa2f0d112ef9
3
+ metadata.gz: d30cf25cfbc7e1cc2e72d5e1eb8ab8179d42b31413a549801a012d5e1be7e640
4
+ data.tar.gz: 53632382865d4bade1e6d56e9872b1b5db690fd9b866cb4d0952dfa8d1156cee
5
5
  SHA512:
6
- metadata.gz: ffec33897e55cd02b237b385dc4e6ffe1d90f2bb87b2fc4b1dbd4bb050f1db9787038885b7fbd77539b9e60fab1a44e1f9567925f7dc8a36eaab4be0d158b2a3
7
- data.tar.gz: cb2c4871d74fd661ee2d947a94535483fd54854fee574d4615a05634df74b97fedb6e01d73f645e8cc70e41de80fbf1eeceaa466855ff757cc320f19130f8362
6
+ metadata.gz: e48f3511c25df04edb79dfd0425c6a84d18808bce5f8d4910cabe024955e6c16f8b856a8ac7fac01cb7721e7628934101e14e7180497a7c6cc1096169290963e
7
+ data.tar.gz: 94758e5adb6be529f4c70715aa0796d1a15bd8d17e91d35c63fdc1b29eccea58c0f36e4c29b0cf911a3691fade34c895922ca9b0384cd3df405f723a97ff642f
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
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
4
+
1
5
  ## 6.0.2
2
6
  - Fix: unable to start with password protected key [#45](https://github.com/logstash-plugins/logstash-output-tcp/pull/45)
3
7
 
data/docs/index.asciidoc CHANGED
@@ -45,6 +45,7 @@ This plugin supports the following configuration options plus the <<plugins-{typ
45
45
  | <<plugins-{type}s-{plugin}-ssl_enable>> |<<boolean,boolean>>|No
46
46
  | <<plugins-{type}s-{plugin}-ssl_key>> |a valid filesystem path|No
47
47
  | <<plugins-{type}s-{plugin}-ssl_key_passphrase>> |<<password,password>>|No
48
+ | <<plugins-{type}s-{plugin}-ssl_supported_protocols>> |<<string,string>>|No
48
49
  | <<plugins-{type}s-{plugin}-ssl_verify>> |<<boolean,boolean>>|No
49
50
  |=======================================================================
50
51
 
@@ -130,6 +131,20 @@ SSL key path
130
131
 
131
132
  SSL key passphrase
132
133
 
134
+ [id="plugins-{type}s-{plugin}-ssl_supported_protocols"]
135
+ ===== `ssl_supported_protocols`
136
+
137
+ * Value type is <<string,string>>
138
+ * Allowed values are: `'TLSv1.1'`, `'TLSv1.2'`, `'TLSv1.3'`
139
+ * Default depends on the JDK being used. With up-to-date Logstash, the default is `['TLSv1.2', 'TLSv1.3']`.
140
+ `'TLSv1.1'` is not considered secure and is only provided for legacy applications.
141
+
142
+ List of allowed SSL/TLS versions to use when establishing a secure connection.
143
+
144
+ NOTE: If you configure the plugin to use `'TLSv1.1'` on any recent JVM, such as the one packaged with Logstash,
145
+ the protocol is disabled by default and needs to be enabled manually by changing `jdk.tls.disabledAlgorithms` in
146
+ the *$JDK_HOME/conf/security/java.security* configuration file. That is, `TLSv1.1` needs to be removed from the list.
147
+
133
148
  [id="plugins-{type}s-{plugin}-ssl_verify"]
134
149
  ===== `ssl_verify`
135
150
 
@@ -51,38 +51,43 @@ 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
+ # NOTE: the default setting [] uses SSL engine defaults
55
+ config :ssl_supported_protocols, :validate => ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], :default => [], :list => true
56
+
54
57
  class Client
55
- public
58
+
56
59
  def initialize(socket, logger)
57
60
  @socket = socket
58
61
  @logger = logger
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
  @socket.write(@queue.pop)
67
69
  rescue => e
68
- @logger.warn("tcp output exception", :socket => @socket,
69
- :exception => e)
70
+ log_warn 'socket write failed:', e, socket: (@socket ? @socket.to_s : nil)
70
71
  break
71
72
  end
72
73
  end
73
74
  end # def run
74
75
 
75
- public
76
76
  def write(msg)
77
77
  @queue.push(msg)
78
78
  end # def write
79
+
80
+ def close
81
+ @socket.close
82
+ rescue => e
83
+ log_warn 'socket close failed:', e, socket: (@socket ? @socket.to_s : nil)
84
+ end
79
85
  end # class Client
80
86
 
81
- private
82
87
  def setup_ssl
83
88
  require "openssl"
84
89
 
85
- @ssl_context = OpenSSL::SSL::SSLContext.new
90
+ @ssl_context = new_ssl_context
86
91
  if @ssl_cert
87
92
  @ssl_context.cert = OpenSSL::X509::Certificate.new(File.read(@ssl_cert))
88
93
  if @ssl_key
@@ -104,50 +109,74 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
104
109
  @ssl_context.cert_store = @cert_store
105
110
  @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
106
111
  end
107
- end # def setup_ssl
108
112
 
109
- public
113
+ @ssl_context.min_version = :TLS1_1 # not strictly required - JVM should have disabled TLSv1
114
+ if ssl_supported_protocols.any?
115
+ disabled_protocols = ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'] - ssl_supported_protocols
116
+ unless OpenSSL::SSL.const_defined? :OP_NO_TLSv1_3 # work-around JRuby-OpenSSL bug - missing constant
117
+ @ssl_context.max_version = :TLS1_2 if disabled_protocols.delete('TLSv1.3')
118
+ end
119
+ # mapping 'TLSv1.2' -> OpenSSL::SSL::OP_NO_TLSv1_2
120
+ disabled_protocols.map! { |v| OpenSSL::SSL.const_get "OP_NO_#{v.sub('.', '_')}" }
121
+ @ssl_context.options = disabled_protocols.reduce(@ssl_context.options, :|)
122
+ end
123
+ @ssl_context
124
+ end
125
+ private :setup_ssl
126
+
127
+ # @note to be able to hook up into #ssl_context from tests
128
+ def new_ssl_context
129
+ OpenSSL::SSL::SSLContext.new
130
+ end
131
+ private :new_ssl_context
132
+
133
+ # @overload Base#register
110
134
  def register
111
135
  require "socket"
112
136
  require "stud/try"
113
- if @ssl_enable
114
- setup_ssl
115
- end # @ssl_enable
137
+ @closed = Concurrent::AtomicBoolean.new(false)
138
+ setup_ssl if @ssl_enable
116
139
 
117
140
  if server?
118
141
  @logger.info("Starting tcp output listener", :address => "#{@host}:#{@port}")
119
142
  begin
120
143
  @server_socket = TCPServer.new(@host, @port)
121
144
  rescue Errno::EADDRINUSE
122
- @logger.error("Could not start TCP server: Address in use",
123
- :host => @host, :port => @port)
145
+ @logger.error("Could not start tcp server: Address in use", host: @host, port: @port)
124
146
  raise
125
147
  end
126
148
  if @ssl_enable
127
149
  @server_socket = OpenSSL::SSL::SSLServer.new(@server_socket, @ssl_context)
128
150
  end # @ssl_enable
129
- @client_threads = []
151
+ @client_threads = Concurrent::Array.new
130
152
 
131
153
  @accept_thread = Thread.new(@server_socket) do |server_socket|
154
+ LogStash::Util.set_thread_name("[#{pipeline_id}]|output|tcp|server_accept")
132
155
  loop do
133
- Thread.start(server_socket.accept) do |client_socket|
156
+ break if @closed.value
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|
134
163
  # monkeypatch a 'peer' method onto the socket.
135
164
  client_socket.instance_eval { class << self; include ::LogStash::Util::SocketPeer end }
136
- @logger.debug("Accepted connection", :client => client_socket.peer,
137
- :server => "#{@host}:#{@port}")
165
+ @logger.debug("accepted connection", client: client_socket.peer, server: "#{@host}:#{@port}")
138
166
  client = Client.new(client_socket, @logger)
139
167
  Thread.current[:client] = client
168
+ LogStash::Util.set_thread_name("[#{pipeline_id}]|output|tcp|client_socket-#{@client_threads.size}")
140
169
  @client_threads << Thread.current
141
- client.run
170
+ client.run unless @closed.value
142
171
  end
143
172
  end
144
173
  end
145
174
 
146
175
  @codec.on_event do |event, payload|
176
+ @client_threads.select!(&:alive?)
147
177
  @client_threads.each do |client_thread|
148
178
  client_thread[:client].write(payload)
149
179
  end
150
- @client_threads.reject! {|t| !t.alive? }
151
180
  end
152
181
  else
153
182
  client_socket = nil
@@ -163,8 +192,7 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
163
192
  # Now send the payload
164
193
  client_socket.syswrite(payload) if w.any?
165
194
  rescue => e
166
- @logger.warn("tcp output exception", :host => @host, :port => @port,
167
- :exception => e, :backtrace => e.backtrace)
195
+ log_warn "client socket failed:", e, host: @host, port: @port, socket: (client_socket ? client_socket.to_s : nil)
168
196
  client_socket.close rescue nil
169
197
  client_socket = nil
170
198
  sleep @reconnect_interval
@@ -172,9 +200,27 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
172
200
  end
173
201
  end
174
202
  end
175
- end # def register
203
+ end
204
+
205
+ # @overload Base#receive
206
+ def receive(event)
207
+ @codec.encode(event)
208
+ end
209
+
210
+ # @overload Base#close
211
+ def close
212
+ @closed.make_true
213
+ @server_socket.close rescue nil if @server_socket
214
+
215
+ return unless @client_threads
216
+ @client_threads.each do |thread|
217
+ client = thread[:client]
218
+ client.close rescue nil if client
219
+ end
220
+ end
176
221
 
177
222
  private
223
+
178
224
  def connect
179
225
  begin
180
226
  client_socket = TCPSocket.new(@host, @port)
@@ -183,29 +229,40 @@ class LogStash::Outputs::Tcp < LogStash::Outputs::Base
183
229
  begin
184
230
  client_socket.connect
185
231
  rescue OpenSSL::SSL::SSLError => ssle
186
- @logger.error("SSL Error", :exception => ssle, :backtrace => ssle.backtrace)
232
+ log_error 'connect ssl failure:', ssle, backtrace: false
187
233
  # NOTE(mrichar1): Hack to prevent hammering peer
188
234
  sleep(5)
189
235
  raise
190
236
  end
191
237
  end
192
238
  client_socket.instance_eval { class << self; include ::LogStash::Util::SocketPeer end }
193
- @logger.debug("Opened connection", :client => "#{client_socket.peer}")
239
+ @logger.debug("opened connection", :client => client_socket.peer)
194
240
  return client_socket
195
- rescue StandardError => e
196
- @logger.error("Failed to connect: #{e.message}", :exception => e.class, :backtrace => e.backtrace)
241
+ rescue => e
242
+ log_error 'failed to connect:', e
197
243
  sleep @reconnect_interval
198
244
  retry
199
245
  end
200
246
  end # def connect
201
247
 
202
- private
203
248
  def server?
204
249
  @mode == "server"
205
250
  end # def server?
206
251
 
207
- public
208
- def receive(event)
209
- @codec.encode(event)
210
- end # def receive
252
+ def pipeline_id
253
+ execution_context.pipeline_id || 'main'
254
+ end
255
+
256
+ def log_warn(msg, e, backtrace: @logger.debug?, **details)
257
+ details = details.merge message: e.message, exception: e.class
258
+ details[:backtrace] = e.backtrace if backtrace
259
+ @logger.warn(msg, details)
260
+ end
261
+
262
+ def log_error(msg, e, backtrace: @logger.info?, **details)
263
+ details = details.merge message: e.message, exception: e.class
264
+ details[:backtrace] = e.backtrace if backtrace
265
+ @logger.error(msg, details)
266
+ end
267
+
211
268
  end # class LogStash::Outputs::Tcp
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
 
3
3
  s.name = 'logstash-output-tcp'
4
- s.version = '6.0.2'
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
 
@@ -4,13 +4,112 @@ require "flores/pki"
4
4
 
5
5
  describe LogStash::Outputs::Tcp do
6
6
  subject { described_class.new(config) }
7
- let(:config) { {
8
- "host" => "localhost",
9
- "port" => 2000 + rand(3000),
10
- } }
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,6 +150,62 @@ describe LogStash::Outputs::Tcp do
51
150
  end
52
151
 
53
152
  end
153
+
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)
163
+ end
164
+
165
+ let(:server_min_version) { nil }
166
+ let(:server_max_version) { nil }
167
+ let(:server_ssl_version) { nil }
168
+
169
+ context 'with supported protocol' do
170
+
171
+ let(:config) { super().merge("ssl_supported_protocols" => ['TLSv1.2']) }
172
+
173
+ let(:server_min_version) { 'TLS1_2' }
174
+
175
+ before { subject.register }
176
+ after { secure_server.close }
177
+
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
185
+
186
+ end
187
+
188
+ context 'with unsupported protocol (on server)' do
189
+
190
+ let(:config) { super().merge("ssl_supported_protocols" => ['TLSv1.1']) }
191
+
192
+ let(:server_min_version) { 'TLS1_2' }
193
+
194
+ before { subject.register }
195
+ after { secure_server.close }
196
+
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
202
+
203
+ Thread.start { secure_server.accept rescue nil }
204
+ expect { subject.receive event }.to throw_symbol(:TEST_DONE)
205
+ end
206
+
207
+ end if LOGSTASH_VERSION > '7.0'
208
+
54
209
  end
55
210
 
56
211
  context "encrypted key using PKCS#1" do
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.2
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-03-21 00:00:00.000000000 Z
11
+ date: 2022-06-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -30,6 +30,20 @@ dependencies:
30
30
  - - "<="
31
31
  - !ruby/object:Gem::Version
32
32
  version: '2.99'
33
+ - !ruby/object:Gem::Dependency
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: 8.1.0
39
+ name: logstash-core
40
+ prerelease: false
41
+ type: :runtime
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 8.1.0
33
47
  - !ruby/object:Gem::Dependency
34
48
  requirement: !ruby/object:Gem::Requirement
35
49
  requirements:
@@ -58,6 +72,20 @@ dependencies:
58
72
  - - ">="
59
73
  - !ruby/object:Gem::Version
60
74
  version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ requirement: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: 0.12.2
81
+ name: jruby-openssl
82
+ prerelease: false
83
+ type: :runtime
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: 0.12.2
61
89
  - !ruby/object:Gem::Dependency
62
90
  requirement: !ruby/object:Gem::Requirement
63
91
  requirements:
@@ -72,6 +100,20 @@ dependencies:
72
100
  - - ">="
73
101
  - !ruby/object:Gem::Version
74
102
  version: '0'
103
+ - !ruby/object:Gem::Dependency
104
+ requirement: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ name: logstash-codec-plain
110
+ prerelease: false
111
+ type: :development
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
75
117
  - !ruby/object:Gem::Dependency
76
118
  requirement: !ruby/object:Gem::Requirement
77
119
  requirements: