logstash-codec-cef 3.0.0-java → 4.0.0-java

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a841a39cd9b28c0794c9cb08e8295c5eea3bb521
4
- data.tar.gz: 9d6a5196496f277d25bf0064da05499c8240f2e1
3
+ metadata.gz: c46dcaf722e0c1935b66dfcf7a7d71e3bcc51844
4
+ data.tar.gz: 160e4b282a46867cbdcada9e3ca1f674ade9343f
5
5
  SHA512:
6
- metadata.gz: 1517164e07a5e5d89d1a2a768b9d0dd676c3175cbf6fbe40742e41d9fd8f163f24d15605dae02d1c2e241be9815e8de6fc6e617812fbb076038512f85af82a11
7
- data.tar.gz: 32de38bce405918a55b762161d1d1ebd603660caeeeb5b0f86fbce8f5ee9b7320f49b961ec13b6714163a75201582d872238b6198fa574f1abe547b682e3dd43
6
+ metadata.gz: b0f028820eb77468b5cb98c2e9a7ebad5205119b9da6fd43b2bd0b193ede44279593cc901a35713c1cf1e7c264e9d2c5bf601cbfe82d79c5a51b4d8086c44a59
7
+ data.tar.gz: bb86b4781182b84949b29bdcd5659b52f5e3a11c0211a5f2a984051ff182d9cd84c2879ba671bbcf18ab529fc2086399d35cb87e334de6827277f88c65fb2773
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 4.0.0
2
+ - Implements the dictionary translation for abbreviated CEF field names from chapter Chapter 2: ArcSight Extension Dictionary page 3 of 39 [CEF specification](https://protect724.hp.com/docs/DOC-1072).
3
+ - add `_cefparsefailure` tag on failed decode
4
+
1
5
  ## 3.0.0
2
6
  - breaking: Updated plugin to use new Java Event APIs
3
7
 
@@ -15,7 +19,6 @@
15
19
  - Config option `sev` is deprecated, use `severity` instead.
16
20
 
17
21
  ## 2.0.0
18
- - Plugins were updated to follow the new shutdown semantic, this mainly allows Logstash to instruct input plugins to terminate gracefully,
22
+ - Plugins were updated to follow the new shutdown semantic, this mainly allows Logstash to instruct input plugins to terminate gracefully,
19
23
  instead of using Thread.raise on the plugins' threads. Ref: https://github.com/elastic/logstash/pull/3895
20
24
  - Dependency on logstash-core update to 2.0
21
-
data/CONTRIBUTORS CHANGED
@@ -11,6 +11,7 @@ Contributors:
11
11
  * Jordan Sissel (jordansissel)
12
12
  * João Duarte (jsvd)
13
13
  * Nick Ethier (nickethier)
14
+ * Nicholas Lim (nich07as)
14
15
  * Pete Fritchman (fetep)
15
16
  * Pier-Hugues Pellerin (ph)
16
17
  * Karl Stoney (Stono)
@@ -5,6 +5,9 @@ require "json"
5
5
  # Implementation of a Logstash codec for the ArcSight Common Event Format (CEF)
6
6
  # Based on Revision 20 of Implementing ArcSight CEF, dated from June 05, 2013
7
7
  # https://protect724.hp.com/servlet/JiveServlet/downloadBody/1072-102-6-4697/CommonEventFormat.pdf
8
+ #
9
+ # If this codec receives a payload from an input that is not a valid CEF message, then it will
10
+ # produce an event with the payload as the 'message' field and a '_cefparsefailure' tag.
8
11
  class LogStash::Codecs::CEF < LogStash::Codecs::Base
9
12
  config_name "cef"
10
13
 
@@ -49,8 +52,18 @@ class LogStash::Codecs::CEF < LogStash::Codecs::Base
49
52
  # Fields to be included in CEV extension part as key/value pairs
50
53
  config :fields, :validate => :array, :default => []
51
54
 
52
- HEADER_FIELDS = ['cef_version','cef_vendor','cef_product','cef_device_version','cef_sigid','cef_name','cef_severity']
53
-
55
+ # Set this flag if you want to have both v1 and v2 fields indexed at the same time. Note that this option will increase
56
+ # the index size and data stored in outputs like Elasticsearch
57
+ # This option is available to ease transition to new schema
58
+ config :deprecated_v1_fields, :validate => :boolean, :default => false, :deprecated => "This setting is being deprecated"
59
+
60
+ HEADER_FIELDS = ['cefVersion','deviceVendor','deviceProduct','deviceVersion','deviceEventClassId','name','severity']
61
+
62
+ # Translating and flattening the CEF extensions with known field names as documented in the Common Event Format whitepaper
63
+ MAPPINGS = { "act" => "deviceAction", "app" => "applicationProtocol", "c6a1" => "deviceCustomIPv6Address1", "c6a1Label" => "deviceCustomIPv6Address1Label", "c6a2" => "deviceCustomIPv6Address2", "c6a2Label" => "deviceCustomIPv6Address2Label", "c6a3" => "deviceCustomIPv6Address3", "c6a3Label" => "deviceCustomIPv6Address3Label", "c6a4" => "deviceCustomIPv6Address4", "c6a4Label" => "deviceCustomIPv6Address4Label", "cat" => "deviceEventCategory", "cfp1" => "deviceCustomFloatingPoint1", "cfp1Label" => "deviceCustomFloatingPoint1Label", "cfp2" => "deviceCustomFloatingPoint2", "cfp2Label" => "deviceCustomFloatingPoint2", "cfp3" => "deviceCustomFloatingPoint3", "cfp3Label" => "deviceCustomFloatingPoint4Label", "cfp4" => "deviceCustomFloatingPoint4", "cfp4Label" => "deviceCustomFloatingPoint4Label", "cn1" => "deviceCustomNumber1", "cn1Label" => "deviceCustomNumber1Label", "cn2" => "deviceCustomNumber2", "cn2Label" => "deviceCustomNumber2Label", "cn3" => "deviceCustomNumber3", "cn3Label" => "deviceCustomNumber3Label", "cnt" => "baseEventCount", "cs1" => "deviceCustomString1", "cs1Label" => "deviceCustomString1Label", "cs2" => "deviceCustomString2", "cs2Label" => "deviceCustomString2Label", "cs3" => "deviceCustomString3", "cs3Label" => "deviceCustomString3Label", "cs4" => "deviceCustomString4", "cs4Label" => "deviceCustomString4Label", "cs5" => "deviceCustomString5", "cs5Label" => "deviceCustomString5Label", "cs6" => "deviceCustomString6", "cs6Label" => "deviceCustomString6Label", "dhost" => "destinationHostName", "dmac" => "destinationMacAddress", "dntdom" => "destinationNTDomain", "dpid" => "destinationProcessId", "dpriv" => "destinationUserPrivileges", "dproc" => "destinationProcessName", "dpt" => "destinationPort", "dst" => "destinationAddress", "duid" => "destinationUserId", "duser" => "destinationUserName", "dvc" => "deviceAddress", "dvchost" => "deviceHostName", "dvcpid" => "deviceProcessId", "end" => "endTime", "fname" => "fileName", "fsize" => "fileSize", "in" => "bytesIn", "msg" => "message", "out" => "bytesOut", "proto" => "transportProtocol", "request" => "requestUrl", "rt" => "receiptTime", "shost" => "sourceHostName", "smac" => "sourceMacAddress", "sntdom" => "sourceNtDomain", "spid" => "sourceProcessId", "spriv" => "sourceUserPrivileges", "sproc" => "sourceProcessName", "spt" => "sourcePort", "src" => "sourceAddress", "start" => "startTime", "suid" => "sourceUserId", "suser" => "sourceUserName", "ahost" => "agentHost", "art" => "agentReceiptTime", "at" => "agentType", "aid" => "agentId", "_cefVer" => "cefVersion", "agt" => "agentAddress", "av" => "agentVersion", "atz" => "agentTimeZone", "dtz" => "destinationTimeZone", "slong" => "sourceLongitude", "slat" => "sourceLatitude", "dlong" => "destinationLongitude", "dlat" => "destinationLatitude", "catdt" => "categoryDeviceType", "mrt" => "managerReceiptTime" }
64
+
65
+ DEPRECATED_HEADER_FIELDS = ['cef_version','cef_vendor','cef_product','cef_device_version','cef_sigid','cef_name','cef_severity']
66
+
54
67
  public
55
68
  def initialize(params={})
56
69
  super(params)
@@ -77,6 +90,12 @@ class LogStash::Codecs::CEF < LogStash::Codecs::Base
77
90
  # TODO: To solve all unescaping cases, regex is not suitable. A little parse should be written.
78
91
  split_data = data.split /(?<=[^\\]\\\\)[\|]|(?<!\\)[\|]/
79
92
 
93
+ # To be invoked when config settings is set to TRUE for V1 field names (cef_ext.<fieldname>) the following code might be removed in upcoming Codec revision
94
+ if deprecated_v1_fields
95
+ handle_v1_fields(event, split_data)
96
+ end
97
+
98
+ # To be invoked with default config settings to utilise the new field name formatting and flatten out the JSON document
80
99
  # Store header fields
81
100
  HEADER_FIELDS.each_with_index do |field_name, index|
82
101
  store_header_field(event,field_name,split_data[index])
@@ -85,14 +104,14 @@ class LogStash::Codecs::CEF < LogStash::Codecs::Base
85
104
  message = split_data[HEADER_FIELDS.size..-1].join('|')
86
105
 
87
106
  # Try and parse out the syslog header if there is one
88
- if event.get('cef_version').include? ' '
89
- split_cef_version= event.get('cef_version').rpartition(' ')
107
+ if event.get('cefVersion').include? ' '
108
+ split_cef_version= event.get('cefVersion').rpartition(' ')
90
109
  event.set('syslog', split_cef_version[0])
91
- event.set('cef_version',split_cef_version[2])
110
+ event.set('cefVersion',split_cef_version[2])
92
111
  end
93
112
 
94
113
  # Get rid of the CEF bit in the version
95
- event.set('cef_version', event.get('cef_version').sub(/^CEF:/, ''))
114
+ event.set('cefVersion', event.get('cefVersion').sub(/^CEF:/, ''))
96
115
 
97
116
  # Strip any whitespace from the message
98
117
  if not message.nil? and message.include? '='
@@ -100,20 +119,36 @@ class LogStash::Codecs::CEF < LogStash::Codecs::Base
100
119
 
101
120
  # If the last KVP has no value, add an empty string, this prevents hash errors below
102
121
  if message.end_with?('=')
103
- message=message + ' ' unless message.end_with?('\=')
122
+ message = message + ' ' unless message.end_with?('\=')
104
123
  end
105
124
 
106
- # Now parse the key value pairs into it
107
- extensions = {}
108
- message = message.split(/ ([\w\.]+)=/)
109
- key, value = message.shift.split('=', 2)
110
- extensions[key] = value.gsub(/\\=/, '=').gsub(/\\\\/, '\\')
111
- Hash[*message].each{ |k, v| extensions[k] = v }
112
- # And save the new has as the extensions
113
- event.set('cef_ext', extensions)
125
+ # Insert custom delimiter to separate key-value pairs, to which some values will contain special characters
126
+ # This separator '|^^^' os tested to be unique
127
+ message = message.gsub((/\s+(\w+=)/),'|^^^\1')
128
+
129
+ # This portion strips out the additional fields from the CEF logs, if needed, the ArcSight connectors will need to map it accordingly
130
+ # Also implemented to safeguard the {dot} notations in ES versions below 2.4x
131
+ message = message.gsub((/ (ad\.\w+.*\]\=[^|^^^]+)/),'')
132
+ message = message.split('|^^^')
133
+
134
+ # Replaces the '=' with '***' to avoid conflict with strings with HTML content namely key-value pairs where the values contain HTML strings
135
+ # Example : requestUrl = http://<testdomain>:<port>?query=A
136
+ for i in 0..message.length-1
137
+ message[i] = message[i].sub(/\=/, "***")
138
+ message[i] = message[i].gsub(/\\=/, '=').gsub(/\\\\/, '\\')
139
+ end
140
+
141
+ message = message.map {|s| k, v = s.split('***'); "#{MAPPINGS[k] || k }=#{v}"}
142
+ message = message.each_with_object({}) do |k|
143
+ key, value = k.split(/\s*=\s*/,2)
144
+ event.set(key, value)
145
+ end
114
146
  end
