diff --git a/lib/logstash/codecs/cef.rb b/lib/logstash/codecs/cef.rb index b108b8e..6446dda 100644 --- a/lib/logstash/codecs/cef.rb +++ b/lib/logstash/codecs/cef.rb @@ -310,6 +310,7 @@ def handle(data, &block) @timestamp_fields.each do |timestamp_field_name| raw_timestamp = extension_fields.delete(timestamp_field_name) or next value = normalize_timestamp(raw_timestamp, device_timezone) + next if value.nil? event.set(timestamp_field_name, value) end end @@ -605,10 +606,11 @@ def desanitize_extension_val(value) def normalize_timestamp(value, device_timezone_name) return nil if value.nil? || value.to_s.strip.empty? - - normalized = @timestamp_normalizer.normalize(value, device_timezone_name).iso8601(9) - - LogStash::Timestamp.new(normalized) + + normalized = @timestamp_normalizer.normalize(value, device_timezone_name) + return nil if normalized.nil? + + LogStash::Timestamp.new(normalized.iso8601(9)) rescue => e @logger.error("Failed to parse CEF timestamp value `#{value}` (#{e.message})") raise InvalidTimestamp.new("Not a valid CEF timestamp: `#{value}`") diff --git a/lib/logstash/codecs/cef/timestamp_normalizer.rb b/lib/logstash/codecs/cef/timestamp_normalizer.rb index 30ad904..0a5161a 100644 --- a/lib/logstash/codecs/cef/timestamp_normalizer.rb +++ b/lib/logstash/codecs/cef/timestamp_normalizer.rb @@ -35,30 +35,35 @@ def initialize(locale:nil, timezone:nil, clock: Clock.systemUTC) .withLocale(java_locale) end - INTEGER_OR_DECIMAL_PATTERN = /\A[1-9][0-9]*(?:\.[0-9]+)?\z/ + INTEGER_OR_DECIMAL_PATTERN = /\A-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?\z/ private_constant :INTEGER_OR_DECIMAL_PATTERN - # @param value [String,Time,Numeric] - # The value to parse. `Time`s are returned without modification, and `Numeric` values - # are treated as millis-since-epoch (as are fully-numeric strings). - # Strings are parsed unsing any of the supported CEF formats, and when the timestamp - # does not encode a year, we assume the year from contextual information like the - # current time. - # @param device_timezone_name [String,nil] (optional): + # @param value [String, Time, Numeric] + # The value to parse. `Time` values are returned without modification. + # Numeric values and fully-numeric strings are treated as milliseconds-since-epoch. + # If the numeric value is less than or equal to zero, `nil` is returned. + # Non-numeric strings are parsed using any of the supported CEF timestamp formats. + # When the timestamp does not encode a year, the year is assumed from contextual + # information like the current time. + # + # @param device_timezone_name [String, nil] (optional) # If known, the time-zone or UTC offset of the device that encoded the timestamp. # This value is used to determine the offset when no offset is encoded in the timestamp. # If not provided, the system default time zone is used instead. - # @return [Time] - def normalize(value, device_timezone_name=nil) - return value if value.kind_of?(Time) - - case value - when Numeric then Time.at(Rational(value, 1000)) - when INTEGER_OR_DECIMAL_PATTERN then Time.at(Rational(value, 1000)) + # + # @return [Time, nil] + + def normalize(value, device_timezone_name = nil) + return value if value.is_a?(Time) + + if value.is_a?(Numeric) || value.to_s =~ INTEGER_OR_DECIMAL_PATTERN + return nil if value.to_f <= 0 + return Time.at(Rational(value, 1000)) else - parse_cef_format_string(value.to_s, device_timezone_name) + return parse_cef_format_string(value.to_s, device_timezone_name) end end + private diff --git a/spec/codecs/cef_spec.rb b/spec/codecs/cef_spec.rb index f805c82..00badc6 100644 --- a/spec/codecs/cef_spec.rb +++ b/spec/codecs/cef_spec.rb @@ -739,6 +739,28 @@ def enriched_event_validation(event) end end end + context 'negative values' do + let(:message_with_negative_start) { %Q{CEF:0|Security|threatmanager|1.0|100|worm successfully stopped|Very-High| eventId=1 msg=Worm successfully stopped start=-7128875765552326520} } + if ecs_select.active_mode != :disabled + it 'not storing a value' do + decode_one(subject, message_with_negative_start) do |event| + expect(event).not_to include '[event][start]' + expect(event.get('[event][start]')).to be nil + end + end + end + end + context 'zero value' do + let(:message_with_zero_start) { %Q{CEF:0|Security|threatmanager|1.0|100|worm successfully stopped|Very-High| eventId=1 msg=Worm successfully stopped start=0} } + if ecs_select.active_mode != :disabled + it 'not storing a value' do + decode_one(subject, message_with_zero_start) do |event| + expect(event).not_to include '[event][start]' + expect(event.get('[event][start]')).to be nil + end + end + end + end end let(:malformed_unescaped_equals_in_extension_value) { %q{CEF:0|FooBar|Web Gateway|1.2.3.45.67|200|Success|2|rt=Sep 07 2018 14:50:39 cat=Access Log dst=1.1.1.1 dhost=foo.example.com suser=redacted src=2.2.2.2 requestMethod=POST request='https://foo.example.com/bar/bingo/1' requestClientApplication='Foo-Bar/2018.1.7; Email:user@example.com; Guid:test=' cs1= cs1Label=Foo Bar} }