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 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
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
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
@@ -0,0 +1,5 @@
1
+ Elasticsearch
2
+ Copyright 2012-2015 Elasticsearch
3
+
4
+ This product includes software developed by The Apache Software
5
+ Foundation (http://www.apache.org/).
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