115
147
 
116
148
  yield event
149
+ rescue => e
150
+ @logger.error("Failed to decode CEF payload. Generating failure event with payload in message field.", :error => e.message, :backtrace => e.backtrace, :data => data)
151
+ yield LogStash::Event.new("message" => data, "tags" => ["_cefparsefailure"])
117
152
  end
118
153
 
119
154
  public
@@ -230,5 +265,44 @@ class LogStash::Codecs::CEF < LogStash::Codecs::Base
230
265
  rescue TypeError, ArgumentError
231
266
  false
232
267
  end
268
+
269
+ def handle_v1_fields(event, split_data)
270
+ # Store header fields
271
+ DEPRECATED_HEADER_FIELDS.each_with_index do |field_name, index|
272
+ store_header_field(event,field_name,split_data[index])
273
+ end
274
+ #Remainder is message
275
+ message = split_data[DEPRECATED_HEADER_FIELDS.size..-1].join('|')
276
+
277
+ # Try and parse out the syslog header if there is one
278
+ if event.get('cef_version').include? ' '
279
+ split_cef_version= event.get('cef_version').rpartition(' ')
280
+ event.set('syslog', split_cef_version[0])
281
+ event.set('cef_version',split_cef_version[2])
282
+ end
283
+
284
+ # Get rid of the CEF bit in the version
285
+ event.set('cef_version', event.get('cef_version').sub(/^CEF:/, ''))
286
+
287
+ # Strip any whitespace from the message
288
+ if not message.nil? and message.include? '='
289
+ message = message.strip
290
+
291
+ # If the last KVP has no value, add an empty string, this prevents hash errors below
292
+ if message.end_with?('=')
293
+ message=message + ' ' unless message.end_with?('\=')
294
+ end
295
+
296
+ # Now parse the key value pairs into it
297
+ extensions = {}
298
+ message = message.split(/ ([\w\.]+)=/)
299
+ key, value = message.shift.split('=', 2)
300
+ extensions[key] = value.gsub(/\\=/, '=').gsub(/\\\\/, '\\')
301
+ Hash[*message].each{ |k, v| extensions[k] = v }
302
+ # And save the new has as the extensions
303
+ event.set('cef_ext', extensions)
304
+ end
305
+
306
+ end
233
307
 
