logstash-codec-cef 6.2.5-java → 6.2.6-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/lib/logstash/codecs/cef/timestamp_normalizer.rb +0 -3
- data/lib/logstash/codecs/cef.rb +72 -16
- data/logstash-codec-cef.gemspec +1 -1
- data/spec/codecs/cef/timestamp_normalizer_spec.rb +0 -1
- data/spec/codecs/cef_spec.rb +157 -10
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 344660a8caa1f5fbdde48422db80b287e6afff7e8c1d3ebdeb5f70269431a514
|
4
|
+
data.tar.gz: 9f061964eae0cdcd46fcefe9b5feefc05c72074935c44d83a8f35c5e54564a6c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 791c750b7085fbefec2e71537d4452174e851bfa28a7d6f046f67d07a543148ebceb51acbc8356b42c90fb2f0fcac28c29ce5f95624ac598070f5389e3f95f72
|
7
|
+
data.tar.gz: c995d8153001929fad98c0dab84664f6af1c6c7b4b873f1be2277d5b0f64e45531178c73fc8fa339ee7c4644c118cf2cfce37c812a4ea6cd6f9d2221d543a470
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
## 6.2.6
|
2
|
+
- Fix: when decoding, escaped newlines and carriage returns in extension values are now correctly decoded into literal newlines and carriage returns respectively [#98](https://github.com/logstash-plugins/logstash-codec-cef/pull/98)
|
3
|
+
- Fix: when decoding, non-CEF payloads are identified and intercepted to prevent data-loss and corruption. They now cause a descriptive log message to be emitted, and are emitted as their own `_cefparsefailure`-tagged event containing the original bytes in its `message` field [#99](https://github.com/logstash-plugins/logstash-codec-cef/issues/99)
|
4
|
+
- Fix: when decoding while configured with a `delimiter`, flushing this codec now correctly consumes the remainder of its internal buffer. This resolves an issue where bytes that are written without a trailing delimiter could be lost [#100](https://github.com/logstash-plugins/logstash-codec-cef/issues/100)
|
5
|
+
|
1
6
|
## 6.2.5
|
2
7
|
- [DOC] Update link to CEF implementation guide [#97](https://github.com/logstash-plugins/logstash-codec-cef/pull/97)
|
3
8
|
|
@@ -84,9 +84,6 @@ class LogStash::Codecs::CEF::TimestampNormalizer
|
|
84
84
|
|
85
85
|
# Ruby's `Time::at(sec, microseconds_with_frac)`
|
86
86
|
Time.at(parsed_time.get_epoch_second, Rational(parsed_time.get_nano, 1000))
|
87
|
-
rescue => e
|
88
|
-
$stderr.puts "parse_cef_format_sgring(#{value.inspect}, #{context_timezone.inspect}) #!=> #{e.message}"
|
89
|
-
raise
|
90
87
|
end
|
91
88
|
|
92
89
|
def resolve_assuming_year(parsed_temporal_accessor)
|
data/lib/logstash/codecs/cef.rb
CHANGED
@@ -102,15 +102,12 @@ class LogStash::Codecs::CEF < LogStash::Codecs::Base
|
|
102
102
|
# - non-pipe characters
|
103
103
|
HEADER_PATTERN = /(?:\\\||\\\\|[^|])*?/
|
104
104
|
|
105
|
-
# Cache of a scanner pattern that _captures_ a HEADER followed by an unescaped pipe
|
106
|
-
|
105
|
+
# Cache of a scanner pattern that _captures_ a HEADER followed by EOF or an unescaped pipe
|
106
|
+
HEADER_NEXT_FIELD_PATTERN = /(#{HEADER_PATTERN})#{Regexp.quote('|')}/
|
107
107
|
|
108
108
|
# Cache of a gsub pattern that matches a backslash-escaped backslash or backslash-escaped pipe, _capturing_ the escaped character
|
109
109
|
HEADER_ESCAPE_CAPTURE = /\\([\\|])/
|
110
110
|
|
111
|
-
# Cache of a gsub pattern that matches a backslash-escaped backslash or backslash-escaped equals, _capturing_ the escaped character
|
112
|
-
EXTENSION_VALUE_ESCAPE_CAPTURE = /\\([\\=])/
|
113
|
-
|
114
111
|
# While the original CEF spec calls out that extension keys must be alphanumeric and must not contain spaces,
|
115
112
|
# in practice many "CEF" producers like the Arcsight smart connector produce non-legal keys including underscores,
|
116
113
|
# commas, periods, and square-bracketed index offsets.
|
@@ -139,8 +136,8 @@ class LogStash::Codecs::CEF < LogStash::Codecs::Base
|
|
139
136
|
# - runs of whitespace that are NOT followed by something that looks like a key-equals sequence
|
140
137
|
EXTENSION_VALUE_PATTERN = /(?:\S|\s++(?!#{EXTENSION_KEY_PATTERN}=))*/
|
141
138
|
|
142
|
-
# Cache of a
|
143
|
-
|
139
|
+
# Cache of a pattern that _captures_ the NEXT extension field key/value pair
|
140
|
+
EXTENSION_NEXT_KEY_VALUE_PATTERN = /^(#{EXTENSION_KEY_PATTERN})=(#{EXTENSION_VALUE_PATTERN})\s*/
|
144
141
|
|
145
142
|
##
|
146
143
|
# @see CEF#sanitize_header_field
|
@@ -160,10 +157,30 @@ class LogStash::Codecs::CEF < LogStash::Codecs::Base
|
|
160
157
|
"=" => "\\=",
|
161
158
|
"\n" => "\\n",
|
162
159
|
"\r" => "\\n",
|
163
|
-
}
|
160
|
+
}.freeze
|
164
161
|
EXTENSION_VALUE_SANITIZER_PATTERN = Regexp.union(EXTENSION_VALUE_SANITIZER_MAPPING.keys)
|
165
162
|
private_constant :EXTENSION_VALUE_SANITIZER_MAPPING, :EXTENSION_VALUE_SANITIZER_PATTERN
|
166
163
|
|
164
|
+
|
165
|
+
LITERAL_BACKSLASH = "\\".freeze
|
166
|
+
private_constant :LITERAL_BACKSLASH
|
167
|
+
LITERAL_NEWLINE = "\n".freeze
|
168
|
+
private_constant :LITERAL_NEWLINE
|
169
|
+
LITERAL_CARRIAGE_RETURN = "\r".freeze
|
170
|
+
private_constant :LITERAL_CARRIAGE_RETURN
|
171
|
+
|
172
|
+
##
|
173
|
+
# @see CEF#desanitize_extension_val
|
174
|
+
EXTENSION_VALUE_SANITIZER_REVERSE_MAPPING = {
|
175
|
+
LITERAL_BACKSLASH+LITERAL_BACKSLASH => LITERAL_BACKSLASH,
|
176
|
+
LITERAL_BACKSLASH+'=' => '=',
|
177
|
+
LITERAL_BACKSLASH+'n' => LITERAL_NEWLINE,
|
178
|
+
LITERAL_BACKSLASH+'r' => LITERAL_CARRIAGE_RETURN,
|
179
|
+
}.freeze
|
180
|
+
EXTENSION_VALUE_SANITIZER_REVERSE_PATTERN = Regexp.union(EXTENSION_VALUE_SANITIZER_REVERSE_MAPPING.keys)
|
181
|
+
private_constant :EXTENSION_VALUE_SANITIZER_REVERSE_MAPPING, :EXTENSION_VALUE_SANITIZER_REVERSE_PATTERN
|
182
|
+
|
183
|
+
|
167
184
|
CEF_PREFIX = 'CEF:'.freeze
|
168
185
|
|
169
186
|
public
|
@@ -193,14 +210,24 @@ class LogStash::Codecs::CEF < LogStash::Codecs::Base
|
|
193
210
|
public
|
194
211
|
def decode(data, &block)
|
195
212
|
if @delimiter
|
213
|
+
@logger.trace("Buffering #{data.bytesize}B of data") if @logger.trace?
|
196
214
|
@buffer.extract(data).each do |line|
|
215
|
+
@logger.trace("Decoding #{line.bytesize + @delimiter.bytesize}B of buffered data") if @logger.trace?
|
197
216
|
handle(line, &block)
|
198
217
|
end
|
199
218
|
else
|
219
|
+
@logger.trace("Decoding #{data.bytesize}B of unbuffered data") if @logger.trace?
|
200
220
|
handle(data, &block)
|
201
221
|
end
|
202
222
|
end
|
203
223
|
|
224
|
+
def flush(&block)
|
225
|
+
if @delimiter && (remainder = @buffer.flush)
|
226
|
+
@logger.trace("Flushing #{remainder.bytesize}B of buffered data") if @logger.trace?
|
227
|
+
handle(remainder, &block) unless remainder.empty?
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
204
231
|
def handle(data, &block)
|
205
232
|
original_data = data.dup
|
206
233
|
event = event_factory.new_event
|
@@ -218,10 +245,16 @@ class LogStash::Codecs::CEF < LogStash::Codecs::Base
|
|
218
245
|
end
|
219
246
|
|
220
247
|
# Use a scanning parser to capture the HEADER_FIELDS
|
221
|
-
unprocessed_data = data
|
222
|
-
|
223
|
-
|
224
|
-
|
248
|
+
unprocessed_data = data.chomp
|
249
|
+
if unprocessed_data.include?(LITERAL_NEWLINE)
|
250
|
+
fail("message is not valid CEF because it contains unescaped newline characters; " +
|
251
|
+
"use the `delimiter` setting to enable in-codec buffering and delimiter-splitting")
|
252
|
+
end
|
253
|
+
@header_fields.each_with_index do |field_name, idx|
|
254
|
+
match_data = HEADER_NEXT_FIELD_PATTERN.match(unprocessed_data)
|
255
|
+
if match_data.nil?
|
256
|
+
fail("message is not valid CEF; found #{idx} of 7 required pipe-terminated header fields")
|
257
|
+
end
|
225
258
|
|
226
259
|
escaped_field_value = match_data[1]
|
227
260
|
next if escaped_field_value.nil?
|
@@ -248,11 +281,14 @@ class LogStash::Codecs::CEF < LogStash::Codecs::Base
|
|
248
281
|
event.set(cef_version_field, delete_cef_prefix(event.get(cef_version_field)))
|
249
282
|
|
250
283
|
# Use a scanning parser to capture the Extension Key/Value Pairs
|
251
|
-
if message && message.
|
284
|
+
if message && !message.empty?
|
252
285
|
message = message.strip
|
253
286
|
extension_fields = {}
|
254
287
|
|
255
|
-
message.
|
288
|
+
while (match = message.match(EXTENSION_NEXT_KEY_VALUE_PATTERN))
|
289
|
+
extension_field_key, raw_extension_field_value = match.captures
|
290
|
+
message = match.post_match
|
291
|
+
|
256
292
|
# expand abbreviated extension field keys
|
257
293
|
extension_field_key = @decode_mapping.fetch(extension_field_key, extension_field_key)
|
258
294
|
|
@@ -260,10 +296,13 @@ class LogStash::Codecs::CEF < LogStash::Codecs::Base
|
|
260
296
|
extension_field_key = extension_field_key.sub(EXTENSION_KEY_ARRAY_CAPTURE, '[\1]\2') if extension_field_key.end_with?(']')
|
261
297
|
|
262
298
|
# process legal extension field value escapes
|
263
|
-
extension_field_value = raw_extension_field_value
|
299
|
+
extension_field_value = desanitize_extension_val(raw_extension_field_value)
|
264
300
|
|
265
301
|
extension_fields[extension_field_key] = extension_field_value
|
266
302
|
end
|
303
|
+
if !message.empty?
|
304
|
+
fail("invalid extensions; keyless value present `#{message}`")
|
305
|
+
end
|
267
306
|
|
268
307
|
# in ECS mode, normalize timestamps including timezone.
|
269
308
|
if ecs_compatibility != :disabled
|
@@ -283,7 +322,7 @@ class LogStash::Codecs::CEF < LogStash::Codecs::Base
|
|
283
322
|
yield event
|
284
323
|
rescue => e
|
285
324
|
@logger.error("Failed to decode CEF payload. Generating failure event with payload in message field.",
|
286
|
-
:
|
325
|
+
log_metadata(:original_data => original_data))
|
287
326
|
yield event_factory.new_event("message" => data, "tags" => ["_cefparsefailure"])
|
288
327
|
end
|
289
328
|
|
@@ -332,6 +371,19 @@ class LogStash::Codecs::CEF < LogStash::Codecs::Base
|
|
332
371
|
@syslog_header = ecs_select[disabled:'syslog',v1:'[log][syslog][header]']
|
333
372
|
end
|
334
373
|
|
374
|
+
##
|
375
|
+
# produces log metadata, injecting the current exception and log-level-relevant backtraces
|
376
|
+
# @param context [Hash{Symbol=>Object}]: the base context
|
377
|
+
def log_metadata(context={})
|
378
|
+
return context unless $!
|
379
|
+
|
380
|
+
exception_context = {}
|
381
|
+
exception_context[:exception] = "#{$!.class}: #{$!.message}"
|
382
|
+
exception_context[:backtrace] = $!.backtrace if @logger.debug?
|
383
|
+
|
384
|
+
exception_context.merge(context)
|
385
|
+
end
|
386
|
+
|
335
387
|
class CEFField
|
336
388
|
##
|
337
389
|
# @param name [String]: the full CEF name of a field
|
@@ -547,6 +599,10 @@ class LogStash::Codecs::CEF < LogStash::Codecs::Base
|
|
547
599
|
.gsub(EXTENSION_VALUE_SANITIZER_PATTERN, EXTENSION_VALUE_SANITIZER_MAPPING)
|
548
600
|
end
|
549
601
|
|
602
|
+
def desanitize_extension_val(value)
|
603
|
+
value.to_s.gsub(EXTENSION_VALUE_SANITIZER_REVERSE_PATTERN, EXTENSION_VALUE_SANITIZER_REVERSE_MAPPING)
|
604
|
+
end
|
605
|
+
|
550
606
|
def normalize_timestamp(value, device_timezone_name)
|
551
607
|
value = @timestamp_normalzer.normalize(value, device_timezone_name).iso8601(9)
|
552
608
|
|
data/logstash-codec-cef.gemspec
CHANGED
@@ -159,7 +159,6 @@ describe LogStash::Codecs::CEF::TimestampNormalizer do
|
|
159
159
|
context 'and handling a yearless date string from mid january' do
|
160
160
|
let(:time_to_parse) { Time.parse("2021-01-17T00:00:08.123456789Z") }
|
161
161
|
it 'assumes that the date being parsed is in the distant past' do
|
162
|
-
$stderr.puts(parsable_string)
|
163
162
|
expect(parsed_result.month).to eq(1)
|
164
163
|
expect(parsed_result.year).to eq(time_of_parse.year)
|
165
164
|
end
|
data/spec/codecs/cef_spec.rb
CHANGED
@@ -409,14 +409,16 @@ describe LogStash::Codecs::CEF do
|
|
409
409
|
# @yieldparam event [Event]
|
410
410
|
# @yieldreturn [void]
|
411
411
|
# @return [Event]
|
412
|
-
def decode_one(codec, data)
|
413
|
-
events = do_decode(codec, data)
|
412
|
+
def decode_one(codec, data, flush: true, &block)
|
413
|
+
events = do_decode(codec, data, flush: flush)
|
414
414
|
fail("Expected one event, got #{events.size} events: #{events.inspect}") unless events.size == 1
|
415
415
|
event = events.first
|
416
416
|
|
417
|
-
if
|
418
|
-
|
419
|
-
|
417
|
+
if block
|
418
|
+
enriched_event_validation(event) do |e|
|
419
|
+
aggregate_failures('decode one') do
|
420
|
+
yield e
|
421
|
+
end
|
420
422
|
end
|
421
423
|
end
|
422
424
|
|
@@ -434,16 +436,35 @@ describe LogStash::Codecs::CEF do
|
|
434
436
|
# @yieldparam event [Event]
|
435
437
|
# @yieldreturn [void]
|
436
438
|
# @return [Array<Event>]
|
437
|
-
def do_decode(codec, data)
|
439
|
+
def do_decode(codec, data, flush: true, &block)
|
438
440
|
events = []
|
439
441
|
codec.decode(data) do |event|
|
440
442
|
events << event
|
441
443
|
end
|
444
|
+
flush && codec.flush do |event|
|
445
|
+
events << event
|
446
|
+
end
|
442
447
|
|
443
|
-
|
448
|
+
if block
|
449
|
+
events.each do |event|
|
450
|
+
enriched_event_validation(event, &block)
|
451
|
+
end
|
452
|
+
end
|
444
453
|
|
445
454
|
events
|
446
455
|
end
|
456
|
+
|
457
|
+
##
|
458
|
+
# Enrich event validation by outputting the serialized event to stderr
|
459
|
+
# if-and-only-if the provided block's rspec expectations are not met.
|
460
|
+
#
|
461
|
+
# @param event [#to_hash_with_metadata]
|
462
|
+
def enriched_event_validation(event)
|
463
|
+
yield(event)
|
464
|
+
rescue RSpec::Expectations::ExpectationNotMetError
|
465
|
+
$stderr.puts("\e[35m#{event.to_hash_with_metadata}\e[0m\n")
|
466
|
+
raise
|
467
|
+
end
|
447
468
|
end
|
448
469
|
|
449
470
|
context "#decode", :ecs_compatibility_support do
|
@@ -452,7 +473,7 @@ describe LogStash::Codecs::CEF do
|
|
452
473
|
allow_any_instance_of(described_class).to receive(:ecs_compatibility).and_return(ecs_compatibility)
|
453
474
|
end
|
454
475
|
|
455
|
-
let
|
476
|
+
let(:message) { "CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10|src=10.0.0.192 dst=12.121.122.82 spt=1232" }
|
456
477
|
|
457
478
|
include DecodeHelpers
|
458
479
|
|
@@ -465,17 +486,126 @@ describe LogStash::Codecs::CEF do
|
|
465
486
|
# Related: https://github.com/elastic/logstash/issues/1645
|
466
487
|
subject(:codec) { LogStash::Codecs::CEF.new("delimiter" => '\r\n') }
|
467
488
|
|
489
|
+
let(:message_two) { "CEF:0|fun|whimsy|1.0|100|trojan successfully stopped|10|src=10.0.0.192 dst=12.121.122.82 spt=1232" }
|
490
|
+
|
491
|
+
# testing implicit flush when
|
468
492
|
it "should parse on the delimiter " do
|
469
|
-
do_decode(subject,message) do |e|
|
493
|
+
do_decode(subject, message, flush: false) do |e|
|
470
494
|
raise Exception.new("Should not get here. If we do, it means the decoder emitted an event before the delimiter was seen?")
|
471
495
|
end
|
472
496
|
|
473
|
-
|
497
|
+
# the delimiter's presence flushes what we already received, but not the new bytes we send
|
498
|
+
decode_one(subject, "\r\n#{message_two}", flush: false) do |e|
|
499
|
+
validate(e)
|
500
|
+
insist { e.get(ecs_select[disabled: "deviceVendor", v1:"[observer][vendor]"]) } == "security"
|
501
|
+
insist { e.get(ecs_select[disabled: "deviceProduct", v1:"[observer][product]"]) } == "threatmanager"
|
502
|
+
end
|
503
|
+
|
504
|
+
# allowing a flush emits the buffered event with our new bits appended
|
505
|
+
decode_one(subject, " split=perfect", flush: true) do |e|
|
506
|
+
validate(e)
|
507
|
+
insist { e.get(ecs_select[disabled: "deviceVendor", v1:"[observer][vendor]"]) } == "fun"
|
508
|
+
insist { e.get(ecs_select[disabled: "deviceProduct", v1:"[observer][product]"]) } == "whimsy"
|
509
|
+
insist { e.get("split") } == "perfect"
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
it 'flushes on close' do
|
514
|
+
# message does NOT have delimiter, but we still get our event
|
515
|
+
decode_one(subject, message, flush: true) do |e|
|
474
516
|
validate(e)
|
475
517
|
insist { e.get(ecs_select[disabled: "deviceVendor", v1:"[observer][vendor]"]) } == "security"
|
476
518
|
insist { e.get(ecs_select[disabled: "deviceProduct", v1:"[observer][product]"]) } == "threatmanager"
|
477
519
|
end
|
478
520
|
end
|
521
|
+
|
522
|
+
it 'emits multiple from a single decode operation' do
|
523
|
+
events = do_decode(subject, "#{message}\r\n#{message_two}")
|
524
|
+
expect(events.size).to eq(2)
|
525
|
+
|
526
|
+
enriched_event_validation(events[0]) do |event|
|
527
|
+
validate(event)
|
528
|
+
insist { event.get(ecs_select[disabled: "deviceVendor", v1:"[observer][vendor]"]) } == "security"
|
529
|
+
insist { event.get(ecs_select[disabled: "deviceProduct", v1:"[observer][product]"]) } == "threatmanager"
|
530
|
+
end
|
531
|
+
|
532
|
+
enriched_event_validation(events[1]) do |event|
|
533
|
+
validate(event)
|
534
|
+
insist { event.get(ecs_select[disabled: "deviceVendor", v1:"[observer][vendor]"]) } == "fun"
|
535
|
+
insist { event.get(ecs_select[disabled: "deviceProduct", v1:"[observer][product]"]) } == "whimsy"
|
536
|
+
end
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
540
|
+
# CEF requires seven pipe-terminated headers before optional extensions
|
541
|
+
context 'with a non-CEF payload' do
|
542
|
+
let(:logger_stub) { double('Logger').as_null_object }
|
543
|
+
before(:each) do
|
544
|
+
allow_any_instance_of(described_class).to receive(:logger).and_return(logger_stub)
|
545
|
+
end
|
546
|
+
|
547
|
+
context 'containing 0 header-like sections' do
|
548
|
+
let(:message) { 'this is not cef' }
|
549
|
+
it 'logs helpfully and produces a tagged event' do
|
550
|
+
do_decode(subject,message) do |event|
|
551
|
+
expect(event.get('tags')).to include('_cefparsefailure')
|
552
|
+
expect(event.get('message')).to eq(message)
|
553
|
+
end
|
554
|
+
expect(logger_stub).to have_received(:error)
|
555
|
+
.with(a_string_including('Failed to decode CEF payload. Generating failure event with payload in message field'),
|
556
|
+
a_hash_including(exception: a_string_including("found 0 of 7 required pipe-terminated header fields"),
|
557
|
+
original_data: message))
|
558
|
+
end
|
559
|
+
end
|
560
|
+
context 'containing 4 header-like sections' do
|
561
|
+
let(:message) { "a|b|c with several \\| escaped\\| pipes|d|bananas" }
|
562
|
+
it 'logs helpfully and produces a tagged event' do
|
563
|
+
do_decode(subject,message) do |event|
|
564
|
+
expect(event.get('tags')).to include('_cefparsefailure')
|
565
|
+
expect(event.get('message')).to eq(message)
|
566
|
+
end
|
567
|
+
expect(logger_stub).to have_received(:error)
|
568
|
+
.with(a_string_including('Failed to decode CEF payload. Generating failure event with payload in message field'),
|
569
|
+
a_hash_including(exception: a_string_including("found 4 of 7 required pipe-terminated header fields"),
|
570
|
+
original_data: message))
|
571
|
+
end
|
572
|
+
end
|
573
|
+
context 'containing non-key/value extensions' do
|
574
|
+
let (:message) { "CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10|this is in the extensions space but it is not valid because it is not equals-separated key/value" }
|
575
|
+
it 'logs helpfully and produces a tagged event' do
|
576
|
+
do_decode(subject,message) do |event|
|
577
|
+
expect(event.get('tags')).to include('_cefparsefailure')
|
578
|
+
expect(event.get('message')).to eq(message)
|
579
|
+
end
|
580
|
+
expect(logger_stub).to have_received(:error)
|
581
|
+
.with(a_string_including('Failed to decode CEF payload. Generating failure event with payload in message field'),
|
582
|
+
a_hash_including(exception: a_string_including("invalid extensions; keyless value present"),
|
583
|
+
original_data: message))
|
584
|
+
end
|
585
|
+
end
|
586
|
+
context 'containing unescaped newlines' do
|
587
|
+
# when not using a `delimiter`, we expect exactly one CEF log per call to decode.
|
588
|
+
let (:message) {
|
589
|
+
<<~EOMESSAGE
|
590
|
+
CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10|src=10.0.0.67
|
591
|
+
CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10|src=10.0.0.67
|
592
|
+
CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10|src=10.0.0.67
|
593
|
+
CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10|src=10.0.0.67
|
594
|
+
CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10|src=10.0.0.67
|
595
|
+
EOMESSAGE
|
596
|
+
}
|
597
|
+
it 'logs helpfully and produces a tagged event' do
|
598
|
+
do_decode(subject, message) do |event|
|
599
|
+
expect(event.get('tags')).to include('_cefparsefailure')
|
600
|
+
expect(event.get('message')).to eq(message)
|
601
|
+
end
|
602
|
+
expect(logger_stub).to have_received(:error)
|
603
|
+
.with(a_string_including('Failed to decode CEF payload. Generating failure event with payload in message field'),
|
604
|
+
a_hash_including(exception: a_string_including("message is not valid CEF because it contains unescaped newline characters",
|
605
|
+
"use the `delimiter` setting to enable in-codec buffering and delimiter-splitting"),
|
606
|
+
original_data: message))
|
607
|
+
end
|
608
|
+
end
|
479
609
|
end
|
480
610
|
|
481
611
|
context 'when a CEF header ends with a pair of properly-escaped backslashes' do
|
@@ -548,6 +678,23 @@ describe LogStash::Codecs::CEF do
|
|
548
678
|
end
|
549
679
|
end
|
550
680
|
|
681
|
+
let(:literal_newline) { "\n" }
|
682
|
+
let(:literal_carriage_return) { "\r" }
|
683
|
+
let(:literal_equals) { "=" }
|
684
|
+
let(:literal_backslash) { "\\" }
|
685
|
+
let(:escaped_newline) { literal_backslash + 'n' }
|
686
|
+
let(:escaped_carriage_return) { literal_backslash + 'r' }
|
687
|
+
let(:escaped_equals) { literal_backslash + literal_equals }
|
688
|
+
let(:escaped_backslash) { literal_backslash + literal_backslash }
|
689
|
+
let(:escaped_sequences_in_extension_value) { "CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10|foo=bar msg=this message has escaped equals #{escaped_equals} and escaped newlines #{escaped_newline} escaped carriage returns #{escaped_carriage_return} and escaped backslashes #{escaped_backslash} in it bar=baz" }
|
690
|
+
it "decodes embedded newlines, carriage regurns, backslashes, and equals signs" do
|
691
|
+
decode_one(subject, escaped_sequences_in_extension_value) do |e|
|
692
|
+
insist { e.get("foo") } == 'bar'
|
693
|
+
insist { e.get("message") } == "this message has escaped equals #{literal_equals} and escaped newlines #{literal_newline} escaped carriage returns #{literal_carriage_return} and escaped backslashes #{literal_backslash} in it"
|
694
|
+
insist { e.get("bar") } == 'baz'
|
695
|
+
end
|
696
|
+
end
|
697
|
+
|
551
698
|
context "zoneless deviceReceiptTime(rt) when deviceTimeZone(dtz) is provided" do
|
552
699
|
let(:cef_formatted_timestamp) { 'Jul 19 2017 10:50:21.127' }
|
553
700
|
let(:zone_name) { 'Europe/Moscow' }
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: logstash-codec-cef
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.2.
|
4
|
+
version: 6.2.6
|
5
5
|
platform: java
|
6
6
|
authors:
|
7
7
|
- Elastic
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-10-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|