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 +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
|