logstash-codec-idmef 0.9.2 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -0
- data/docs/index.asciidoc +61 -22
- data/lib/logstash/codecs/idmef-message.dtd +661 -0
- data/lib/logstash/codecs/idmef.rb +283 -202
- data/logstash-codec-idmef.gemspec +1 -1
- data/spec/codecs/idmef_spec.rb +102 -12
- metadata +17 -17
@@ -25,8 +25,8 @@ class LogStash::Codecs::IDMEF < LogStash::Codecs::Base
|
|
25
25
|
# tcp {
|
26
26
|
# codec => idmef {
|
27
27
|
# paths => {
|
28
|
-
# "alert.classification.text" => "
|
29
|
-
# "alert.target(0).node.name" => "
|
28
|
+
# "alert.classification.text" => "%{message}"
|
29
|
+
# "alert.target(0).node.name" => "%{host}"
|
30
30
|
# "alert.analyzer(0).name" => "ACME"
|
31
31
|
# }
|
32
32
|
# }
|
@@ -37,33 +37,34 @@ class LogStash::Codecs::IDMEF < LogStash::Codecs::Base
|
|
37
37
|
# The keys of the hash are IDMEF path as described here:
|
38
38
|
# https://redmine.secef.net/projects/secef/wiki/LibPrelude_IDMEF_path
|
39
39
|
#
|
40
|
-
# The values of the hash are values to set in final IDMEF. If
|
41
|
-
#
|
40
|
+
# The values of the hash are values to set in final IDMEF. If there is
|
41
|
+
# %{name} inside the string, the plugin try to retrieve the value from
|
42
|
+
# the event and create the final string.
|
42
43
|
config :paths, :validate => :array, :default => {}
|
43
44
|
|
44
45
|
# Try to use default paths mapping or not.
|
45
46
|
#
|
46
47
|
# Default paths are:
|
47
|
-
# * alert.
|
48
|
-
# * alert.
|
49
|
-
# * alert.create_time: "
|
50
|
-
# * alert.
|
51
|
-
# * alert.
|
52
|
-
# * alert.
|
53
|
-
# * alert.source(0).node.
|
54
|
-
# * alert.source(0).
|
55
|
-
# * alert.source(0).service.
|
56
|
-
# * alert.
|
57
|
-
# * alert.target(0).node.
|
58
|
-
# * alert.target(0).
|
59
|
-
# * alert.target(0).service.
|
60
|
-
# * alert.target(0).
|
61
|
-
# * alert.target(0).user.user_id(0).
|
62
|
-
# * alert.target(0).
|
63
|
-
# * alert.target(0).process.
|
64
|
-
# * alert.
|
65
|
-
# * alert.assessment.impact.severity: ["
|
66
|
-
# * alert.assessment.action.description: ["
|
48
|
+
# * "alert.analyzer(0).name": ["%{product}", "%{devname}"]
|
49
|
+
# * "alert.analyzer(0).manufacturer": ["%{vendor}"]
|
50
|
+
# * "alert.create_time": ["%{@timestamp}"]
|
51
|
+
# * "alert.detect_time": ["%{@timestamp}"]
|
52
|
+
# * "alert.analyzer_time": ["%{@timestamp}"]
|
53
|
+
# * "alert.source(0).node.address(0).address": ["%{srcip}", "%{src}"]
|
54
|
+
# * "alert.source(0).node.name": ["%{shost}", "%{srchost}", "%{shostname}", "%{srchostname}", "%{sname}", "%{srcname}"]
|
55
|
+
# * "alert.source(0).service.port": ["%{spt}", "%{sport}", "%{s_port}"]
|
56
|
+
# * "alert.source(0).service.name": ["%{sservice}", "%{srcservice}"]
|
57
|
+
# * "alert.target(0).node.address(0).address": ["%{hostip}", "%{dstip}", "%{dst}", "%{ip}"]
|
58
|
+
# * "alert.target(0).node.name": ["%{host}", "%{hostname}", "%{shost}", "%{srchost}", "%{shostname}", "%{srchostname}", "%{sname}", "%{srcname}"]
|
59
|
+
# * "alert.target(0).service.port": ["%{dpt}", "%{dport}", "%{d_port}"]
|
60
|
+
# * "alert.target(0).service.name": ["%{service}", "%{service_id}", "%{dservice}", "%{dstservice}"]
|
61
|
+
# * "alert.target(0).user.user_id(0).name": ["%{user}", "%{dstuser}", "%{duser}"]
|
62
|
+
# * "alert.target(0).user.user_id(0).number": ["%{uid}", "%{dstuid}", "%{duid}"]
|
63
|
+
# * "alert.target(0).process.name": ["%{proc}", "%{process}"]
|
64
|
+
# * "alert.target(0).process.pid": ["%{dpid}", "%{pid}"]
|
65
|
+
# * "alert.classification.text": ["%{rule_name}", "%{event}", "%{message}"]
|
66
|
+
# * "alert.assessment.impact.severity": ["%{severity}", "%{level}"]
|
67
|
+
# * "alert.assessment.action.description": ["%{action}"]
|
67
68
|
config :defaults, :validate => :boolean, :default => true
|
68
69
|
|
69
70
|
# When an alert is transformed in IDMEF, the remaining fields of the initial
|
@@ -71,6 +72,9 @@ class LogStash::Codecs::IDMEF < LogStash::Codecs::Base
|
|
71
72
|
# translation, set this setting to `false`.
|
72
73
|
config :additionaldata, :validate => :boolean, :default => true
|
73
74
|
|
75
|
+
# Validate the generated XML with IDMEF DTD.
|
76
|
+
config :validate_xml, :validate => :boolean, :default => false
|
77
|
+
|
74
78
|
# IDMEF can defined two types of message:
|
75
79
|
# * alert
|
76
80
|
#
|
@@ -91,8 +95,10 @@ class LogStash::Codecs::IDMEF < LogStash::Codecs::Base
|
|
91
95
|
# failed.
|
92
96
|
config :type, :validate => :string, :default => "alert"
|
93
97
|
|
98
|
+
@@IDMEF_Time_Format = "%FT%T%:z"
|
99
|
+
|
94
100
|
# RFC 4765: UserID Class
|
95
|
-
IDMEFUserId = { :type => :class,
|
101
|
+
@@IDMEFUserId = { :type => :class,
|
96
102
|
:name => "UserId",
|
97
103
|
"name" => { :type => :list_value, :name => "name" },
|
98
104
|
"type" => { :type => :attr, :name => "type", :default => "original-user" },
|
@@ -101,43 +107,43 @@ class LogStash::Codecs::IDMEF < LogStash::Codecs::Base
|
|
101
107
|
}
|
102
108
|
|
103
109
|
# RFC 4765: User Class
|
104
|
-
IDMEFUser = { :type => :class,
|
110
|
+
@@IDMEFUser = { :type => :class,
|
105
111
|
:name => "User",
|
106
112
|
"category" => { :type => :attr, :name => "category", :default => "unknown" },
|
107
|
-
"user_id" => { :type => :list_class, :class => IDMEFUserId }
|
113
|
+
"user_id" => { :type => :list_class, :class => @@IDMEFUserId }
|
108
114
|
}
|
109
115
|
|
110
116
|
# RFC 4765: FileAccess Class
|
111
|
-
IDMEFFileAccess = { :type => :class,
|
117
|
+
@@IDMEFFileAccess = { :type => :class,
|
112
118
|
:name => "FileAccess",
|
113
|
-
"user_id" => { :type => :list_class, :class => IDMEFUserId }
|
119
|
+
"user_id" => { :type => :list_class, :class => @@IDMEFUserId }
|
114
120
|
}
|
115
121
|
|
116
122
|
# RFC 4765: File Class
|
117
|
-
IDMEFFile = { :type => :class,
|
123
|
+
@@IDMEFFile = { :type => :class,
|
118
124
|
:name => "File",
|
119
125
|
"category" => { :type => :attr, :name => "category" },
|
120
126
|
"fstype" => { :type => :attr, :name => "fstype" },
|
121
127
|
"file-type" => { :type => :attr, :name => "file-type" },
|
122
128
|
"name" => { :type => :list_value, :name => "name" },
|
123
129
|
"path" => { :type => :list_value, :name => "path" },
|
124
|
-
"file_access" => { :type => :list_class, :class => IDMEFFileAccess }
|
130
|
+
"file_access" => { :type => :list_class, :class => @@IDMEFFileAccess }
|
125
131
|
}
|
126
132
|
|
127
133
|
# RFC 4765: WebService Class
|
128
|
-
IDMEFWebService = { :type => :class,
|
134
|
+
@@IDMEFWebService = { :type => :class,
|
129
135
|
:name => "WebService",
|
130
136
|
"url" => { :type => :list_value, :name => "url" }
|
131
137
|
}
|
132
138
|
|
133
139
|
# RFC 4765: SNMPService Class
|
134
|
-
IDMEFSNMPService = { :type => :class,
|
140
|
+
@@IDMEFSNMPService = { :type => :class,
|
135
141
|
:name => "SNMPService",
|
136
142
|
"command" => { :type => :list_value, :name => "command" }
|
137
143
|
}
|
138
144
|
|
139
145
|
# RFC 4765: Service Class
|
140
|
-
IDMEFService = { :type => :class,
|
146
|
+
@@IDMEFService = { :type => :class,
|
141
147
|
:name => "Service",
|
142
148
|
"ip_version" => { :type => :attr, :name => "ip_version" },
|
143
149
|
"iana_protocol_number" => { :type => :attr, :name => "iana_protocol_number" },
|
@@ -146,12 +152,12 @@ class LogStash::Codecs::IDMEF < LogStash::Codecs::Base
|
|
146
152
|
"port" => { :type => :list_value, :name => "port" },
|
147
153
|
"portlist" => { :type => :list_value, :name => "portlist" },
|
148
154
|
"protocol" => { :type => :list_value, :name => "protocol" },
|
149
|
-
"web_service" => { :type => :list_class, :class => IDMEFWebService },
|
150
|
-
"snmp_service" => { :type => :list_class, :class => IDMEFSNMPService }
|
155
|
+
"web_service" => { :type => :list_class, :class => @@IDMEFWebService },
|
156
|
+
"snmp_service" => { :type => :list_class, :class => @@IDMEFSNMPService }
|
151
157
|
}
|
152
158
|
|
153
159
|
# RFC 4765: Address Class
|
154
|
-
IDMEFAddress = { :type => :class,
|
160
|
+
@@IDMEFAddress = { :type => :class,
|
155
161
|
:name => "Address",
|
156
162
|
"category" => { :type => :attr, :name => "category", :default => "unknown" },
|
157
163
|
"vlan-name" => { :type => :attr, :name => "vlan-name" },
|
@@ -161,16 +167,16 @@ class LogStash::Codecs::IDMEF < LogStash::Codecs::Base
|
|
161
167
|
}
|
162
168
|
|
163
169
|
# RFC 4765: Node Class
|
164
|
-
IDMEFNode = { :type => :class,
|
170
|
+
@@IDMEFNode = { :type => :class,
|
165
171
|
:name => "Node",
|
166
172
|
"category" => { :type => :attr, :name => "category", :default => "unknown" },
|
167
173
|
"location" => { :type => :list_value, :name => "location" },
|
168
174
|
"name" => { :type => :list_value, :name => "name" },
|
169
|
-
"address" => { :type => :list_class, :class => IDMEFAddress },
|
175
|
+
"address" => { :type => :list_class, :class => @@IDMEFAddress },
|
170
176
|
}
|
171
177
|
|
172
178
|
# RFC 4765: Process Class
|
173
|
-
IDMEFProcess = { :type => :class,
|
179
|
+
@@IDMEFProcess = { :type => :class,
|
174
180
|
:name => "Process",
|
175
181
|
"name" => { :type => :list_value, :name => "name" },
|
176
182
|
"pid" => { :type => :list_value, :name => "pid" },
|
@@ -180,7 +186,7 @@ class LogStash::Codecs::IDMEF < LogStash::Codecs::Base
|
|
180
186
|
}
|
181
187
|
|
182
188
|
# RFC 4765: Analyzer Class
|
183
|
-
IDMEFAnalyzer = { :type => :class,
|
189
|
+
@@IDMEFAnalyzer = { :type => :class,
|
184
190
|
:name => "Analyzer",
|
185
191
|
"analyzerid" => { :type => :attr, :name => "analyzerid" },
|
186
192
|
"name" => { :type => :attr, :name => "name" },
|
@@ -190,36 +196,36 @@ class LogStash::Codecs::IDMEF < LogStash::Codecs::Base
|
|
190
196
|
"class" => { :type => :attr, :name => "class" },
|
191
197
|
"ostype" => { :type => :attr, :name => "ostype" },
|
192
198
|
"osversion" => { :type => :attr, :name => "osversion" },
|
193
|
-
"node" => { :type => :list_class, :class => IDMEFNode },
|
194
|
-
"process" => { :type => :list_class, :class => IDMEFProcess },
|
199
|
+
"node" => { :type => :list_class, :class => @@IDMEFNode },
|
200
|
+
"process" => { :type => :list_class, :class => @@IDMEFProcess },
|
195
201
|
}
|
196
|
-
IDMEFAnalyzer["analyzer"] = { :type => :list_class, :class => IDMEFAnalyzer }
|
202
|
+
@@IDMEFAnalyzer["analyzer"] = { :type => :list_class, :class => @@IDMEFAnalyzer }
|
197
203
|
|
198
204
|
# RFC 4765: Source Class
|
199
|
-
IDMEFSource = { :type => :class,
|
205
|
+
@@IDMEFSource = { :type => :class,
|
200
206
|
:name => "Source",
|
201
207
|
"spoofed" => { :type => :attr, :name => "spoofed", :default => "unknown" },
|
202
208
|
"interface" => { :type => :attr, :name => "interface" },
|
203
|
-
"node" => { :type => :list_class, :class => IDMEFNode },
|
204
|
-
"user" => { :type => :list_class, :class => IDMEFUser },
|
205
|
-
"process" => { :type => :list_class, :class => IDMEFProcess },
|
206
|
-
"service" => { :type => :list_class, :class => IDMEFService },
|
209
|
+
"node" => { :type => :list_class, :class => @@IDMEFNode },
|
210
|
+
"user" => { :type => :list_class, :class => @@IDMEFUser },
|
211
|
+
"process" => { :type => :list_class, :class => @@IDMEFProcess },
|
212
|
+
"service" => { :type => :list_class, :class => @@IDMEFService },
|
207
213
|
}
|
208
214
|
|
209
215
|
# RFC 4765: Target Class
|
210
|
-
IDMEFTarget = { :type => :class,
|
216
|
+
@@IDMEFTarget = { :type => :class,
|
211
217
|
:name => "Target",
|
212
218
|
"decoy" => { :type => :attr, :name => "decoy", :default => "unknown" },
|
213
219
|
"interface" => { :type => :attr, :name => "interface" },
|
214
|
-
"node" => { :type => :list_class, :class => IDMEFNode },
|
215
|
-
"user" => { :type => :list_class, :class => IDMEFUser },
|
216
|
-
"process" => { :type => :list_class, :class => IDMEFProcess },
|
217
|
-
"service" => { :type => :list_class, :class => IDMEFService },
|
218
|
-
"file" => { :type => :list_class, :class => IDMEFFile }
|
220
|
+
"node" => { :type => :list_class, :class => @@IDMEFNode },
|
221
|
+
"user" => { :type => :list_class, :class => @@IDMEFUser },
|
222
|
+
"process" => { :type => :list_class, :class => @@IDMEFProcess },
|
223
|
+
"service" => { :type => :list_class, :class => @@IDMEFService },
|
224
|
+
"file" => { :type => :list_class, :class => @@IDMEFFile }
|
219
225
|
}
|
220
226
|
|
221
227
|
# RFC 4765: Impact Class
|
222
|
-
IDMEFImpact = { :type => :class,
|
228
|
+
@@IDMEFImpact = { :type => :class,
|
223
229
|
:name => "Impact",
|
224
230
|
"severity" => { :type => :attr, :name => "severity" },
|
225
231
|
"completion" => { :type => :attr, :name => "completion" },
|
@@ -227,21 +233,21 @@ class LogStash::Codecs::IDMEF < LogStash::Codecs::Base
|
|
227
233
|
}
|
228
234
|
|
229
235
|
# RFC 4765: Action Class
|
230
|
-
IDMEFAction = { :type => :class,
|
236
|
+
@@IDMEFAction = { :type => :class,
|
231
237
|
:name => "Action",
|
232
238
|
"category" => { :type => :attr, :name => "category", :default => "other" },
|
233
239
|
"description" => { :type => :value },
|
234
240
|
}
|
235
241
|
|
236
242
|
# RFC 4765: Confidence Class
|
237
|
-
IDMEFConfidence = { :type => :class,
|
243
|
+
@@IDMEFConfidence = { :type => :class,
|
238
244
|
:name => "Confidence",
|
239
245
|
"rating" => { :type => :attr, :name => "rating", :default => "numeric" },
|
240
246
|
"confidence" => { :type => :value },
|
241
247
|
}
|
242
248
|
|
243
249
|
# RFC 4765: Reference Class
|
244
|
-
IDMEFReference = { :type => :class,
|
250
|
+
@@IDMEFReference = { :type => :class,
|
245
251
|
:name => "Reference",
|
246
252
|
"origin" => { :type => :attr, :name => "origin", :default => "unknown" },
|
247
253
|
"meaning" => { :type => :attr, :name => "meaning" },
|
@@ -250,160 +256,217 @@ class LogStash::Codecs::IDMEF < LogStash::Codecs::Base
|
|
250
256
|
}
|
251
257
|
|
252
258
|
# RFC 4765: AdditionalData Class
|
253
|
-
IDMEFAdditionalData = { :type => :class,
|
259
|
+
@@IDMEFAdditionalData = { :type => :class,
|
254
260
|
:name => "AdditionalData",
|
255
261
|
"meaning" => { :type => :attr, :name => "meaning" },
|
256
262
|
"type" => { :type => :attr, :name => "type" },
|
257
263
|
"data" => { :type => :list_value, :name => :type }
|
258
264
|
}
|
259
265
|
# RFC 4765: CorrelationAlert Class
|
260
|
-
IDMEFCorrelationAlert = { :type => :class,
|
266
|
+
@@IDMEFCorrelationAlert = { :type => :class,
|
261
267
|
:name => "CorrelationAlert",
|
262
268
|
"name" => { :type => :list_value, :name => "name" },
|
263
269
|
"alertident" => { :type => :list_value, :name => "alertident" }
|
264
270
|
}
|
265
271
|
|
266
272
|
# RFC 4765: Assessment Class
|
267
|
-
IDMEFAssessment = { :type => :class,
|
273
|
+
@@IDMEFAssessment = { :type => :class,
|
268
274
|
:name => "Assessment",
|
269
|
-
"impact" => { :type => :list_class, :class => IDMEFImpact },
|
270
|
-
"action" => { :type => :list_class, :class => IDMEFAction },
|
271
|
-
"confidence" => { :type => :list_class, :class => IDMEFConfidence }
|
275
|
+
"impact" => { :type => :list_class, :class => @@IDMEFImpact },
|
276
|
+
"action" => { :type => :list_class, :class => @@IDMEFAction },
|
277
|
+
"confidence" => { :type => :list_class, :class => @@IDMEFConfidence }
|
272
278
|
}
|
273
279
|
|
274
280
|
# RFC 4765: Classification Class
|
275
|
-
IDMEFClassification = { :type => :class,
|
281
|
+
@@IDMEFClassification = { :type => :class,
|
276
282
|
:name => "Classification",
|
277
283
|
"text" => { :type => :attr, :name => "text" },
|
278
|
-
"reference" => { :type => :list_class, :class => IDMEFReference }
|
284
|
+
"reference" => { :type => :list_class, :class => @@IDMEFReference }
|
279
285
|
}
|
280
286
|
|
281
287
|
# RFC 4765: Alert Class
|
282
|
-
IDMEFAlert = { :type => :class,
|
288
|
+
@@IDMEFAlert = { :type => :class,
|
283
289
|
:name => "Alert",
|
284
290
|
"messageid" => { :type => :attr, :name => "messageid" },
|
285
291
|
"create_time" => { :type => :list_value, :name => "CreateTime", :format => :datetime},
|
286
292
|
"detect_time" => { :type => :list_value, :name => "DetectTime", :format => :datetime },
|
287
293
|
"analyzer_time" => { :type => :list_value, :name => "AnalyzerTime", :format => :datetime },
|
288
|
-
"analyzer" => { :type => :list_class, :class => IDMEFAnalyzer },
|
289
|
-
"classification" => { :type => :list_class, :class => IDMEFClassification },
|
290
|
-
"source" => { :type => :list_class, :class => IDMEFSource },
|
291
|
-
"target" => { :type => :list_class, :class => IDMEFTarget },
|
292
|
-
"assessment" => { :type => :list_class, :class => IDMEFAssessment },
|
293
|
-
"additional_data" => { :type => :list_class, :class => IDMEFAdditionalData },
|
294
|
-
"correlation_alert" => { :type => :list_class, :class => IDMEFCorrelationAlert },
|
294
|
+
"analyzer" => { :type => :list_class, :class => @@IDMEFAnalyzer },
|
295
|
+
"classification" => { :type => :list_class, :class => @@IDMEFClassification },
|
296
|
+
"source" => { :type => :list_class, :class => @@IDMEFSource },
|
297
|
+
"target" => { :type => :list_class, :class => @@IDMEFTarget },
|
298
|
+
"assessment" => { :type => :list_class, :class => @@IDMEFAssessment },
|
299
|
+
"additional_data" => { :type => :list_class, :class => @@IDMEFAdditionalData },
|
300
|
+
"correlation_alert" => { :type => :list_class, :class => @@IDMEFCorrelationAlert },
|
295
301
|
}
|
296
302
|
|
297
303
|
# RFC 4765: Message Class
|
298
|
-
IDMEFMessage = { :type => :class,
|
304
|
+
@@IDMEFMessage = { :type => :class,
|
299
305
|
:name => "IDMEF-Message",
|
300
|
-
"alert" => { :type => :list_class, :class => IDMEFAlert },
|
306
|
+
"alert" => { :type => :list_class, :class => @@IDMEFAlert },
|
301
307
|
}
|
308
|
+
|
309
|
+
@@local_paths = {
|
310
|
+
"alert.analyzer(0).name" => ["%{product}", "%{devname}"],
|
311
|
+
"alert.analyzer(0).manufacturer" => ["%{vendor}"],
|
312
|
+
"alert.create_time" => ["%{@timestamp}"],
|
313
|
+
"alert.detect_time" => ["%{@timestamp}"],
|
314
|
+
"alert.analyzer_time" => ["%{@timestamp}"],
|
315
|
+
"alert.source(0).node.address(0).address" => ["%{srcip}", "%{src}"],
|
316
|
+
"alert.source(0).node.name" => ["%{shost}", "%{srchost}", "%{shostname}", "%{srchostname}", "%{sname}", "%{srcname}"],
|
317
|
+
"alert.source(0).service.port" => ["%{spt}", "%{sport}", "%{s_port}"],
|
318
|
+
"alert.source(0).service.name" => ["%{sservice}", "%{srcservice}"],
|
319
|
+
"alert.target(0).node.address(0).address" => ["%{hostip}", "%{dstip}", "%{dst}", "%{ip}"],
|
320
|
+
"alert.target(0).node.name" => ["%{host}", "%{hostname}", "%{shost}", "%{srchost}", "%{shostname}", "%{srchostname}", "%{sname}", "%{srcname}"],
|
321
|
+
"alert.target(0).service.port" => ["%{dpt}", "%{dport}", "%{d_port}"],
|
322
|
+
"alert.target(0).service.name" => ["%{service}", "%{service_id}", "%{dservice}", "%{dstservice}"],
|
323
|
+
"alert.target(0).user.user_id(0).name" => ["%{user}", "%{dstuser}", "%{duser}"],
|
324
|
+
"alert.target(0).user.user_id(0).number" => ["%{uid}", "%{dstuid}", "%{duid}"],
|
325
|
+
"alert.target(0).process.name" => ["%{proc}", "%{process}"],
|
326
|
+
"alert.target(0).process.pid" => ["%{dpid}", "%{pid}"],
|
327
|
+
"alert.classification.text" => ["%{rule_name}", "%{event}", "%{message}"],
|
328
|
+
"alert.assessment.impact.severity" => ["%{severity}", "%{level}"],
|
329
|
+
"alert.assessment.action.description" => ["%{action}"],
|
330
|
+
}
|
331
|
+
|
302
332
|
private
|
303
333
|
def idmefpaths_to_xml(event, paths, doc = nil)
|
334
|
+
# create the document if not existing
|
304
335
|
if doc.nil?
|
305
336
|
doc = Nokogiri::XML::Document.new
|
337
|
+
if @validate_xml
|
338
|
+
doc.create_external_subset('IDMEF-Message', nil, @dtd_path)
|
339
|
+
end
|
306
340
|
doc.root = Nokogiri::XML::Node.new('IDMEF-Message', doc)
|
307
341
|
doc.root.add_namespace_definition('idmef', 'http://iana.org/idmef')
|
308
342
|
end
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
value.each do |v|
|
315
|
-
if v.to_s.start_with?("$")
|
316
|
-
c = ''
|
317
|
-
f = true
|
318
|
-
v[1..-1].split('.').each do |ppath|
|
319
|
-
if !event.get(c + '[' + ppath + ']').nil?
|
320
|
-
c = c + '[' + ppath + ']'
|
321
|
-
else
|
322
|
-
f = false
|
323
|
-
end
|
324
|
-
end
|
325
|
-
if !f then next end
|
326
|
-
value = event.get(c)
|
327
|
-
event_to_remove << c
|
328
|
-
else
|
329
|
-
value = v
|
330
|
-
end
|
331
|
-
end
|
332
|
-
if value.kind_of?(Array) or value.to_s.empty?
|
333
|
-
next
|
343
|
+
|
344
|
+
# translate all path inot the xml
|
345
|
+
paths.each do |path, values|
|
346
|
+
if !values.kind_of?(Array)
|
347
|
+
values = [values]
|
334
348
|
end
|
335
|
-
|
336
|
-
|
349
|
+
|
350
|
+
formated_value = nil
|
351
|
+
values.each do |value|
|
352
|
+
formated_value = event.sprintf(value)
|
353
|
+
# value is looking for non existing variable in event
|
354
|
+
if /%{[^}]+}/.match(formated_value).nil?
|
355
|
+
break
|
356
|
+
end
|
357
|
+
|
358
|
+
if formated_value == value
|
359
|
+
formated_value = nil
|
360
|
+
end
|
337
361
|
end
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
362
|
+
|
363
|
+
next if formated_value.nil? or formated_value.empty?
|
364
|
+
|
365
|
+
@utf8_charset.convert(formated_value)
|
366
|
+
|
367
|
+
xml_current_node = doc.root
|
368
|
+
rfc_current_class = @@IDMEFMessage
|
369
|
+
# path is an idmef path. example : alert.classification.text
|
370
|
+
path.split('.').each do |idmefpath_name|
|
371
|
+
# handle listed_path like alert.target(0).node.address(0).address
|
372
|
+
listed_path = idmefpath_name.match(/^(.*)\((\d+)\)/)
|
373
|
+
idmefpath_index = nil
|
374
|
+
if listed_path
|
375
|
+
idmefpath_name = listed_path[1]
|
376
|
+
idmefpath_index = (listed_path ? listed_path[2] : 0).to_i
|
345
377
|
end
|
346
378
|
|
347
|
-
|
348
|
-
|
349
|
-
|
379
|
+
idmefpath_rfc_elm = rfc_current_class[idmefpath_name]
|
380
|
+
|
381
|
+
idmef_node_name = nil
|
382
|
+
if rfc_current_class[idmefpath_name][:type] == :list_class
|
383
|
+
idmef_node_name = idmefpath_rfc_elm[:class][:name]
|
384
|
+
end
|
350
385
|
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
386
|
+
# rfc class with multiple elements
|
387
|
+
if !idmefpath_index.nil? && idmefpath_rfc_elm[:type] == :list_class
|
388
|
+
idmef_nodes = xml_current_node.xpath(idmef_node_name)
|
389
|
+
|
390
|
+
if idmef_nodes.empty?
|
391
|
+
idmef_node = Nokogiri::XML::Node.new(idmefpath_rfc_elm[:class][:name], doc)
|
392
|
+
xml_current_node << idmef_node
|
393
|
+
|
394
|
+
elsif idmef_nodes.length <= idmefpath_index
|
395
|
+
idmef_node = idmef_nodes[-1]
|
396
|
+
(idmef_nodes.length..idmefpath_index).each do |idx|
|
397
|
+
tmp_node = Nokogiri::XML::Node.new(idmefpath_rfc_elm[:class][:name], doc)
|
398
|
+
idmef_node.after(tmp_node)
|
399
|
+
idmef_node = tmp_node
|
362
400
|
end
|
363
|
-
|
364
|
-
|
401
|
+
|
402
|
+
elsif idmef_nodes.length > idmefpath_index
|
403
|
+
idmef_node = idmef_nodes[idmefpath_index]
|
365
404
|
end
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
405
|
+
|
406
|
+
xml_current_node = idmef_node
|
407
|
+
rfc_current_class = idmefpath_rfc_elm[:class]
|
408
|
+
|
409
|
+
# rfc class with on element
|
410
|
+
elsif idmefpath_index.nil? && idmefpath_rfc_elm[:type] == :list_class
|
411
|
+
idmef_node = xml_current_node.xpath(idmef_node_name).first
|
412
|
+
idmef_node = idmef_node || Nokogiri::XML::Node.new(idmefpath_rfc_elm[:class][:name], doc)
|
413
|
+
xml_current_node << idmef_node
|
414
|
+
xml_current_node = idmef_node
|
415
|
+
rfc_current_class = idmefpath_rfc_elm[:class]
|
416
|
+
|
417
|
+
# rfc multiple values
|
418
|
+
elsif idmefpath_rfc_elm[:type] == :list_value
|
419
|
+
if rfc_current_class[idmefpath_name][:name] == :type
|
420
|
+
node_name = xml_current_node["type"]
|
421
|
+
else
|
422
|
+
node_name = rfc_current_class[idmefpath_name][:name]
|
376
423
|
end
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
424
|
+
|
425
|
+
idmef_node = Nokogiri::XML::Node.new(node_name, doc)
|
426
|
+
|
427
|
+
# reformat datetime with the expected format described in idmef rfc
|
428
|
+
if rfc_current_class[idmefpath_name][:format] == :datetime
|
429
|
+
alert_time = DateTime.parse(formated_value)
|
430
|
+
formated_value = alert_time.strftime(@@IDMEF_Time_Format)
|
431
|
+
seconds = alert_time.to_time.to_i + 2208988800
|
432
|
+
seconds_fraction = (alert_time.to_time.usec. / (1000000.0 / (2 ** 32))).to_i
|
433
|
+
idmef_node["ntpstamp"] = "0x%08x.0x%08x" % [seconds, seconds_fraction]
|
384
434
|
end
|
385
|
-
|
435
|
+
|
436
|
+
idmef_node.content = formated_value
|
437
|
+
|
438
|
+
xml_current_node << idmef_node
|
439
|
+
|
440
|
+
# rfc attribute
|
441
|
+
elsif idmefpath_rfc_elm[:type] == :attr
|
442
|
+
xml_current_node[rfc_current_class[idmefpath_name][:name]] = formated_value.to_s
|
443
|
+
|
386
444
|
end
|
387
|
-
|
388
|
-
|
389
|
-
|
445
|
+
|
446
|
+
# set default values as described in rfc
|
447
|
+
rfc_current_class.each do |element, value|
|
448
|
+
# value is a ref, a string or a hash, we want hashs
|
449
|
+
next if !value.respond_to?(:each_pair)
|
450
|
+
|
451
|
+
if value[:default] && value[:type] == :attr && !xml_current_node[value[:name]]
|
452
|
+
xml_current_node[value[:name]] = value[:default]
|
390
453
|
end
|
391
454
|
end
|
392
455
|
end
|
393
456
|
end
|
394
|
-
event_to_remove.each do |v|
|
395
|
-
event.remove(v)
|
396
|
-
end
|
397
457
|
return doc
|
398
458
|
end
|
399
459
|
|
400
460
|
private
|
401
461
|
def xml_to_string(doc)
|
402
|
-
|
462
|
+
# add namespace "idmef"
|
463
|
+
doc.root.traverse do |node|
|
403
464
|
if node.type != Nokogiri::XML::Node::TEXT_NODE
|
404
465
|
node.name = 'idmef:' + node.name
|
405
466
|
end
|
406
|
-
|
467
|
+
end
|
468
|
+
|
469
|
+
# return a oneline xml without spaces
|
407
470
|
return doc.serialize(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML).sub("\n", "").strip
|
408
471
|
end
|
409
472
|
|
@@ -413,75 +476,93 @@ class LogStash::Codecs::IDMEF < LogStash::Codecs::Base
|
|
413
476
|
@utf8_charset = LogStash::Util::Charset.new('UTF-8')
|
414
477
|
@utf8_charset.logger = self.logger
|
415
478
|
|
416
|
-
@local_paths = {
|
417
|
-
"alert.analyzer(0).name" => ["$product", "$devname"],
|
418
|
-
"alert.analyzer(0).manufacturer" => "$vendor",
|
419
|
-
"alert.create_time" => "$@timestamp",
|
420
|
-
"alert.detect_time" => "$@timestamp",
|
421
|
-
"alert.analyzer_time" => "$@timestamp",
|
422
|
-
"alert.source(0).node.address(0).address" => ["$srcip", "$src"],
|
423
|
-
"alert.source(0).node.name" => ["$shost", "$srchost", "$shostname", "$srchostname", "$sname", "$srcname"],
|
424
|
-
"alert.source(0).service.port" => ["$spt", "$sport", "$s_port"],
|
425
|
-
"alert.source(0).service.name" => ["$sservice", "$srcservice"],
|
426
|
-
"alert.target(0).node.address(0).address" => ["$hostip", "$dstip", "$dst", "$ip"],
|
427
|
-
"alert.target(0).node.name" => ["$host", "$hostname", "$shost", "$srchost", "$shostname", "$srchostname", "$sname", "$srcname"],
|
428
|
-
"alert.target(0).service.port" => ["$dpt", "$dport", "$d_port"],
|
429
|
-
"alert.target(0).service.name" => ["$service", "$service_id", "$dservice", "$dstservice",],
|
430
|
-
"alert.target(0).user.user_id(0).name" => ["$user", "$dstuser", "$duser"],
|
431
|
-
"alert.target(0).user.user_id(0).number" => ["$uid", "$dstuid", "$duid"],
|
432
|
-
"alert.target(0).process.name" => ["$proc", "$process"],
|
433
|
-
"alert.target(0).process.pid" => ["$dpid", "$pid"],
|
434
|
-
"alert.classification.text" => ["$rule_name", "$event", "$message"],
|
435
|
-
"alert.assessment.impact.severity" => ["$severity", "$level"],
|
436
|
-
"alert.assessment.action.description" => ["$action"],
|
437
|
-
}
|
438
479
|
if @defaults
|
439
|
-
@allpaths =
|
480
|
+
@allpaths = @@local_paths.merge(@paths)
|
440
481
|
else
|
441
482
|
@allpaths = @paths
|
442
483
|
end
|
484
|
+
|
485
|
+
if @additionaldata
|
486
|
+
# Find all event's keys already used in @@IDMEF paths values
|
487
|
+
@allpaths_event_keys = []
|
488
|
+
@allpaths.each do |key, values|
|
489
|
+
if !values.kind_of?(Array)
|
490
|
+
values = [values]
|
491
|
+
end
|
492
|
+
|
493
|
+
values.each do |value|
|
494
|
+
match = value.match(/%{([^}]+)}/)
|
495
|
+
if match
|
496
|
+
@allpaths_event_keys += match.captures
|
497
|
+
end
|
498
|
+
end
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
if @validate_xml
|
503
|
+
@dtd_path = File.dirname(File.expand_path(__FILE__)) + "/idmef-message.dtd"
|
504
|
+
@dtd_options = Nokogiri::XML::ParseOptions.new()
|
505
|
+
@dtd_options.recover
|
506
|
+
@dtd_options.dtdload
|
507
|
+
@dtd_options.dtdvalid
|
508
|
+
end
|
443
509
|
end
|
444
510
|
|
445
511
|
public
|
446
512
|
def encode(event)
|
447
|
-
#
|
448
|
-
|
513
|
+
# Set messageid and analyzerid
|
514
|
+
paths = { "%s.messageid" % @type => java.util.UUID.randomUUID.to_s,
|
515
|
+
"%s.analyzer(0).analyzerid" % @type => Socket.gethostname.to_s
|
516
|
+
}
|
449
517
|
|
450
|
-
#
|
451
|
-
|
518
|
+
# CreateTime is required in IDMEF RFC
|
519
|
+
if !@allpaths.include? "alert.create_time"
|
520
|
+
paths["alert.create_time"] = DateTime.now().strftime(@@IDMEF_Time_Format)
|
521
|
+
end
|
452
522
|
|
453
|
-
#
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
523
|
+
# Classification is required in IDMEF RFC
|
524
|
+
if !@allpaths.include? "alert.classification.text"
|
525
|
+
paths["alert.classification.text"] = "Unknown alert"
|
526
|
+
end
|
527
|
+
|
528
|
+
xml = idmefpaths_to_xml(event, paths)
|
458
529
|
|
459
|
-
# Set paths
|
460
|
-
xml = idmefpaths_to_xml(
|
530
|
+
# Set configured paths
|
531
|
+
xml = idmefpaths_to_xml(event, @allpaths, xml)
|
461
532
|
|
462
|
-
#
|
533
|
+
# Add unused event data to IDMEF additional data
|
463
534
|
if @additionaldata
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
535
|
+
additionaldata_idx = xml.xpath('/idmef-message/alert/addionnaldata').length
|
536
|
+
|
537
|
+
event.to_hash.each do |key, value|
|
538
|
+
next if value.to_s.empty? or @allpaths_event_keys.include? key
|
539
|
+
|
469
540
|
if value.kind_of?(Integer)
|
470
|
-
|
541
|
+
value_type = "integer"
|
471
542
|
elsif value.kind_of?(Float)
|
472
|
-
|
543
|
+
value_type = "real"
|
473
544
|
else
|
474
|
-
|
545
|
+
value_type = "string"
|
475
546
|
end
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
547
|
+
|
548
|
+
paths = { "alert.additional_data(%d).meaning" % additionaldata_idx => key,
|
549
|
+
"alert.additional_data(%d).type" % additionaldata_idx => value_type,
|
550
|
+
"alert.additional_data(%d).data" % additionaldata_idx => value.to_s,
|
551
|
+
}
|
552
|
+
|
553
|
+
xml = idmefpaths_to_xml(event, paths , xml)
|
554
|
+
additionaldata_idx += 1
|
482
555
|
end
|
483
556
|
end
|
484
557
|
|
558
|
+
if @validate_xml
|
559
|
+
xml_dtd = Nokogiri::XML.parse(xml.to_xml, nil, nil, @dtd_options)
|
560
|
+
if !xml_dtd.validate.nil? and !xml_dtd.validate.empty?
|
561
|
+
raise "IDMEF XML generated is not valid. Errors: %s." % xml_dtd.validate.join(', ')
|
562
|
+
end
|
563
|
+
xml.external_subset.remove
|
564
|
+
end
|
565
|
+
|
485
566
|
# Create the XML
|
486
567
|
@on_event.call(event, xml_to_string(xml) + NL)
|
487
568
|
end
|