Skip to content

Commit daca182

Browse files
authored
Merge pull request #1 from aboutsource/feature/sensu-check
Added sensu check and gem files
2 parents 65c9d29 + 98a2b36 commit daca182

10 files changed

Lines changed: 221 additions & 0 deletions

File tree

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/.bundle/
2+
/.yardoc
3+
/Gemfile.lock
4+
/_yardoc/
5+
/coverage/
6+
/doc/
7+
/pkg/
8+
/spec/reports/
9+
/tmp/
10+
/vendor/bundle

Gemfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
source 'https://rubygems.org'
2+
3+
# Specify your gem's dependencies in quayio-scanner.gemspec
4+
gemspec

LICENSE.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2017 Benjamin Meichsner
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

Rakefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require 'bundler/gem_tasks'
2+
task default: :spec
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#! /usr/bin/env ruby
2+
#
3+
# check-container-vulnerabilities
4+
#
5+
# DESCRIPTION:
6+
#
7+
# This plugin attempts to fetch vulnerabilties for all running containers
8+
#
9+
# OUTPUT:
10+
# plain text
11+
#
12+
# PLATFORMS:
13+
# Linux
14+
#
15+
# DEPENDENCIES:
16+
# gem: sensu-plugin
17+
# gem: docker-api
18+
# gem: rest-client
19+
#
20+
# USAGE:
21+
# ./check-container-vulnerabilities.rb -d <docker-url> -t <quay-io-oauth-token>
22+
#
23+
24+
require 'sensu-plugin/check/cli'
25+
require 'quayio/scanner'
26+
27+
class CheckContainerVulnerabilities < Sensu::Plugin::Check::CLI
28+
option :docker_url,
29+
description: 'Docker URL',
30+
short: '-d URL',
31+
long: '--docker-url URL',
32+
default: 'unix:///var/run/docker.sock'
33+
34+
option :quayio_token,
35+
description: 'Quay.io oauth token',
36+
short: '-t TOKEN',
37+
long: '--quayio-token TOKEN'
38+
39+
def run
40+
status, message = Quayio::Scanner::Check.new(config[:docker_url],
41+
config[:quayio_token]).run
42+
43+
if status == :ok
44+
ok message
45+
else
46+
critical message
47+
end
48+
end
49+
end

lib/quayio/scanner.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
require 'quayio/scanner/version'
2+
require 'quayio/scanner/check'
3+
4+
module Quayio
5+
module Scanner
6+
end
7+
end

lib/quayio/scanner/check.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
require 'quayio/scanner/image'
2+
require 'docker'
3+
4+
module Quayio
5+
module Scanner
6+
class Check < Struct.new(:docker_url, :quayio_token)
7+
def run
8+
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+
18+
if vulnerable_images.empty?
19+
[:ok, "#{containers.size} Containers are ok"]
20+
else
21+
[:critical, "The images are insecure: #{vulnerable_images.join(', ')}"]
22+
end
23+
end
24+
end
25+
end
26+
end

lib/quayio/scanner/image.rb

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
require 'rest-client'
2+
3+
module Quayio
4+
module Scanner
5+
class Image < Struct.new(:name, :quayio_token)
6+
RELEVANT_SEVERITIES = %w(High Critical)
7+
8+
def vulnerable?
9+
quayio? && image_exists? && scanned? && high_vulnerabilities_present?
10+
end
11+
12+
private
13+
14+
def quayio?
15+
name.match(%r{^quay.io\/})
16+
end
17+
18+
def image_exists?
19+
raw_image
20+
end
21+
22+
def scanned?
23+
raw_scan['status'] == 'scanned'
24+
end
25+
26+
def high_vulnerabilities_present?
27+
raw_scan['data']['Layer']['Features'].detect do |f|
28+
f['Vulnerabilities'] &&
29+
f['Vulnerabilities']
30+
.detect { |v| RELEVANT_SEVERITIES.include?(v['Severity']) }
31+
end
32+
end
33+
34+
def repo
35+
name.split(':').first.gsub(%r{quay.io\/}, '')
36+
end
37+
38+
def tag
39+
name.split(':').last
40+
end
41+
42+
def raw_image
43+
return @raw_image if defined? @raw_image
44+
45+
@raw_image = begin
46+
JSON.parse(
47+
RestClient.get("https://quay.io/api/v1/repository/#{repo}/tag/#{tag}/images",
48+
authorization: "Bearer #{quayio_token}", accept: :json)
49+
)['images'].first
50+
rescue RestClient::ExceptionWithResponse => err
51+
return nil if err.http_code == 404 # ignore unknown repos
52+
raise err
53+
end
54+
end
55+
56+
def raw_scan
57+
return @raw_scan if defined? @raw_scan
58+
59+
@raw_scan = begin
60+
JSON.parse(
61+
RestClient.get("https://quay.io/api/v1/repository/#{repo}/image/#{raw_image['id']}/security?vulnerabilities=true",
62+
authorization: "Bearer #{quayio_token}", accept: :json)
63+
)
64+
end
65+
end
66+
end
67+
end
68+
end

lib/quayio/scanner/version.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module Quayio
2+
module Scanner
3+
VERSION = '0.1.0'.freeze
4+
end
5+
end

quayio-scanner.gemspec

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# coding: utf-8
2+
3+
lib = File.expand_path('../lib', __FILE__)
4+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5+
require 'quayio/scanner/version'
6+
7+
Gem::Specification.new do |spec|
8+
spec.name = 'quayio-scanner'
9+
spec.version = Quayio::Scanner::VERSION
10+
spec.authors = ['Benjamin Meichsner']
11+
spec.email = ['benjamin.meichsner@aboutsource.net']
12+
13+
spec.summary = 'Scan quay.io for vulnerabilties in running containers.'
14+
spec.homepage = 'https://github.com/aboutsource/quayio-scanner'
15+
spec.license = 'MIT'
16+
17+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
18+
f.match(%r{^(test|spec|features)/})
19+
end
20+
spec.executables = Dir.glob('bin/**/*.rb').map { |file| File.basename(file) }
21+
spec.require_paths = ['lib']
22+
23+
spec.add_dependency 'sensu-plugin'
24+
spec.add_dependency 'docker-api'
25+
spec.add_dependency 'rest-client'
26+
spec.add_development_dependency 'bundler', '~> 1.14'
27+
spec.add_development_dependency 'rake', '~> 10.0'
28+
spec.add_development_dependency 'rubocop'
29+
end

0 commit comments

Comments
 (0)