234
308
  end
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
 
3
3
  s.name = 'logstash-codec-cef'
4
- s.version = '3.0.0'
4
+ s.version = '4.0.0'
5
5
  s.platform = 'java'
6
6
  s.licenses = ['Apache License (2.0)']
7
7
  s.summary = "CEF codec to parse and encode CEF formated logs"
@@ -1,5 +1,4 @@
1
1
  # encoding: utf-8
2
-
3
2
  require "logstash/devutils/rspec/spec_helper"
4
3
  require "logstash/codecs/cef"
5
4
  require "logstash/event"
@@ -320,6 +319,193 @@ describe LogStash::Codecs::CEF do
320
319
  context "#decode" do
321
320
  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" }
322
321
 
322
+ def validate(e)
323
+ insist { e.is_a?(LogStash::Event) }
324
+ insist { e.get('cefVersion') } == "0"
325
+ insist { e.get('deviceVersion') } == "1.0"
326
+ insist { e.get('deviceEventClassId') } == "100"
327
+ insist { e.get('name') } == "trojan successfully stopped"
328
+ insist { e.get('severity') } == "10"
329
+ end
330
+
331
+ it "should parse the cef headers" do
332
+ subject.decode(message) do |e|
333
+ validate(e)
334
+ insist { e.get("deviceVendor") } == "security"
335
+ insist { e.get("deviceProduct") } == "threatmanager"
336
+ end
337
+ end
338
+
339
+ it "should parse the cef body" do
340
+ subject.decode(message) do |e|
341
+ insist { e.get("sourceAddress")} == "10.0.0.192"
342
+ insist { e.get("destinationAddress") } == "12.121.122.82"
343
+ insist { e.get("sourcePort") } == "1232"
344
+ end
345
+ end
346
+
347
+ let (:missing_headers) { "CEF:0|||1.0|100|trojan successfully stopped|10|src=10.0.0.192 dst=12.121.122.82 spt=1232" }
348
+ it "should be OK with missing CEF headers (multiple pipes in sequence)" do
349
+ subject.decode(missing_headers) do |e|
350
+ validate(e)
351
+ insist { e.get("deviceVendor") } == ""
352
+ insist { e.get("deviceProduct") } == ""
353
+ end
354
+ end
355
+
356
+ let (:leading_whitespace) { "CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10| src=10.0.0.192 dst=12.121.122.82 spt=1232" }
357
+ it "should strip leading whitespace from the message" do
358
+ subject.decode(leading_whitespace) do |e|
359
+ validate(e)
360
+ end
361
+ end
362
+
363
+ let (:escaped_pipes) { 'CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10|moo=this\|has an escaped pipe' }
364
+ it "should be OK with escaped pipes in the message" do
365
+ subject.decode(escaped_pipes) do |e|
366
+ insist { e.get("moo") } == 'this\|has an escaped pipe'
367
+ end
368
+ end
369
+
370
+ let (:pipes_in_message) {'CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10|moo=this|has an pipe'}
371
+ it "should be OK with not escaped pipes in the message" do
372
+ subject.decode(pipes_in_message) do |e|
373
+ insist { e.get("moo") } == 'this|has an pipe'
374
+ end
375
+ end
376
+
377
+ let (:equal_in_message) {'CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10|moo=this =has = equals\='}
378
+ it "should be OK with equal in the message" do
379
+ subject.decode(equal_in_message) do |e|
380
+ insist { e.get("moo") } == 'this =has = equals='
381
+ end
382
+ end
383
+
384
+ let (:escaped_backslash_in_header) {'CEF:0|secu\\\\rity|threat\\\\manager|1.\\\\0|10\\\\0|tro\\\\jan successfully stopped|\\\\10|'}
385
+ it "should be OK with escaped backslash in the headers" do
386
+ subject.decode(escaped_backslash_in_header) do |e|
387
+ insist { e.get("cefVersion") } == '0'
388
+ insist { e.get("deviceVendor") } == 'secu\\rity'
389
+ insist { e.get("deviceProduct") } == 'threat\\manager'
390
+ insist { e.get("deviceVersion") } == '1.\\0'
391
+ insist { e.get("deviceEventClassId") } == '10\\0'
392
+ insist { e.get("name") } == 'tro\\jan successfully stopped'
393
+ insist { e.get("severity") } == '\\10'
394
+ end
395
+ end
396
+
397
+ let (:escaped_backslash_in_header_edge_case) {'CEF:0|security\\\\\\||threatmanager\\\\|1.0|100|trojan successfully stopped|10|'}
398
+ it "should be OK with escaped backslash in the headers (edge case: escaped slash in front of pipe)" do
399
+ subject.decode(escaped_backslash_in_header_edge_case) do |e|
400
+ validate(e)
401
+ insist { e.get("deviceVendor") } == 'security\\|'
402
+ insist { e.get("deviceProduct") } == 'threatmanager\\'
403
+ end
404
+ end
405
+
406
+ let (:escaped_pipes_in_header) {'CEF:0|secu\\|rity|threatmanager\\||1.\\|0|10\\|0|tro\\|jan successfully stopped|\\|10|'}
407
+ it "should be OK with escaped pipes in the headers" do
408
+ subject.decode(escaped_pipes_in_header) do |e|
409
+ insist { e.get("cefVersion") } == '0'
410
+ insist { e.get("deviceVendor") } == 'secu|rity'
411
+ insist { e.get("deviceProduct") } == 'threatmanager|'
412
+ insist { e.get("deviceVersion") } == '1.|0'
413
+ insist { e.get("deviceEventClassId") } == '10|0'
414
+ insist { e.get("name") } == 'tro|jan successfully stopped'
415
+ insist { e.get("severity") } == '|10'
416
+ end
417
+ end
418
+
419
+ let (:backslash_in_message) {'CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10|moo=this \\has \\ backslashs\\'}
420
+ it "should be OK with backslashs in the message" do
421
+ subject.decode(backslash_in_message) do |e|
422
+ insist { e.get("moo") } == 'this \\has \\ backslashs\\'
423
+ end
424
+ end
425
+
426
+ let (:equal_in_header) {'CEF:0|security|threatmanager=equal|1.0|100|trojan successfully stopped|10|'}
427
+ it "should be OK with equal in the headers" do
428
+ subject.decode(equal_in_header) do |e|
429
+ validate(e)
430
+ insist { e.get("deviceProduct") } == "threatmanager=equal"
431
+ end
432
+ end
433
+
434
+ let (:spaces_in_between_keys) {'CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10| src=10.0.0.192 dst=12.121.122.82 spt=1232'}
435
+ it "should be OK to have one or more spaces between keys" do
436
+ subject.decode(spaces_in_between_keys) do |e|
437
+ validate(e)
438
+ insist { e.get("sourceAddress") } == "10.0.0.192"
439
+ insist { e.get("destinationAddress") } == "12.121.122.82"
440
+ insist { e.get("sourcePort") } == "1232"
441
+ end
442
+ end
443
+
444
+ let (:allow_spaces_in_values) {'CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10|src=10.0.0.192 dst=12.121.122.82 spt=1232 dproc=InternetExplorer x.x.x.x'}
445
+ it "should be OK to have one or more spaces in values" do
446
+ subject.decode(allow_spaces_in_values) do |e|
447
+ validate(e)
448
+ insist { e.get("sourceAddress") } == "10.0.0.192"
449
+ insist { e.get("destinationAddress") } == "12.121.122.82"
450
+ insist { e.get("sourcePort") } == "1232"
451
+ insist { e.get("destinationProcessName") } == "InternetExplorer x.x.x.x"
452
+ end
453
+ end
454
+
455
+ let (:trim_additional_fields_with_dot_notations) {'CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10|src=10.0.0.192 dst=12.121.122.82 ad.field[0]=field0 ad.name[1]=new_name'}
456
+ it "should remove ad.fields" do
457
+ subject.decode(trim_additional_fields_with_dot_notations) do |e|
458
+ validate(e)
459
+ insist { e.get("sourceAddress") } == "10.0.0.192"
460
+ insist { e.get("destinationAddress") } == "12.121.122.82"
461
+ insist { e.get("ad.field[0]") } == nil
462
+ insist { e.get("ad.name[1]") } == nil
463
+ end
464
+ end
465
+
466
+ let (:preserve_unmatched_key_mappings) {'CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10|src=10.0.0.192 dst=12.121.122.82 new_key_by_device=new_values here'}
467
+ it "should remove ad.fields" do
468
+ subject.decode(preserve_unmatched_key_mappings) do |e|
469
+ validate(e)
470
+ insist { e.get("sourceAddress") } == "10.0.0.192"
471
+ insist { e.get("destinationAddress") } == "12.121.122.82"
472
+ insist { e.get("new_key_by_device") } == "new_values here"
473
+ end
474
+ end
475
+
476
+ let (:translate_abbreviated_cef_fields) {'CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10|src=10.0.0.192 dst=12.121.122.82 proto=TCP shost=source.host.name dhost=destination.host.name spt=11024 dpt=9200'}
477
+ it "should translate most known abbreviated CEF field names" do
478
+ subject.decode(translate_abbreviated_cef_fields) do |e|
479
+ validate(e)
480
+ insist { e.get("sourceAddress") } == "10.0.0.192"
481
+ insist { e.get("destinationAddress") } == "12.121.122.82"
482
+ insist { e.get("transportProtocol") } == "TCP"
483
+ insist { e.get("sourceHostName") } == "source.host.name"
484
+ insist { e.get("destinationHostName") } == "destination.host.name"
485
+ insist { e.get("sourcePort") } == "11024"
486
+ insist { e.get("destinationPort") } == "9200"
487
+ end
488
+ end
489
+
490
+ let (:syslog) { "Syslogdate Sysloghost CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10|src=10.0.0.192 dst=12.121.122.82 spt=1232" }
491
+ it "Should detect headers before CEF starts" do
492
+ subject.decode(syslog) do |e|
493
+ validate(e)
494
+ insist { e.get('syslog') } == 'Syslogdate Sysloghost'
495
+ end
496
+ end
497
+ end
498
+
499
+ context "decode with deprecated version option" do
500
+ 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" }
501
+ let(:options) {
502
+ {
503
+ "deprecated_v1_fields" => true
504
+ }
505
+ }
506
+
507
+ subject(:codec) { LogStash::Codecs::CEF.new(options) }
508
+
323
509
  def validate(e)
