|
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 commit comments