logstash-codec-cef 3.0.0-java → 4.0.0-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 -2
- data/CONTRIBUTORS +1 -0
- data/lib/logstash/codecs/cef.rb +89 -15
- data/logstash-codec-cef.gemspec +1 -1
- data/spec/codecs/cef_spec.rb +230 -17
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c46dcaf722e0c1935b66dfcf7a7d71e3bcc51844
|
4
|
+
data.tar.gz: 160e4b282a46867cbdcada9e3ca1f674ade9343f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/lib/logstash/codecs/cef.rb
CHANGED
@@ -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
|
-
|
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('
|
89
|
-
split_cef_version= event.get('
|
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('
|
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('
|
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
|
-
#
|
107
|
-
|
108
|
-
message = message.
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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
|
data/logstash-codec-cef.gemspec
CHANGED
data/spec/codecs/cef_spec.rb
CHANGED
@@ -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 = "%{
|
466
|
-
codec.product = "%{
|
467
|
-
codec.version = "%{
|
468
|
-
codec.signature = "%{
|
469
|
-
codec.name = "%{
|
470
|
-
codec.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("
|
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('
|
476
|
-
expect(e.get('
|
477
|
-
expect(e.get('
|
478
|
-
expect(e.get('
|
479
|
-
expect(e.get('
|
480
|
-
expect(e.get('
|
481
|
-
|
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:
|
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-
|
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
|