324
510
  insist { e.is_a?(LogStash::Event) }
325
511
  insist { e.get('cef_version') } == "0"
@@ -327,6 +513,11 @@ describe LogStash::Codecs::CEF do
327
513
  insist { e.get('cef_sigid') } == "100"
328
514
  insist { e.get('cef_name') } == "trojan successfully stopped"
329
515
  insist { e.get('cef_severity') } == "10"
516
+ insist { e.get('cefVersion') } == "0"
517
+ insist { e.get('deviceVersion') } == "1.0"
518
+ insist { e.get('deviceEventClassId') } == "100"
519
+ insist { e.get('name') } == "trojan successfully stopped"
520
+ insist { e.get('severity') } == "10"
330
521
  end
331
522
 
332
523
  it "should parse the cef headers" do
@@ -335,6 +526,8 @@ describe LogStash::Codecs::CEF do
335
526
  ext = e.get('cef_ext')
336
527
  insist { e.get("cef_vendor") } == "security"
337
528
  insist { e.get("cef_product") } == "threatmanager"
529
+ insist { e.get("deviceVendor") } == "security"
530
+ insist { e.get("deviceProduct") } == "threatmanager"
338
531
  end
339
532
  end
340
533
 
@@ -344,6 +537,9 @@ describe LogStash::Codecs::CEF do
344
537
  insist { ext['src'] } == "10.0.0.192"
