chelsea 0.0.23 → 0.0.28

Sign up to get free protection for your applications and to get access to all the features.
@@ -15,6 +15,7 @@
15
15
  #
16
16
 
17
17
  # frozen_string_literal: true
18
+
18
19
  require 'pastel'
19
20
  require 'bundler'
20
21
  require 'bundler/lockfile_parser'
@@ -31,11 +32,10 @@ module Chelsea
31
32
  # Class to collect and audit packages from a Gemfile.lock
32
33
  class Gems
33
34
  attr_accessor :deps
34
- def initialize(file:, verbose:, options: { 'format': 'text' })
35
+
36
+ def initialize(file:, verbose:, options: { format: 'text' }) # rubocop:disable Metrics/MethodLength
35
37
  @verbose = verbose
36
- unless File.file?(file) || file.nil?
37
- raise 'Gemfile.lock not found, check --file path'
38
- end
38
+ raise 'Gemfile.lock not found, check --file path' unless File.file?(file) || file.nil?
39
39
 
40
40
  _silence_stderr unless @verbose
41
41
 
@@ -52,7 +52,7 @@ module Chelsea
52
52
  # Audits depenencies using deps library and prints results
53
53
  # using formatter library
54
54
 
55
- def execute
55
+ def execute # rubocop:disable Metrics/MethodLength
56
56
  server_response, dependencies, reverse_dependencies = audit
57
57
  if dependencies.nil?
58
58
  _print_err 'No dependencies retrieved. Exiting.'
@@ -62,20 +62,19 @@ module Chelsea
62
62
  _print_success 'No vulnerability data retrieved from server. Exiting.'
63
63
  return
64
64
  end
65
- results = @formatter.get_results(server_response, reverse_dependencies)
65
+ results = @formatter.fetch_results(server_response, reverse_dependencies)
66
66
  @formatter.do_print(results)
67
67
 
68
68
  server_response.map { |r| r['vulnerabilities'].length.positive? }.any?
69
69
  end
70
70
 
71
71
  def collect_iq
72
- dependencies = @deps.dependencies
73
- dependencies
72
+ @deps.dependencies
74
73
  end
75
74
 
76
75
  # Runs through auditing algorithm, raising exceptions
77
76
  # on REST calls made by @deps.get_vulns
78
- def audit
77
+ def audit # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
79
78
  # This spinner management is out of control
80
79
  # we should wrap a block with start and stop messages,
81
80
  # or use a stack to ensure all spinners stop.
@@ -84,7 +83,7 @@ module Chelsea
84
83
  begin
85
84
  dependencies = @deps.dependencies
86
85
  spin.success('...done.')
87
- rescue StandardError => e
86
+ rescue StandardError
88
87
  spin.stop
89
88
  _print_err "Parsing dependency line #{gem} failed."
90
89
  end
@@ -100,16 +99,16 @@ module Chelsea
100
99
  begin
101
100
  server_response = @client.get_vulns(coordinates)
102
101
  spin.success('...done.')
103
- rescue SocketError => e
102
+ rescue SocketError
104
103
  spin.stop('...request failed.')
105
104
  _print_err 'Socket error getting data from OSS Index server.'
106
105
  rescue RestClient::RequestFailed => e
107
106
  spin.stop('...request failed.')
108
107
  _print_err "Error getting data from OSS Index server:#{e.response}."
109
- rescue RestClient::ResourceNotFound => e
108
+ rescue RestClient::ResourceNotFound
110
109
  spin.stop('...request failed.')
111
110
  _print_err 'Error getting data from OSS Index server. Resource not found.'
112
- rescue Errno::ECONNREFUSED => e
111
+ rescue Errno::ECONNREFUSED
113
112
  spin.stop('...request failed.')
114
113
  _print_err 'Error getting data from OSS Index server. Connection refused.'
115
114
  end
@@ -122,12 +121,12 @@ module Chelsea
122
121
  $stderr.reopen('/dev/null', 'w')
123
122
  end
