logstash-codec-cef 3.0.0-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +21 -0
- data/CONTRIBUTORS +22 -0
- data/Gemfile +2 -0
- data/LICENSE +13 -0
- data/NOTICE.TXT +5 -0
- data/README.md +107 -0
- data/lib/logstash/codecs/cef.rb +234 -0
- data/logstash-codec-cef.gemspec +28 -0
- data/spec/codecs/cef_spec.rb +487 -0
- metadata +89 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a841a39cd9b28c0794c9cb08e8295c5eea3bb521
|
4
|
+
data.tar.gz: 9d6a5196496f277d25bf0064da05499c8240f2e1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1517164e07a5e5d89d1a2a768b9d0dd676c3175cbf6fbe40742e41d9fd8f163f24d15605dae02d1c2e241be9815e8de6fc6e617812fbb076038512f85af82a11
|
7
|
+
data.tar.gz: 32de38bce405918a55b762161d1d1ebd603660caeeeb5b0f86fbce8f5ee9b7320f49b961ec13b6714163a75201582d872238b6198fa574f1abe547b682e3dd43
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
## 3.0.0
|
2
|
+
- breaking: Updated plugin to use new Java Event APIs
|
3
|
+
|
4
|
+
## 2.1.3
|
5
|
+
- Switch in-place sub! to sub when extracting `cef_version`. new Logstash Java Event does not support in-place String changes.
|
6
|
+
|
7
|
+
## 2.1.2
|
8
|
+
- Depend on logstash-core-plugin-api instead of logstash-core, removing the need to mass update plugins on major releases of logstash
|
9
|
+
|
10
|
+
## 2.1.1
|
11
|
+
- New dependency requirements for logstash-core for the 5.0 release
|
12
|
+
|
13
|
+
## 2.1.0
|
14
|
+
- Implements `encode` with escaping according to the [CEF specification](https://protect724.hp.com/docs/DOC-1072).
|
15
|
+
- Config option `sev` is deprecated, use `severity` instead.
|
16
|
+
|
17
|
+
## 2.0.0
|
18
|
+
- Plugins were updated to follow the new shutdown semantic, this mainly allows Logstash to instruct input plugins to terminate gracefully,
|
19
|
+
instead of using Thread.raise on the plugins' threads. Ref: https://github.com/elastic/logstash/pull/3895
|
20
|
+
- Dependency on logstash-core update to 2.0
|
21
|
+
|
data/CONTRIBUTORS
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The following is a list of people who have contributed ideas, code, bug
|
2
|
+
reports, or in general have helped logstash along its way.
|
3
|
+
|
4
|
+
Maintainers:
|
5
|
+
* Lucas Bremgartner (breml)
|
6
|
+
|
7
|
+
Contributors:
|
8
|
+
* Aaron Mildenstein (untergeek)
|
9
|
+
* Colin Surprenant (colinsurprenant)
|
10
|
+
* Jason Kendall (coolacid)
|
11
|
+
* Jordan Sissel (jordansissel)
|
12
|
+
* João Duarte (jsvd)
|
13
|
+
* Nick Ethier (nickethier)
|
14
|
+
* Pete Fritchman (fetep)
|
15
|
+
* Pier-Hugues Pellerin (ph)
|
16
|
+
* Karl Stoney (Stono)
|
17
|
+
* Lucas Bremgartner (breml)
|
18
|
+
|
19
|
+
Note: If you've sent us patches, bug reports, or otherwise contributed to
|
20
|
+
Logstash, and you aren't on the list above and want to be, please let us know
|
21
|
+
and we'll make sure you're here. Contributions from folks like you are what make
|
22
|
+
open source awesome.
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright (c) 2012–2016 Elasticsearch <http://www.elastic.co>
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/NOTICE.TXT
ADDED
data/README.md
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
# Logstash Plugin
|
2
|
+
|
3
|
+
[![Travis Build Status](https://travis-ci.org/logstash-plugins/logstash-codec-cef.svg)](https://travis-ci.org/logstash-plugins/logstash-codec-cef)
|
4
|
+
|
5
|
+
This is a plugin for [Logstash](https://github.com/elastic/logstash).
|
6
|
+
|
7
|
+
It is fully free and fully open source. The license is Apache 2.0, meaning you are pretty much free to use it however you want in whatever way.
|
8
|
+
|
9
|
+
## Documentation
|
10
|
+
|
11
|
+
Logstash provides infrastructure to automatically generate documentation for this plugin. We use the asciidoc format to write documentation so any comments in the source code will be first converted into asciidoc and then into html. All plugin documentation are placed under one [central location](http://www.elastic.co/guide/en/logstash/current/).
|
12
|
+
|
13
|
+
- For formatting code or config example, you can use the asciidoc `[source,ruby]` directive
|
14
|
+
- For more asciidoc formatting tips, see the excellent reference here https://github.com/elastic/docs#asciidoc-guide
|
15
|
+
|
16
|
+
## Need Help?
|
17
|
+
|
18
|
+
Need help? Try #logstash on freenode IRC or the https://discuss.elastic.co/c/logstash discussion forum.
|
19
|
+
|
20
|
+
## Developing with Docker
|
21
|
+
You can use a docker container with all of the requirements pre installed to save you installing the development environment on your host.
|
22
|
+
|
23
|
+
### 1. Starting the container
|
24
|
+
Simply type `docker-compose run devenv` and you'll be entered into the container. Then you'll need to do `jruby -S bundle install` to get all the dependencies down.
|
25
|
+
|
26
|
+
### 2. Running tests
|
27
|
+
Once you've done #1 above, you can run your tests with `jruby -S bundle exec rspec`
|
28
|
+
|
29
|
+
## Developing without Docker
|
30
|
+
|
31
|
+
### 1. Plugin Developement and Testing
|
32
|
+
|
33
|
+
#### Code
|
34
|
+
- To get started, you'll need JRuby with the Bundler gem installed.
|
35
|
+
|
36
|
+
- Create a new plugin or clone and existing from the GitHub [logstash-plugins](https://github.com/logstash-plugins) organization. We also provide [example plugins](https://github.com/logstash-plugins?query=example).
|
37
|
+
|
38
|
+
- Install dependencies
|
39
|
+
```sh
|
40
|
+
bundle install
|
41
|
+
```
|
42
|
+
|
43
|
+
#### Test
|
44
|
+
|
45
|
+
- Update your dependencies
|
46
|
+
|
47
|
+
```sh
|
48
|
+
bundle install
|
49
|
+
```
|
50
|
+
|
51
|
+
- Run tests
|
52
|
+
|
53
|
+
```sh
|
54
|
+
bundle exec rspec
|
55
|
+
```
|
56
|
+
|
57
|
+
### 2. Running your unpublished Plugin in Logstash
|
58
|
+
|
59
|
+
#### 2.1 Run in a local Logstash clone
|
60
|
+
|
61
|
+
- Edit Logstash `Gemfile` and add the local plugin path, for example:
|
62
|
+
```ruby
|
63
|
+
gem "logstash-filter-awesome", :path => "/your/local/logstash-filter-awesome"
|
64
|
+
```
|
65
|
+
- Install plugin
|
66
|
+
```sh
|
67
|
+
# Logstash 2.3 and higher
|
68
|
+
bin/logstash-plugin install --no-verify
|
69
|
+
|
70
|
+
# Prior to Logstash 2.3
|
71
|
+
bin/plugin install --no-verify
|
72
|
+
|
73
|
+
```
|
74
|
+
- Run Logstash with your plugin
|
75
|
+
```sh
|
76
|
+
bin/logstash -e 'filter {awesome {}}'
|
77
|
+
```
|
78
|
+
At this point any modifications to the plugin code will be applied to this local Logstash setup. After modifying the plugin, simply rerun Logstash.
|
79
|
+
|
80
|
+
#### 2.2 Run in an installed Logstash
|
81
|
+
|
82
|
+
You can use the same **2.1** method to run your plugin in an installed Logstash by editing its `Gemfile` and pointing the `:path` to your local plugin development directory or you can build the gem and install it using:
|
83
|
+
|
84
|
+
- Build your plugin gem
|
85
|
+
```sh
|
86
|
+
gem build logstash-filter-awesome.gemspec
|
87
|
+
```
|
88
|
+
- Install the plugin from the Logstash home
|
89
|
+
```sh
|
90
|
+
# Logstash 2.3 and higher
|
91
|
+
bin/logstash-plugin install --no-verify
|
92
|
+
|
93
|
+
# Prior to Logstash 2.3
|
94
|
+
bin/plugin install --no-verify
|
95
|
+
|
96
|
+
```
|
97
|
+
- Start Logstash and proceed to test the plugin
|
98
|
+
|
99
|
+
## Contributing
|
100
|
+
|
101
|
+
All contributions are welcome: ideas, patches, documentation, bug reports, complaints, and even something you drew up on a napkin.
|
102
|
+
|
103
|
+
Programming is not a required skill. Whatever you've seen about open source and maintainers or community members saying "send patches or die" - you will not see that here.
|
104
|
+
|
105
|
+
It is more important to the community that you are able to contribute.
|
106
|
+
|
107
|
+
For more information about contributing, see the [CONTRIBUTING](https://github.com/elastic/logstash/blob/master/CONTRIBUTING.md) file.
|
@@ -0,0 +1,234 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/codecs/base"
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
# Implementation of a Logstash codec for the ArcSight Common Event Format (CEF)
|
6
|
+
# Based on Revision 20 of Implementing ArcSight CEF, dated from June 05, 2013
|
7
|
+
# https://protect724.hp.com/servlet/JiveServlet/downloadBody/1072-102-6-4697/CommonEventFormat.pdf
|
8
|
+
class LogStash::Codecs::CEF < LogStash::Codecs::Base
|
9
|
+
config_name "cef"
|
10
|
+
|
11
|
+
# Device vendor field in CEF header. The new value can include `%{foo}` strings
|
12
|
+
# to help you build a new value from other parts of the event.
|
13
|
+
config :vendor, :validate => :string, :default => "Elasticsearch"
|
14
|
+
|
15
|
+
# Device product field in CEF header. The new value can include `%{foo}` strings
|
16
|
+
# to help you build a new value from other parts of the event.
|
17
|
+
config :product, :validate => :string, :default => "Logstash"
|
18
|
+
|
19
|
+
# Device version field in CEF header. The new value can include `%{foo}` strings
|
20
|
+
# to help you build a new value from other parts of the event.
|
21
|
+
config :version, :validate => :string, :default => "1.0"
|
22
|
+
|
23
|
+
# Signature ID field in CEF header. The new value can include `%{foo}` strings
|
24
|
+
# to help you build a new value from other parts of the event.
|
25
|
+
config :signature, :validate => :string, :default => "Logstash"
|
26
|
+
|
27
|
+
# Name field in CEF header. The new value can include `%{foo}` strings
|
28
|
+
# to help you build a new value from other parts of the event.
|
29
|
+
config :name, :validate => :string, :default => "Logstash"
|
30
|
+
|
31
|
+
# Deprecated severity field for CEF header. The new value can include `%{foo}` strings
|
32
|
+
# to help you build a new value from other parts of the event.
|
33
|
+
#
|
34
|
+
# This field is used only if :severity is unchanged set to the default value.
|
35
|
+
#
|
36
|
+
# Defined as field of type string to allow sprintf. The value will be validated
|
37
|
+
# to be an integer in the range from 0 to 10 (including).
|
38
|
+
# All invalid values will be mapped to the default of 6.
|
39
|
+
config :sev, :validate => :string, :default => "6", :deprecated => "This setting is being deprecated, use :severity instead."
|
40
|
+
|
41
|
+
# Severity field in CEF header. The new value can include `%{foo}` strings
|
42
|
+
# to help you build a new value from other parts of the event.
|
43
|
+
#
|
44
|
+
# Defined as field of type string to allow sprintf. The value will be validated
|
45
|
+
# to be an integer in the range from 0 to 10 (including).
|
46
|
+
# All invalid values will be mapped to the default of 6.
|
47
|
+
config :severity, :validate => :string, :default => "6"
|
48
|
+
|
49
|
+
# Fields to be included in CEV extension part as key/value pairs
|
50
|
+
config :fields, :validate => :array, :default => []
|
51
|
+
|
52
|
+
HEADER_FIELDS = ['cef_version','cef_vendor','cef_product','cef_device_version','cef_sigid','cef_name','cef_severity']
|
53
|
+
|
54
|
+
public
|
55
|
+
def initialize(params={})
|
56
|
+
super(params)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
def store_header_field(event,field_name,field_data)
|
61
|
+
#Unescape pipes and backslash in header fields
|
62
|
+
event.set(field_name,field_data.gsub(/\\\|/, '|').gsub(/\\\\/, '\\')) unless field_data.nil?
|
63
|
+
end
|
64
|
+
|
65
|
+
public
|
66
|
+
def decode(data)
|
67
|
+
# Strip any quotations at the start and end, flex connectors seem to send this
|
68
|
+
if data[0] == "\""
|
69
|
+
data = data[1..-2]
|
70
|
+
end
|
71
|
+
event = LogStash::Event.new
|
72
|
+
|
73
|
+
# Split by the pipes, pipes in the extension part are perfectly valid and do not need escaping
|
74
|
+
# The better solution for the splitting regex would be /(?<!\\(\\\\)*)[\|]/, but this
|
75
|
+
# gives an "SyntaxError: (RegexpError) invalid pattern in look-behind" for the variable length look behind.
|
76
|
+
# Therefore one edge case is not handled properly: \\| (this should split, but it does not, because the escaped \ is not recognized)
|
77
|
+
# TODO: To solve all unescaping cases, regex is not suitable. A little parse should be written.
|
78
|
+
split_data = data.split /(?<=[^\\]\\\\)[\|]|(?<!\\)[\|]/
|
79
|
+
|
80
|
+
# Store header fields
|
81
|
+
HEADER_FIELDS.each_with_index do |field_name, index|
|
82
|
+
store_header_field(event,field_name,split_data[index])
|
83
|
+
end
|
84
|
+
#Remainder is message
|
85
|
+
message = split_data[HEADER_FIELDS.size..-1].join('|')
|
86
|
+
|
87
|
+
# 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(' ')
|
90
|
+
event.set('syslog', split_cef_version[0])
|
91
|
+
event.set('cef_version',split_cef_version[2])
|
92
|
+
end
|
93
|
+
|
94
|
+
# Get rid of the CEF bit in the version
|
95
|
+
event.set('cef_version', event.get('cef_version').sub(/^CEF:/, ''))
|
96
|
+
|
97
|
+
# Strip any whitespace from the message
|
98
|
+
if not message.nil? and message.include? '='
|
99
|
+
message = message.strip
|
100
|
+
|
101
|
+
# If the last KVP has no value, add an empty string, this prevents hash errors below
|
102
|
+
if message.end_with?('=')
|
103
|
+
message=message + ' ' unless message.end_with?('\=')
|
104
|
+
end
|
105
|
+
|
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)
|
114
|
+
end
|
115
|
+
|
116
|
+
yield event
|
117
|
+
end
|
118
|
+
|
119
|
+
public
|
120
|
+
def encode(event)
|
121
|
+
# "CEF:0|Elasticsearch|Logstash|1.0|Signature|Name|Sev|"
|
122
|
+
|
123
|
+
vendor = sanitize_header_field(event.sprintf(@vendor))
|
124
|
+
vendor = self.class.get_config["vendor"][:default] if vendor == ""
|
125
|
+
|
126
|
+
product = sanitize_header_field(event.sprintf(@product))
|
127
|
+
product = self.class.get_config["product"][:default] if product == ""
|
128
|
+
|
129
|
+
version = sanitize_header_field(event.sprintf(@version))
|
130
|
+
version = self.class.get_config["version"][:default] if version == ""
|
131
|
+
|
132
|
+
signature = sanitize_header_field(event.sprintf(@signature))
|
133
|
+
signature = self.class.get_config["signature"][:default] if signature == ""
|
134
|
+
|
135
|
+
name = sanitize_header_field(event.sprintf(@name))
|
136
|
+
name = self.class.get_config["name"][:default] if name == ""
|
137
|
+
|
138
|
+
# :sev is deprecated and therefore only considered if :severity equals the default setting or is invalid
|
139
|
+
severity = sanitize_severity(event, @severity)
|
140
|
+
if severity == self.class.get_config["severity"][:default]
|
141
|
+
# Use deprecated setting sev
|
142
|
+
severity = sanitize_severity(event, @sev)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Should also probably set the fields sent
|
146
|
+
header = ["CEF:0", vendor, product, version, signature, name, severity].join("|")
|
147
|
+
values = @fields.map {|fieldname| get_value(fieldname, event)}.compact.join(" ")
|
148
|
+
|
149
|
+
@on_event.call(event, "#{header}|#{values}\n")
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
# Escape pipes and backslashes in the header. Equal signs are ok.
|
155
|
+
# Newlines are forbidden.
|
156
|
+
def sanitize_header_field(value)
|
157
|
+
output = ""
|
158
|
+
|
159
|
+
value = value.to_s.gsub(/\r\n/, "\n")
|
160
|
+
|
161
|
+
value.each_char{|c|
|
162
|
+
case c
|
163
|
+
when "\\", "|"
|
164
|
+
output += "\\" + c
|
165
|
+
when "\n", "\r"
|
166
|
+
output += " "
|
167
|
+
else
|
168
|
+
output += c
|
169
|
+
end
|
170
|
+
}
|
171
|
+
|
172
|
+
return output
|
173
|
+
end
|
174
|
+
|
175
|
+
# Keys must be made up of a single word, with no spaces
|
176
|
+
# must be alphanumeric
|
177
|
+
def sanitize_extension_key(value)
|
178
|
+
value = value.to_s.gsub(/[^a-zA-Z0-9]/, "")
|
179
|
+
return value
|
180
|
+
end
|
181
|
+
|
182
|
+
# Escape equal signs in the extensions. Canonicalize newlines.
|
183
|
+
# CEF spec leaves it up to us to choose \r or \n for newline.
|
184
|
+
# We choose \n as the default.
|
185
|
+
def sanitize_extension_val(value)
|
186
|
+
output = ""
|
187
|
+
|
188
|
+
value = value.to_s.gsub(/\r\n/, "\n")
|
189
|
+
|
190
|
+
value.each_char{|c|
|
191
|
+
case c
|
192
|
+
when "\\", "="
|
193
|
+
output += "\\" + c
|
194
|
+
when "\n", "\r"
|
195
|
+
output += "\\n"
|
196
|
+
else
|
197
|
+
output += c
|
198
|
+
end
|
199
|
+
}
|
200
|
+
|
201
|
+
return output
|
202
|
+
end
|
203
|
+
|
204
|
+
def get_value(fieldname, event)
|
205
|
+
val = event.get(fieldname)
|
206
|
+
|
207
|
+
return nil if val.nil?
|
208
|
+
|
209
|
+
case val
|
210
|
+
when Array, Hash
|
211
|
+
return "#{sanitize_extension_key(fieldname)}=#{sanitize_extension_val(val.to_json)}"
|
212
|
+
when LogStash::Timestamp
|
213
|
+
return "#{sanitize_extension_key(fieldname)}=#{val.to_s}"
|
214
|
+
else
|
215
|
+
return "#{sanitize_extension_key(fieldname)}=#{sanitize_extension_val(val)}"
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def sanitize_severity(event, severity)
|
220
|
+
severity = sanitize_header_field(event.sprintf(severity)).strip
|
221
|
+
severity = self.class.get_config["severity"][:default] unless valid_severity?(severity)
|
222
|
+
severity = severity.to_i.to_s
|
223
|
+
end
|
224
|
+
|
225
|
+
def valid_severity?(sev)
|
226
|
+
f = Float(sev)
|
227
|
+
# check if it's an integer or a float with no remainder
|
228
|
+
# and if the value is between 0 and 10 (inclusive)
|
229
|
+
(f % 1 == 0) && f.between?(0,10)
|
230
|
+
rescue TypeError, ArgumentError
|
231
|
+
false
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
|
3
|
+
s.name = 'logstash-codec-cef'
|
4
|
+
s.version = '3.0.0'
|
5
|
+
s.platform = 'java'
|
6
|
+
s.licenses = ['Apache License (2.0)']
|
7
|
+
s.summary = "CEF codec to parse and encode CEF formated logs"
|
8
|
+
s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
|
9
|
+
s.authors = ["Elastic"]
|
10
|
+
s.email = 'info@elastic.co'
|
11
|
+
s.homepage = "http://www.elastic.co/guide/en/logstash/current/index.html"
|
12
|
+
s.require_paths = ["lib"]
|
13
|
+
|
14
|
+
# Files
|
15
|
+
s.files = Dir['lib/**/*','spec/**/*','vendor/**/*','*.gemspec','*.md','CONTRIBUTORS','Gemfile','LICENSE','NOTICE.TXT']
|
16
|
+
|
17
|
+
# Tests
|
18
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
19
|
+
|
20
|
+
# Special flag to let us know this is actually a logstash plugin
|
21
|
+
s.metadata = { "logstash_plugin" => "true", "logstash_group" => "codec" }
|
22
|
+
|
23
|
+
# Gem dependencies
|
24
|
+
s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
|
25
|
+
|
26
|
+
s.add_development_dependency 'logstash-devutils'
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,487 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "logstash/devutils/rspec/spec_helper"
|
4
|
+
require "logstash/codecs/cef"
|
5
|
+
require "logstash/event"
|
6
|
+
require "json"
|
7
|
+
|
8
|
+
describe LogStash::Codecs::CEF do
|
9
|
+
subject do
|
10
|
+
next LogStash::Codecs::CEF.new
|
11
|
+
end
|
12
|
+
|
13
|
+
context "#encode" do
|
14
|
+
subject(:codec) { LogStash::Codecs::CEF.new }
|
15
|
+
|
16
|
+
let(:results) { [] }
|
17
|
+
|
18
|
+
it "should not fail if fields is nil" do
|
19
|
+
codec.on_event{|data, newdata| results << newdata}
|
20
|
+
event = LogStash::Event.new("foo" => "bar")
|
21
|
+
codec.encode(event)
|
22
|
+
expect(results.first).to match(/^CEF:0\|Elasticsearch\|Logstash\|1.0\|Logstash\|Logstash\|6\|$/m)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should assert all header fields are present" do
|
26
|
+
codec.on_event{|data, newdata| results << newdata}
|
27
|
+
codec.fields = []
|
28
|
+
event = LogStash::Event.new("foo" => "bar")
|
29
|
+
codec.encode(event)
|
30
|
+
expect(results.first).to match(/^CEF:0\|Elasticsearch\|Logstash\|1.0\|Logstash\|Logstash\|6\|$/m)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should use default values for empty header fields" do
|
34
|
+
codec.on_event{|data, newdata| results << newdata}
|
35
|
+
codec.vendor = ""
|
36
|
+
codec.product = ""
|
37
|
+
codec.version = ""
|
38
|
+
codec.signature = ""
|
39
|
+
codec.name = ""
|
40
|
+
codec.severity = ""
|
41
|
+
codec.fields = []
|
42
|
+
event = LogStash::Event.new("foo" => "bar")
|
43
|
+
codec.encode(event)
|
44
|
+
expect(results.first).to match(/^CEF:0\|Elasticsearch\|Logstash\|1.0\|Logstash\|Logstash\|6\|$/m)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should use configured values for header fields" do
|
48
|
+
codec.on_event{|data, newdata| results << newdata}
|
49
|
+
codec.vendor = "vendor"
|
50
|
+
codec.product = "product"
|
51
|
+
codec.version = "2.0"
|
52
|
+
codec.signature = "signature"
|
53
|
+
codec.name = "name"
|
54
|
+
codec.severity = "1"
|
55
|
+
codec.fields = []
|
56
|
+
event = LogStash::Event.new("foo" => "bar")
|
57
|
+
codec.encode(event)
|
58
|
+
expect(results.first).to match(/^CEF:0\|vendor\|product\|2.0\|signature\|name\|1\|$/m)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should use sprintf for header fields" do
|
62
|
+
codec.on_event{|data, newdata| results << newdata}
|
63
|
+
codec.vendor = "%{vendor}"
|
64
|
+
codec.product = "%{product}"
|
65
|
+
codec.version = "%{version}"
|
66
|
+
codec.signature = "%{signature}"
|
67
|
+
codec.name = "%{name}"
|
68
|
+
codec.severity = "%{severity}"
|
69
|
+
codec.fields = []
|
70
|
+
event = LogStash::Event.new("vendor" => "vendor", "product" => "product", "version" => "2.0", "signature" => "signature", "name" => "name", "severity" => "1")
|
71
|
+
codec.encode(event)
|
72
|
+
expect(results.first).to match(/^CEF:0\|vendor\|product\|2.0\|signature\|name\|1\|$/m)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should use default, if severity is not numeric" do
|
76
|
+
codec.on_event{|data, newdata| results << newdata}
|
77
|
+
codec.severity = "foo"
|
78
|
+
codec.fields = []
|
79
|
+
event = LogStash::Event.new("foo" => "bar")
|
80
|
+
codec.encode(event)
|
81
|
+
expect(results.first).to match(/^CEF:0\|Elasticsearch\|Logstash\|1.0\|Logstash\|Logstash\|6\|$/m)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should use default, if severity is > 10" do
|
85
|
+
codec.on_event{|data, newdata| results << newdata}
|
86
|
+
codec.severity = "11"
|
87
|
+
codec.fields = []
|
88
|
+
event = LogStash::Event.new("foo" => "bar")
|
89
|
+
codec.encode(event)
|
90
|
+
expect(results.first).to match(/^CEF:0\|Elasticsearch\|Logstash\|1.0\|Logstash\|Logstash\|6\|$/m)
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should use default, if severity is < 0" do
|
94
|
+
codec.on_event{|data, newdata| results << newdata}
|
95
|
+
codec.severity = "-1"
|
96
|
+
codec.fields = []
|
97
|
+
event = LogStash::Event.new("foo" => "bar")
|
98
|
+
codec.encode(event)
|
99
|
+
expect(results.first).to match(/^CEF:0\|Elasticsearch\|Logstash\|1.0\|Logstash\|Logstash\|6\|$/m)
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should use default, if severity is float with decimal part" do
|
103
|
+
codec.on_event{|data, newdata| results << newdata}
|
104
|
+
codec.severity = "5.4"
|
105
|
+
codec.fields = []
|
106
|
+
event = LogStash::Event.new("foo" => "bar")
|
107
|
+
codec.encode(event)
|
108
|
+
expect(results.first).to match(/^CEF:0\|Elasticsearch\|Logstash\|1.0\|Logstash\|Logstash\|6\|$/m)
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should append fields as key/value pairs in cef extension part" do
|
112
|
+
codec.on_event{|data, newdata| results << newdata}
|
113
|
+
codec.fields = [ "foo", "bar" ]
|
114
|
+
event = LogStash::Event.new("foo" => "foo value", "bar" => "bar value")
|
115
|
+
codec.encode(event)
|
116
|
+
expect(results.first).to match(/^CEF:0\|Elasticsearch\|Logstash\|1.0\|Logstash\|Logstash\|6\|foo=foo value bar=bar value$/m)
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should ignore fields in fields if not present in event" do
|
120
|
+
codec.on_event{|data, newdata| results << newdata}
|
121
|
+
codec.fields = [ "foo", "bar", "baz" ]
|
122
|
+
event = LogStash::Event.new("foo" => "foo value", "baz" => "baz value")
|
123
|
+
codec.encode(event)
|
124
|
+
expect(results.first).to match(/^CEF:0\|Elasticsearch\|Logstash\|1.0\|Logstash\|Logstash\|6\|foo=foo value baz=baz value$/m)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should sanitize header fields" do
|
128
|
+
codec.on_event{|data, newdata| results << newdata}
|
129
|
+
codec.vendor = "ven\ndor"
|
130
|
+
codec.product = "pro|duct"
|
131
|
+
codec.version = "ver\\sion"
|
132
|
+
codec.signature = "sig\r\nnature"
|
133
|
+
codec.name = "na\rme"
|
134
|
+
codec.severity = "4\n"
|
135
|
+
codec.fields = []
|
136
|
+
event = LogStash::Event.new("foo" => "bar")
|
137
|
+
codec.encode(event)
|
138
|
+
expect(results.first).to match(/^CEF:0\|ven dor\|pro\\\|duct\|ver\\\\sion\|sig nature\|na me\|4\|$/m)
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should sanitize extension keys" do
|
142
|
+
codec.on_event{|data, newdata| results << newdata}
|
143
|
+
codec.fields = [ "f o\no", "@b-a_r" ]
|
144
|
+
event = LogStash::Event.new("f o\no" => "foo value", "@b-a_r" => "bar value")
|
145
|
+
codec.encode(event)
|
146
|
+
expect(results.first).to match(/^CEF:0\|Elasticsearch\|Logstash\|1.0\|Logstash\|Logstash\|6\|foo=foo value bar=bar value$/m)
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should sanitize extension values" do
|
150
|
+
codec.on_event{|data, newdata| results << newdata}
|
151
|
+
codec.fields = [ "foo", "bar", "baz" ]
|
152
|
+
event = LogStash::Event.new("foo" => "foo\\value\n", "bar" => "bar=value\r")
|
153
|
+
codec.encode(event)
|
154
|
+
expect(results.first).to match(/^CEF:0\|Elasticsearch\|Logstash\|1.0\|Logstash\|Logstash\|6\|foo=foo\\\\value\\n bar=bar\\=value\\n$/m)
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should encode a hash value" do
|
158
|
+
codec.on_event{|data, newdata| results << newdata}
|
159
|
+
codec.fields = [ "foo" ]
|
160
|
+
event = LogStash::Event.new("foo" => { "bar" => "bar value", "baz" => "baz value" })
|
161
|
+
codec.encode(event)
|
162
|
+
foo = results.first[/^CEF:0\|Elasticsearch\|Logstash\|1.0\|Logstash\|Logstash\|6\|foo=(.*)$/, 1]
|
163
|
+
expect(foo).not_to be_nil
|
164
|
+
foo_hash = JSON.parse(foo)
|
165
|
+
expect(foo_hash).to eq({"bar" => "bar value", "baz" => "baz value"})
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should encode an array value" do
|
169
|
+
codec.on_event{|data, newdata| results << newdata}
|
170
|
+
codec.fields = [ "foo" ]
|
171
|
+
event = LogStash::Event.new("foo" => [ "bar", "baz" ])
|
172
|
+
codec.encode(event)
|
173
|
+
foo = results.first[/^CEF:0\|Elasticsearch\|Logstash\|1.0\|Logstash\|Logstash\|6\|foo=(.*)$/, 1]
|
174
|
+
expect(foo).not_to be_nil
|
175
|
+
foo_array = JSON.parse(foo)
|
176
|
+
expect(foo_array).to eq(["bar", "baz"])
|
177
|
+
end
|
178
|
+
|
179
|
+
it "should encode a hash in an array value" do
|
180
|
+
codec.on_event{|data, newdata| results << newdata}
|
181
|
+
codec.fields = [ "foo" ]
|
182
|
+
event = LogStash::Event.new("foo" => [ { "bar" => "bar value" }, "baz" ])
|
183
|
+
codec.encode(event)
|
184
|
+
foo = results.first[/^CEF:0\|Elasticsearch\|Logstash\|1.0\|Logstash\|Logstash\|6\|foo=(.*)$/, 1]
|
185
|
+
expect(foo).not_to be_nil
|
186
|
+
foo_array = JSON.parse(foo)
|
187
|
+
expect(foo_array).to eq([{"bar" => "bar value"}, "baz"])
|
188
|
+
end
|
189
|
+
|
190
|
+
it "should encode a LogStash::Timestamp" do
|
191
|
+
codec.on_event{|data, newdata| results << newdata}
|
192
|
+
codec.fields = [ "foo" ]
|
193
|
+
event = LogStash::Event.new("foo" => LogStash::Timestamp.new)
|
194
|
+
codec.encode(event)
|
195
|
+
expect(results.first).to match(/^CEF:0\|Elasticsearch\|Logstash\|1.0\|Logstash\|Logstash\|6\|foo=[0-9TZ.:-]+$/m)
|
196
|
+
end
|
197
|
+
|
198
|
+
it "should use severity (instead of depricated sev), if severity is set)" do
|
199
|
+
codec.on_event{|data, newdata| results << newdata}
|
200
|
+
codec.sev = "4"
|
201
|
+
codec.severity = "5"
|
202
|
+
codec.fields = []
|
203
|
+
event = LogStash::Event.new("foo" => "bar")
|
204
|
+
codec.encode(event)
|
205
|
+
expect(results.first).to match(/^CEF:0\|Elasticsearch\|Logstash\|1.0\|Logstash\|Logstash\|5\|$/m)
|
206
|
+
end
|
207
|
+
|
208
|
+
it "should use deprecated sev, if severity is not set (equals default value)" do
|
209
|
+
codec.on_event{|data, newdata| results << newdata}
|
210
|
+
codec.sev = "4"
|
211
|
+
codec.fields = []
|
212
|
+
event = LogStash::Event.new("foo" => "bar")
|
213
|
+
codec.encode(event)
|
214
|
+
expect(results.first).to match(/^CEF:0\|Elasticsearch\|Logstash\|1.0\|Logstash\|Logstash\|4\|$/m)
|
215
|
+
end
|
216
|
+
|
217
|
+
it "should use deprecated sev, if severity is explicitly set to default value)" do
|
218
|
+
codec.on_event{|data, newdata| results << newdata}
|
219
|
+
codec.sev = "4"
|
220
|
+
codec.severity = "6"
|
221
|
+
codec.fields = []
|
222
|
+
event = LogStash::Event.new("foo" => "bar")
|
223
|
+
codec.encode(event)
|
224
|
+
expect(results.first).to match(/^CEF:0\|Elasticsearch\|Logstash\|1.0\|Logstash\|Logstash\|4\|$/m)
|
225
|
+
end
|
226
|
+
|
227
|
+
it "should use deprecated sev, if severity is invalid" do
|
228
|
+
codec.on_event{|data, newdata| results << newdata}
|
229
|
+
codec.sev = "4"
|
230
|
+
codec.severity = ""
|
231
|
+
codec.fields = []
|
232
|
+
event = LogStash::Event.new("foo" => "bar")
|
233
|
+
codec.encode(event)
|
234
|
+
expect(results.first).to match(/^CEF:0\|Elasticsearch\|Logstash\|1.0\|Logstash\|Logstash\|4\|$/m)
|
235
|
+
end
|
236
|
+
|
237
|
+
it "should use default value, if severity is not set and sev is invalid" do
|
238
|
+
codec.on_event{|data, newdata| results << newdata}
|
239
|
+
codec.sev = ""
|
240
|
+
codec.fields = []
|
241
|
+
event = LogStash::Event.new("foo" => "bar")
|
242
|
+
codec.encode(event)
|
243
|
+
expect(results.first).to match(/^CEF:0\|Elasticsearch\|Logstash\|1.0\|Logstash\|Logstash\|6\|$/m)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
context "sanitize header field" do
|
248
|
+
subject(:codec) { LogStash::Codecs::CEF.new }
|
249
|
+
|
250
|
+
it "should sanitize" do
|
251
|
+
expect(codec.send(:sanitize_header_field, "foo")).to be == "foo"
|
252
|
+
expect(codec.send(:sanitize_header_field, "foo\nbar")).to be == "foo bar"
|
253
|
+
expect(codec.send(:sanitize_header_field, "foo\rbar")).to be == "foo bar"
|
254
|
+
expect(codec.send(:sanitize_header_field, "foo\r\nbar")).to be == "foo bar"
|
255
|
+
expect(codec.send(:sanitize_header_field, "foo\r\nbar\r\nbaz")).to be == "foo bar baz"
|
256
|
+
expect(codec.send(:sanitize_header_field, "foo\\bar")).to be == "foo\\\\bar"
|
257
|
+
expect(codec.send(:sanitize_header_field, "foo|bar")).to be == "foo\\|bar"
|
258
|
+
expect(codec.send(:sanitize_header_field, "foo=bar")).to be == "foo=bar"
|
259
|
+
expect(codec.send(:sanitize_header_field, 123)).to be == "123" # Input value is a Fixnum
|
260
|
+
expect(codec.send(:sanitize_header_field, 123.123)).to be == "123.123" # Input value is a Float
|
261
|
+
expect(codec.send(:sanitize_header_field, [])).to be == "[]" # Input value is an Array
|
262
|
+
expect(codec.send(:sanitize_header_field, {})).to be == "{}" # Input value is a Hash
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
context "sanitize extension key" do
|
267
|
+
subject(:codec) { LogStash::Codecs::CEF.new }
|
268
|
+
|
269
|
+
it "should sanitize" do
|
270
|
+
expect(codec.send(:sanitize_extension_key, " foo ")).to be == "foo"
|
271
|
+
expect(codec.send(:sanitize_extension_key, " FOO 123 ")).to be == "FOO123"
|
272
|
+
expect(codec.send(:sanitize_extension_key, "foo\nbar\rbaz")).to be == "foobarbaz"
|
273
|
+
expect(codec.send(:sanitize_extension_key, "Foo_Bar\r\nBaz")).to be == "FooBarBaz"
|
274
|
+
expect(codec.send(:sanitize_extension_key, "foo-@bar=baz")).to be == "foobarbaz"
|
275
|
+
expect(codec.send(:sanitize_extension_key, "[foo]|bar.baz")).to be == "foobarbaz"
|
276
|
+
expect(codec.send(:sanitize_extension_key, 123)).to be == "123" # Input value is a Fixnum
|
277
|
+
expect(codec.send(:sanitize_extension_key, 123.123)).to be == "123123" # Input value is a Float, "." is not allowed and therefore removed
|
278
|
+
expect(codec.send(:sanitize_extension_key, [])).to be == "" # Input value is an Array, "[" and "]" are not allowed and therefore removed
|
279
|
+
expect(codec.send(:sanitize_extension_key, {})).to be == "" # Input value is a Hash, "{" and "}" are not allowed and therefore removed
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
context "sanitize extension value" do
|
284
|
+
subject(:codec) { LogStash::Codecs::CEF.new }
|
285
|
+
|
286
|
+
it "should sanitize" do
|
287
|
+
expect(codec.send(:sanitize_extension_val, "foo")).to be == "foo"
|
288
|
+
expect(codec.send(:sanitize_extension_val, "foo\nbar")).to be == "foo\\nbar"
|
289
|
+
expect(codec.send(:sanitize_extension_val, "foo\rbar")).to be == "foo\\nbar"
|
290
|
+
expect(codec.send(:sanitize_extension_val, "foo\r\nbar")).to be == "foo\\nbar"
|
291
|
+
expect(codec.send(:sanitize_extension_val, "foo\r\nbar\r\nbaz")).to be == "foo\\nbar\\nbaz"
|
292
|
+
expect(codec.send(:sanitize_extension_val, "foo\\bar")).to be == "foo\\\\bar"
|
293
|
+
expect(codec.send(:sanitize_extension_val, "foo|bar")).to be == "foo|bar"
|
294
|
+
expect(codec.send(:sanitize_extension_val, "foo=bar")).to be == "foo\\=bar"
|
295
|
+
expect(codec.send(:sanitize_extension_val, 123)).to be == "123" # Input value is a Fixnum
|
296
|
+
expect(codec.send(:sanitize_extension_val, 123.123)).to be == "123.123" # Input value is a Float
|
297
|
+
expect(codec.send(:sanitize_extension_val, [])).to be == "[]" # Input value is an Array
|
298
|
+
expect(codec.send(:sanitize_extension_val, {})).to be == "{}" # Input value is a Hash
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
context "valid_severity?" do
|
303
|
+
subject(:codec) { LogStash::Codecs::CEF.new }
|
304
|
+
|
305
|
+
it "should validate severity" do
|
306
|
+
expect(codec.send(:valid_severity?, nil)).to be == false
|
307
|
+
expect(codec.send(:valid_severity?, "")).to be == false
|
308
|
+
expect(codec.send(:valid_severity?, "foo")).to be == false
|
309
|
+
expect(codec.send(:valid_severity?, "1.5")).to be == false
|
310
|
+
expect(codec.send(:valid_severity?, "-1")).to be == false
|
311
|
+
expect(codec.send(:valid_severity?, "11")).to be == false
|
312
|
+
expect(codec.send(:valid_severity?, "0")).to be == true
|
313
|
+
expect(codec.send(:valid_severity?, "10")).to be == true
|
314
|
+
expect(codec.send(:valid_severity?, "1.0")).to be == true
|
315
|
+
expect(codec.send(:valid_severity?, 1)).to be == true
|
316
|
+
expect(codec.send(:valid_severity?, 1.0)).to be == true
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
context "#decode" do
|
321
|
+
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
|
+
|
323
|
+
def validate(e)
|
324
|
+
insist { e.is_a?(LogStash::Event) }
|
325
|
+
insist { e.get('cef_version') } == "0"
|
326
|
+
insist { e.get('cef_device_version') } == "1.0"
|
327
|
+
insist { e.get('cef_sigid') } == "100"
|
328
|
+
insist { e.get('cef_name') } == "trojan successfully stopped"
|
329
|
+
insist { e.get('cef_severity') } == "10"
|
330
|
+
end
|
331
|
+
|
332
|
+
it "should parse the cef headers" do
|
333
|
+
subject.decode(message) do |e|
|
334
|
+
validate(e)
|
335
|
+
ext = e.get('cef_ext')
|
336
|
+
insist { e.get("cef_vendor") } == "security"
|
337
|
+
insist { e.get("cef_product") } == "threatmanager"
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
it "should parse the cef body" do
|
342
|
+
subject.decode(message) do |e|
|
343
|
+
ext = e.get('cef_ext')
|
344
|
+
insist { ext['src'] } == "10.0.0.192"
|
345
|
+
insist { ext['dst'] } == "12.121.122.82"
|
346
|
+
insist { ext['spt'] } == "1232"
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
let (:no_ext) { "CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10|" }
|
351
|
+
it "should be OK with no extension dictionary" do
|
352
|
+
subject.decode(no_ext) do |e|
|
353
|
+
validate(e)
|
354
|
+
insist { e.get("cef_ext") } == nil
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
let (:missing_headers) { "CEF:0|||1.0|100|trojan successfully stopped|10|src=10.0.0.192 dst=12.121.122.82 spt=1232" }
|
359
|
+
it "should be OK with missing CEF headers (multiple pipes in sequence)" do
|
360
|
+
subject.decode(missing_headers) do |e|
|
361
|
+
validate(e)
|
362
|
+
insist { e.get("cef_vendor") } == ""
|
363
|
+
insist { e.get("cef_product") } == ""
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
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" }
|
368
|
+
it "should strip leading whitespace from the message" do
|
369
|
+
subject.decode(leading_whitespace) do |e|
|
370
|
+
validate(e)
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
let (:escaped_pipes) { 'CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10|moo=this\|has an escaped pipe' }
|
375
|
+
it "should be OK with escaped pipes in the message" do
|
376
|
+
subject.decode(escaped_pipes) do |e|
|
377
|
+
ext = e.get('cef_ext')
|
378
|
+
insist { ext['moo'] } == 'this\|has an escaped pipe'
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
let (:pipes_in_message) {'CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10|moo=this|has an pipe'}
|
383
|
+
it "should be OK with not escaped pipes in the message" do
|
384
|
+
subject.decode(pipes_in_message) do |e|
|
385
|
+
ext = e.get('cef_ext')
|
386
|
+
insist { ext['moo'] } == 'this|has an pipe'
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
let (:escaped_equal_in_message) {'CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10|moo=this \=has escaped \= equals\='}
|
391
|
+
it "should be OK with escaped equal in the message" do
|
392
|
+
subject.decode(escaped_equal_in_message) do |e|
|
393
|
+
ext = e.get('cef_ext')
|
394
|
+
insist { ext['moo'] } == 'this =has escaped = equals='
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
let (:escaped_backslash_in_header) {'CEF:0|secu\\\\rity|threat\\\\manager|1.\\\\0|10\\\\0|tro\\\\jan successfully stopped|\\\\10|'}
|
399
|
+
it "should be OK with escaped backslash in the headers" do
|
400
|
+
subject.decode(escaped_backslash_in_header) do |e|
|
401
|
+
insist { e.get("cef_version") } == '0'
|
402
|
+
insist { e.get("cef_vendor") } == 'secu\\rity'
|
403
|
+
insist { e.get("cef_product") } == 'threat\\manager'
|
404
|
+
insist { e.get("cef_device_version") } == '1.\\0'
|
405
|
+
insist { e.get("cef_sigid") } == '10\\0'
|
406
|
+
insist { e.get("cef_name") } == 'tro\\jan successfully stopped'
|
407
|
+
insist { e.get("cef_severity") } == '\\10'
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
let (:escaped_backslash_in_header_edge_case) {'CEF:0|security\\\\\\||threatmanager\\\\|1.0|100|trojan successfully stopped|10|'}
|
412
|
+
it "should be OK with escaped backslash in the headers (edge case: escaped slash in front of pipe)" do
|
413
|
+
subject.decode(escaped_backslash_in_header_edge_case) do |e|
|
414
|
+
validate(e)
|
415
|
+
insist { e.get("cef_vendor") } == 'security\\|'
|
416
|
+
insist { e.get("cef_product") } == 'threatmanager\\'
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
let (:escaped_pipes_in_header) {'CEF:0|secu\\|rity|threatmanager\\||1.\\|0|10\\|0|tro\\|jan successfully stopped|\\|10|'}
|
421
|
+
it "should be OK with escaped pipes in the headers" do
|
422
|
+
subject.decode(escaped_pipes_in_header) do |e|
|
423
|
+
insist { e.get("cef_version") } == '0'
|
424
|
+
insist { e.get("cef_vendor") } == 'secu|rity'
|
425
|
+
insist { e.get("cef_product") } == 'threatmanager|'
|
426
|
+
insist { e.get("cef_device_version") } == '1.|0'
|
427
|
+
insist { e.get("cef_sigid") } == '10|0'
|
428
|
+
insist { e.get("cef_name") } == 'tro|jan successfully stopped'
|
429
|
+
insist { e.get("cef_severity") } == '|10'
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
let (:escaped_backslash_in_message) {'CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10|moo=this \\\\has escaped \\\\ backslashs\\\\'}
|
434
|
+
it "should be OK with escaped backslashs in the message" do
|
435
|
+
subject.decode(escaped_backslash_in_message) do |e|
|
436
|
+
ext = e.get('cef_ext')
|
437
|
+
insist { ext['moo'] } == 'this \\has escaped \\ backslashs\\'
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
let (:equal_in_header) {'CEF:0|security|threatmanager=equal|1.0|100|trojan successfully stopped|10|'}
|
442
|
+
it "should be OK with equal in the headers" do
|
443
|
+
subject.decode(equal_in_header) do |e|
|
444
|
+
validate(e)
|
445
|
+
insist { e.get("cef_product") } == "threatmanager=equal"
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
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" }
|
450
|
+
it "Should detect headers before CEF starts" do
|
451
|
+
subject.decode(syslog) do |e|
|
452
|
+
validate(e)
|
453
|
+
insist { e.get('syslog') } == 'Syslogdate Sysloghost'
|
454
|
+
end
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
context "encode and decode" do
|
459
|
+
subject(:codec) { LogStash::Codecs::CEF.new }
|
460
|
+
|
461
|
+
let(:results) { [] }
|
462
|
+
|
463
|
+
it "should return an equal event if encoded and decoded again" do
|
464
|
+
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}"
|
471
|
+
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")
|
473
|
+
codec.encode(event)
|
474
|
+
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')
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: logstash-codec-cef
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 3.0.0
|
5
|
+
platform: java
|
6
|
+
authors:
|
7
|
+
- Elastic
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-09-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '1.60'
|
19
|
+
- - "<="
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.99'
|
22
|
+
name: logstash-core-plugin-api
|
23
|
+
prerelease: false
|
24
|
+
type: :runtime
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.60'
|
30
|
+
- - "<="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2.99'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
name: logstash-devutils
|
40
|
+
prerelease: false
|
41
|
+
type: :development
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
description: This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program
|
48
|
+
email: info@elastic.co
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- CHANGELOG.md
|
54
|
+
- CONTRIBUTORS
|
55
|
+
- Gemfile
|
56
|
+
- LICENSE
|
57
|
+
- NOTICE.TXT
|
58
|
+
- README.md
|
59
|
+
- lib/logstash/codecs/cef.rb
|
60
|
+
- logstash-codec-cef.gemspec
|
61
|
+
- spec/codecs/cef_spec.rb
|
62
|
+
homepage: http://www.elastic.co/guide/en/logstash/current/index.html
|
63
|
+
licenses:
|
64
|
+
- Apache License (2.0)
|
65
|
+
metadata:
|
66
|
+
logstash_plugin: 'true'
|
67
|
+
logstash_group: codec
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
requirements: []
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 2.4.8
|
85
|
+
signing_key:
|
86
|
+
specification_version: 4
|
87
|
+
summary: CEF codec to parse and encode CEF formated logs
|
88
|
+
test_files:
|
89
|
+
- spec/codecs/cef_spec.rb
|