logstash-codec-cef 6.2.5-java → 6.2.6-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.
- 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
         |