fluent-plugin-jfrog-siem 0.1.7 → 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: 1d5598c727b3f7567c6ca432c96b12252e45387787ed75ec55ce98024f936725
4
- data.tar.gz: e5d9aaed273061f7abbe7c41a337d6346bfac8896ffc88c182888b96770646d8
3
+ metadata.gz: e34dd9d1fb60e95b931f7c67bb508e635057b9e2f349f681eb2b54cb182c2396
4
+ data.tar.gz: b0acda337195accb81ce6bf3ee7bf05ef7e79630793dbc7553c0df5e541e26a3
5
5
  SHA512:
6
- metadata.gz: a1b43ff8df04ecf7e89a30632e2174c7a55724b1ae6357ef564d4719ebfec699601ac5dd1936c8275758196ad2c1bfc260262c4f7e3490658c132b5f456eb2ca
7
- data.tar.gz: 0a77aa48d288ad3ca06e3dfe2bef07fff81ad368fd834d686c55a3e39963ce332334389ff2f753692dcf06e65f97a83cc440f66c906f62093ba682fa7d55e232
6
+ metadata.gz: 9116c506cf562af1566e6001748f5464752b2c07aace2d9f6fe04573c5a97db4cbf0e638d048dea536387559a71fa493fad882dc55650f1597c4030ab13be962
7
+ data.tar.gz: 2aaeaa3f4a22753db0b97c35b93938c4049cb9037f56f3cc6199dc222034a9d26609e1906973c0b9f11960f856227b2cf93ab000e8bd677aee9045df0bd684b0
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/CHANGELOG.md ADDED
@@ -0,0 +1,36 @@
1
+ # JFrog Fluentd SIEM Input Plugin Changelog
2
+ All changes to the SIEM plugin will be documented in this file.
3
+
4
+ ## [1.0.0] - May 18, 2020
5
+ * [BREAKING] Using JFrog API Key for authentication
6
+
7
+ ## [0.1.9] - May 17, 2021
8
+ * Handling the case where violations are left in a batch to be processed
9
+
10
+ ## [0.1.8] - May 10, 2021
11
+ * Fixing persist, not persist item conditions
12
+
13
+ ## [0.1.7] - April 21, 2021
14
+ * Adding policies and rules to payload
15
+
16
+ ## [0.1.6] - April 13, 2021
17
+ * Adding additonal parameters to match with access logs for correlation
18
+
19
+ ## [0.1.5] - March 29, 2021
20
+ * Normalizing the format of Impacted Artifact, fixing properties not found case
21
+
22
+ ## [0.1.4] - February 02, 2021
23
+ * Adding dependencies, gemspec updates
24
+
25
+ ## [0.1.3] - January 21, 2021
26
+ * Fixing thread pool issues (moving loop inside a thread pool)
27
+
28
+ ## [0.1.2] - November 17, 2020
29
+ * Changes to better README
30
+
31
+ ## [0.1.1] - November 17, 2020
32
+ * Adding dependencies to gemspec
33
+
34
+ ## [0.1.0] - October 05, 2020
35
+ * Initial release of Jfrog Logs Analytic integration
36
+
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
@@ -68,20 +68,27 @@ 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
81
87
  Integration is done by setting up Xray. Obtain JPD url and access token for API. Configure the source directive parameters specified below
82
88
  * **tag** (string) (required): The value is the tag assigned to the generated events.
83
89
  * **jpd_url** (string) (required): JPD url required to pull Xray SIEM violations
84
- * **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
85
92
  * **pos_file** (string) (required): Position file to record last SIEM violation pulled
86
93
  * **batch_size** (integer) (optional): Batch size for processing violations
87
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,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.7"
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
@@ -30,12 +30,12 @@ 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: ""
34
- config_param :pos_file, :string, default: ""
33
+ config_param :username, :string, default: ""
34
+ config_param :apikey, :string, default: ""
35
35
  config_param :batch_size, :integer, default: 25
36
- config_param :thread_count, :integer, default: 5
37
36
  config_param :wait_interval, :integer, default: 60
38
-
37
+ config_param :from_date, :string, default: ""
38
+ config_param :pos_file_path, :string, default: ""
39
39
 
40
40
  # `configure` is called before `start`.
41
41
  # 'conf' is a `Hash` that includes the configuration parameters.
@@ -50,26 +50,22 @@ module Fluent
50
50
  raise Fluent::ConfigError, "Must define the JPD URL to pull Xray SIEM violations."
51
51
  end
52
52
 
53
- if @access_token == ""
54
- raise Fluent::ConfigError, "Must define the access token to use for authentication."
55
- end
56
-
57
- if @pos_file == ""
58
- raise Fluent::ConfigError, "Must define a position file to record last SIEM violation pulled."
53
+ if @username == ""
54
+ raise Fluent::ConfigError, "Must define the username to use for authentication."
59
55
  end
60
56
 