124
123
 
125
- def _print_err(s)
126
- puts @pastel.red.bold(s)
124
+ def _print_err(msg)
125
+ puts @pastel.red.bold(msg)
127
126
  end
128
127
 
129
- def _print_success(s)
130
- puts @pastel.green.bold(s)
128
+ def _print_success(msg)
129
+ puts @pastel.green.bold(msg)
131
130
  end
132
131
  end
133
132
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # Copyright 2019-Present Sonatype Inc.
3
5
  #
@@ -17,12 +19,13 @@
17
19
  require 'rest-client'
18
20
  require 'json'
19
21
  require 'pastel'
22
+ require 'uri'
20
23
 
21
24
  require_relative 'spinner'
22
25
 
23
26
  module Chelsea
24
- class IQClient
25
-
27
+ # IQ audit operations
28
+ class IQClient # rubocop:disable Metrics/ClassLength
26
29
  DEFAULT_OPTIONS = {
27
30
  public_application_id: 'testapp',
28
31
  server_url: 'http://localhost:8070',
@@ -30,15 +33,16 @@ module Chelsea
30
33
  auth_token: 'admin123',
31
34
  internal_application_id: '',
32
35
  stage: 'build'
33
- }
36
+ }.freeze
37
+
34
38
  def initialize(options: DEFAULT_OPTIONS)
35
39
  @options = options
36
40
  @pastel = Pastel.new
37
41
  @spinner = Chelsea::Spinner.new
38
42
  end
39
43
 
40
- def post_sbom(sbom)
41
- spin = @spinner.spin_msg "Submitting sbom to Nexus IQ Server"
44
+ def post_sbom(sbom) # rubocop:disable Metrics/MethodLength
45
+ spin = @spinner.spin_msg 'Submitting sbom to Nexus IQ Server'
42
46
  @internal_application_id = _get_internal_application_id
43
47
  resource = RestClient::Resource.new(
44
48
  _api_url,
@@ -46,8 +50,8 @@ module Chelsea
46
50
  password: @options[:auth_token]
47
51
  )
48
52
  res = resource.post sbom.to_s, _headers.merge(content_type: 'application/xml')
49
- unless res.code != 202
50
- spin.success("...done.")
53
+ if res.code == 202
54
+ spin.success('...done.')
51
55
  status_url(res)
52
56
  else
53
57
  spin.stop('...request failed.')
@@ -61,33 +65,51 @@ module Chelsea
61
65
  end
62
66
 
63
67
  def poll_status(url)
64
- spin = @spinner.spin_msg "Polling Nexus IQ Server for results"
68
+ spin = @spinner.spin_msg 'Polling Nexus IQ Server for results'
65
69
  loop do
66
- begin
67
- res = _poll_iq_server(url)
68
- if res.code == 200
69
- spin.success("...done.")
70
- _handle_response(res)
71
- break
72
- end
73
- rescue
74
- sleep(1)
70
+ res = _poll_iq_server(url)
71
+ if res.code == 200
72
+ spin.success('...done.')
73
+ return _handle_response(res)
75
74
  end
75
+ rescue StandardError
76
+ sleep(1)
76
77
  end
77
78
  end
78
79
 
80
+ # colors to use when printing message
81
+ COLOR_FAILURE = 31
82
+ COLOR_WARNING = 33 # want yellow, but doesn't appear to print
83
+ COLOR_NONE = 32
84
+ # Known policy actions
85
+ POLICY_ACTION_FAILURE = 'Failure'
86
+ POLICY_ACTION_WARNING = 'Warning'
87
+ POLICY_ACTION_NONE = 'None'
88
+
79
89
  private
80
90
 
81
- def _handle_response(res)
91
+ def _handle_response(res) # rubocop:disable Metrics/MethodLength
82
92
  res = JSON.parse(res.body)
