fluent-plugin-jfrog-siem 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 306eb5d59fd5e00e2e8feda0339a3b635f1e61aaabff9312ad372dc714c3ff8f
4
- data.tar.gz: 469ea7950f9d96236a88159a797f17077b31f5c3d7ddc19ca91f4d6209963a9a
3
+ metadata.gz: e34dd9d1fb60e95b931f7c67bb508e635057b9e2f349f681eb2b54cb182c2396
4
+ data.tar.gz: b0acda337195accb81ce6bf3ee7bf05ef7e79630793dbc7553c0df5e541e26a3
5
5
  SHA512:
6
- metadata.gz: 02db6faa97750196fd42a0b04a8a8f517dfa0a26ad585778b8283b4cce9814ff239d96eb0a9ca34ef02a2f66bab9bb632cf8c0194afe7a3e4902ea08f4d9fd77
7
- data.tar.gz: 745bef31330a205aac78d2f49e2b25f6f924f52c3d8a0be35d3d1f4aee77f1c862b8e8124a95ed62d90befc3e5a351061cbe1b902482484e3778081404737527
6
+ metadata.gz: 9116c506cf562af1566e6001748f5464752b2c07aace2d9f6fe04573c5a97db4cbf0e638d048dea536387559a71fa493fad882dc55650f1597c4030ab13be962
7
+ data.tar.gz: 2aaeaa3f4a22753db0b97c35b93938c4049cb9037f56f3cc6199dc222034a9d26609e1906973c0b9f11960f856227b2cf93ab000e8bd677aee9045df0bd684b0
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/Rakefile CHANGED
@@ -4,7 +4,7 @@ Bundler::GemHelper.install_tasks
4
4
  require "rake/testtask"
5
5
 
6
6
  Rake::TestTask.new(:test) do |t|
7
- t.libs.push("lib", "test")
7
+ t.libs.push("lib", "test", 'lib/fluent/plugin')
8
8
  t.test_files = FileList["test/**/test_*.rb"]
9
9
  t.verbose = true
10
10
  t.warning = false
@@ -3,7 +3,7 @@ $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 = "1.0.0"
6
+ spec.version = "2.0.0"
7
7
  spec.authors = ["John Peterson", "Mahitha Byreddy"]
8
8
  spec.email = ["johnp@jfrog.com", "mahithab@jfrog.com"]
9
9
 
@@ -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 "thread", "~> 0.2.2"
28
- spec.add_runtime_dependency "thread", "~> 0.2.2"
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,108 +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
- # Determine if we need to persist this record or not
128
- persistItem = true
129
- if waiting_for_violations
130
- if created_date <= last_created_date
131
- # "not persisting it - waiting for violations"
132
- # waiting and same last timestamp (left violations in batch)
133
- persistItem = false
134
- end
135
- if created_date > last_created_date
136
- # new violation while waiting
137
- persistItem = true
138
- waiting_for_violations = false
139
- end
140
- else
141
- if created_date < last_created_date
142
- # "persisting everything"
143
- persistItem = true
144
- end
145
- end
90
+ last_created_date = get_last_item_create_date()
146
91
 
147
- # Publish the record to fluentd
148
- if persistItem
149
-
150
- now = Fluent::Engine.now
151
- router.emit(@tag, now, item)
152
-
153
- # write to the pos_file created_date_string
154
- open(@pos_file, 'a') do |f|
155
- f << "#{created_date_string}\n"
156
- end
157
-
158
- # Mark this as the last record successfully processed
159
- last_created_date_string = created_date_string
160
- last_created_date = created_date
161
-
162
- # Grab violation detail url and add to url list to process w/ thread pool
163
- xray_violation_details_url=item['violation_details_url']
164
- xray_violation_urls_list.append(xray_violation_details_url)
165
- end
166
- end
167
-
168
- # iterate over url array adding to thread pool each url.
169
- # limit max workers to thread count to prevent overloading xray.
170
- thread_pool = Thread.pool(thread_count)
171
- thread_pool.process {
172
- for xray_violation_url in xray_violation_urls_list do
173
- pull_violation_details(xray_violation_url)
174
- end
175
- }
176
- thread_pool.shutdown
177
-
178
- # reduce left violations by jump size (not all batches have full item count??)
179
- left_violations = left_violations - @batch_size
180
- if left_violations <= 0
181
- waiting_for_violations = true
182
- sleep(@wait_interval)
183
- else
184
- # Grab the next record to process for the violation details url
185
- waiting_for_violations = false
186
- offset_count = offset_count + 1
187
- xray_json={"filters": { "created_from": last_created_date_string }, "pagination": {"order_by": "created","limit": @batch_size , "offset": offset_count } }
188
- end
189
- end
190
- end
191
-
192
-
193
- # pull the last item create date from the pos_file return created_date_string
194
- def get_last_item_create_date()
195
- if(!(File.exist?(@pos_file)))
196
- @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")
197
94
  end
198
- return IO.readlines(@pos_file).last
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
199
101
  end
200
102
 
201
103
  #call home functionality
@@ -209,119 +111,27 @@ module Fluent
209
111
  :password => @apikey,
210
112
  :headers => { :accept => :json, :content_type => :json}
211
113
  ).execute do |response, request, result|
212
- puts "Posting call home information"
114
+ puts "Posting call home information"
213
115
  end
214
116
  end
215
117
 