345
538
  insist { ext['dst'] } == "12.121.122.82"
346
539
  insist { ext['spt'] } == "1232"
540
+ insist { e.get("sourceAddress")} == "10.0.0.192"
541
+ insist { e.get("destinationAddress") } == "12.121.122.82"
542
+ insist { e.get("sourcePort") } == "1232"
347
543
  end
348
544
  end
349
545
 
@@ -361,6 +557,8 @@ describe LogStash::Codecs::CEF do
361
557
  validate(e)
362
558
  insist { e.get("cef_vendor") } == ""
363
559
  insist { e.get("cef_product") } == ""
560
+ insist { e.get("deviceVendor") } == ""
561
+ insist { e.get("deviceProduct") } == ""
364
562
  end
365
563
  end
366
564
 
@@ -427,6 +625,13 @@ describe LogStash::Codecs::CEF do
427
625
  insist { e.get("cef_sigid") } == '10|0'
428
626
  insist { e.get("cef_name") } == 'tro|jan successfully stopped'
429
627
  insist { e.get("cef_severity") } == '|10'
628
+ insist { e.get("cefVersion") } == '0'
629
+ insist { e.get("deviceVendor") } == 'secu|rity'
630
+ insist { e.get("deviceProduct") } == 'threatmanager|'
631
+ insist { e.get("deviceVersion") } == '1.|0'
632
+ insist { e.get("deviceEventClassId") } == '10|0'
633
+ insist { e.get("name") } == 'tro|jan successfully stopped'
634
+ insist { e.get("severity") } == '|10'
430
635
  end