83
- unless res['policyAction'] == 'Failure'
84
- puts @pastel.white.bold("Hi! Chelsea here, no policy violations for this audit!")
85
- puts @pastel.white.bold("Report URL: #{res['reportHtmlUrl']}")
86
- exit 0
93
+ # get absolute report url
94
+ absolute_report_html_url = URI.join(@options[:server_url], res['reportHtmlUrl'])
95
+
96
+ case res['policyAction']
97
+ when POLICY_ACTION_FAILURE
98
+ ['Hi! Chelsea here, you have some policy violations to clean up!'\
99
+ "\nReport URL: #{absolute_report_html_url}",
100
+ COLOR_FAILURE, 1]
101
+ when POLICY_ACTION_WARNING
102
+ ['Hi! Chelsea here, you have some policy warnings to peck at!'\
103
+ "\nReport URL: #{absolute_report_html_url}",
104
+ COLOR_WARNING, 0]
105
+ when POLICY_ACTION_NONE
106
+ ['Hi! Chelsea here, no policy violations for this audit!'\
107
+ "\nReport URL: #{absolute_report_html_url}",
108
+ COLOR_NONE, 0]
87
109
  else
88
- puts @pastel.red.bold("Hi! Chelsea here, you have some policy violations to clean up!")
89
- puts @pastel.red.bold("Report URL: #{res['reportHtmlUrl']}")
90
- exit 1
110
+ ['Hi! Chelsea here, no policy violations for this audit, but unknown policy action!'\
111
+ "\nReport URL: #{absolute_report_html_url}",
112
+ COLOR_FAILURE, 1]
91
113
  end
92
114
  end
93
115
 
@@ -115,33 +137,37 @@ module Chelsea
115
137
  res['statusUrl']
116
138
  end
117
139
 
118
- private
119
-
120
- def _poll_status
140
+ def _poll_status # rubocop:disable Metrics/MethodLength
121
141
  return unless @status_url
122
142
 
123
143
  loop do
124
- begin
125
- res = check_status(@status_url)
126
- if res.code == 200
127
- puts JSON.parse(res.body)
128
- break
129
- end
130
- rescue RestClient::ResourceNotFound => _e
131
- print '.'
132
- sleep(1)
144
+ res = check_status(@status_url)
145
+ if res.code == 200
146
+ puts JSON.parse(res.body)
147
+ break
133
148
  end
149
+ rescue RestClient::ResourceNotFound => _e
150
+ print '.'
151
+ sleep(1)
134
152
  end
135
153
  end
136
154
 
137
- def _get_internal_application_id
155
+ def _get_internal_application_id # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
138
156
  resource = RestClient::Resource.new(
139
157
  _internal_application_id_api_url,
140
158
  user: @options[:username],
141
159
  password: @options[:auth_token]
142
160
  )
143
161
  res = resource.get _headers
162
+ if res.code != 200
163
+ puts "failure reading application id: #{@options[:public_application_id]}. response status: #{res.code}"
164
+ return
165
+ end
144
166
  body = JSON.parse(res)
167
+ if body['applications'].empty?
168
+ puts "failed to get internal application id for IQ application id: #{@options[:public_application_id]}"
169
+ return
170
+ end
145
171
  body['applications'][0]['id']
146
172
  end
147
173
 
@@ -150,7 +176,9 @@ module Chelsea
150
176
  end
151
177
 
152
178
  def _api_url
179
+ # rubocop:disable Layout/LineLength
153
180
  "#{@options[:server_url]}/api/v2/scan/applications/#{@internal_application_id}/sources/chelsea?stageId=#{@options[:stage]}"
181
+ # rubocop:enable Layout/LineLength
154
182
  end
155
183
 
156
184
  def _internal_application_id_api_url
@@ -21,11 +21,12 @@ require 'rest-client'
21
21
  require_relative 'db'
22
22
 
23
23
  module Chelsea
24
+ # OSS Index audit operations
24
25
  class OSSIndex
25
26
  DEFAULT_OPTIONS = {
26
27
  oss_index_username: '',
27
28
  oss_index_user_token: ''
28
- }
29
+ }.freeze
29
30
  def initialize(options: DEFAULT_OPTIONS)
