logstash-input-beats 6.0.11-java → 6.1.1-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,7 +9,16 @@ module LogStash module Inputs class Beats
9
9
  ts = coerce_ts(hash.delete("@timestamp"))
10
10
 
11
11
  event.set("@timestamp", ts) unless ts.nil?
12
- hash.each { |k, v| event.set(k, v) }
12
+ hash.each do |k, v|
13
+ #could be a nested map, so we need to merge and not overwrite
14
+ existing_value = event.get(k)
15
+ if existing_value.is_a?(Hash)
16
+ existing_value = existing_value.merge(v)
17
+ else
18
+ existing_value = v
19
+ end
20
+ event.set(k, existing_value)
21
+ end
13
22
  super(event)
14
23
  event.tag("beats_input_codec_#{codec_name}_applied") if include_codec_tag?
15
24
  event
@@ -15,8 +15,8 @@ module LogStash module Inputs class Beats
15
15
  return unless @input.add_hostname
16
16
  host = event.get("[beat][hostname]")
17
17
 
18
- if host && event.get("host").nil?
19
- event.set("host", host)
18
+ if host && event.get(@input.field_hostname).nil?
19
+ event.set(@input.field_hostname, host)
20
20
  end
21
21
  end
22
22
 
@@ -31,7 +31,9 @@ module LogStash module Inputs class Beats
31
31
  hash = message.getData
32
32
  ip_address = ip_address(ctx)
33
33
 
34
- hash['@metadata']['ip_address'] = ip_address unless ip_address.nil? || hash['@metadata'].nil?
34
+ unless ip_address.nil? || hash['@metadata'].nil?
35
+ set_nested(hash, @input.field_hostip, ip_address)
36
+ end
35
37
  target_field = extract_target_field(hash)
36
38
 
37
39
  extract_tls_peer(hash, ctx)
@@ -140,11 +142,12 @@ module LogStash module Inputs class Beats
140
142
  end
141
143
 
142
144
  if tls_verified
145
+ set_nested(hash, @field_tls_protocol_version, tls_session.getProtocol())
146
+ set_nested(hash, @field_tls_peer_subject, tls_session.getPeerPrincipal().getName())
147
+ set_nested(hash, @field_tls_cipher, tls_session.getCipherSuite())
148
+
143
149
  hash['@metadata']['tls_peer'] = {
144
- :status => "verified",
145
- :protocol => tls_session.getProtocol(),
146
- :subject => tls_session.getPeerPrincipal().getName(),
147
- :cipher_suite => tls_session.getCipherSuite()
150
+ :status => "verified"
148
151
  }
149
152
  else
