quayio-scanner 0.1.5 → 0.2.3

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: 5df176d74b68e0b31a5408b2be36bac9c0cb2171650acb160cfedd90a143de2c
4
- data.tar.gz: aab6b7098d7a848294613428d8b514589c949f5d9f1fdf9041af06932d12856c
3
+ metadata.gz: ec3e0ce31e72f8fb58ce5bb62ec17af8395f8cbb0dfe6825bd8409e8388167a3
4
+ data.tar.gz: af37eec22d47077ad5c6cdb761b18071864ab628d459b15ed7130c645a09edc4
5
5
  SHA512:
6
- metadata.gz: 1da63def2a66714fff1b69b709150b444a0b149f7da03c1a9bcdec87f895a1b73a44887d9ded63c928dca958f5edc82ecc06a074957c3103f96958dccb0f2183
7
- data.tar.gz: a0c809340d55c456aa639ecebe00926722ece6f156efee325f713fcb25ef371ca8cc44ad828665767175f31fce558300665eb976ebbb8d9d8ac346d386cd1b96
6
+ metadata.gz: 194cca2abb4781442a8730a9ad0afb5097bc0e63d9dcd1a4c1dc0c92c6832af5020fd3e8dceb44fa5cd9c56da8bff986669146cd2ba8c141c165203fa5d09ee2
7
+ data.tar.gz: 4ac42a474343fae8c5ce01141cf85ebf514a3d37050fc93f28f7cb5202c231ab1976b94112f7aff278a00c3fac3082a7f1f454e0291ac60b9e7be764689a8d1c
data/.gitignore CHANGED
@@ -1,6 +1,5 @@
1
1
  /.bundle/
2
2
  /.yardoc
3
- /Gemfile.lock
4
3
  /_yardoc/
5
4
  /coverage/
6
5
  /doc/
data/.rubocop.yml ADDED
@@ -0,0 +1,14 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.3
3
+
4
+ Style/FrozenStringLiteralComment:
5
+ Enabled: false
6
+
7
+ Style/Documentation:
8
+ Enabled: false
9
+
10
+ Metrics/MethodLength:
11
+ Max: 50
12
+
13
+ Metrics/BlockLength:
14
+ Max: 200
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.7.0
data/Gemfile.lock ADDED
@@ -0,0 +1,86 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ quayio-scanner (0.2.3)
5
+ docker-api (~> 1.33)
6
+ rest-client (~> 2.1)
7
+ sensu-plugin (~> 4.0)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ ast (2.4.1)
13
+ diff-lcs (1.4.4)
14
+ docker-api (1.34.2)
15
+ excon (>= 0.47.0)
16
+ multi_json
17
+ domain_name (0.5.20190701)
18
+ unf (>= 0.0.5, < 1.0.0)
19
+ excon (0.85.0)
20
+ http-accept (1.7.0)
21
+ http-cookie (1.0.4)
22
+ domain_name (~> 0.5)
23
+ json (2.5.1)
24
+ mime-types (3.3.1)
25
+ mime-types-data (~> 3.2015)
26
+ mime-types-data (3.2021.0704)
27
+ mixlib-cli (1.7.0)
28
+ multi_json (1.15.0)
29
+ netrc (0.11.0)
30
+ parallel (1.19.2)
31
+ parser (2.7.2.0)
32
+ ast (~> 2.4.1)
33
+ rainbow (3.0.0)
34
+ rake (10.5.0)
35
+ regexp_parser (1.8.2)
36
+ rest-client (2.1.0)
37
+ http-accept (>= 1.7.0, < 2.0)
38
+ http-cookie (>= 1.0.2, < 2.0)
39
+ mime-types (>= 1.16, < 4.0)
40
+ netrc (~> 0.8)
41
+ rexml (3.2.4)
42
+ rspec (3.9.0)
43
+ rspec-core (~> 3.9.0)
44
+ rspec-expectations (~> 3.9.0)
45
+ rspec-mocks (~> 3.9.0)
46
+ rspec-core (3.9.3)
47
+ rspec-support (~> 3.9.3)
48
+ rspec-expectations (3.9.3)
49
+ diff-lcs (>= 1.2.0, < 2.0)
50
+ rspec-support (~> 3.9.0)
51
+ rspec-mocks (3.9.1)
52
+ diff-lcs (>= 1.2.0, < 2.0)
53
+ rspec-support (~> 3.9.0)
54
+ rspec-support (3.9.4)
55
+ rubocop (0.93.1)
56
+ parallel (~> 1.10)
57
+ parser (>= 2.7.1.5)
58
+ rainbow (>= 2.2.2, < 4.0)
59
+ regexp_parser (>= 1.8)
60
+ rexml
61
+ rubocop-ast (>= 0.6.0)
62
+ ruby-progressbar (~> 1.7)
63
+ unicode-display_width (>= 1.4.0, < 2.0)
64
+ rubocop-ast (1.1.0)
65
+ parser (>= 2.7.1.5)
66
+ ruby-progressbar (1.10.1)
67
+ sensu-plugin (4.0.0)
68
+ json (< 3.0.0)
69
+ mixlib-cli (~> 1.5)
70
+ unf (0.1.4)
71
+ unf_ext
72
+ unf_ext (0.0.7.7)
73
+ unicode-display_width (1.7.0)
74
+
75
+ PLATFORMS
76
+ ruby
77
+
78
+ DEPENDENCIES
79
+ bundler (~> 2.2)
80
+ quayio-scanner!
81
+ rake (~> 10.0)
82
+ rspec (~> 3.7)
83
+ rubocop (~> 0.49)
84
+
85
+ BUNDLED WITH
86
+ 2.2.23
data/README.md CHANGED
@@ -26,3 +26,7 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/abouts
26
26
  ## License