30
31
  @oss_index_user_name = options[:oss_index_user_name]
31
32
  @oss_index_user_token = options[:oss_index_user_token]
@@ -37,13 +38,11 @@ module Chelsea
37
38
 
38
39
  def get_vulns(coordinates)
39
40
  remaining_coordinates, cached_server_response = _cache(coordinates)
40
- unless remaining_coordinates['coordinates'].count.positive?
41
- return cached_server_response
42
- end
41
+ return cached_server_response unless remaining_coordinates['coordinates'].count.positive?
43
42
 
44
43
  remaining_coordinates['coordinates'].each_slice(128).to_a.each do |coords|
45
44
  res_json = JSON.parse(call_oss_index({ 'coordinates' => coords }))
46
- cached_server_response = cached_server_response.concat(res_json)
45
+ cached_server_response.concat(res_json)
47
46
  @db.save_values_to_db(res_json)
48
47
  end
49
48
 
@@ -57,15 +56,15 @@ module Chelsea
57
56
 
58
57
  private
59
58
 
60
- def _cache(coordinates)
59
+ def _cache(coordinates) # rubocop:disable Metrics/MethodLength
61
60
  new_coords = { 'coordinates' => [] }
62
61
  cached_server_response = []
63
62
  coordinates['coordinates'].each do |coord|
64
63
  record = @db.get_cached_value_from_db(coord)
65
- if !record.nil?
66
- cached_server_response << record
67
- else
64
+ if record.nil?
68
65
  new_coords['coordinates'].push(coord)
66
+ else
67
+ cached_server_response << record
69
68
  end
70
69
  end
71
70
  [new_coords, cached_server_response]
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # Copyright 2019-Present Sonatype Inc.
3
5
  #
@@ -18,8 +20,9 @@ require 'tty-spinner'
18
20
  require 'pastel'
19
21
 
20
22
  module Chelsea
23
+ # the spinner we use in Chelsea, needs work
21
24
  class Spinner
22
- def initialize()
25
+ def initialize
23
26
  @pastel = Pastel.new
24
27
  end
25
28
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # Copyright 2019-Present Sonatype Inc.
3
5
  #
@@ -15,5 +17,5 @@
15
17
  #
16
18
 
17
19
  module Chelsea
18
- VERSION = '0.0.23'.freeze
20
+ VERSION = '0.0.28'
19
21
  end
@@ -9,4 +9,5 @@
9
9
  <exclude>src/chelsea.gemspec</exclude>
10
10
  <exclude>src/CODE_OF_CONDUCT.md</exclude>
11
11
  <exclude>src/CONTRIBUTORS.md</exclude>
12
+ <exclude>src/SECURITY.md</exclude>
12
13
  </excludes>
metadata CHANGED
@@ -1,57 +1,49 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chelsea
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.23
4
+ version: 0.0.28
5
5
  platform: ruby
6
6
  authors:
7
7
  - Allister Beharry
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-07 00:00:00.000000000 Z
11
+ date: 2021-01-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: tty-font
14
+ name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: 0.5.0
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
17
+ - - ">="
25
18
  - !ruby/object:Gem::Version
26
- version: 0.5.0
27
- - !ruby/object:Gem::Dependency
28
- name: tty-spinner
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
19
+ version: 1.2.0
20
+ - - "<"
32
21
  - !ruby/object:Gem::Version
33
- version: 0.9.3
22
+ version: '3'
34
23
  type: :runtime
35
24
  prerelease: false
36
25
  version_requirements: !ruby/object:Gem::Requirement
37
26
  requirements:
38
- - - "~>"
27
+ - - ">="
39
28
  - !ruby/object:Gem::Version
40
- version: 0.9.3
29
+ version: 1.2.0
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '3'
41
33
  - !ruby/object:Gem::Dependency
42
- name: slop
34
+ name: ox
43
35
  requirement: !ruby/object:Gem::Requirement
