chelsea 0.0.27 → 0.0.32

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #
2
4
  # Copyright 2019-Present Sonatype Inc.
3
5
  #
@@ -18,12 +20,14 @@ require 'json'
18
20
  require_relative 'formatter'
19
21
 
20
22
  module Chelsea
23
+ # Produce output in json format
21
24
  class JsonFormatter < Formatter
22
25
  def initialize(options)
26
+ super()
23
27
  @options = options
24
28
  end
25
29
 
26
- def get_results(server_response, reverse_deps)
30
+ def fetch_results(server_response, _reverse_deps)
27
31
  server_response.to_json
28
32
  end
29
33
 
@@ -1,3 +1,6 @@
1
+ # rubocop:disable Style/FrozenStringLiteralComment
2
+ # rubocop:enable Style/FrozenStringLiteralComment
3
+
1
4
  #
2
5
  # Copyright 2019-Present Sonatype Inc.
3
6
  #
@@ -19,13 +22,16 @@ require 'tty-table'
19
22
  require_relative 'formatter'
20
23
 
21
24
  module Chelsea
25
+ # Produce output in text format
22
26
  class TextFormatter < Formatter
23
27
  def initialize(options)
28
+ super()
24
29
  @options = options
25
30
  @pastel = Pastel.new
26
31
  end
27
32
 
28
- def get_results(server_response, reverse_dependencies)
33
+ # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/AbcSize
34
+ def fetch_results(server_response, reverse_dependencies) # rubocop:disable Metrics/MethodLength
29
35
  response = ''
30
36
  if @options[:verbose]
31
37
  response += "\n"\
@@ -65,6 +71,7 @@ module Chelsea
65
71
  response += table.render(:unicode)
66
72
  response
67
73
  end
74
+ # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/AbcSize
68
75
 
69
76
  def do_print(results)
70
77
  puts results
@@ -109,9 +116,7 @@ module Chelsea
109
116
  def _get_reverse_deps(coords, name)
110
117
  coords.each_with_object('') do |dep, s|
111
118
  dep.each do |gran|
112
- if gran.class == String && !gran.include?(name)
113
- s << "\tRequired by: #{gran}\n"
114
- end
119
+ s << "\tRequired by: #{gran}\n" if gran.instance_of?(String) && !gran.include?(name)
115
120
  end
116
121
  end
117
122
  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,14 @@
17
19
  require 'ox'
18
20
  require_relative 'formatter'
19
21
  module Chelsea
22
+ # Produce output in xml format
20
23
  class XMLFormatter < Formatter
21
24
  def initialize(options)
25
+ super()
22
26
  @options = options
23
27
  end
24
28
 
25
- def get_results(server_response, reverse_deps)
29
+ def fetch_results(server_response, _reverse_deps) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
26
30
  doc = Ox::Document.new
27
31
  instruct = Ox::Instruct.new(:xml)
28
32
  instruct[:version] = '1.0'
@@ -37,13 +41,13 @@ module Chelsea
37
41
 
38
42
  server_response.each do |coord|
39
43
  testcase = Ox::Element.new('testcase')
40
- testcase[:classname] = coord["coordinates"]
41
- testcase[:name] = coord["coordinates"]
44
+ testcase[:classname] = coord['coordinates']
45
+ testcase[:name] = coord['coordinates']
42
46
 
43
47
  if coord['vulnerabilities'].length.positive?
44
48
  failure = Ox::Element.new('failure')
45
- failure[:type] = "Vulnerable Dependency"
46
- failure << get_vulnerability_block(coord["vulnerabilities"])
49
+ failure[:type] = 'Vulnerable Dependency'
50
+ failure << get_vulnerability_block(coord['vulnerabilities'])
47
51
  testcase << failure
48
52
  testsuite << testcase
49
53
  elsif @options[:verbose]
@@ -58,20 +62,20 @@ module Chelsea
58
62
  puts Ox.dump(results)