431
636
  end
432
637
 
@@ -451,7 +656,16 @@ describe LogStash::Codecs::CEF do
451
656
  subject.decode(syslog) do |e|
452
657
  validate(e)
453
658
  insist { e.get('syslog') } == 'Syslogdate Sysloghost'
454
- end
659
+ end
660
+ end
661
+
662
+ context "when payload is not in CEF" do
663
+ let (:message) { "potatoes" }
664
+ it "Should detect headers before CEF starts" do
665
+ subject.decode(message) do |e|
666
+ insist { e.get('tags') } == ['_cefparsefailure']
667
+ end
668
+ end
455
669
  end
456
670
  end
457
671
 
@@ -462,24 +676,23 @@ describe LogStash::Codecs::CEF do
462
676
 
463
677
  it "should return an equal event if encoded and decoded again" do
464
678
  codec.on_event{|data, newdata| results << newdata}
465
- codec.vendor = "%{cef_vendor}"
466
- codec.product = "%{cef_product}"
467
- codec.version = "%{cef_device_version}"
468
- codec.signature = "%{cef_sigid}"
469
- codec.name = "%{cef_name}"
470
- codec.severity = "%{cef_severity}"
679
+ codec.vendor = "%{deviceVendor}"
680
+ codec.product = "%{deviceProduct}"
681
+ codec.version = "%{deviceVersion}"
682
+ codec.signature = "%{deviceEventClassId}"
683
+ codec.name = "%{name}"
684
+ codec.severity = "%{severity}"
471
685
  codec.fields = [ "foo" ]