44
36
  requirements:
45
37
  - - "~>"
46
38
  - !ruby/object:Gem::Version
47
- version: 4.8.1
39
+ version: 2.13.2
48
40
  type: :runtime
49
41
  prerelease: false
50
42
  version_requirements: !ruby/object:Gem::Requirement
51
43
  requirements:
52
44
  - - "~>"
53
45
  - !ruby/object:Gem::Version
54
- version: 4.8.1
46
+ version: 2.13.2
55
47
  - !ruby/object:Gem::Dependency
56
48
  name: pastel
57
49
  requirement: !ruby/object:Gem::Requirement
@@ -81,144 +73,137 @@ dependencies:
81
73
  - !ruby/object:Gem::Version
82
74
  version: 2.0.2
83
75
  - !ruby/object:Gem::Dependency
84
- name: bundler
76
+ name: slop
85
77
  requirement: !ruby/object:Gem::Requirement
86
78
  requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: 1.2.0
90
- - - "<"
79
+ - - "~>"
91
80
  - !ruby/object:Gem::Version
92
- version: '3'
81
+ version: 4.8.1
93
82
  type: :runtime
94
83
  prerelease: false
95
84
  version_requirements: !ruby/object:Gem::Requirement
96
85
  requirements:
97
- - - ">="
98
- - !ruby/object:Gem::Version
99
- version: 1.2.0
100
- - - "<"
86
+ - - "~>"
101
87
  - !ruby/object:Gem::Version
102
- version: '3'
88
+ version: 4.8.1
103
89
  - !ruby/object:Gem::Dependency
104
- name: ox
90
+ name: tty-font
105
91
  requirement: !ruby/object:Gem::Requirement
106
92
  requirements:
107
93
  - - "~>"
108
94
  - !ruby/object:Gem::Version
109
- version: 2.13.2
95
+ version: 0.5.0
110
96
  type: :runtime
111
97
  prerelease: false
112
98
  version_requirements: !ruby/object:Gem::Requirement
113
99
  requirements:
114
100
  - - "~>"
115
101
  - !ruby/object:Gem::Version
116
- version: 2.13.2
102
+ version: 0.5.0
117
103
  - !ruby/object:Gem::Dependency
118
- name: tty-table
104
+ name: tty-spinner
119
105
  requirement: !ruby/object:Gem::Requirement
120
106
  requirements:
121
107
  - - "~>"
122
108
  - !ruby/object:Gem::Version
123
- version: 0.11.0
109
+ version: 0.9.3
124
110
  type: :runtime
125
111
  prerelease: false
126
112
  version_requirements: !ruby/object:Gem::Requirement
127
113
  requirements:
128
114
  - - "~>"
129
115
  - !ruby/object:Gem::Version
130
- version: 0.11.0
116
+ version: 0.9.3
131
117
  - !ruby/object:Gem::Dependency
132
- name: rake
118
+ name: tty-table
133
119
  requirement: !ruby/object:Gem::Requirement
134
120
  requirements:
135
121
  - - "~>"
136
122
  - !ruby/object:Gem::Version
137
- version: '12.3'
138
- type: :development
123
+ version: 0.11.0
124
+ type: :runtime
139
125
  prerelease: false
140
126
  version_requirements: !ruby/object:Gem::Requirement
141
127
  requirements:
142
128
  - - "~>"
143
129
  - !ruby/object:Gem::Version
144
- version: '12.3'
130
+ version: 0.11.0
145
131
  - !ruby/object:Gem::Dependency
146
- name: rspec
132
+ name: byebug
147
133
  requirement: !ruby/object:Gem::Requirement
148
134
  requirements:
149
135
  - - "~>"
150
136
  - !ruby/object:Gem::Version
151
- version: '3.0'
137
+ version: 11.1.2
152
138
  type: :development
153
139
  prerelease: false
154
140
  version_requirements: !ruby/object:Gem::Requirement
155
141
  requirements:
156
142
  - - "~>"
