logstash-input-beats 2.1.4 → 2.2.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 +4 -0
- data/lib/logstash/inputs/beats.rb +38 -10
- data/lib/lumberjack/beats/client.rb +65 -16
- data/lib/lumberjack/beats/server.rb +99 -26
- data/logstash-input-beats.gemspec +2 -1
- data/spec/integration/filebeat_spec.rb +235 -0
- data/spec/integration/logstash_forwarder_spec.rb +102 -0
- data/spec/integration_spec.rb +340 -48
- data/spec/support/client_process_helpers.rb +27 -0
- data/spec/support/file_helpers.rb +61 -0
- data/spec/support/flores_extensions.rb +42 -0
- data/spec/support/integration_shared_context.rb +55 -0
- metadata +28 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2b44b745b034e4879863dafa18e2a47f6c5077cb
|
4
|
+
data.tar.gz: be9744dfb679a87d2c62d9c70d0556c5a4101e91
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c9a92bec0e4ae62083feb22c6736c474b31fe49b3853f6ae48c9dcd5d71259ac38da73fc340853f171f5fc3f4167bcf71a5e99071e5aee4d1fb8c8667ad94514
|
7
|
+
data.tar.gz: aadca9c26ab53386668cd0e4544178d0485cdd0d2e29546dc8bd261593a1711930a3ff598ab7c9c57f8a672cee34d82fab31a2ba25c4edad6e945349df55cc17
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# 2.2.0
|
2
|
+
- The server can now do client side verification by providing a list of certificate authorities and configuring the `ssl_verify_mode`,
|
3
|
+
the server can use `peer`, if the client send a certificate it will be validated. Using `force_peer` will make sure the client provide a certificate
|
4
|
+
and it will be validated with the know CA. #8
|
1
5
|
# 2.1.4
|
2
6
|
- Change the `logger#warn` for `logger.debug` when a peer get disconnected, keep alive check from proxy can generate a lot of logs #46
|
3
7
|
# 2.1.3
|
@@ -68,6 +68,27 @@ class LogStash::Inputs::Beats < LogStash::Inputs::Base
|
|
68
68
|
# SSL key passphrase to use.
|
69
69
|
config :ssl_key_passphrase, :validate => :password
|
70
70
|
|
71
|
+
# Validate client certificates against theses authorities
|
72
|
+
# You can defined multiples files or path, all the certificates will
|
73
|
+
# be read and added to the trust store. You need to configure the `ssl_verify_mode`
|
74
|
+
# to `peer` or `force_peer` to enable the verification.
|
75
|
+
#
|
76
|
+
# This feature only support certificate directly signed by your root ca.
|
77
|
+
# Intermediate CA are currently not supported.
|
78
|
+
#
|
79
|
+
config :ssl_certificate_authorities, :validate => :array, :default => []
|
80
|
+
|
81
|
+
# By default the server dont do any client verification,
|
82
|
+
#
|
83
|
+
# `peer` will make the server ask the client to provide a certificate,
|
84
|
+
# if the client provide the certificate it will be validated.
|
85
|
+
#
|
86
|
+
# `force_peer` will make the server ask the client for their certificate, if the clients
|
87
|
+
# doesn't provide it the connection will be closed.
|
88
|
+
#
|
89
|
+
# This option need to be used with `ssl_certificate_authorities` and a defined list of CA.
|
90
|
+
config :ssl_verify_mode, :validate => ["none", "peer", "force_peer"], :default => "none"
|
91
|
+
|
71
92
|
# The number of seconds before we raise a timeout,
|
72
93
|
# this option is useful to control how much time to wait if something is blocking the pipeline.
|
73
94
|
config :congestion_threshold, :validate => :number, :default => 5
|
@@ -87,9 +108,16 @@ class LogStash::Inputs::Beats < LogStash::Inputs::Base
|
|
87
108
|
end
|
88
109
|
|
89
110
|
@logger.info("Beats inputs: Starting input listener", :address => "#{@host}:#{@port}")
|
90
|
-
|
91
|
-
|
92
|
-
|
111
|
+
|
112
|
+
|
113
|
+
@lumberjack = Lumberjack::Beats::Server.new(:address => @host,
|
114
|
+
:port => @port,
|
115
|
+
:ssl => @ssl,
|
116
|
+
:ssl_certificate => @ssl_certificate,
|
117
|
+
:ssl_key => @ssl_key,
|
118
|
+
:ssl_key_passphrase => @ssl_key_passphrase,
|
119
|
+
:ssl_certificate_authorities => @ssl_certificate_authorities,
|
120
|
+
:ssl_verify_mode => @ssl_verify_mode)
|
93
121
|
|
94
122
|
# in 1.5 the main SizeQueue doesnt have the concept of timeout
|
95
123
|
# We are using a small plugin buffer to move events to the internal queue
|
@@ -103,8 +131,8 @@ class LogStash::Inputs::Beats < LogStash::Inputs::Base
|
|
103
131
|
@codec = LogStash::Codecs::IdentityMapCodec.new(@codec)
|
104
132
|
|
105
133
|
# Keep a list of active connections so we can flush their codec on shutdown
|
106
|
-
|
107
|
-
# Use threadsafe gem, since we have a strict dependency on concurrent-ruby 0.9.2
|
134
|
+
|
135
|
+
# Use threadsafe gem, since we have a strict dependency on concurrent-ruby 0.9.2
|
108
136
|
# in the core
|
109
137
|
@connections_list = ThreadSafe::Hash.new
|
110
138
|
end # def register
|
@@ -127,13 +155,13 @@ class LogStash::Inputs::Beats < LogStash::Inputs::Base
|
|
127
155
|
connection = @lumberjack.accept # call that creates a new connection
|
128
156
|
# if the connection is nil the connection was closed upstream,
|
129
157
|
# so we will try in another iteration to recover or stop.
|
130
|
-
next if connection.nil?
|
158
|
+
next if connection.nil?
|
131
159
|
|
132
|
-
Thread.new do
|
160
|
+
Thread.new do
|
133
161
|
handle_new_connection(connection)
|
134
162
|
end
|
135
163
|
else
|
136
|
-
@logger.warn("Beats input: the pipeline is blocked, temporary refusing new connection.",
|
164
|
+
@logger.warn("Beats input: the pipeline is blocked, temporary refusing new connection.",
|
137
165
|
:reconnect_backoff_sleep => RECONNECT_BACKOFF_SLEEP)
|
138
166
|
sleep(RECONNECT_BACKOFF_SLEEP)
|
139
167
|
end
|
@@ -186,9 +214,9 @@ class LogStash::Inputs::Beats < LogStash::Inputs::Base
|
|
186
214
|
@logger.warn("Beats input: The circuit breaker has detected a slowdown or stall in the pipeline, the input is closing the current connection and rejecting new connection until the pipeline recover.",
|
187
215
|
:exception => e.class)
|
188
216
|
rescue Exception => e # If we have a malformed packet we should handle that so the input doesn't crash completely.
|
189
|
-
@logger.error("Beats input: unhandled exception",
|
217
|
+
@logger.error("Beats input: unhandled exception",
|
190
218
|
:exception => e,
|
191
|
-
:backtrace => e.backtrace)
|
219
|
+
:backtrace => e.backtrace)
|
192
220
|
ensure
|
193
221
|
transformer = LogStash::Inputs::BeatsSupport::EventTransformCommon.new(self)
|
194
222
|
|
@@ -12,14 +12,21 @@ module Lumberjack module Beats
|
|
12
12
|
:port => 0,
|
13
13
|
:addresses => [],
|
14
14
|
:ssl_certificate => nil,
|
15
|
+
:ssl_certificate_key => nil,
|
16
|
+
:ssl_certificate_authorities => nil,
|
15
17
|
:ssl => true,
|
16
18
|
:json => false,
|
17
19
|
}.merge(opts)
|
18
20
|
|
19
|
-
@opts[:addresses] =
|
21
|
+
@opts[:addresses] = Array(@opts[:addresses])
|
20
22
|
raise "Must set a port." if @opts[:port] == 0
|
21
23
|
raise "Must set atleast one address" if @opts[:addresses].empty? == 0
|
22
|
-
|
24
|
+
|
25
|
+
if @opts[:ssl]
|
26
|
+
if @opts[:ssl_certificate_authorities].nil? && (@opts[:ssl_certificate].nil? || @opts[:ssl_certificate_key].nil?)
|
27
|
+
raise "Must set a ssl certificate or path"
|
28
|
+
end
|
29
|
+
end
|
23
30
|
|
24
31
|
@socket = connect
|
25
32
|
end
|
@@ -67,36 +74,78 @@ module Lumberjack module Beats
|
|
67
74
|
@opts = {
|
68
75
|
:port => 0,
|
69
76
|
:address => "127.0.0.1",
|
77
|
+
:ssl_certificate_authorities => [], # use the same naming as beats' TLS options
|
70
78
|
:ssl_certificate => nil,
|
79
|
+
:ssl_certificate_key => nil,
|
80
|
+
:ssl_certificate_password => nil,
|
71
81
|
:ssl => true,
|
72
82
|
:json => false,
|
73
83
|
}.merge(opts)
|
74
84
|
@host = @opts[:address]
|
75
85
|
|
76
|
-
connection_start
|
86
|
+
connection_start
|
77
87
|
end
|
78
88
|
|
79
89
|
private
|
80
|
-
def connection_start
|
81
|
-
tcp_socket = TCPSocket.new(opts[:address], opts[:port])
|
82
|
-
|
90
|
+
def connection_start
|
91
|
+
tcp_socket = TCPSocket.new(@opts[:address], @opts[:port])
|
92
|
+
|
93
|
+
if !@opts[:ssl]
|
83
94
|
@socket = tcp_socket
|
84
95
|
else
|
85
|
-
certificate = OpenSSL::X509::Certificate.new(File.read(opts[:ssl_certificate]))
|
86
96
|
|
87
|
-
|
88
|
-
|
97
|
+
@socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, setup_ssl)
|
98
|
+
@socket.connect
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
def setup_ssl
|
104
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
89
105
|
|
90
|
-
|
91
|
-
|
92
|
-
|
106
|
+
ssl_context.cert = certificate
|
107
|
+
ssl_context.key = private_key
|
108
|
+
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
109
|
+
ssl_context.cert_store = trust_store
|
110
|
+
ssl_context
|
111
|
+
end
|
93
112
|
|
94
|
-
|
95
|
-
|
113
|
+
private
|
114
|
+
def certificate
|
115
|
+
if @opts[:ssl_certificate]
|
116
|
+
OpenSSL::X509::Certificate.new(File.open(@opts[:ssl_certificate]))
|
96
117
|
end
|
97
118
|
end
|
98
119
|
|
99
|
-
private
|
120
|
+
private
|
121
|
+
def private_key
|
122
|
+
OpenSSL::PKey::RSA.new(File.read(@opts[:ssl_certificate_key]), @opts[:ssl_certificate_password]) if @opts[:ssl_certificate_key]
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
def trust_store
|
127
|
+
store = OpenSSL::X509::Store.new
|
128
|
+
|
129
|
+
Array(@opts[:ssl_certificate_authorities]).each do |certificate_authority|
|
130
|
+
if File.file?(certificate_authority)
|
131
|
+
store.add_file(certificate_authority)
|
132
|
+
else
|
133
|
+
# add_path is no implemented under jruby
|
134
|
+
# so recursively try to load all the certificate from this directory
|
135
|
+
# https://github.com/jruby/jruby-openssl/blob/master/src/main/java/org/jruby/ext/openssl/X509Store.java#L159
|
136
|
+
if !!(RUBY_PLATFORM == "java")
|
137
|
+
Dir.glob(File.join(certificate_authority, "**", "*")).each { |f| store.add_file(f) }
|
138
|
+
else
|
139
|
+
store.add_path(certificate_authority)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
store
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
private
|
100
149
|
def inc
|
101
150
|
@sequence = 0 if @sequence + 1 > Lumberjack::Beats::SEQUENCE_MAX
|
102
151
|
@sequence = @sequence + 1
|
@@ -134,7 +183,7 @@ module Lumberjack module Beats
|
|
134
183
|
ack(elements.size)
|
135
184
|
end
|
136
185
|
|
137
|
-
private
|
186
|
+
private
|
138
187
|
def compress_payload(payload)
|
139
188
|
compress = Zlib::Deflate.deflate(payload)
|
140
189
|
["1", "C", compress.bytesize, compress].pack("AANA*")
|
@@ -29,35 +29,33 @@ module Lumberjack module Beats
|
|
29
29
|
:ssl => true,
|
30
30
|
:ssl_certificate => nil,
|
31
31
|
:ssl_key => nil,
|
32
|
-
:ssl_key_passphrase => nil
|
32
|
+
:ssl_key_passphrase => nil,
|
33
|
+
:ssl_certificate_authorities => nil,
|
34
|
+
:ssl_verify_mode => :none # By default we dont verify client
|
33
35
|
}.merge(options)
|
34
36
|
|
35
37
|
if @options[:ssl]
|
36
|
-
[:
|
37
|
-
|
38
|
-
|
39
|
-
|
38
|
+
if verify_client?(@options[:ssl_verify_mode]) && certificate_authorities.empty?
|
39
|
+
raise "When `ssl_verify_mode` is set to `peer` OR `force_peer` you need to specify the `ssl_certificate_authorities`"
|
40
|
+
end
|
41
|
+
|
42
|
+
if !verify_client?(@options[:ssl_verify_mode]) && certificate_authorities.size > 0
|
43
|
+
raise "When `ssl_certificate_authorities` is configured you need to set `ssl_verify_mode` to either `peer` or `force_peer`"
|
44
|
+
end
|
45
|
+
|
46
|
+
if @options[:ssl_certificate].nil? || @options[:ssl_key].nil?
|
47
|
+
raise "You must specify `ssl_certificate` AND `ssl_key`"
|
40
48
|
end
|
41
49
|
end
|
42
50
|
|
43
51
|
@server = TCPServer.new(@options[:address], @options[:port])
|
44
|
-
|
45
52
|
@close = Concurrent::AtomicBoolean.new
|
53
|
+
@port = retrieve_current_port
|
46
54
|
|
47
|
-
|
48
|
-
# TCPServer#addr == [ address_family, port, address, address ]
|
49
|
-
@port = @server.addr[1]
|
50
|
-
|
51
|
-
if @options[:ssl]
|
52
|
-
# load SSL certificate
|
53
|
-
@ssl = OpenSSL::SSL::SSLContext.new
|
54
|
-
@ssl.cert = OpenSSL::X509::Certificate.new(File.read(@options[:ssl_certificate]))
|
55
|
-
@ssl.key = OpenSSL::PKey::RSA.new(File.read(@options[:ssl_key]),
|
56
|
-
@options[:ssl_key_passphrase])
|
57
|
-
end
|
55
|
+
setup_ssl if ssl?
|
58
56
|
end # def initialize
|
59
57
|
|
60
|
-
# Server#run method, allow the library to manage all the connection
|
58
|
+
# Server#run method, allow the library to manage all the connection
|
61
59
|
# threads, this handing is quite minimal and don't handler
|
62
60
|
# all the possible cases deconnection/connection.
|
63
61
|
#
|
@@ -76,8 +74,8 @@ module Lumberjack module Beats
|
|
76
74
|
Thread.new(connection) do |connection|
|
77
75
|
begin
|
78
76
|
connection.run(&block)
|
79
|
-
rescue Lumberjack::Beats::Connection::ConnectionClosed
|
80
|
-
# Connection will raise a wrapped exception upstream,
|
77
|
+
rescue Lumberjack::Beats::Connection::ConnectionClosed
|
78
|
+
# Connection will raise a wrapped exception upstream,
|
81
79
|
# but if the threads are managed by the library we can simply ignore it.
|
82
80
|
#
|
83
81
|
# Note: This follow the previous behavior of the perfect silence.
|
@@ -87,7 +85,7 @@ module Lumberjack module Beats
|
|
87
85
|
end # def run
|
88
86
|
|
89
87
|
def ssl?
|
90
|
-
@ssl
|
88
|
+
@options[:ssl]
|
91
89
|
end
|
92
90
|
|
93
91
|
def accept(&block)
|
@@ -139,13 +137,88 @@ module Lumberjack module Beats
|
|
139
137
|
@close.make_true
|
140
138
|
@server.close unless @server.closed?
|
141
139
|
end
|
142
|
-
|
140
|
+
|
141
|
+
private
|
142
|
+
def verify_client?(mode)
|
143
|
+
mode = mode.to_sym
|
144
|
+
mode == :peer || mode == :force_peer
|
145
|
+
end
|
146
|
+
|
147
|
+
def retrieve_current_port
|
148
|
+
# Query the port in case the port number is '0'
|
149
|
+
# TCPServer#addr == [ address_family, port, address, address ]
|
150
|
+
@server.addr[1]
|
151
|
+
end
|
152
|
+
|
153
|
+
def certificate_authorities
|
154
|
+
Array(@options[:ssl_certificate_authorities])
|
155
|
+
end
|
156
|
+
|
157
|
+
def server_private_key
|
158
|
+
OpenSSL::PKey::RSA.new(File.read(@options[:ssl_key]), @options[:ssl_key_passphrase])
|
159
|
+
end
|
160
|
+
|
161
|
+
def server_certificate
|
162
|
+
OpenSSL::X509::Certificate.new(File.read(@options[:ssl_certificate]))
|
163
|
+
end
|
164
|
+
|
165
|
+
def verify_mode
|
166
|
+
case @options[:ssl_verify_mode].to_sym
|
167
|
+
when :none
|
168
|
+
OpenSSL::SSL::VERIFY_NONE
|
169
|
+
when :peer
|
170
|
+
OpenSSL::SSL::VERIFY_PEER
|
171
|
+
when :force_peer
|
172
|
+
OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def jruby?
|
177
|
+
RUBY_PLATFORM == "java"
|
178
|
+
end
|
179
|
+
|
180
|
+
def trust_store
|
181
|
+
store = OpenSSL::X509::Store.new
|
182
|
+
|
183
|
+
if certificate_authorities.size > 0
|
184
|
+
certificate_authorities.each do |certificate_authority|
|
185
|
+
if File.file?(certificate_authority)
|
186
|
+
store.add_file(certificate_authority)
|
187
|
+
else
|
188
|
+
# `#add_path` is not implemented under jruby
|
189
|
+
# so recursively try to load all the certificate from this directory
|
190
|
+
# https://github.com/jruby/jruby-openssl/blob/master/src/main/java/org/jruby/ext/openssl/X509Store.java#L159
|
191
|
+
if jruby?
|
192
|
+
Dir.glob(File.join(certificate_authority, "**", "*")).each { |f| store.add_file(f) }
|
193
|
+
else
|
194
|
+
store.add_path(certificate_authority)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
store
|
201
|
+
end
|
202
|
+
|
203
|
+
def setup_ssl
|
204
|
+
@ssl = OpenSSL::SSL::SSLContext.new
|
205
|
+
|
206
|
+
# @ssl.verify_callback = lambda do |preverify_ok, context|
|
207
|
+
# require "pry"
|
208
|
+
# binding.pry
|
209
|
+
# end
|
210
|
+
@ssl.cert_store = trust_store
|
211
|
+
@ssl.verify_mode = verify_mode
|
212
|
+
# @ssl.ca_file = certificate_authorities.first
|
213
|
+
@ssl.cert = server_certificate
|
214
|
+
@ssl.key = server_private_key
|
215
|
+
end
|
216
|
+
end
|
143
217
|
|
144
218
|
class Parser
|
145
219
|
PROTOCOL_VERSION_1 = "1".ord
|
146
220
|
PROTOCOL_VERSION_2 = "2".ord
|
147
221
|
|
148
|
-
SUPPORTED_PROTOCOLS = [PROTOCOL_VERSION_1, PROTOCOL_VERSION_2]
|
149
222
|
class UnsupportedProtocol < StandardError; end
|
150
223
|
|
151
224
|
def initialize
|
@@ -242,7 +315,7 @@ module Lumberjack module Beats
|
|
242
315
|
end
|
243
316
|
|
244
317
|
def supported_protocol?(version)
|
245
|
-
|
318
|
+
PROTOCOL_VERSION_2 == version || PROTOCOL_VERSION_1 == version
|
246
319
|
end
|
247
320
|
|
248
321
|
def window_size(&block)
|
@@ -313,7 +386,7 @@ module Lumberjack module Beats
|
|
313
386
|
end # class Parser
|
314
387
|
|
315
388
|
class Connection
|
316
|
-
# Wrap the original exception into a common one,
|
389
|
+
# Wrap the original exception into a common one,
|
317
390
|
# to make upstream managing and reporting easier
|
318
391
|
# But lets make sure we keep the meaning of the original exception.
|
319
392
|
class ConnectionClosed < StandardError
|
@@ -351,7 +424,7 @@ module Lumberjack module Beats
|
|
351
424
|
|
352
425
|
@server = server
|
353
426
|
@ack_handler = nil
|
354
|
-
|
427
|
+
|
355
428
|
# Fetch the details of the host before reading anything from the socket
|
356
429
|
# se we can use that information when debugging connection issues with
|
357
430
|
# remote hosts.
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "logstash-input-beats"
|
3
|
-
s.version = "2.
|
3
|
+
s.version = "2.2.0"
|
4
4
|
s.licenses = ["Apache License (2.0)"]
|
5
5
|
s.summary = "Receive events using the lumberjack protocol."
|
6
6
|
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"
|
@@ -33,5 +33,6 @@ Gem::Specification.new do |s|
|
|
33
33
|
s.add_development_dependency "rspec-wait"
|
34
34
|
s.add_development_dependency "logstash-devutils", "~> 0.0.18"
|
35
35
|
s.add_development_dependency "logstash-codec-json"
|
36
|
+
s.add_development_dependency "childprocess" # To make filebeat/LSF integration test easier to write.
|
36
37
|
end
|
37
38
|
|
@@ -0,0 +1,235 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/inputs/beats"
|
3
|
+
require "stud/temporary"
|
4
|
+
require "flores/pki"
|
5
|
+
require "fileutils"
|
6
|
+
require "thread"
|
7
|
+
require "spec_helper"
|
8
|
+
require "yaml"
|
9
|
+
require "fileutils"
|
10
|
+
require "flores/pki"
|
11
|
+
require_relative "../support/flores_extensions"
|
12
|
+
require_relative "../support/file_helpers"
|
13
|
+
require_relative "../support/integration_shared_context"
|
14
|
+
require_relative "../support/client_process_helpers"
|
15
|
+
|
16
|
+
FILEBEAT_BINARY = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "vendor", "filebeat", "filebeat"))
|
17
|
+
|
18
|
+
describe "Filebeat", :integration => true do
|
19
|
+
include ClientProcessHelpers
|
20
|
+
include FileHelpers
|
21
|
+
|
22
|
+
before :all do
|
23
|
+
unless File.exist?(FILEBEAT_BINARY)
|
24
|
+
raise "Cannot find `Filebeat` binary in `vendor/filebeat`. Did you run `bundle exec rake test:integration:setup` before running the integration suite?"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
include_context "beats configuration"
|
29
|
+
|
30
|
+
# Filebeat related variables
|
31
|
+
let(:cmd) { [filebeat_exec, "-c", filebeat_config_path, "-e", "-v"] }
|
32
|
+
|
33
|
+
let(:filebeat_exec) { FILEBEAT_BINARY }
|
34
|
+
|
35
|
+
let_empty_tmp_file(:registry_file)
|
36
|
+
let(:filebeat_config) do
|
37
|
+
{
|
38
|
+
"filebeat" => {
|
39
|
+
"prospectors" => [{ "paths" => [log_file], "input_type" => "log" }],
|
40
|
+
"registry_file" => registry_file,
|
41
|
+
"scan_frequency" => "1s"
|
42
|
+
},
|
43
|
+
"output" => {
|
44
|
+
"logstash" => { "hosts" => ["#{host}:#{port}"] },
|
45
|
+
"logging" => { "level" => "debug" }
|
46
|
+
}
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
let_tmp_file(:filebeat_config_path) { YAML.dump(filebeat_config) }
|
51
|
+
before :each do
|
52
|
+
start_client
|
53
|
+
sleep(2) # give some time to FB to send somthing
|
54
|
+
stop_client
|
55
|
+
end
|
56
|
+
|
57
|
+
###########################################################
|
58
|
+
shared_context "Root CA" do
|
59
|
+
let(:root_ca) { Flores::PKI.generate("CN=root.localhost") }
|
60
|
+
let(:root_ca_certificate) { root_ca.first }
|
61
|
+
let(:root_ca_key) { root_ca.last }
|
62
|
+
let_tmp_file(:root_ca_certificate_file) { root_ca_certificate }
|
63
|
+
end
|
64
|
+
|
65
|
+
shared_context "Intermediate CA" do
|
66
|
+
let(:intermediate_ca) { Flores::PKI.create_intermediate_certificate("CN=intermediate.localhost", root_ca_certificate, root_ca_key) }
|
67
|
+
let(:intermediate_ca_certificate) { intermediate_ca.first }
|
68
|
+
let(:intermediate_ca_key) { intermediate_ca.last }
|
69
|
+
let_tmp_file(:certificate_authorities_chain) { Flores::PKI.chain_certificates(root_ca_certificate, intermediate_ca_certificate) }
|
70
|
+
end
|
71
|
+
|
72
|
+
############################################################
|
73
|
+
# Actuals tests
|
74
|
+
context "Plain TCP" do
|
75
|
+
include_examples "send events"
|
76
|
+
end
|
77
|
+
|
78
|
+
context "TLS" do
|
79
|
+
context "Server verification" do
|
80
|
+
let(:filebeat_config) do
|
81
|
+
super.merge({
|
82
|
+
"output" => {
|
83
|
+
"logstash" => {
|
84
|
+
"hosts" => ["#{host}:#{port}"],
|
85
|
+
"tls" => { "certificate_authorities" => certificate_authorities }
|
86
|
+
},
|
87
|
+
"logging" => { "level" => "debug" }
|
88
|
+
}})
|
89
|
+
end
|
90
|
+
|
91
|
+
let(:input_config) do
|
92
|
+
super.merge({
|
93
|
+
"ssl" => true,
|
94
|
+
"ssl_certificate" => certificate_file,
|
95
|
+
"ssl_key" => certificate_key_file
|
96
|
+
})
|
97
|
+
end
|
98
|
+
|
99
|
+
let(:certificate_authorities) { [certificate_file] }
|
100
|
+
let(:certificate_data) { Flores::PKI.generate }
|
101
|
+
let_tmp_file(:certificate_key_file) { certificate_data.last }
|
102
|
+
let_tmp_file(:certificate_file) { certificate_data.first }
|
103
|
+
|
104
|
+
context "self signed certificate" do
|
105
|
+
include_examples "send events"
|
106
|
+
end
|
107
|
+
|
108
|
+
context "CA root" do
|
109
|
+
include_context "Root CA"
|
110
|
+
|
111
|
+
context "directly signed client certificate" do
|
112
|
+
let(:certificate_authorities) { [root_ca_certificate_file] }
|
113
|
+
let(:certificate_data) { Flores::PKI.create_client_certicate("CN=localhost", root_ca_certificate, root_ca_key) }
|
114
|
+
|
115
|
+
include_examples "send events"
|
116
|
+
end
|
117
|
+
|
118
|
+
context "intermediate CA signs client certificate" do
|
119
|
+
include_context "Intermediate CA"
|
120
|
+
|
121
|
+
let(:certificate_data) { Flores::PKI.create_client_certicate("CN=localhost", intermediate_ca_certificate, intermediate_ca_key) }
|
122
|
+
let(:certificate_authorities) { [certificate_authorities_chain] }
|
123
|
+
|
124
|
+
include_examples "send events"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
context "Client verification / Mutual validation" do
|
129
|
+
let(:filebeat_config) do
|
130
|
+
super.merge({
|
131
|
+
"output" => {
|
132
|
+
"logstash" => {
|
133
|
+
"hosts" => ["#{host}:#{port}"],
|
134
|
+
"tls" => {
|
135
|
+
"certificate_authorities" => certificate_authorities,
|
136
|
+
"certificate" => certificate_file,
|
137
|
+
"certificate_key" => certificate_key_file
|
138
|
+
}
|
139
|
+
},
|
140
|
+
"logging" => { "level" => "debug" }
|
141
|
+
}})
|
142
|
+
end
|
143
|
+
|
144
|
+
let(:input_config) do
|
145
|
+
super.merge({
|
146
|
+
"ssl" => true,
|
147
|
+
"ssl_certificate_authorities" => certificate_authorities,
|
148
|
+
"ssl_certificate" => server_certificate_file,
|
149
|
+
"ssl_key" => server_certificate_key_file,
|
150
|
+
"ssl_verify_mode" => "force_peer"
|
151
|
+
})
|
152
|
+
end
|
153
|
+
|
154
|
+
context "with a self signed certificate" do
|
155
|
+
let(:certificate_authorities) { [certificate_file] }
|
156
|
+
let(:certificate_data) { Flores::PKI.generate }
|
157
|
+
let_tmp_file(:certificate_key_file) { certificate_data.last }
|
158
|
+
let_tmp_file(:certificate_file) { certificate_data.first }
|
159
|
+
let_tmp_file(:server_certificate_file) { certificate_data.first }
|
160
|
+
let_tmp_file(:server_certificate_key_file) { certificate_data.last }
|
161
|
+
|
162
|
+
include_examples "send events"
|
163
|
+
end
|
164
|
+
|
165
|
+
context "CA root" do
|
166
|
+
include_context "Root CA"
|
167
|
+
|
168
|
+
let_tmp_file(:server_certificate_file) { server_certificate_data.first }
|
169
|
+
let_tmp_file(:server_certificate_key_file) { server_certificate_data.last }
|
170
|
+
|
171
|
+
context "directly signed client certificate" do
|
172
|
+
let(:certificate_authorities) { [root_ca_certificate_file] }
|
173
|
+
let(:certificate_data) { Flores::PKI.create_client_certicate("CN=localhost", root_ca_certificate, root_ca_key) }
|
174
|
+
let(:server_certificate_data) { Flores::PKI.create_client_certicate("CN=localhost", root_ca_certificate, root_ca_key) }
|
175
|
+
|
176
|
+
include_examples "send events"
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
# Doesnt work because of this issues in `jruby-openssl`
|
181
|
+
# https://github.com/jruby/jruby-openssl/issues/84
|
182
|
+
xcontext "intermediate create server and client certificate" do
|
183
|
+
include_context "Intermediate CA"
|
184
|
+
|
185
|
+
let(:certificate_data) { Flores::PKI.create_client_certicate("CN=localhost", intermediate_ca_certificate, intermediate_ca_key) }
|
186
|
+
let(:server_certificate_data) { Flores::PKI.create_client_certicate("CN=localhost", intermediate_ca_certificate, intermediate_ca_key) }
|
187
|
+
let(:certificate_authorities) { [certificate_authorities_chain] }
|
188
|
+
|
189
|
+
include_examples "send events"
|
190
|
+
end
|
191
|
+
|
192
|
+
context "and Secondary CA multiples clients" do
|
193
|
+
context "with CA in different files" do
|
194
|
+
let(:secondary_ca) { Flores::PKI.generate }
|
195
|
+
let(:secondary_ca_key) { secondary_ca.last }
|
196
|
+
let(:secondary_ca_certificate) { secondary_ca.first }
|
197
|
+
let_tmp_file(:secondary_ca_certificate_file) { secondary_ca.first }
|
198
|
+
|
199
|
+
let(:secondary_client_certificate_data) { Flores::PKI.create_client_certicate("CN=localhost", secondary_ca_certificate, secondary_ca_key) }
|
200
|
+
let_tmp_file(:secondary_client_certificate_file) { secondary_client_certificate_data.first }
|
201
|
+
let_tmp_file(:secondary_client_certificate_key_file) { secondary_client_certificate_data.last }
|
202
|
+
let(:certificate_authorities) { [root_ca_certificate_file, secondary_ca_certificate_file] }
|
203
|
+
let(:certificate_data) { Flores::PKI.create_client_certicate("CN=localhost", root_ca_certificate, root_ca_key) }
|
204
|
+
|
205
|
+
let(:server_certificate_data) { Flores::PKI.create_client_certicate("CN=localhost", root_ca_certificate, root_ca_key) }
|
206
|
+
|
207
|
+
context "client from primary CA" do
|
208
|
+
include_examples "send events"
|
209
|
+
end
|
210
|
+
|
211
|
+
context "client from secondary CA" do
|
212
|
+
let(:filebeat_config) do
|
213
|
+
super.merge({
|
214
|
+
"output" => {
|
215
|
+
"logstash" => {
|
216
|
+
"hosts" => ["#{host}:#{port}"],
|
217
|
+
"tls" => {
|
218
|
+
"certificate_authorities" => certificate_authorities,
|
219
|
+
"certificate" => secondary_client_certificate_file,
|
220
|
+
"certificate_key" => secondary_client_certificate_key_file
|
221
|
+
}
|
222
|
+
},
|
223
|
+
"logging" => { "level" => "debug" }
|
224
|
+
}})
|
225
|
+
end
|
226
|
+
|
227
|
+
include_examples "send events"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|