fluent-plugin-jfrog-siem 0.1.8 → 2.0.1

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: 4e222c0afdaf25a0aa38e4167236c2f06b5c7f9c889ca544f3a05677609c2c1e
4
- data.tar.gz: '097fd2e7d1b8b0b8394e11efa9abbcfa5d0e67412d3284bdd5c5fe47badde641'
3
+ metadata.gz: ea8dc8e85e1874da646a83a7aea1ab32a63057cfaedd0cce5b30a63461898b9c
4
+ data.tar.gz: 4d38c3858e8432b13cd25e1fde6a82619d037274037173fd0825b3b1236e811e
5
5
  SHA512:
6
- metadata.gz: 18b38f238bef87f6e015aa6e6eda87ba58586e6e85ef5f29d58392b91ad1fc5c12da33777ce7b477f222d966df2c5eb7082d1cbe757cc712703adb4511945057
7
- data.tar.gz: 49a13e9f1aeec783f9e7f35ca9750d3d3c4398d1e6facfa029dbfa6dec8a27d083941e995722cdb7ffa66937fb747bc079fe439e534e5dc23023e7543d56dc4b
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
- * **access_token** (string) (required): [Access token](https://www.jfrog.com/confluence/display/JFROG/Access+Tokens) to authenticate Xray
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
@@ -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,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.8"
7
- spec.authors = ["John Peterson", "Mahitha Byreddy"]
8
- spec.email = ["johnp@jfrog.com", "mahithab@jfrog.com"]
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/log-analytics"
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 "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,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
- # 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
- 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
- # Publish the record to fluentd
142
- if persistItem
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
- 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
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
- puts "Posting call home information"
114
+ puts "Posting call home information"
207
115
  end
208
116
  end
209
117
 
210
- # queries the xray API for violations based upon the input json
211
- def get_xray_violations_detail(xray_violation_detail_url)
212
- response = RestClient::Request.new(
213
- :method => :get,
214
- :url => xray_violation_detail_url,
215
- :user => @username,
216
- :password => @apikey
217
- ).execute do |response, request, result|
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 pull_violation_details(xray_violation_detail_url)
309
- begin
310
- detailResp=get_xray_violations_detail(xray_violation_detail_url)
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
+