fluent-plugin-jfrog-siem 0.1.3 → 0.1.8

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: cd2cc7532e266f9d58b4dbbf18479c1f34fd975649f7aa5ab9e5e671f449025b
4
- data.tar.gz: fa8943afc154c4d6136ac1605f5bf85a240ce6ac58bf3ee61f42eb162f447aa6
3
+ metadata.gz: 4e222c0afdaf25a0aa38e4167236c2f06b5c7f9c889ca544f3a05677609c2c1e
4
+ data.tar.gz: '097fd2e7d1b8b0b8394e11efa9abbcfa5d0e67412d3284bdd5c5fe47badde641'
5
5
  SHA512:
6
- metadata.gz: b47739ce004b1d48fddd026188aed933e4397b3730eb1d9bde1511f8bd623dc687246fa0ad85a91e814aa46175e5b0aee44213e12f18be97f078fb71b44904f7
7
- data.tar.gz: a377d04e8b41cb108402036158d82632390db472cc4e7a97655110465e79a939e2bc90a25945b4932463f0561294c6f4af46fe70fa730141a403e1665a960a6e
6
+ metadata.gz: 18b38f238bef87f6e015aa6e6eda87ba58586e6e85ef5f29d58392b91ad1fc5c12da33777ce7b477f222d966df2c5eb7082d1cbe757cc712703adb4511945057
7
+ data.tar.gz: 49a13e9f1aeec783f9e7f35ca9750d3d3c4398d1e6facfa029dbfa6dec8a27d083941e995722cdb7ffa66937fb747bc079fe439e534e5dc23023e7543d56dc4b
data/Gemfile CHANGED
@@ -1,2 +1,3 @@
1
1
  source "https://rubygems.org"
2
+
2
3
  gemspec
data/README.md CHANGED
@@ -68,13 +68,19 @@ Splunk:
68
68
 
