quayio-scanner 0.1.2 → 0.2.1

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
- SHA1:
3
- metadata.gz: 68516160274a0b51bf316fb40c10bf80cf09c23c
4
- data.tar.gz: 65a0702a0bd1377aeec19a4167bdf94e62d2d49e
2
+ SHA256:
3
+ metadata.gz: 668671ffe57f1e5b1f27d5d6f6ace098e89f5aeb3dc5999d0f091c8f9fa05bb3
4
+ data.tar.gz: 5dd2530e763c2807b2dc36355b4787d201512e3d899924232768cd91653ba3bf
5
5
  SHA512:
6
- metadata.gz: 7526a15532727f2831863844dc1d014e89990a9c1d4de86c7e01b5f6ce313e76f1123cdae6aa1a182b7b81d3b960336c17643b20a4e74af535c948f16c1759b9
7
- data.tar.gz: f82dd17253b0fa4bf26d11e3cc43ac18ce602836681b548644c3118f59a4e12674959302d0af073dcf2d6fa414746adb24a9b522951319c454d72f43f8d66b3b
6
+ metadata.gz: b55a1e1ad424c964f8a29540317dc6f8107e2dcc5f1b7724a45df76f5a40e87fb6405d0d273f58ea3eec3dd0afe29904dfeb3ef77a65950bc6614bb444389f59
7
+ data.tar.gz: 920b574586ed4ac4428076a8593aca5914cefbeb6c7909472517905830e01319a4dca816f9038c12c6bb1a6cfbe926dc1f4d1df8631cba34058a81b9f7d46f1f
@@ -0,0 +1,11 @@
1
+ Style/FrozenStringLiteralComment:
2
+ Enabled: false
3
+
4
+ Style/Documentation:
5
+ Enabled: false
6
+
7
+ Metrics/MethodLength:
8
+ Max: 50
9
+
10
+ Metrics/BlockLength:
11
+ Max: 200
data/Rakefile CHANGED
@@ -1,2 +1,7 @@
1
1
  require 'bundler/gem_tasks'
2
- task default: :spec
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
4
+
5
+ RuboCop::RakeTask.new
6
+
7
+ task default: %i[rubocop]
@@ -36,9 +36,17 @@ class CheckContainerVulnerabilities < Sensu::Plugin::Check::CLI
36
36
  short: '-t TOKEN',
37
37
  long: '--quayio-token TOKEN'
38
38
 
39
+ option :whitelist,
40
+ description: 'Vulnerability whitelist',
41
+ short: '-w WHITELIST[,WHITELIST]',
42
+ long: '--whitelist WHITELIST[,WHITELIST]',
43
+ default: '',
44
+ proc: proc { |w| w.split(',') }
45
+
39
46
  def run
40
47
  status, message = Quayio::Scanner::Check.new(config[:docker_url],
41
- config[:quayio_token]).run
48
+ config[:quayio_token],
49
+ config[:whitelist]).run
42
50
 
43
51
  if status == :ok
44
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)
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) }
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,68 +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)
7
- RELEVANT_SEVERITIES = %w(Medium High Critical)
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
8
18
 
9
19
  def vulnerable?
10
- quayio? && image_exists? && scanned? && high_vulnerabilities_present?
20
+ quayio? && scanned? && vulnerabilities_present?
11
21
  end
12
22
 
13
23
  private
14
24
 
15
25
  def quayio?
16
- name.match(%r{^quay.io\/})
17
- end
18
-
19
- def image_exists?
20
- raw_image
26
+ # safe guard, do not trust QUAY_IO_REPO_NAME regex match
27
+ !!name.match(%r{^quay.io\/})
21
28
  end
22
29
 
23
30
  def scanned?
24
31
  raw_scan['status'] == 'scanned'
25
32
  end
26
33
 
27
- def high_vulnerabilities_present?
28
- raw_scan['data']['Layer']['Features'].detect do |f|
29
- f['Vulnerabilities'] &&
30
- f['Vulnerabilities']
31
- .detect { |v| RELEVANT_SEVERITIES.include?(v['Severity']) }
32
- end
33
- end
34
-
35
- def repo
36
- name.split(':').first.gsub(%r{quay.io\/}, '')
37
- end
38
-
39
- def tag
40
- name.split(':').last
41
- end
42
-
43
- def raw_image
44
- return @raw_image if defined? @raw_image
45
-
46
- @raw_image = begin
47
- JSON.parse(
48
- RestClient.get("https://quay.io/api/v1/repository/#{repo}/tag/#{tag}/images",
49
- authorization: "Bearer #{quayio_token}", accept: :json)
50
- )['images'].first
51
- rescue RestClient::ExceptionWithResponse => err
52
- return nil if err.http_code == 404 # ignore unknown repos
53
- raise err
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'])
38
+ end
54
39
  end