59
63
  end
60
64
 
61
- def get_vulnerability_block(vulnerabilities)
62
- vulnBlock = String.new
65
+ def get_vulnerability_block(vulnerabilities) # rubocop:disable Metrics/MethodLength
66
+ vuln_block = ''
63
67
  vulnerabilities.each do |vuln|
64
- vulnBlock += "Vulnerability Title: #{vuln["title"]}\n"\
65
- "ID: #{vuln["id"]}\n"\
66
- "Description: #{vuln["description"]}\n"\
67
- "CVSS Score: #{vuln["cvssScore"]}\n"\
68
- "CVSS Vector: #{vuln["cvssVector"]}\n"\
69
- "CVE: #{vuln["cve"]}\n"\
70
- "Reference: #{vuln["reference"]}"\
68
+ vuln_block += "Vulnerability Title: #{vuln['title']}\n"\
69
+ "ID: #{vuln['id']}\n"\
70
+ "Description: #{vuln['description']}\n"\
71
+ "CVSS Score: #{vuln['cvssScore']}\n"\
72
+ "CVSS Vector: #{vuln['cvssVector']}\n"\
73
+ "CVE: #{vuln['cve']}\n"\
74
+ "Reference: #{vuln['reference']}"\
71
75
  "\n"
72
76
  end
73
-
74
- vulnBlock
77
+
78
+ vuln_block
75
79
  end
76
80
  end
77
81
  end
data/lib/chelsea/gems.rb CHANGED
@@ -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
  #
@@ -22,8 +24,8 @@ require 'uri'
22
24
  require_relative 'spinner'
23
25
 
24
26
  module Chelsea
25
- class IQClient
26
-
27
+ # IQ audit operations
28
+ class IQClient # rubocop:disable Metrics/ClassLength
27
29
  DEFAULT_OPTIONS = {
28
30
  public_application_id: 'testapp',
29
31
  server_url: 'http://localhost:8070',
@@ -31,7 +33,7 @@ module Chelsea
31
33
  auth_token: 'admin123',
32
34
  internal_application_id: '',
33
35
  stage: 'build'
34
- }
36
+ }.freeze
35
37
 
36
38
  def initialize(options: DEFAULT_OPTIONS)
37
39
  @options = options
@@ -39,8 +41,8 @@ module Chelsea
39
41
  @spinner = Chelsea::Spinner.new
40
42
  end
41
43
 
42
- def post_sbom(sbom)
43
- 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'
44
46
  @internal_application_id = _get_internal_application_id
45
47
  resource = RestClient::Resource.new(
46
48
  _api_url,
@@ -48,12 +50,12 @@ module Chelsea
48
50
  password: @options[:auth_token]
49
51
  )
50
52
  res = resource.post sbom.to_s, _headers.merge(content_type: 'application/xml')
51
- if res.code != 202
53
+ if res.code == 202
54
+ spin.success('...done.')
55
+ status_url(res)
56
+ else
52
57
  spin.stop('...request failed.')
53
58
  nil
54
- else
55
- spin.success("...done.")
56
- status_url(res)
57
59
  end
58
60
  end
59
61
 
@@ -63,17 +65,15 @@ module Chelsea
63
65
  end
64
66
 
65
67
  def poll_status(url)
66
- spin = @spinner.spin_msg "Polling Nexus IQ Server for results"
68
+ spin = @spinner.spin_msg 'Polling Nexus IQ Server for results'
67
69
  loop do
68
- begin
69
- res = _poll_iq_server(url)
70
- if res.code == 200
71
- spin.success("...done.")
72
- return _handle_response(res)
73
- end
74
- rescue
75
- sleep(1)
70
+ res = _poll_iq_server(url)
71
+ if res.code == 200
72
+ spin.success('...done.')
73
+ return _handle_response(res)
76
74
  end
75
+ rescue StandardError
76
+ sleep(1)
77
77
  end
78
78
  end
79
79
 
