fluent-plugin-jfrog-siem 0.1.8 → 2.0.1
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/.rspec +1 -0
- data/Gemfile.lock +90 -0
- data/README.md +2 -1
- data/Rakefile +1 -1
- data/fluent-plugin-jfrog-siem.gemspec +11 -6
- data/lib/fluent/plugin/in_jfrog_siem.rb +31 -215
- data/lib/fluent/plugin/position_file.rb +32 -0
- data/lib/fluent/plugin/violations.json +380 -0
- data/lib/fluent/plugin/xray.rb +164 -0
- data/spec/position_file_spec.rb +56 -0
- data/spec/spec_helper.rb +111 -0
- data/spec/xray_spec.rb +135 -0
- data/test/helper.rb +1 -0
- data/test/plugin/test_in_jfrog_siem.rb +14 -7
- metadata +78 -13
- data/elastic.conf +0 -18
- data/splunk.conf +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ea8dc8e85e1874da646a83a7aea1ab32a63057cfaedd0cce5b30a63461898b9c
|
4
|
+
data.tar.gz: 4d38c3858e8432b13cd25e1fde6a82619d037274037173fd0825b3b1236e811e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9289b75176b973729f922e61b8a1c17481ad896f1bed3e9baae687f6387ecf8d968523ec229da36d22f6d6ef9b052ab8e9cab5d652310e2f4c8d0dfb8ef81f8
|
7
|
+
data.tar.gz: 9bf33d52a6265a39dceb9bb8e1c99d395fcc0e654b2586d180379c390ac76e86b0dcf680985d01cecf9daaa3237d34e7e9848a2d2a0f6b4461c3e59065a5ff2f
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
fluent-plugin-jfrog-siem (1.0.0)
|
5
|
+
concurrent-ruby (~> 1.1.8)
|
6
|
+
concurrent-ruby-edge
|
7
|
+
fluentd (>= 0.14.10, < 2)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
concurrent-ruby (1.1.8)
|
13
|
+
concurrent-ruby-edge (0.6.0)
|
14
|
+
concurrent-ruby (~> 1.1.6)
|
15
|
+
cool.io (1.7.1)
|
16
|
+
diff-lcs (1.4.4)
|
17
|
+
domain_name (0.5.20190701)
|
18
|
+
unf (>= 0.0.5, < 1.0.0)
|
19
|
+
fluentd (1.13.1)
|
20
|
+
bundler
|
21
|
+
cool.io (>= 1.4.5, < 2.0.0)
|
22
|
+
http_parser.rb (>= 0.5.1, < 0.7.0)
|
23
|
+
msgpack (>= 1.3.1, < 2.0.0)
|
24
|
+
serverengine (>= 2.2.2, < 3.0.0)
|
25
|
+
sigdump (~> 0.2.2)
|
26
|
+
strptime (>= 0.2.2, < 1.0.0)
|
27
|
+
tzinfo (>= 1.0, < 3.0)
|
28
|
+
tzinfo-data (~> 1.0)
|
29
|
+
webrick (>= 1.4.2, < 1.8.0)
|
30
|
+
yajl-ruby (~> 1.0)
|
31
|
+
http-accept (1.7.0)
|
32
|
+
http-cookie (1.0.3)
|
33
|
+
domain_name (~> 0.5)
|
34
|
+
http_parser.rb (0.6.0)
|
35
|
+
mime-types (3.3.1)
|
36
|
+
mime-types-data (~> 3.2015)
|
37
|
+
mime-types-data (3.2021.0225)
|
38
|
+
msgpack (1.4.2)
|
39
|
+
netrc (0.11.0)
|
40
|
+
power_assert (2.0.0)
|
41
|
+
rake (12.3.3)
|
42
|
+
rest-client (2.1.0)
|
43
|
+
http-accept (>= 1.7.0, < 2.0)
|
44
|
+
http-cookie (>= 1.0.2, < 2.0)
|
45
|
+
mime-types (>= 1.16, < 4.0)
|
46
|
+
netrc (~> 0.8)
|
47
|
+
rspec (3.10.0)
|
48
|
+
rspec-core (~> 3.10.0)
|
49
|
+
rspec-expectations (~> 3.10.0)
|
50
|
+
rspec-mocks (~> 3.10.0)
|
51
|
+
rspec-core (3.10.1)
|
52
|
+
rspec-support (~> 3.10.0)
|
53
|
+
rspec-expectations (3.10.1)
|
54
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
55
|
+
rspec-support (~> 3.10.0)
|
56
|
+
rspec-mocks (3.10.2)
|
57
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
58
|
+
rspec-support (~> 3.10.0)
|
59
|
+
rspec-support (3.10.2)
|
60
|
+
serverengine (2.2.4)
|
61
|
+
sigdump (~> 0.2.2)
|
62
|
+
sigdump (0.2.4)
|
63
|
+
strptime (0.2.5)
|
64
|
+
test-unit (3.4.2)
|
65
|
+
power_assert
|
66
|
+
tzinfo (2.0.4)
|
67
|
+
concurrent-ruby (~> 1.0)
|
68
|
+
tzinfo-data (1.2021.1)
|
69
|
+
tzinfo (>= 1.0.0)
|
70
|
+
unf (0.1.4)
|
71
|
+
unf_ext
|
72
|
+
unf_ext (0.0.7.7)
|
73
|
+
webrick (1.7.0)
|
74
|
+
yajl-ruby (1.4.1)
|
75
|
+
|
76
|
+
PLATFORMS
|
77
|
+
x86_64-darwin-20
|
78
|
+
|
79
|
+
DEPENDENCIES
|
80
|
+
bundler (~> 2.0)
|
81
|
+
concurrent-ruby (~> 1.1.8)
|
82
|
+
concurrent-ruby-edge
|
83
|
+
fluent-plugin-jfrog-siem!
|
84
|
+
rake (~> 12.0)
|
85
|
+
rest-client (~> 2.0)
|
86
|
+
rspec (~> 3.10.0)
|
87
|
+
test-unit (~> 3.0)
|
88
|
+
|
89
|
+
BUNDLED WITH
|
90
|
+
2.2.3
|
data/README.md
CHANGED
@@ -87,7 +87,8 @@ wget https://raw.githubusercontent.com/jfrog/log-analytics-datadog/master/siem/d
|
|
87
87
|
Integration is done by setting up Xray. Obtain JPD url and access token for API. Configure the source directive parameters specified below
|
88
88
|
* **tag** (string) (required): The value is the tag assigned to the generated events.
|
89
89
|
* **jpd_url** (string) (required): JPD url required to pull Xray SIEM violations
|
90
|
-
* **
|
90
|
+
* **apikey** (string) (required): API Key is the [Artifactory API Key](https://www.jfrog.com/confluence/display/JFROG/User+Profile#UserProfile-APIKey) for authentication
|
91
|
+
* **username** (string) (required): USER is the Artifactory username for authentication
|
91
92
|
* **pos_file** (string) (required): Position file to record last SIEM violation pulled
|
92
93
|
* **batch_size** (integer) (optional): Batch size for processing violations
|
93
94
|
* Default value: `25`
|
data/Rakefile
CHANGED
@@ -3,13 +3,13 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |spec|
|
5
5
|
spec.name = "fluent-plugin-jfrog-siem"
|
6
|
-
spec.version = "0.1
|
7
|
-
spec.authors = ["
|
8
|
-
spec.email = ["
|
6
|
+
spec.version = "2.0.1"
|
7
|
+
spec.authors = ["Mahitha Byreddy", "Sudhindra Rao"]
|
8
|
+
spec.email = ["mahithab@jfrog.com", "sudhindrar@jfrog.com"]
|
9
9
|
|
10
10
|
spec.summary = %q{JFrog SIEM fluent input plugin will send the SIEM events from JFrog Xray to Fluentd}
|
11
11
|
spec.description = %q{JFrog SIEM fluent input plugin will send the SIEM events from JFrog Xray to Fluentd which can then be delivered to whatever output plugin specified}
|
12
|
-
spec.homepage = "https://github.com/jfrog/
|
12
|
+
spec.homepage = "https://github.com/jfrog/fluent-plugin-jfrog-siem"
|
13
13
|
spec.license = "Apache-2.0"
|
14
14
|
|
15
15
|
test_files, files = `git ls-files -z`.split("\x0").partition do |f|
|
@@ -24,7 +24,12 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_development_dependency "rake", "~> 12.0"
|
25
25
|
spec.add_development_dependency "test-unit", "~> 3.0"
|
26
26
|
spec.add_development_dependency "rest-client", "~> 2.0"
|
27
|
-
spec.add_development_dependency "
|
28
|
-
spec.
|
27
|
+
spec.add_development_dependency "concurrent-ruby", "~> 1.1.8"
|
28
|
+
spec.add_development_dependency "concurrent-ruby-edge", '>= 0'
|
29
|
+
spec.add_development_dependency 'rspec', '~> 3.10.0'
|
30
|
+
|
31
|
+
spec.add_runtime_dependency "rest-client", "~> 2.0"
|
32
|
+
spec.add_runtime_dependency "concurrent-ruby", "~> 1.1.8"
|
33
|
+
spec.add_runtime_dependency "concurrent-ruby-edge", '>= 0'
|
29
34
|
spec.add_runtime_dependency "fluentd", [">= 0.14.10", "< 2"]
|
30
35
|
end
|
@@ -14,10 +14,10 @@
|
|
14
14
|
# limitations under the License.
|
15
15
|
require "fluent/plugin/input"
|
16
16
|
require "rest-client"
|
17
|
-
require "thread/pool"
|
18
|
-
require "json"
|
19
17
|
require "date"
|
20
18
|
require "uri"
|
19
|
+
require "fluent/plugin/xray"
|
20
|
+
require "fluent/plugin/position_file"
|
21
21
|
|
22
22
|
module Fluent
|
23
23
|
module Plugin
|
@@ -32,11 +32,10 @@ module Fluent
|
|
32
32
|
config_param :jpd_url, :string, default: ""
|
33
33
|
config_param :username, :string, default: ""
|
34
34
|
config_param :apikey, :string, default: ""
|
35
|
-
config_param :pos_file, :string, default: ""
|
36
35
|
config_param :batch_size, :integer, default: 25
|
37
|
-
config_param :thread_count, :integer, default: 5
|
38
36
|
config_param :wait_interval, :integer, default: 60
|
39
|
-
|
37
|
+
config_param :from_date, :string, default: ""
|
38
|
+
config_param :pos_file_path, :string, default: ""
|
40
39
|
|
41
40
|
# `configure` is called before `start`.
|
42
41
|
# 'conf' is a `Hash` that includes the configuration parameters.
|
@@ -59,22 +58,14 @@ module Fluent
|
|
59
58
|
raise Fluent::ConfigError, "Must define the API Key to use for authentication."
|
60
59
|
end
|
61
60
|
|
62
|
-
if @pos_file == ""
|
63
|
-
raise Fluent::ConfigError, "Must define a position file to record last SIEM violation pulled."
|
64
|
-
end
|
65
|
-
|
66
|
-
if @thread_count < 1
|
67
|
-
raise Fluent::ConfigError, "Must define at least one thread to process violation details."
|
68
|
-
end
|
69
|
-
|
70
|
-
if @thread_count > @batch_size
|
71
|
-
raise Fluent::ConfigError, "Violation detail url thread count exceeds batch size."
|
72
|
-
end
|
73
|
-
|
74
61
|
if @wait_interval < 1
|
75
62
|
raise Fluent::ConfigError, "Wait interval must be greater than 1 to wait between pulling new events."
|
76
63
|
end
|
77
64
|
|
65
|
+
if @from_date == ""
|
66
|
+
puts "From date not specified, so getting violations from current date if pos_file doesn't exist"
|
67
|
+
end
|
68
|
+
|
78
69
|
end
|
79
70
|
|
80
71
|
|
@@ -94,102 +85,19 @@ module Fluent
|
|
94
85
|
|
95
86
|
|
96
87
|
def run
|
97
|
-
call_home(@jpd_url)
|
98
|
-
# runs the violation pull
|
99
|
-
last_created_date_string = get_last_item_create_date()
|
100
|
-
begin
|
101
|
-
last_created_date = DateTime.parse(last_created_date_string).strftime("%Y-%m-%dT%H:%M:%SZ")
|
102
|
-
rescue
|
103
|
-
last_created_date = DateTime.parse("1970-01-01T00:00:00Z").strftime("%Y-%m-%dT%H:%M:%SZ")
|
104
|
-
end
|
105
|
-
offset_count=1
|
106
|
-
left_violations=0
|
107
|
-
waiting_for_violations = false
|
108
|
-
xray_json={"filters": { "created_from": last_created_date }, "pagination": {"order_by": "created","limit": @batch_size ,"offset": offset_count } }
|
109
|
-
|
110
|
-
while true
|
111
|
-
# Grab the batch of records
|
112
|
-
resp=get_xray_violations(xray_json, @jpd_url)
|
113
|
-
number_of_violations = JSON.parse(resp)['total_violations']
|
114
|
-
if left_violations <= 0
|
115
|
-
left_violations = number_of_violations
|
116
|
-
end
|
117
|
-
|
118
|
-
xray_violation_urls_list = []
|
119
|
-
for index in 0..JSON.parse(resp)['violations'].length-1 do
|
120
|
-
# Get the violation
|
121
|
-
item = JSON.parse(resp)['violations'][index]
|
122
|
-
|
123
|
-
# Get the created date and check if we should skip (already processed) or process this record.
|
124
|
-
created_date_string = item['created']
|
125
|
-
created_date = DateTime.parse(created_date_string).strftime("%Y-%m-%dT%H:%M:%SZ")
|
88
|
+
# call_home(@jpd_url)
|
126
89
|
|
127
|
-
|
128
|
-
persistItem = true
|
129
|
-
if waiting_for_violations
|
130
|
-
if created_date <= last_created_date
|
131
|
-
# "not persisting it - waiting for violations"
|
132
|
-
persistItem = false
|
133
|
-
end
|
134
|
-
else
|
135
|
-
if created_date < last_created_date
|
136
|
-
# "persisting everything"
|
137
|
-
persistItem = true
|
138
|
-
end
|
139
|
-
end
|
90
|
+
last_created_date = get_last_item_create_date()
|
140
91
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
now = Fluent::Engine.now
|
145
|
-
router.emit(@tag, now, item)
|
146
|
-
|
147
|
-
# write to the pos_file created_date_string
|
148
|
-
open(@pos_file, 'a') do |f|
|
149
|
-
f << "#{created_date_string}\n"
|
150
|
-
end
|
151
|
-
|
152
|
-
# Mark this as the last record successfully processed
|
153
|
-
last_created_date_string = created_date_string
|
154
|
-
last_created_date = created_date
|
155
|
-
|
156
|
-
# Grab violation detail url and add to url list to process w/ thread pool
|
157
|
-
xray_violation_details_url=item['violation_details_url']
|
158
|
-
xray_violation_urls_list.append(xray_violation_details_url)
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
# iterate over url array adding to thread pool each url.
|
163
|
-
# limit max workers to thread count to prevent overloading xray.
|
164
|
-
thread_pool = Thread.pool(thread_count)
|
165
|
-
thread_pool.process {
|
166
|
-
for xray_violation_url in xray_violation_urls_list do
|
167
|
-
pull_violation_details(xray_violation_url)
|
168
|
-
end
|
169
|
-
}
|
170
|
-
thread_pool.shutdown
|
171
|
-
|
172
|
-
# reduce left violations by jump size (not all batches have full item count??)
|
173
|
-
left_violations = left_violations - @batch_size
|
174
|
-
if left_violations <= 0
|
175
|
-
waiting_for_violations = true
|
176
|
-
sleep(@wait_interval)
|
177
|
-
else
|
178
|
-
# Grab the next record to process for the violation details url
|
179
|
-
waiting_for_violations = false
|
180
|
-
offset_count = offset_count + 1
|
181
|
-
xray_json={"filters": { "created_from": last_created_date_string }, "pagination": {"order_by": "created","limit": @batch_size , "offset": offset_count } }
|
182
|
-
end
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
|
187
|
-
# pull the last item create date from the pos_file return created_date_string
|
188
|
-
def get_last_item_create_date()
|
189
|
-
if(!(File.exist?(@pos_file)))
|
190
|
-
@pos_file = File.new(@pos_file, "w")
|
92
|
+
if (@from_date != "")
|
93
|
+
last_created_date = DateTime.parse(@from_date).strftime("%Y-%m-%dT%H:%M:%SZ")
|
191
94
|
end
|
192
|
-
|
95
|
+
date_since = last_created_date
|
96
|
+
puts "Getting queries from #{date_since}"
|
97
|
+
xray = Xray.new(@jpd_url, @username, @apikey, @wait_interval, @batch_size, @pos_file_path, router, @tag)
|
98
|
+
violations_channel = xray.violations(date_since)
|
99
|
+
xray.violation_details(violations_channel)
|
100
|
+
sleep 100
|
193
101
|
end
|
194
102
|
|
195
103
|
#call home functionality
|
@@ -203,119 +111,27 @@ module Fluent
|
|
203
111
|
:password => @apikey,
|
204
112
|
:headers => { :accept => :json, :content_type => :json}
|
205
113
|
).execute do |response, request, result|
|
206
|
-
|
114
|
+
puts "Posting call home information"
|
207
115
|
end
|
208
116
|
end
|
209
117
|
|
210
|
-
#
|
211
|
-
def
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
case response.code
|
219
|
-
when 200
|
220
|
-
return response.to_str
|
221
|
-
else
|
222
|
-
raise Fluent::ConfigError, "Cannot reach Artifactory URL to pull Xray SIEM violations."
|
223
|
-
end
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
|
228
|
-
# queries the xray API for violations based upon the input json
|
229
|
-
def get_xray_violations(xray_json, jpd_url)
|
230
|
-
response = RestClient::Request.new(
|
231
|
-
:method => :post,
|
232
|
-
:url => jpd_url + "/xray/api/v1/violations",
|
233
|
-
:payload => xray_json.to_json,
|
234
|
-
:user => @username,
|
235
|
-
:password => @apikey,
|
236
|
-
:headers => { :accept => :json, :content_type => :json}
|
237
|
-
).execute do |response, request, result|
|
238
|
-
case response.code
|
239
|
-
when 200
|
240
|
-
return response.to_str
|
241
|
-
else
|
242
|
-
raise Fluent::ConfigError, "Cannot reach Artifactory URL to pull Xray SIEM violations."
|
243
|
-
end
|
244
|
-
end
|
245
|
-
end
|
246
|
-
|
247
|
-
# normalizes Xray data according to common information models for all log-vendors
|
248
|
-
def data_normalization(detailResp)
|
249
|
-
detailResp_json = JSON.parse(detailResp)
|
250
|
-
cve = []
|
251
|
-
cvss_v2_list = []
|
252
|
-
cvss_v3_list = []
|
253
|
-
policy_list = []
|
254
|
-
rule_list = []
|
255
|
-
impacted_artifact_url_list = []
|
256
|
-
if detailResp_json.key?('properties')
|
257
|
-
properties = detailResp_json['properties']
|
258
|
-
for index in 0..properties.length-1 do
|
259
|
-
if properties[index].key?('cve')
|
260
|
-
cve.push(properties[index]['cve'])
|
261
|
-
end
|
262
|
-
if properties[index].key?('cvss_v2')
|
263
|
-
cvss_v2_list.push(properties[index]['cvss_v2'])
|
264
|
-
end
|
265
|
-
if properties[index].key?('cvss_v3')
|
266
|
-
cvss_v3_list.push(properties[index]['cvss_v3'])
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
detailResp_json["cve"] = cve.sort.reverse[0]
|
271
|
-
cvss_v2 = cvss_v2_list.sort.reverse[0]
|
272
|
-
cvss_v3 = cvss_v3_list.sort.reverse[0]
|
273
|
-
if !cvss_v3.nil?
|
274
|
-
cvss = cvss_v3
|
275
|
-
elsif !cvss_v2.nil?
|
276
|
-
cvss = cvss_v2
|
277
|
-
end
|
278
|
-
cvss_score = cvss[0..2]
|
279
|
-
cvss_version = cvss.split(':')[1][0..2]
|
280
|
-
detailResp_json["cvss_score"] = cvss_score
|
281
|
-
detailResp_json["cvss_version"] = cvss_version
|
282
|
-
end
|
283
|
-
|
284
|
-
if detailResp_json.key?('matched_policies')
|
285
|
-
matched_policies = detailResp_json['matched_policies']
|
286
|
-
for index in 0..matched_policies.length-1 do
|
287
|
-
if matched_policies[index].key?('policy')
|
288
|
-
policy_list.push(matched_policies[index]['policy'])
|
289
|
-
end
|
290
|
-
if matched_policies[index].key?('rule')
|
291
|
-
rule_list.push(matched_policies[index]['rule'])
|
292
|
-
end
|
293
|
-
end
|
294
|
-
detailResp_json['policies'] = policy_list
|
295
|
-
detailResp_json['rules'] = rule_list
|
296
|
-
end
|
297
|
-
|
298
|
-
impacted_artifacts = detailResp_json['impacted_artifacts']
|
299
|
-
for impacted_artifact in impacted_artifacts do
|
300
|
-
matchdata = impacted_artifact.match /default\/(?<repo_name>[^\/]*)\/(?<path>.*)/
|
301
|
-
impacted_artifact_url = matchdata['repo_name'] + ":" + matchdata['path'] + " "
|
302
|
-
impacted_artifact_url_list.append(impacted_artifact_url)
|
118
|
+
# pull the last item create date from the pos_file return created_date_string
|
119
|
+
def get_last_item_create_date()
|
120
|
+
recent_pos_file = get_recent_pos_file()
|
121
|
+
if recent_pos_file != nil
|
122
|
+
last_created_date_string = IO.readlines(recent_pos_file).last
|
123
|
+
return DateTime.parse(last_created_date_string).strftime("%Y-%m-%dT%H:%M:%SZ")
|
124
|
+
else
|
125
|
+
return DateTime.now.strftime("%Y-%m-%dT%H:%M:%SZ")
|
303
126
|
end
|
304
|
-
detailResp_json['impacted_artifacts_url'] = impacted_artifact_url_list
|
305
|
-
return detailResp_json
|
306
127
|
end
|
307
128
|
|
308
|
-
def
|
309
|
-
|
310
|
-
|
311
|
-
time = Fluent::Engine.now
|
312
|
-
detailResp_json = data_normalization(detailResp)
|
313
|
-
router.emit(@tag, time, detailResp_json)
|
314
|
-
rescue
|
315
|
-
raise Fluent::ConfigError, "Error pulling violation details url #{xray_violation_detail_url}"
|
316
|
-
end
|
129
|
+
def get_recent_pos_file()
|
130
|
+
pos_file = @pos_file_path + "*.siem.pos"
|
131
|
+
return Dir.glob(pos_file).sort.last
|
317
132
|
end
|
318
133
|
|
319
134
|
end
|
320
135
|
end
|
321
136
|
end
|
137
|
+
|