157
143
  - !ruby/object:Gem::Version
158
- version: '3.0'
144
+ version: 11.1.2
159
145
  - !ruby/object:Gem::Dependency
160
- name: rspec_junit_formatter
146
+ name: rake
161
147
  requirement: !ruby/object:Gem::Requirement
162
148
  requirements:
163
149
  - - "~>"
164
150
  - !ruby/object:Gem::Version
165
- version: 0.4.1
151
+ version: '12.3'
166
152
  type: :development
167
153
  prerelease: false
168
154
  version_requirements: !ruby/object:Gem::Requirement
169
155
  requirements:
170
156
  - - "~>"
171
157
  - !ruby/object:Gem::Version
172
- version: 0.4.1
158
+ version: '12.3'
173
159
  - !ruby/object:Gem::Dependency
174
- name: webmock
160
+ name: rspec
175
161
  requirement: !ruby/object:Gem::Requirement
176
162
  requirements:
177
163
  - - "~>"
178
164
  - !ruby/object:Gem::Version
179
- version: 3.8.3
165
+ version: '3.0'
180
166
  type: :development
181
167
  prerelease: false
182
168
  version_requirements: !ruby/object:Gem::Requirement
183
169
  requirements:
184
170
  - - "~>"
185
171
  - !ruby/object:Gem::Version
186
- version: 3.8.3
172
+ version: '3.0'
187
173
  - !ruby/object:Gem::Dependency
188
- name: byebug
174
+ name: rspec_junit_formatter
189
175
  requirement: !ruby/object:Gem::Requirement
190
176
  requirements:
191
177
  - - "~>"
192
178
  - !ruby/object:Gem::Version
193
- version: 11.1.2
179
+ version: 0.4.1
194
180
  type: :development
195
181
  prerelease: false
196
182
  version_requirements: !ruby/object:Gem::Requirement
197
183
  requirements:
198
184
  - - "~>"
199
185
  - !ruby/object:Gem::Version
200
- version: 11.1.2
186
+ version: 0.4.1
201
187
  - !ruby/object:Gem::Dependency
202
- name: pry
188
+ name: webmock
203
189
  requirement: !ruby/object:Gem::Requirement
204
190
  requirements:
205
- - - ">="
191
+ - - "~>"
206
192
  - !ruby/object:Gem::Version
207
- version: '0'
193
+ version: 3.8.3
208
194
  type: :development
209
195
  prerelease: false
210
196
  version_requirements: !ruby/object:Gem::Requirement
211
197
  requirements:
212
- - - ">="
198
+ - - "~>"
213
199
  - !ruby/object:Gem::Version
214
- version: '0'
200
+ version: 3.8.3
215
201
  description:
216
202
  email:
217
203
  - allister.beharry@gmail.com
218
204
  executables:
219
205
  - chelsea
220
206
  - console
221
- - setup
222
207
  extensions: []
223
208
  extra_rdoc_files: []
224
209
  files:
@@ -232,6 +217,7 @@ files:
232
217
  - ".github/pull_request_template.md"
233
218
  - ".gitignore"
234
219
  - ".rspec"
220
+ - ".rubocop.yml"
235
221
  - ".vscode/launch.json"
236
222
  - CODE_OF_CONDUCT.md
237
223
  - CONTRIBUTORS.md
@@ -242,9 +228,9 @@ files:
242
228
  - LICENSE
243
229
  - README.md
244
230
  - Rakefile
231
+ - SECURITY.md
245
232
  - bin/chelsea
246
233
  - bin/console
247
- - bin/setup
248
234
  - chelsea
249
235
  - chelsea.gemspec
250
236
  - docs/images/chelsea.png
@@ -282,7 +268,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
282
268
  requirements:
283
269
  - - ">="
284
270
  - !ruby/object:Gem::Version
285
- version: '0'
271
+ version: 2.6.6
286
272
  required_rubygems_version: !ruby/object:Gem::Requirement
287
273
  requirements:
288
274
  - - ">="