55
40
  end
56
41
 
57
42
  def raw_scan
58
- return @raw_scan if defined? @raw_scan
59
-
60
- @raw_scan = begin
61
- JSON.parse(
62
- RestClient.get("https://quay.io/api/v1/repository/#{repo}/image/#{raw_image['id']}/security?vulnerabilities=true",
63
- authorization: "Bearer #{quayio_token}", accept: :json)
64
- )
65
- end
43
+ @raw_scan ||= repository.scan
66
44
  end
67
45
  end
68
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.2'.freeze
3
+ VERSION = '0.2.1'.freeze
4
4
  end
5
5
  end
@@ -1,6 +1,4 @@
1
- # coding: utf-8
2
-
3
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path('lib', __dir__)
4
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
3
  require 'quayio/scanner/version'
6
4
 
@@ -14,16 +12,19 @@ Gem::Specification.new do |spec|
14
12
  spec.homepage = 'https://github.com/aboutsource/quayio-scanner'
15
13
  spec.license = 'MIT'
16
14
 
15
+ spec.required_ruby_version = '>= 2.4.0'
16
+
17
17
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
18
  f.match(%r{^(test|spec|features)/})
19
19
  end
20
20
  spec.executables = Dir.glob('bin/**/*.rb').map { |file| File.basename(file) }
21
21
  spec.require_paths = ['lib']
22
22
 
23
- spec.add_dependency 'sensu-plugin', '~> 2.1'
24
23
  spec.add_dependency 'docker-api', '~> 1.33'
25
24
  spec.add_dependency 'rest-client', '~> 2.0'
26
- spec.add_development_dependency 'bundler', '~> 1.14'
25
+ spec.add_dependency 'sensu-plugin', '~> 2.1'
26
+ spec.add_development_dependency 'bundler'
27
27
  spec.add_development_dependency 'rake', '~> 10.0'
28
+ spec.add_development_dependency 'rspec', '~> 3.7'
28
29
  spec.add_development_dependency 'rubocop', '~> 0.49'
29
30
  end
metadata CHANGED
@@ -1,71 +1,71 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quayio-scanner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin Meichsner
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-01-30 00:00:00.000000000 Z
11
+ date: 2020-10-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: sensu-plugin
14
+ name: docker-api
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '2.1'
19
+ version: '1.33'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '2.1'
26
+ version: '1.33'
27
27
  - !ruby/object:Gem::Dependency
28
- name: docker-api
28
+ name: rest-client
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.33'
33
+ version: '2.0'
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: '1.33'
40
+ version: '2.0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rest-client
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.0'
47
+ version: '2.1'
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.0'
54
+ version: '2.1'
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: '0'
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: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rake
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.7'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.7'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: rubocop
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -94,7 +108,7 @@ dependencies:
94
108
  - - "~>"
95
109
  - !ruby/object:Gem::Version
96
110
  version: '0.49'
97
- description:
111
+ description:
98
112
  email:
99
113
  - benjamin.meichsner@aboutsource.net
100
114
  executables:
@@ -103,6 +117,7 @@ extensions: []
103
117
  extra_rdoc_files: []
104
118
  files:
105
119
  - ".gitignore"
120
+ - ".rubocop.yml"
106
121
  - Gemfile
107
122
  - LICENSE.txt
108
123
  - README.md
@@ -111,13 +126,14 @@ files:
111
126
  - lib/quayio/scanner.rb
112
127
  - lib/quayio/scanner/check.rb
113
128
  - lib/quayio/scanner/image.rb
129
+ - lib/quayio/scanner/repository.rb
114
130
  - lib/quayio/scanner/version.rb
115
131
  - quayio-scanner.gemspec
116
132
  homepage: https://github.com/aboutsource/quayio-scanner
117
133
  licenses:
118
134
  - MIT
119
135
  metadata: {}
120
- post_install_message:
136
+ post_install_message:
121
137
  rdoc_options: []
122
138
  require_paths:
123
139
  - lib
@@ -125,16 +141,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
125
141
  requirements:
126
142
  - - ">="
127
143
  - !ruby/object:Gem::Version
128
- version: '0'
144
+ version: 2.4.0
129
145
  required_rubygems_version: !ruby/object:Gem::Requirement
130
146
  requirements:
131
147
  - - ">="
132
148
  - !ruby/object:Gem::Version
133
149
  version: '0'
134
150
  requirements: []
135
- rubyforge_project:
136
- rubygems_version: 2.6.14
137
- signing_key:
151
+ rubygems_version: 3.1.4
152
+ signing_key:
138
153
  specification_version: 4
139
154
  summary: Scan quay.io for vulnerabilties in running docker containers.
140
155
  test_files: []