216
- # queries the xray API for violations based upon the input json
217
- def get_xray_violations_detail(xray_violation_detail_url)
218
- response = RestClient::Request.new(
219
- :method => :get,
220
- :url => xray_violation_detail_url,
221
- :user => @username,
222
- :password => @apikey
223
- ).execute do |response, request, result|
224
- case response.code
225
- when 200
226
- return response.to_str
227
- else
228
- raise Fluent::ConfigError, "Cannot reach Artifactory URL to pull Xray SIEM violations."
229
- end
230
- end
231
- end
232
-
233
-
234
- # queries the xray API for violations based upon the input json
235
- def get_xray_violations(xray_json, jpd_url)
236
- response = RestClient::Request.new(
237
- :method => :post,
238
- :url => jpd_url + "/xray/api/v1/violations",
239
- :payload => xray_json.to_json,
240
- :user => @username,
241
- :password => @apikey,
242
- :headers => { :accept => :json, :content_type => :json}
243
- ).execute do |response, request, result|
244
- case response.code
245
- when 200
246
- return response.to_str
247
- else
248
- raise Fluent::ConfigError, "Cannot reach Artifactory URL to pull Xray SIEM violations."
249
- end
250
- end
251
- end
252
-
253
- # normalizes Xray data according to common information models for all log-vendors
254
- def data_normalization(detailResp)
255
- detailResp_json = JSON.parse(detailResp)
256
- cve = []
257
- cvss_v2_list = []
258
- cvss_v3_list = []
259
- policy_list = []
260
- rule_list = []
261
- impacted_artifact_url_list = []
262
- if detailResp_json.key?('properties')
263
- properties = detailResp_json['properties']
264
- for index in 0..properties.length-1 do
265
- if properties[index].key?('cve')
266
- cve.push(properties[index]['cve'])
267
- end
268
- if properties[index].key?('cvss_v2')
269
- cvss_v2_list.push(properties[index]['cvss_v2'])
270
- end
271
- if properties[index].key?('cvss_v3')
272
- cvss_v3_list.push(properties[index]['cvss_v3'])
273
- end
274
- end
275
-
276
- detailResp_json["cve"] = cve.sort.reverse[0]
277
- cvss_v2 = cvss_v2_list.sort.reverse[0]
278
- cvss_v3 = cvss_v3_list.sort.reverse[0]
279
- if !cvss_v3.nil?
280
- cvss = cvss_v3
281
- elsif !cvss_v2.nil?
282
- cvss = cvss_v2
283
- end
284
- cvss_score = cvss[0..2]
285
- cvss_version = cvss.split(':')[1][0..2]
286
- detailResp_json["cvss_score"] = cvss_score
287
- detailResp_json["cvss_version"] = cvss_version
288
- end
289
-
290
- if detailResp_json.key?('matched_policies')
291
- matched_policies = detailResp_json['matched_policies']
292
- for index in 0..matched_policies.length-1 do
293
- if matched_policies[index].key?('policy')
294
- policy_list.push(matched_policies[index]['policy'])
295
- end
296
- if matched_policies[index].key?('rule')
297
- rule_list.push(matched_policies[index]['rule'])
298
- end
299
- end
300
- detailResp_json['policies'] = policy_list
301
- detailResp_json['rules'] = rule_list
302
- end
303
-
304
- impacted_artifacts = detailResp_json['impacted_artifacts']
305
- for impacted_artifact in impacted_artifacts do
306
- matchdata = impacted_artifact.match /default\/(?<repo_name>[^\/]*)\/(?<path>.*)/
307
- impacted_artifact_url = matchdata['repo_name'] + ":" + matchdata['path'] + " "
308
- 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")
309
126
  end
310
- detailResp_json['impacted_artifacts_url'] = impacted_artifact_url_list
311
- return detailResp_json
312
127
  end
313
128
 
314
- def pull_violation_details(xray_violation_detail_url)
315
- begin
316
- detailResp=get_xray_violations_detail(xray_violation_detail_url)
317
- time = Fluent::Engine.now
318
- detailResp_json = data_normalization(detailResp)
319
- router.emit(@tag, time, detailResp_json)
320
- rescue
321
- raise Fluent::ConfigError, "Error pulling violation details url #{xray_violation_detail_url}"
322
- end
129
+ def get_recent_pos_file()
130
+ pos_file = @pos_file_path + "*.siem.pos"
131
+ return Dir.glob(pos_file).sort.last
323
132
  end
324
133
 
325
134
  end
326
135
  end
327
136
  end
137
+
@@ -0,0 +1,32 @@
1
+ class PositionFile
2
+
3
+ def initialize(pos_file_path)
4
+ @pos_file_path = pos_file_path
5
+ end
6
+
7
+ def processed?(violation)
8
+ File.exist?(pos_file_name(violation)) && found?(violation)
9
+ end
10
+
11
+ def write(violation)
12
+ File.open(pos_file_name(violation), 'a') do |f|
13
+ f << violation_entry(violation)
14
+ f << "\n"
15
+ end
16
+ end
17
+
18
+ private
19
+ def found?(violation)
20
+ return File.open(pos_file_name(violation)) { |f| f.find { |line| line.include? violation_entry(violation) } }
21
+ end
22
+
23
+ def violation_entry(violation)
24
+ created_date = DateTime.parse(violation['created']).strftime("%Y-%m-%dT%H:%M:%SZ")
25
+ [created_date, violation['watch_name'], violation['issue_id']].join(',')
26
+ end
27
+
28
+ def pos_file_name(violation)
29
+ pos_file_date = DateTime.parse(violation['created']).strftime("%Y-%m-%d")
30
+ @pos_file_path + "jfrog_siem_log_#{pos_file_date}.siem.pos"
31
+ end
32
+ end