61
- if @thread_count < 1
62
- raise Fluent::ConfigError, "Must define at least one thread to process violation details."
63
- end
64
-
65
- if @thread_count > @batch_size
66
- raise Fluent::ConfigError, "Violation detail url thread count exceeds batch size."
57
+ if @apikey == ""
58
+ raise Fluent::ConfigError, "Must define the API Key to use for authentication."
67
59
  end
68
60
 
69
61
  if @wait_interval < 1
70
62
  raise Fluent::ConfigError, "Wait interval must be greater than 1 to wait between pulling new events."
71
63
  end
72
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
+
73
69
  end
74
70
 
75
71
 
@@ -89,223 +85,53 @@ module Fluent
89
85
 
90
86
 
91
87
  def run
92
- call_home(@jpd_url, @access_token)
93
- # runs the violation pull
94
- last_created_date_string = get_last_item_create_date()
95
- begin
96
- last_created_date = DateTime.parse(last_created_date_string).strftime("%Y-%m-%dT%H:%M:%SZ")
97
- rescue
98
- last_created_date = DateTime.parse("1970-01-01T00:00:00Z").strftime("%Y-%m-%dT%H:%M:%SZ")
99
- end
100
- offset_count=1
101
- left_violations=0
102
- waiting_for_violations = false
103
- xray_json={"filters": { "created_from": last_created_date }, "pagination": {"order_by": "created","limit": @batch_size ,"offset": offset_count } }
104
-
105
- while true
106
- # Grab the batch of records
107
- resp=get_xray_violations(xray_json, @jpd_url, @access_token)
108
- number_of_violations = JSON.parse(resp)['total_violations']
109
- if left_violations <= 0
110
- left_violations = number_of_violations
111
- end
112
-
113
- xray_violation_urls_list = []
114
- for index in 0..JSON.parse(resp)['violations'].length-1 do
115
- # Get the violation
116
- item = JSON.parse(resp)['violations'][index]
117
-
118
- # Get the created date and check if we should skip (already processed) or process this record.
119
- created_date_string = item['created']
120
- created_date = DateTime.parse(created_date_string).strftime("%Y-%m-%dT%H:%M:%SZ")
88
+ # call_home(@jpd_url)
121
89
 
122
- # Determine if we need to persist this record or not
123
- persistItem = true
124
- if waiting_for_violations
125
- if created_date <= last_created_date
126
- # "not persisting it - waiting for violations"
127
- persistItem = false
128
- end
129
- else
130
- if created_date < last_created_date
131
- # "persisting everything"
132
- persistItem = true
133
- end
134
- end
90
+ last_created_date = get_last_item_create_date()
135
91
 
136
- # Publish the record to fluentd
137
- if persistItem
138
-
139
- now = Fluent::Engine.now
140
- router.emit(@tag, now, item)
141
-
142
- # write to the pos_file created_date_string
143
- open(@pos_file, 'a') do |f|
144
- f << "#{created_date_string}\n"
145
- end
146
-
147
- # Mark this as the last record successfully processed
148
- last_created_date_string = created_date_string
149
- last_created_date = created_date
150
-
151
- # Grab violation detail url and add to url list to process w/ thread pool
152
- xray_violation_details_url=item['violation_details_url']
153
- xray_violation_urls_list.append(xray_violation_details_url)
154
- end
155
- end
156
-
157
- # iterate over url array adding to thread pool each url.
158
- # limit max workers to thread count to prevent overloading xray.
159
- thread_pool = Thread.pool(thread_count)
160
- thread_pool.process {
161
- for xray_violation_url in xray_violation_urls_list do
162
- pull_violation_details(xray_violation_url, @access_token)
163
- end
164
- }
165
- thread_pool.shutdown
166
-
167
- # reduce left violations by jump size (not all batches have full item count??)
168
- left_violations = left_violations - @batch_size
169
- if left_violations <= 0
170
- waiting_for_violations = true
171
- sleep(@wait_interval)
172
- else
173
- # Grab the next record to process for the violation details url
174
- waiting_for_violations = false
175
- offset_count = offset_count + 1
176
- xray_json={"filters": { "created_from": last_created_date_string }, "pagination": {"order_by": "created","limit": @batch_size , "offset": offset_count } }
177
- end
178
- end
179
- end
180
-
181
-
182
- # pull the last item create date from the pos_file return created_date_string
183
- def get_last_item_create_date()
184
- if(!(File.exist?(@pos_file)))
185
- @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")
186
94
  end
187
- 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
188
101
  end
189
102
 
190
103
  #call home functionality
191
- def call_home(jpd_url, access_token)
104
+ def call_home(jpd_url)
192
105
  call_home_json = { "productId": "jfrogLogAnalytics/v0.5.1", "features": [ { "featureId": "Platform/Xray" }, { "featureId": "Channel/xrayeventsiem" } ] }
