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

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.
@@ -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}"],