fluent-plugin-jfrog-siem 1.0.0 → 2.0.0

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 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