193
106
  response = RestClient::Request.new(
194
107
  :method => :post,
195
108
  :url => jpd_url + "/artifactory/api/system/usage",
196
109
  :payload => call_home_json.to_json,
197
- :headers => { :accept => :json, :content_type => :json, Authorization:'Bearer ' + access_token }
198
- ).execute do |response, request, result|
199
- puts "Posting call home information"
200
- end
201
- end
202
-
203
- # queries the xray API for violations based upon the input json
204
- def get_xray_violations_detail(xray_violation_detail_url, access_token)
205
- response = RestClient::Request.new(
206
- :method => :get,
207
- :url => xray_violation_detail_url,
208
- headers: {Authorization:'Bearer ' + access_token}
209
- ).execute do |response, request, result|
210
- case response.code
211
- when 200
212
- return response.to_str
213
- else
214
- raise Fluent::StandardError, "Cannot reach Artifactory URL to pull Xray SIEM violations."
215
- end
216
- end
217
- end
218
-
219
-
220
- # queries the xray API for violations based upon the input json
221
- def get_xray_violations(xray_json, jpd_url, access_token)
222
- response = RestClient::Request.new(
223
- :method => :post,
224
- :url => jpd_url + "/xray/api/v1/violations",
225
- :payload => xray_json.to_json,
226
- :headers => { :accept => :json, :content_type => :json, Authorization:'Bearer ' + access_token }
110
+ :user => @username,
111
+ :password => @apikey,
112
+ :headers => { :accept => :json, :content_type => :json}
227
113
  ).execute do |response, request, result|
228
- case response.code
229
- when 200
230
- return response.to_str
231
- else
232
- raise Fluent::StandardError, "Cannot reach Artifactory URL to pull Xray SIEM violations."
233
- end
114
+ puts "Posting call home information"
234
115
  end
235
116
  end
236
117
 
237
- # normalizes Xray data according to common information models for all log-vendors
238
- def data_normalization(detailResp)
239
- detailResp_json = JSON.parse(detailResp)
240
- cve = []
241
- cvss_v2_list = []
242
- cvss_v3_list = []
243
- policy_list = []
244
- rule_list = []
245
- impacted_artifact_url_list = []
246
- if detailResp_json.key?('properties')
247
- properties = detailResp_json['properties']
248
- for index in 0..properties.length-1 do
249
- if properties[index].key?('cve')
250
- cve.push(properties[index]['cve'])
251
- end
252
- if properties[index].key?('cvss_v2')
253
- cvss_v2_list.push(properties[index]['cvss_v2'])
254
- end
255
- if properties[index].key?('cvss_v3')
256
- cvss_v3_list.push(properties[index]['cvss_v3'])
257
- end
258
- end
259
-
260
- detailResp_json["cve"] = cve.sort.reverse[0]
261
- cvss_v2 = cvss_v2_list.sort.reverse[0]
262
- cvss_v3 = cvss_v3_list.sort.reverse[0]
263
- if !cvss_v3.nil?
264
- cvss = cvss_v3
265
- elsif !cvss_v2.nil?
266
- cvss = cvss_v2
267
- end
268
- cvss_score = cvss[0..2]
269
- cvss_version = cvss.split(':')[1][0..2]
270
- detailResp_json["cvss_score"] = cvss_score
271
- detailResp_json["cvss_version"] = cvss_version
272
- end
273
-
274
- if detailResp_json.key?('matched_policies')
275
- matched_policies = detailResp_json['matched_policies']
276
- for index in 0..matched_policies.length-1 do
277
- if matched_policies[index].key?('policy')
278
- policy_list.push(matched_policies[index]['policy'])
279
- end
280
- if matched_policies[index].key?('rule')
281
- rule_list.push(matched_policies[index]['rule'])
282
- end
283
- end
284
- detailResp_json['policies'] = policy_list
285
- detailResp_json['rules'] = rule_list
286
- end
287
-
288
- impacted_artifacts = detailResp_json['impacted_artifacts']
289
- for impacted_artifact in impacted_artifacts do
290
- matchdata = impacted_artifact.match /default\/(?<repo_name>[^\/]*)\/(?<path>.*)/
291
- impacted_artifact_url = matchdata['repo_name'] + ":" + matchdata['path'] + " "
292
- 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")
293
126
  end
294
- detailResp_json['impacted_artifacts_url'] = impacted_artifact_url_list
295
- return detailResp_json
296
127
  end
297
128
 
298
- def pull_violation_details(xray_violation_detail_url, access_token)
299
- begin
300
- detailResp=get_xray_violations_detail(xray_violation_detail_url, access_token)
301
- time = Fluent::Engine.now
302
- detailResp_json = data_normalization(detailResp)
303
- router.emit(@tag, time, detailResp_json)
304
- rescue
305
- raise Fluent::StandardError, "Error pulling violation details url #{xray_violation_detail_url}"
306
- end
129
+ def get_recent_pos_file()
130
+ pos_file = @pos_file_path + "*.siem.pos"
131
+ return Dir.glob(pos_file).sort.last
307
132
  end
308
133
 
309
134
  end
310
135
  end
311
136
  end
137
+