69
69
  Splunk setup can be found at [README.](https://github.com/jfrog/log-analytics-splunk/blob/master/README.md)
70
70
  ````text
71
- wget https://raw.githubusercontent.com/jfrog/log-analytics/master/fluentd/plugins/input/fluent-plugin-jfrog-siem/splunk.conf
71
+ wget https://raw.githubusercontent.com/jfrog/log-analytics-splunk/master/siem/splunk_siem.conf
72
72
  ````
73
73
  Elasticsearch:
74
74
 
75
75
  Elasticsearch Kibana setup can be found at [README.](https://github.com/jfrog/log-analytics-elastic/blob/master/README.md)
76
76
  ````text
77
- wget https://raw.githubusercontent.com/jfrog/log-analytics/master/fluentd/plugins/input/fluent-plugin-jfrog-siem/elastic.conf
77
+ wget https://raw.githubusercontent.com/jfrog/log-analytics-elastic/master/siem/elastic_siem.conf
78
+ ````
79
+ Datadog:
80
+
81
+ Datadog setup can be found at [README.](https://github.com/jfrog/log-analytics-datadog/blob/master/README.md)
82
+ ````text
83
+ wget https://raw.githubusercontent.com/jfrog/log-analytics-datadog/master/siem/datadog_siem.conf
78
84
  ````
79
85
 
80
86
  #### Configuration parameters
@@ -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 = "0.1.3"
6
+ spec.version = "0.1.8"
7
7
  spec.authors = ["John Peterson", "Mahitha Byreddy"]
8
8
  spec.email = ["johnp@jfrog.com", "mahithab@jfrog.com"]
9
9
 
@@ -24,5 +24,7 @@ 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
29
  spec.add_runtime_dependency "fluentd", [">= 0.14.10", "< 2"]
28
30
  end
@@ -30,7 +30,8 @@ module Fluent
30
30
  # `:default` means that the parameter is optional.
31
31
  config_param :tag, :string, default: ""
32
32
  config_param :jpd_url, :string, default: ""
33
- config_param :access_token, :string, default: ""
33
+ config_param :username, :string, default: ""
34
+ config_param :apikey, :string, default: ""
34
35
  config_param :pos_file, :string, default: ""
35
36
  config_param :batch_size, :integer, default: 25
36
37
  config_param :thread_count, :integer, default: 5
@@ -50,8 +51,12 @@ module Fluent
50
51
  raise Fluent::ConfigError, "Must define the JPD URL to pull Xray SIEM violations."
51
52
  end
52
53
 
53
- if @access_token == ""
54
- raise Fluent::ConfigError, "Must define the access token to use for authentication."
54
+ if @username == ""
55
+ raise Fluent::ConfigError, "Must define the username to use for authentication."
56
+ end
57
+
58
+ if @apikey == ""
59
+ raise Fluent::ConfigError, "Must define the API Key to use for authentication."
55
60
  end
56
61
 
57
62
  if @pos_file == ""
@@ -89,7 +94,7 @@ module Fluent
89
94
 
90
95
 
91
96
  def run
92
- call_home(@jpd_url, @access_token)
97
+ call_home(@jpd_url)
93
98
  # runs the violation pull
94
99
  last_created_date_string = get_last_item_create_date()
95
100
  begin
@@ -99,11 +104,12 @@ module Fluent
99
104
  end
100
105
  offset_count=1
101
106
  left_violations=0
107
+ waiting_for_violations = false
102
108
  xray_json={"filters": { "created_from": last_created_date }, "pagination": {"order_by": "created","limit": @batch_size ,"offset": offset_count } }
103
109
 
104
110
  while true
105
111
  # Grab the batch of records
106
- resp=get_xray_violations(xray_json, @jpd_url, @access_token)
112
+ resp=get_xray_violations(xray_json, @jpd_url)
107
113
  number_of_violations = JSON.parse(resp)['total_violations']
108
114
  if left_violations <= 0
109
115
  left_violations = number_of_violations
@@ -120,8 +126,16 @@ module Fluent
120
126
 
121
127
  # Determine if we need to persist this record or not
122
128
  persistItem = true
123
- if created_date < last_created_date
124
- persistItem = false
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
125
139
  end
126
140
 
127
141
  # Publish the record to fluentd
@@ -148,20 +162,21 @@ module Fluent
148
162
  # iterate over url array adding to thread pool each url.
149
163
  # limit max workers to thread count to prevent overloading xray.
150
164
  thread_pool = Thread.pool(thread_count)
151
- for xray_violation_url in xray_violation_urls_list do
152
- thread_pool.process {
153
- pull_violation_details(xray_violation_url, @access_token)
154
- }
155
- end
156
-
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
+ }
157
170
  thread_pool.shutdown
158
171
 
159
172
  # reduce left violations by jump size (not all batches have full item count??)
160
173
  left_violations = left_violations - @batch_size
161
174
  if left_violations <= 0
175
+ waiting_for_violations = true
162
176
  sleep(@wait_interval)
163
177
  else
164
178
  # Grab the next record to process for the violation details url
179
+ waiting_for_violations = false
165
180
  offset_count = offset_count + 1
166
181
  xray_json={"filters": { "created_from": last_created_date_string }, "pagination": {"order_by": "created","limit": @batch_size , "offset": offset_count } }
167
182
  end
@@ -178,48 +193,53 @@ module Fluent
178
193
  end
179
194
 
180
195
  #call home functionality
181
- def call_home(jpd_url, access_token)
196
+ def call_home(jpd_url)
182
197
  call_home_json = { "productId": "jfrogLogAnalytics/v0.5.1", "features": [ { "featureId": "Platform/Xray" }, { "featureId": "Channel/xrayeventsiem" } ] }
183
198
  response = RestClient::Request.new(
184
199
  :method => :post,
185
200
  :url => jpd_url + "/artifactory/api/system/usage",
186
201
  :payload => call_home_json.to_json,
187
- :headers => { :accept => :json, :content_type => :json, Authorization:'Bearer ' + access_token }
202
+ :user => @username,
203
+ :password => @apikey,
204
+ :headers => { :accept => :json, :content_type => :json}
188
205
  ).execute do |response, request, result|
189
206
  puts "Posting call home information"
190
207
  end
191
208
  end
192
209
 
193
210
  # queries the xray API for violations based upon the input json
194
- def get_xray_violations_detail(xray_violation_detail_url, access_token)
211
+ def get_xray_violations_detail(xray_violation_detail_url)
195
212
  response = RestClient::Request.new(
196
213
  :method => :get,
197
214
  :url => xray_violation_detail_url,
198
- headers: {Authorization:'Bearer ' + access_token}
215
+ :user => @username,
216
+ :password => @apikey
199
217
  ).execute do |response, request, result|
200
218
  case response.code
201
219
  when 200
202
220
  return response.to_str
203
221
  else
204
- raise Fluent::StandardError, "Cannot reach Artifactory URL to pull Xray SIEM violations."
222
+ raise Fluent::ConfigError, "Cannot reach Artifactory URL to pull Xray SIEM violations."
205
223
  end
206
224
  end
207
225
  end
208
226
 
209
227
 
210
228
  # queries the xray API for violations based upon the input json
211
- def get_xray_violations(xray_json, jpd_url, access_token)
229
+ def get_xray_violations(xray_json, jpd_url)
212
230
  response = RestClient::Request.new(
213
231
  :method => :post,
214
232
  :url => jpd_url + "/xray/api/v1/violations",
215
233
  :payload => xray_json.to_json,
216
- :headers => { :accept => :json, :content_type => :json, Authorization:'Bearer ' + access_token }
234
+ :user => @username,
235
+ :password => @apikey,
236
+ :headers => { :accept => :json, :content_type => :json}
217
237
  ).execute do |response, request, result|
218
238
  case response.code
219
239
  when 200
220
240
  return response.to_str
221
241
  else
222
- raise Fluent::StandardError, "Cannot reach Artifactory URL to pull Xray SIEM violations."
242
+ raise Fluent::ConfigError, "Cannot reach Artifactory URL to pull Xray SIEM violations."
223
243
  end
224
244
  end
225
245
  end
@@ -227,44 +247,72 @@ module Fluent
227
247
  # normalizes Xray data according to common information models for all log-vendors
228
248
  def data_normalization(detailResp)
229
249
  detailResp_json = JSON.parse(detailResp)
230
- properties = detailResp_json['properties']
231
250
  cve = []
232
251
  cvss_v2_list = []
233
252
  cvss_v3_list = []
234
- for index in 0..properties.length-1 do
235
- if properties[index].key?('cve')
236
- cve.push(properties[index]['cve'])
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
237
268
  end
238
- if properties[index].key?('cvss_v2')
239
- cvss_v2_list.push(properties[index]['cvss_v2'])
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
240
277
  end
241
- if properties[index].key?('cvss_v3')
242
- cvss_v3_list.push(properties[index]['cvss_v3'])
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
243
293
  end
294
+ detailResp_json['policies'] = policy_list
295
+ detailResp_json['rules'] = rule_list
244
296
  end
245
- detailResp_json["cve"] = cve.sort.reverse[0]
246
- cvss_v2 = cvss_v2_list.sort.reverse[0]
247
- cvss_v3 = cvss_v3_list.sort.reverse[0]
248
- if cvss_v3.length() > 0
249
- cvss = cvss_v3
250
- elsif cvss_v2.length() > 0
251
- cvss = cvss_v2
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)
252
303
  end
253
- cvss_score = cvss[0..2]
254
- cvss_version = cvss.split(':')[1][0..2]
255
- detailResp_json["cvss_score"] = cvss_score
256
- detailResp_json["cvss_version"] = cvss_version
304
+ detailResp_json['impacted_artifacts_url'] = impacted_artifact_url_list
257
305
  return detailResp_json
258
306
  end
259
307
 
260
- def pull_violation_details(xray_violation_detail_url, access_token)
308
+ def pull_violation_details(xray_violation_detail_url)
261
309
  begin
262
- detailResp=get_xray_violations_detail(xray_violation_detail_url, access_token)
310
+ detailResp=get_xray_violations_detail(xray_violation_detail_url)
263
311
  time = Fluent::Engine.now
264
312
  detailResp_json = data_normalization(detailResp)
265
313
  router.emit(@tag, time, detailResp_json)
266
314
  rescue
267
- raise Fluent::StandardError, "Error pulling violation details url #{xray_violation_detail_url}"
315
+ raise Fluent::ConfigError, "Error pulling violation details url #{xray_violation_detail_url}"
268
316
  end
269
317
  end
270
318
 
@@ -13,8 +13,9 @@ class JfrogSiemInputTest < Test::Unit::TestCase
13
13
  # Default configuration for tests
14
14
  CONFIG = %[
15
15
  tag "test_tag"
16
- jpd_url <jpd_url>
17
- access_token <access_token>
16
+ jpd_url JPD_URL
17
+ username USER
18
+ apikey API_KEY
18
19
  pos_file "test_pos.txt"
19
20
  ]
20
21
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-jfrog-siem
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Peterson
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-01-21 00:00:00.000000000 Z
12
+ date: 2021-05-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -67,6 +67,34 @@ dependencies:
67
67
  - - "~>"
68
68
  - !ruby/object:Gem::Version
69
69
  version: '2.0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: thread
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: 0.2.2
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: 0.2.2
84
+ - !ruby/object:Gem::Dependency
85
+ name: thread
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: 0.2.2
91
+ type: :runtime
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: 0.2.2
70
98
  - !ruby/object:Gem::Dependency
71
99
  name: fluentd
72
100
  requirement: !ruby/object:Gem::Requirement