@@ -88,28 +88,28 @@ module Chelsea
88
88
 
89
89
  private
90
90
 
91
- def _handle_response(res)
91
+ def _handle_response(res) # rubocop:disable Metrics/MethodLength
92
92
  res = JSON.parse(res.body)
93
93
  # get absolute report url
94
94
  absolute_report_html_url = URI.join(@options[:server_url], res['reportHtmlUrl'])
95
95
 
96
96
  case res['policyAction']
97
97
  when POLICY_ACTION_FAILURE
98
- return "Hi! Chelsea here, you have some policy violations to clean up!"\
98
+ ['Hi! Chelsea here, you have some policy violations to clean up!'\
99
99
  "\nReport URL: #{absolute_report_html_url}",
100
- COLOR_FAILURE, 1
100
+ COLOR_FAILURE, 1]
101
101
  when POLICY_ACTION_WARNING
102
- return "Hi! Chelsea here, you have some policy warnings to peck at!"\
102
+ ['Hi! Chelsea here, you have some policy warnings to peck at!'\
103
103
  "\nReport URL: #{absolute_report_html_url}",
104
- COLOR_WARNING, 0
104
+ COLOR_WARNING, 0]
105
105
  when POLICY_ACTION_NONE
106
- return "Hi! Chelsea here, no policy violations for this audit!"\
106
+ ['Hi! Chelsea here, no policy violations for this audit!'\
107
107
  "\nReport URL: #{absolute_report_html_url}",
108
- COLOR_NONE, 0
108
+ COLOR_NONE, 0]
109
109
  else
110
- return "Hi! Chelsea here, no policy violations for this audit, but unknown policy action!"\
110
+ ['Hi! Chelsea here, no policy violations for this audit, but unknown policy action!'\
111
111
  "\nReport URL: #{absolute_report_html_url}",
112
- COLOR_FAILURE, 1
112
+ COLOR_FAILURE, 1]
113
113
  end
114
114
  end
115
115
 
@@ -137,26 +137,22 @@ module Chelsea
137
137
  res['statusUrl']
138
138
  end
139
139
 
140
- private
141
-
142
- def _poll_status
140
+ def _poll_status # rubocop:disable Metrics/MethodLength
143
141
  return unless @status_url
144
142
 
145
143
  loop do
146
- begin
147
- res = check_status(@status_url)
148
- if res.code == 200
149
- puts JSON.parse(res.body)
150
- break
151
- end
152
- rescue RestClient::ResourceNotFound => _e
153
- print '.'
154
- sleep(1)
144
+ res = check_status(@status_url)
145
+ if res.code == 200
146
+ puts JSON.parse(res.body)
147
+ break
155
148
  end
149
+ rescue RestClient::ResourceNotFound => _e
150
+ print '.'
151
+ sleep(1)
156
152
  end
157
153
  end
158
154
 
159
- def _get_internal_application_id
155
+ def _get_internal_application_id # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
160
156
  resource = RestClient::Resource.new(
161
157
  _internal_application_id_api_url,
162
158
  user: @options[:username],
@@ -164,7 +160,7 @@ module Chelsea
164
160
  )
165
161
  res = resource.get _headers
166
162
  if res.code != 200
167
- puts "failed to get internal application id for IQ application id: #{@options[:public_application_id]}. response status: #{res.code}"
163
+ puts "failure reading application id: #{@options[:public_application_id]}. response status: #{res.code}"
168
164
  return
169
165
  end
170
166
  body = JSON.parse(res)
@@ -180,7 +176,9 @@ module Chelsea
180
176
  end
181
177
 
182
178
  def _api_url
179
+ # rubocop:disable Layout/LineLength
183
180
  "#{@options[:server_url]}/api/v2/scan/applications/#{@internal_application_id}/sources/chelsea?stageId=#{@options[:stage]}"
181
+ # rubocop:enable Layout/LineLength
184
182
  end
185
183
 
186
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]