150
153
  hash['@metadata']['tls_peer'] = {
@@ -154,6 +157,28 @@ module LogStash module Inputs class Beats
154
157
  end
155
158
  end
156
159
 
160
+ # set the value for field_name into the hash, nesting into sub-hashes and creating hashes where necessary
161
+ public #only to make it testable
162
+ def set_nested(hash, field_name, value)
163
+ field_ref = Java::OrgLogstash::FieldReference.from(field_name)
164
+ # create @metadata sub-hash if needed
165
+ if field_ref.type == Java::OrgLogstash::FieldReference::META_CHILD
166
+ unless hash.key?("@metadata")
167
+ hash["@metadata"] = {}
168
+ end
169
+ nesting_hash = hash["@metadata"]
170
+ else
171
+ nesting_hash = hash
172
+ end
173
+
174
+ field_ref.path.each do |token|
175
+ nesting_hash[token] = {} unless nesting_hash.key?(token)
176
+ nesting_hash = nesting_hash[token]
177
+ end
178
+ nesting_hash[field_ref.key] = value
179
+ end
180
+
181
+ private
157
182
  def extract_target_field(hash)
158
183
  if from_filebeat?(hash)
159
184
  hash.delete(FILEBEAT_LOG_LINE_FIELD).to_s
@@ -27,6 +27,7 @@ Gem::Specification.new do |s|
27
27
  s.add_runtime_dependency "thread_safe", "~> 0.3.5"
28
28
  s.add_runtime_dependency "logstash-codec-multiline", ">= 2.0.5"
29
29
  s.add_runtime_dependency 'jar-dependencies', '~> 0.3', '>= 0.3.4'
30
+ s.add_runtime_dependency 'logstash-mixin-ecs_compatibility_support', '~>1.1'
30
31
 
31
32
  s.add_development_dependency "flores", "~>0.0.6"
32
33
  s.add_development_dependency "rspec"
@@ -29,7 +29,8 @@ describe LogStash::Inputs::Beats::DecodedEventTransform do
29
29
 
30
30
  subject { described_class.new(input).transform(event, map) }
31
31
 
32
- include_examples "Common Event Transformation"
32
+ include_examples "Common Event Transformation", :disabled, "host"
33
+ include_examples "Common Event Transformation", :v1, "[@metadata][input][beats][host][name]"
33
34
 
34
35
  it "tags the event" do
35
36
  expect(subject.get("tags")).to include("beats_input_codec_plain_applied")
@@ -43,7 +44,7 @@ describe LogStash::Inputs::Beats::DecodedEventTransform do
43
44
  context "map contains a timestamp" do
44
45
  context "when its valid" do
45
46
  let(:timestamp) { Time.now }
46
- let(:map) { super.merge({"@timestamp" => timestamp }) }
47
+ let(:map) { super().merge({"@timestamp" => timestamp }) }
47
48
 
48
49
  it "uses as the event timestamp" do
49
50
  expect(subject.get("@timestamp")).to eq(LogStash::Timestamp.coerce(timestamp))
@@ -51,7 +52,7 @@ describe LogStash::Inputs::Beats::DecodedEventTransform do
51
52
  end
52
53
 
53
54
  context "when its not valid" do
54
- let(:map) { super.merge({"@timestamp" => "invalid" }) }
55
+ let(:map) { super().merge({"@timestamp" => "invalid" }) }
55
56
 
56
57
  it "fallback the current time" do
57
58
  expect(subject.get("@timestamp")).to be_kind_of(LogStash::Timestamp)
@@ -7,5 +7,6 @@ require "spec_helper"
7
7
  describe LogStash::Inputs::Beats::EventTransformCommon do
8
8
  subject { described_class.new(input).transform(event) }
9
9
 
10
- include_examples "Common Event Transformation"
10
+ include_examples "Common Event Transformation", :disabled, "host"
11
+ include_examples "Common Event Transformation", :v1, "[@metadata][input][beats][host][name]"
11
12
  end
@@ -52,10 +52,73 @@ class DummyCodec < LogStash::Codecs::Base
52
52
  end
53
53
  end
54
54
 
55
+ shared_examples "when the message is from any libbeat" do |ecs_compatibility, host_field_name|
56
+ let(:input) do
57
+ input = LogStash::Inputs::Beats.new({ "port" => 5555, "codec" => codec, "ecs_compatibility" => "#{ecs_compatibility}" })
58
+ input.register
59
+ input
60
+ end
61
+
62
+ #Requires data modeled as Java, not Ruby since the actual code pulls from Java backed (Netty) object
63
+ let(:data) do
64
+ d = HashMap.new
65
+ d.put('@metadata', HashMap.new)
66
+ d.put('metric', 1)
67
+ d.put('name', "super-stats")
68
+ d
69
+ end
70
+
71
+ let(:message) { MockMessage.new("abc", data)}
72
+
73
+ it "extract the event" do
74
+ subject.onNewMessage(ctx, message)
75
+ event = queue.pop
76
+ expect(event.get("message")).to be_nil
77
+ expect(event.get("metric")).to eq(1)
78
+ expect(event.get("name")).to eq("super-stats")
79
+ expect(event.get(host_field_name)).to eq(ip_address)
80
+ end
81
+
82
+ context 'when the remote address is nil' do
83
+ let(:ctx) { OngoingMethodMock.new("remoteAddress", nil)}
84
+
85
+ it 'extracts the event' do
86
+ subject.onNewMessage(ctx, message)
87
+ event = queue.pop
88
+ expect(event.get("message")).to be_nil
89
+ expect(event.get("metric")).to eq(1)
90
+ expect(event.get("name")).to eq("super-stats")
91
+ expect(event.get(host_field_name)).to eq(nil)
92
+ end
93
+ end
94
+
95
+ context 'when getting the remote address raises' do
96
+ let(:raising_ctx) { double("context")}
97
+
98
+ before do
99
+ allow(raising_ctx).to receive(:channel).and_raise("nope")
100
+ subject.onNewConnection(raising_ctx)
101
+ end
102
+
103
+ it 'extracts the event' do
104
+ subject.onNewMessage(raising_ctx, message)
105
+ event = queue.pop
106
+ expect(event.get("message")).to be_nil
107
+ expect(event.get("metric")).to eq(1)
108
+ expect(event.get("name")).to eq("super-stats")
109
+ expect(event.get(host_field_name)).to eq(nil)
110
+ end
111
+ end
112
+ end
113
+
55
114
  describe LogStash::Inputs::Beats::MessageListener do
56
115
  let(:queue) { Queue.new }
57
116
  let(:codec) { DummyCodec.new }
58
- let(:input) { LogStash::Inputs::Beats.new({ "port" => 5555, "codec" => codec }) }
117
+ let(:input) do
118
+ input = LogStash::Inputs::Beats.new({ "port" => 5555, "codec" => codec })
119
+ input.register
120
+ input
121
+ end
59
122
 
60
123
  let(:ip_address) { "10.0.0.1" }
61
124
  let(:remote_address) { OngoingMethodMock.new("getHostAddress", ip_address) }
@@ -146,59 +209,8 @@ describe LogStash::Inputs::Beats::MessageListener do
146
209
  end
147
210
  end
148
211
 
149
- context "when the message is from any libbeat" do
150
- #Requires data modeled as Java, not Ruby since the actual code pulls from Java backed (Netty) object
151
- let(:data) do
152
- d = HashMap.new
153
- d.put('@metadata', HashMap.new)
154
- d.put('metric', 1)
155
- d.put('name', "super-stats")
156
- d
157
- end
158
-
159
- let(:message) { MockMessage.new("abc", data)}
160
-
161
- it "extract the event" do
162
- subject.onNewMessage(ctx, message)
163
- event = queue.pop
164
- expect(event.get("message")).to be_nil
165
- expect(event.get("metric")).to eq(1)
166
- expect(event.get("name")).to eq("super-stats")
167
- expect(event.get("[@metadata][ip_address]")).to eq(ip_address)
168
- end
169
-
170
- context 'when the remote address is nil' do
171
- let(:ctx) { OngoingMethodMock.new("remoteAddress", nil)}
172
-
173
- it 'extracts the event' do
174
- subject.onNewMessage(ctx, message)
175
- event = queue.pop
176
- expect(event.get("message")).to be_nil
177
- expect(event.get("metric")).to eq(1)
178
- expect(event.get("name")).to eq("super-stats")
179
- expect(event.get("[@metadata][ip_address]")).to eq(nil)
180
- end
181
- end
182
-
183
- context 'when getting the remote address raises' do
184
- let(:raising_ctx) { double("context")}
185
-
186
- before do
187
- allow(raising_ctx).to receive(:channel).and_raise("nope")
188
- subject.onNewConnection(raising_ctx)
189
- end
190
-
191
- it 'extracts the event' do
192
- subject.onNewMessage(raising_ctx, message)
193
- event = queue.pop
194
- expect(event.get("message")).to be_nil
195
- expect(event.get("metric")).to eq(1)
196
- expect(event.get("name")).to eq("super-stats")
197
- expect(event.get("[@metadata][ip_address]")).to eq(nil)
198
- end
199
- end
200
-
201
- end
212
+ it_behaves_like "when the message is from any libbeat", :disabled, "[@metadata][ip_address]"
213
+ it_behaves_like "when the message is from any libbeat", :v1, "[@metadata][input][beats][host][ip]"
202
214
  end
203
215
 
204
216
  context "onException" do
@@ -222,4 +234,20 @@ describe LogStash::Inputs::Beats::MessageListener do
222
234
  expect(queue).not_to be_empty
223
235
  end
224
236
  end
237
+
238
+ context "set_nested" do
239
+ let(:hash) {{}}
240
+
241
+ it "creates correctly the nested maps" do
242
+ subject.set_nested(hash, "[root][inner][leaf]", 5)
243
+ expect(hash["root"]["inner"]["leaf"]).to eq(5)
244
+ end
245
+
246
+ it "doesn't overwrite existing the nested maps" do
247
+ hash = {"root" => {"foo" => {"bar" => "Hello"}}}
248
+ subject.set_nested(hash, "[root][inner][leaf]", 5)
249
+ expect(hash["root"]["inner"]["leaf"]).to eq(5)
250
+ expect(hash["root"]["foo"]["bar"]).to eq("Hello")
251
+ end
252
+ end
225
253
  end
@@ -18,7 +18,8 @@ describe LogStash::Inputs::Beats::RawEventTransform do
18
18
 
19
19
  subject { described_class.new(input).transform(event) }
20
20
 
21
- include_examples "Common Event Transformation"
21
+ include_examples "Common Event Transformation", :disabled, "host"
22
+ include_examples "Common Event Transformation", :v1, "[@metadata][input][beats][host][name]"
22
23
 
23
24
  it "tags the event" do
24
25
  expect(subject.get("tags")).to include("beats_input_raw_event")
@@ -13,11 +13,19 @@ describe LogStash::Inputs::Beats do
13
13
  let(:certificate) { BeatsInputTest.certificate }
14
14
  let(:port) { BeatsInputTest.random_port }
15
15
  let(:queue) { Queue.new }
16
- let(:config) { { "port" => 0, "ssl_certificate" => certificate.ssl_cert, "ssl_key" => certificate.ssl_key, "type" => "example", "tags" => "beats"} }
16
+ let(:config) do
17
+ {
18
+ "port" => 0,
19
+ "ssl_certificate" => certificate.ssl_cert,
20
+ "ssl_key" => certificate.ssl_key,
21
+ "type" => "example",
22
+ "tags" => "beats"
23
+ }
24
+ end
17
25
 
18
26
  context "#register" do
19
27
  context "host related configuration" do
20
- let(:config) { super.merge!({ "host" => host, "port" => port, "client_inactivity_timeout" => client_inactivity_timeout, "executor_threads" => threads }) }
28
+ let(:config) { super().merge("host" => host, "port" => port, "client_inactivity_timeout" => client_inactivity_timeout, "executor_threads" => threads) }
21
29
  let(:host) { "192.168.1.20" }
22
30
  let(:port) { 9000 }
23
31
  let(:client_inactivity_timeout) { 400 }
@@ -38,38 +46,55 @@ describe LogStash::Inputs::Beats do
38
46
 
39
47
  context "with ssl enabled" do
40
48
  context "without certificate configuration" do
41
- let(:config) {{ "port" => 0, "ssl" => true, "ssl_key" => certificate.ssl_key, "type" => "example", "tags" => "beats" }}
49
+ let(:config) { { "port" => 0, "ssl" => true, "ssl_key" => certificate.ssl_key, "type" => "example" } }
42
50
 
43
51
  it "should fail to register the plugin with ConfigurationError" do
44
52
  plugin = LogStash::Inputs::Beats.new(config)
45
- expect {plugin.register}.to raise_error(LogStash::ConfigurationError)
53
+ expect { plugin.register }.to raise_error(LogStash::ConfigurationError)
46
54
  end
47
55
  end
48
56
 
49
57
  context "without key configuration" do
50
- let(:config) { { "port" => 0, "ssl" => true, "ssl_certificate" => certificate.ssl_cert, "type" => "example", "tags" => "Beats"} }
58
+ let(:config) { { "port" => 0, "ssl" => true, "ssl_certificate" => certificate.ssl_cert, "type" => "example" } }
51
59
  it "should fail to register the plugin with ConfigurationError" do
52
60
  plugin = LogStash::Inputs::Beats.new(config)
53
- expect {plugin.register}.to raise_error(LogStash::ConfigurationError)
61
+ expect { plugin.register }.to raise_error(LogStash::ConfigurationError)
62
+ end
63
+ end
64
+
65
+ context "with invalid key configuration" do
66
+ let(:p12_key) { certificate.p12_key }
67
+ let(:config) { { "port" => 0, "ssl" => true, "ssl_certificate" => certificate.ssl_cert, "ssl_key" => p12_key } }
68
+ it "should fail to register the plugin" do
69
+ plugin = LogStash::Inputs::Beats.new(config)
70
+ expect( plugin.logger ).to receive(:error) do |msg, opts|
71
+ expect( msg ).to match /.*?configuration invalid/
72
+ expect( opts[:message] ).to match /does not contain valid private key/
73
+ end
74
+ expect { plugin.register }.to raise_error(LogStash::ConfigurationError)
54
75
  end
55
76
  end
56
77
 
57
78
  context "with invalid ciphers" do
58
- let(:config) { { "port" => 0, "ssl" => true, "ssl_certificate" => certificate.ssl_cert, "type" => "example", "tags" => "Beats", "cipher_suites" => "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA38"} }
79
+ let(:config) { super().merge("ssl" => true, "cipher_suites" => "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA38") }
59
80
 
60
81
  it "should raise a configuration error" do
61
82
  plugin = LogStash::Inputs::Beats.new(config)
83
+ expect( plugin.logger ).to receive(:error) do |msg, opts|
84
+ expect( msg ).to match /.*?configuration invalid/
85
+ expect( opts[:message] ).to match /TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA38.*? not available/
86
+ end
62
87
  expect { plugin.register }.to raise_error(LogStash::ConfigurationError)
63
88
  end
64
89
  end
65
90
 
66
91
  context "verify_mode" do
67
92
  context "verify_mode configured to PEER" do
68
- let(:config) { { "port" => 0, "ssl" => true, "ssl_verify_mode" => "peer", "ssl_certificate" => certificate.ssl_cert, "ssl_key" => certificate.ssl_key, "type" => "example", "tags" => "Beats"} }
93
+ let(:config) { super().merge("ssl" => true, "ssl_verify_mode" => "peer") }
69
94
 
70
95
  it "raise a ConfigurationError when certificate_authorities is not set" do
71
96
  plugin = LogStash::Inputs::Beats.new(config)
72
- expect {plugin.register}.to raise_error(LogStash::ConfigurationError, "Using `verify_mode` set to PEER or FORCE_PEER, requires the configuration of `certificate_authorities`")
97
+ expect {plugin.register}.to raise_error(LogStash::ConfigurationError, "ssl_certificate_authorities => is a required setting when ssl_verify_mode => 'peer' is configured")
73
98
  end
74
99
 
75
100
  it "doesn't raise a configuration error when certificate_authorities is set" do
@@ -80,11 +105,11 @@ describe LogStash::Inputs::Beats do
80
105
  end
81
106
 
82
107
  context "verify_mode configured to FORCE_PEER" do
83
- let(:config) { { "port" => 0, "ssl" => true, "ssl_verify_mode" => "force_peer", "ssl_certificate" => certificate.ssl_cert, "ssl_key" => certificate.ssl_key, "type" => "example", "tags" => "Beats"} }
108
+ let(:config) { super().merge("ssl" => true, "ssl_verify_mode" => "force_peer") }
84
109
 
85
110
  it "raise a ConfigurationError when certificate_authorities is not set" do
86
111
  plugin = LogStash::Inputs::Beats.new(config)
87
- expect {plugin.register}.to raise_error(LogStash::ConfigurationError, "Using `verify_mode` set to PEER or FORCE_PEER, requires the configuration of `certificate_authorities`")
112
+ expect {plugin.register}.to raise_error(LogStash::ConfigurationError, "ssl_certificate_authorities => is a required setting when ssl_verify_mode => 'force_peer' is configured")
88
113
  end
89
114
 
90
115
  it "doesn't raise a configuration error when certificate_authorities is set" do
@@ -98,7 +123,7 @@ describe LogStash::Inputs::Beats do
98
123
 
99
124
  context "with ssl disabled" do
100
125
  context "and certificate configuration" do
101
- let(:config) { { "port" => 0, "ssl" => false, "ssl_certificate" => certificate.ssl_cert, "type" => "example", "tags" => "Beats" } }
126
+ let(:config) { { "port" => 0, "ssl" => false, "ssl_certificate" => certificate.ssl_cert, "type" => "example", "tags" => "Beats" } }
102
127
 
103
128
  it "should not fail" do
104
129
  plugin = LogStash::Inputs::Beats.new(config)
@@ -129,7 +154,7 @@ describe LogStash::Inputs::Beats do
129
154
  let(:codec) { LogStash::Codecs::Multiline.new("pattern" => '^2015',
130
155
  "what" => "previous",
131
156
  "negate" => true) }
132
- let(:config) { super.merge({ "codec" => codec }) }
157
+ let(:config) { super().merge({ "codec" => codec }) }
133
158
 
134
159
  it "raise a ConfigurationError when multiline codec is set" do
135
160
  plugin = LogStash::Inputs::Beats.new(config)
@@ -86,7 +86,7 @@ describe "Filebeat", :integration => true do
86
86
  end
87
87
 
88
88
  context "without pipelining" do
89
- let(:filebeat_config) { config = super; config["output"]["logstash"]["pipelining"] = 0; config }
89
+ let(:filebeat_config) { config = super(); config["output"]["logstash"]["pipelining"] = 0; config }
90
90
  include_examples "send events"
91
91
 
92
92
  context "with large batches" do
@@ -99,7 +99,7 @@ describe "Filebeat", :integration => true do
99
99
  context "TLS" do
100
100
  context "Server verification" do
101
101
  let(:filebeat_config) do
102
- super.merge({
102
+ super().merge({
103
103
  "output" => {
104
104
  "logstash" => {
105
105
  "hosts" => ["#{host}:#{port}"],
@@ -111,7 +111,7 @@ describe "Filebeat", :integration => true do
111
111
  end
112
112
 
113
113
  let(:input_config) do
114
- super.merge({
114
+ super().merge({
115
115
  "ssl" => true,
116
116
  "ssl_certificate" => certificate_file,
117
117
  "ssl_key" => certificate_key_file
@@ -129,7 +129,7 @@ describe "Filebeat", :integration => true do
129
129
 
130
130
  context "when specifying a cipher" do
131
131
  let(:filebeat_config) do
132
- super.merge({
132
+ super().merge({
133
133
  "output" => {
134
134
  "logstash" => {
135
135
  "hosts" => ["#{host}:#{port}"],
@@ -145,7 +145,7 @@ describe "Filebeat", :integration => true do
145
145
  end
146
146
 
147
147
  let(:input_config) {
148
- super.merge({
148
+ super().merge({
149
149
  "cipher_suites" => [logstash_cipher],
150
150
  "tls_min_version" => "1.2"
151
151
  })
@@ -194,7 +194,7 @@ describe "Filebeat", :integration => true do
194
194
  LogStash::Inputs::Beats.new(input_config)
195
195
  }
196
196
  let(:input_config) {
197
- super.merge({
197
+ super().merge({
198
198
  "ssl_key_passphrase" => passphrase,
199
199
  "ssl_key" => certificate_key_file_pkcs8
200
200
  })}
@@ -229,7 +229,7 @@ describe "Filebeat", :integration => true do
229
229
 
230
230
  context "Client verification / Mutual validation" do
231
231
  let(:filebeat_config) do
232
- super.merge({
232
+ super().merge({
233
233
  "output" => {
234
234
  "logstash" => {
235
235
  "hosts" => ["#{host}:#{port}"],
@@ -245,7 +245,7 @@ describe "Filebeat", :integration => true do
245
245
  end
246
246
 
247
247
  let(:input_config) do
248
- super.merge({
248
+ super().merge({
249
249
  "ssl" => true,
250
250
  "ssl_certificate_authorities" => certificate_authorities,
251
251
  "ssl_certificate" => server_certificate_file,
@@ -327,7 +327,7 @@ describe "Filebeat", :integration => true do
327
327
 
328
328
  context "client from secondary CA" do
329
329
  let(:filebeat_config) do
330
- super.merge({
330
+ super().merge({
331
331
  "output" => {
332
332
  "logstash" => {
333
333
  "hosts" => ["#{host}:#{port}"],