27
27
 
28
28
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
29
+
30
+ ## Security
31
+
32
+ * [Snyk](https://app.snyk.io/org/about-source/project/6eb2d381-87e7-49c4-a47f-ccad97f33ae3)
@@ -44,8 +44,9 @@ class CheckContainerVulnerabilities < Sensu::Plugin::Check::CLI
44
44
  proc: proc { |w| w.split(',') }
45
45
 
46
46
  def run
47
- status, message = Quayio::Scanner::Check.new(
48
- config[:docker_url], config[:quayio_token], config[:whitelist]).run
47
+ status, message = Quayio::Scanner::Check.new(config[:docker_url],
48
+ config[:quayio_token],
49
+ config[:whitelist]).run
49
50
 
50
51
  if status == :ok
51
52
  ok message
@@ -1,5 +1,7 @@
1
- require 'quayio/scanner/version'
2
1
  require 'quayio/scanner/check'
2
+ require 'quayio/scanner/image'
3
+ require 'quayio/scanner/repository'
4
+ require 'quayio/scanner/version'
3
5
 
4
6
  module Quayio
5
7
  module Scanner
@@ -1,19 +1,10 @@
1
- require 'quayio/scanner/image'
2
1
  require 'docker'
3
2
 
4
3
  module Quayio
5
4
  module Scanner
6
- class Check < Struct.new(:docker_url, :quayio_token, :whitelist)
5
+ Check = Struct.new(:docker_url, :quayio_token, :whitelist) do
7
6
  def run
8
7
  Docker.url = docker_url
9
- containers = Docker::Container.all
10
- .map { |dc| dc.json['Config']['Image'] }
11
- .uniq
12
-
13
- vulnerable_images = containers
14
- .map { |container| Image.new(container, quayio_token, whitelist) }
15
- .select(&:vulnerable?)
16
- .map(&:name)
17
8
 
18
9
  if vulnerable_images.empty?
19
10
  [:ok, "#{containers.size} Containers are ok"]
@@ -21,6 +12,22 @@ module Quayio
21
12
  [:critical, "The images are insecure: #{vulnerable_images.join(', ')}"]
22
13
  end
23
14
  end
15
+
16
+ private
17
+
18
+ def containers
19
+ Docker::Container
20
+ .all
21
+ .map { |dc| dc.json['Config']['Image'] }
22
+ .uniq
23
+ end
24
+
25
+ def vulnerable_images
26
+ containers
27
+ .map { |container| Image.new(container, quayio_token, whitelist) }
28
+ .select(&:vulnerable?)
29
+ .map(&:name)
30
+ end
24
31
  end
25
32
  end
26
33
  end
@@ -1,78 +1,46 @@
1
- require 'json'
2
- require 'rest-client'
3
-
4
1
  module Quayio
5
2
  module Scanner
6
- class Image < Struct.new(:name, :quayio_token, :whitelist)
7
- RELEVANT_SEVERITIES = %w(High Critical)
8
- MAX_ATTEMPTS = 5
3
+ class Image
4
+ RELEVANT_SEVERITIES = %w[High Critical].freeze
5
+ QUAY_IO_REPO_NAME = %r{quay.io\/(?<org>[\w-]+)\/(?<repo>[\w-]+):(?<tag>[\w\.-]+)}.freeze
6
+
7
+ attr_reader :name, :whitelist, :repository
8
+
9
+ def initialize(name, quayio_token, whitelist)
10
+ @name = name
11
+ @whitelist = whitelist
12
+
13
+ @name.match(QUAY_IO_REPO_NAME) do |r|
14
+ org, repo, tag = r.captures
15
+ @repository = Repository.new(quayio_token, org, repo, tag)
16
+ end
17
+ end
9
18
 
10
19
  def vulnerable?
11
- quayio? && image_exists? && scanned? && high_vulnerabilities_present?
20
+ quayio? && scanned? && vulnerabilities_present?
12
21
  end
13
22
 
14
23
  private
15
24
 
16
25
  def quayio?
17
- name.match(%r{^quay.io\/})
18
- end
19
-
20
- def image_exists?
21
- raw_image
26
+ # safe guard, do not trust QUAY_IO_REPO_NAME regex match
27
+ !!name.match(%r{^quay.io\/})
22
28
  end
23
29
 
24
30
  def scanned?
25
31
  raw_scan['status'] == 'scanned'
26
32
  end
27
33
 
28
- def high_vulnerabilities_present?
29
- raw_scan['data']['Layer']['Features'].detect do |f|
30
- f['Vulnerabilities'] && f['Vulnerabilities'].detect do |v|
31
- RELEVANT_SEVERITIES.include?(v['Severity']) &&
32
- !whitelist.include?(v['Name'])
34
+ def vulnerabilities_present?
35
+ !!raw_scan['data']['Layer']['Features'].detect do |f|
36
+ f['Vulnerabilities']&.detect do |v|
37
+ RELEVANT_SEVERITIES.include?(v['Severity']) && !whitelist.include?(v['Name'])
33
38
  end
34
39
  end
35
40
  end
36
41
 
37
- def repo
38
- name.split(':').first.gsub(%r{quay.io\/}, '')
39
- end
40
-
41
- def tag
42
- name.split(':').last
43
- end
44
-
45
- def raw_image
46
- return @raw_image if defined? @raw_image
47
-
48
- (1..MAX_ATTEMPTS).each do |attempt|
49
- begin
50
- response = RestClient.get(
51
- "https://quay.io/api/v1/repository/#{repo}/tag/#{tag}/images",
52
- authorization: "Bearer #{quayio_token}",
53
- accept: :json)
54
- rescue RestClient::ExceptionWithResponse => err
55
- return nil if err.http_code == 404 # ignore unknown repos
56
- if err.http_code == 520 and attempt < MAX_ATTEMPTS
57
- sleep(rand(10))
58
- next
59
- end
60
- raise err
61
- end
62
- @raw_image = JSON.parse(response)['images'].first
63
- return @raw_image
64
- end
65
- end
66
-
67
42
  def raw_scan
68
- return @raw_scan if defined? @raw_scan
69
-
70
- @raw_scan = begin
71
- JSON.parse(
72
- RestClient.get("https://quay.io/api/v1/repository/#{repo}/image/#{raw_image['id']}/security?vulnerabilities=true",
73
- authorization: "Bearer #{quayio_token}", accept: :json)
74
- )
75
- end
43
+ @raw_scan ||= repository.scan
76
44
  end
77
45
  end
78
46
  end
@@ -0,0 +1,46 @@
1
+ require 'rest-client'
2
+ require 'json'
3
+
4
+ module Quayio
5
+ module Scanner
6
+ Repository = Struct.new(:quayio_token, :org, :repo, :tag) do
7
+ MAX_ATTEMPTS = 5
8
+
9
+ def id
10
+ @id ||= fetch_id
11
+ end
12
+
13
+ def scan
14
+ api_call("/image/#{id}/security?vulnerabilities=true")
15
+ end
16
+
17
+ private
18
+
19
+ def fetch_id
20
+ result = api_call("/tag/#{tag}/images")
21
+ (result['images'].first)['id']
22
+ end
23
+
24
+ def api_call(uri)
25
+ (1..Float::INFINITY).each do |attempt|
26
+ begin
27
+ response = RestClient.get(
28
+ "https://quay.io/api/v1/repository/#{org}/#{repo}#{uri}",
29
+ authorization: "Bearer #{quayio_token}",
30
+ accept: :json,
31
+ open_timeout: 15
32
+ )
33
+ return JSON.parse(response)
34
+ rescue RestClient::Exception => e
35
+ raise e if attempt >= MAX_ATTEMPTS
36
+
37
+ # retry later, if we hit cdn rate limiting or on connection errors
38
+ raise e unless e.http_code == 520 || e.http_code.nil?
39
+
40
+ sleep(rand(10))
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,5 +1,5 @@
1
1
  module Quayio
2
2
  module Scanner
3
- VERSION = '0.1.5'.freeze
3
+ VERSION = '0.2.3'.freeze
4
4
  end
5
5
  end
@@ -12,6 +12,8 @@ Gem::Specification.new do |spec|
12
12
  spec.homepage = 'https://github.com/aboutsource/quayio-scanner'
13
13
  spec.license = 'MIT'
14
14
 
15
+ spec.required_ruby_version = '>= 2.3.0'
16
+
15
17
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
16
18
  f.match(%r{^(test|spec|features)/})
17
19
  end
@@ -19,9 +21,9 @@ Gem::Specification.new do |spec|
19
21
  spec.require_paths = ['lib']
20
22
 
21
23
  spec.add_dependency 'docker-api', '~> 1.33'
22
- spec.add_dependency 'rest-client', '~> 2.0'
23
- spec.add_dependency 'sensu-plugin', '~> 2.1'
24
- spec.add_development_dependency 'bundler', '~> 1.14'
24
+ spec.add_dependency 'rest-client', '~> 2.1'
25
+ spec.add_dependency 'sensu-plugin', '~> 4.0'
26
+ spec.add_development_dependency 'bundler', '~> 2.2'
25
27
  spec.add_development_dependency 'rake', '~> 10.0'
26
28
  spec.add_development_dependency 'rspec', '~> 3.7'
27
29
  spec.add_development_dependency 'rubocop', '~> 0.49'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quayio-scanner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin Meichsner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-31 00:00:00.000000000 Z
11
+ date: 2021-08-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: docker-api
@@ -30,42 +30,42 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '2.0'
33
+ version: '2.1'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '2.0'
40
+ version: '2.1'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: sensu-plugin
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '2.1'
47
+ version: '4.0'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '2.1'
54
+ version: '4.0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '1.14'
61
+ version: '2.2'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '1.14'
68
+ version: '2.2'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rake
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -117,7 +117,10 @@ extensions: []
117
117
  extra_rdoc_files: []
118
118
  files:
119
119
  - ".gitignore"
120
+ - ".rubocop.yml"
121
+ - ".ruby-version"
120
122
  - Gemfile
123
+ - Gemfile.lock
121
124
  - LICENSE.txt
122
125
  - README.md
123
126
  - Rakefile
@@ -125,6 +128,7 @@ files:
125
128
  - lib/quayio/scanner.rb
126
129
  - lib/quayio/scanner/check.rb
127
130
  - lib/quayio/scanner/image.rb
131
+ - lib/quayio/scanner/repository.rb
128
132
  - lib/quayio/scanner/version.rb
129
133
  - quayio-scanner.gemspec
130
134
  homepage: https://github.com/aboutsource/quayio-scanner
@@ -139,7 +143,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
139
143
  requirements:
140
144
  - - ">="
141
145
  - !ruby/object:Gem::Version
142
- version: '0'
146
+ version: 2.3.0
143
147
  required_rubygems_version: !ruby/object:Gem::Requirement
144
148
  requirements:
145
149
  - - ">="