472
- event = LogStash::Event.new("cef_vendor" => "vendor", "cef_product" => "product", "cef_device_version" => "2.0", "cef_sigid" => "signature", "cef_name" => "name", "cef_severity" => "1", "foo" => "bar")
686
+ event = LogStash::Event.new("deviceVendor" => "vendor", "deviceProduct" => "product", "deviceVersion" => "2.0", "deviceEventClassId" => "signature", "name" => "name", "severity" => "1", "foo" => "bar")
473
687
  codec.encode(event)
474
688
  codec.decode(results.first) do |e|
475
- expect(e.get('cef_vendor')).to be == event.get('cef_vendor')
476
- expect(e.get('cef_product')).to be == event.get('cef_product')
477
- expect(e.get('cef_device_version')).to be == event.get('cef_device_version')
478
- expect(e.get('cef_sigid')).to be == event.get('cef_sigid')
479
- expect(e.get('cef_name')).to be == event.get('cef_name')
480
- expect(e.get('cef_severity')).to be == event.get('cef_severity')
481
- # decode saves extensions as hash to 'cef_ext'
482
- expect(e.get('[cef_ext][foo]')).to be == event.get('foo')
689
+ expect(e.get('deviceVendor')).to be == event.get('deviceVendor')
690
+ expect(e.get('deviceProduct')).to be == event.get('deviceProduct')
691
+ expect(e.get('deviceVersion')).to be == event.get('deviceVersion')
692
+ expect(e.get('deviceEventClassId')).to be == event.get('deviceEventClassId')
693
+ expect(e.get('name')).to be == event.get('name')
694
+ expect(e.get('severity')).to be == event.get('severity')
695
+ expect(e.get('foo')).to be == event.get('foo')
483
696
  end
484
697
  end
485
698
  end
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: 3.0.0
4
+ version: 4.0.0
5
5
  platform: java
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-09-22 00:00:00.000000000 Z
11
+ date: 2016-11-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement