fluent-plugin-jfrog-siem 0.1.7 → 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: 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
+