This repository has been archived by the owner on Apr 17, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 470
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Scanners supported: CoreOS Clair, zypper-docker (experimental, based on an unmerged branch). I've also included a `dummy` scanner, for development purposes. Signed-off-by: Miquel Sabaté Solà <msabate@suse.com> Signed-off-by: Vítor Avelino <vavelino@suse.com>
- Loading branch information
1 parent
c7c2bf0
commit 4cd875c
Showing
14 changed files
with
421 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// setTypeahead sets up the typeahead plugin for the given url. This function | ||
// also assumes that there is an element with the following selector | ||
// ".remote .typeahead". | ||
export const setTypeahead = function (url) { | ||
var bloodhound; | ||
|
||
$('.remote .typeahead').typeahead('destroy'); | ||
bloodhound = new Bloodhound({ | ||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'), | ||
queryTokenizer: Bloodhound.tokenizers.whitespace, | ||
remote: { | ||
cache: false, | ||
url: url, | ||
wildcard: '%QUERY', | ||
}, | ||
}); | ||
|
||
bloodhound.initialize(); | ||
|
||
$('.remote .typeahead').typeahead({ highlight: true }, { | ||
displayKey: 'name', | ||
source: bloodhound.ttAdapter(), | ||
}); | ||
}; | ||
|
||
export default { | ||
setTypeahead, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
require "portus/security" | ||
|
||
if ARGV.size != 2 | ||
puts "Usage: rails runner bin/security.rb <image> <tag>" | ||
exit 1 | ||
end | ||
|
||
image, tag = ARGV | ||
sec = ::Portus::Security.new(image, tag) | ||
vulns = sec.vulnerabilities | ||
|
||
vulns.each do |name, result| | ||
hsh = {} | ||
|
||
n = name.to_s.capitalize | ||
print "#{n}\n" + ("=" * n.size) + "\n" | ||
|
||
if result.nil? | ||
print "\nWork in progress...\n" | ||
next | ||
end | ||
|
||
result.each do |v| | ||
hsh[v["Severity"]] = 0 unless hsh.include?(v["Severity"]) | ||
hsh[v["Severity"]] += 1 | ||
|
||
puts "#{v["Name"]}: #{v["Severity"]}" | ||
puts "" | ||
puts v["Link"].to_s | ||
puts "---------------" | ||
end | ||
|
||
print "\nFound #{result.size} vulnerabilities:\n\n" | ||
hsh.each { |k, v| puts "#{k}: #{v}" } | ||
puts "" | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# API | ||
|
||
This is a work-in-progress document that tries to achieve what I'll be doing for | ||
this hackweek, and what it should be done in the near future regarding the API. | ||
|
||
## Hackweek | ||
|
||
### Authentication | ||
|
||
There should be a way to have the authentication part working as | ||
expected. It should be as simple as possible. | ||
|
||
### Vulnerabilities | ||
|
||
This will be part of the "Repositories & tags" endpoints. There should be a way | ||
to fetch the list of vulnerabilities for the given repo + tag. Suggested paths: | ||
|
||
* `/repositories/<image>/vulnerabilities` | ||
* `/repositories/<image>/tags/<tag>/vulnerabilities` | ||
|
||
Note that the second form should be clarified once I get the repositories#show | ||
page straight. So, for now I'll only implement the first one. | ||
|
||
## Near future | ||
|
||
Most of these routes already exist, but they should be implemented for the new | ||
scheme as well. | ||
|
||
### Administration | ||
|
||
- Create & update registries. | ||
- Fetch activities. | ||
|
||
### Application tokens | ||
|
||
Not sure if this conflicts with the authentication part. If it doesn't, then the | ||
`create` and the `destroy` actions should be re-implemented so it responds back | ||
with JSON data. | ||
|
||
### Namespaces & teams | ||
|
||
All actions for creating, updating and destroying teams and namespaces. | ||
|
||
### Repositories and tags | ||
|
||
List and show actions should be implemented (along with the already existing | ||
vulnerabilities endpoints). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
require "portus/security_backends/clair" | ||
require "portus/security_backends/dummy" | ||
require "portus/security_backends/zypper" | ||
|
||
module Portus | ||
class Security | ||
BACKENDS = [ | ||
::Portus::SecurityBackend::Clair, | ||
::Portus::SecurityBackend::Dummy, | ||
::Portus::SecurityBackend::Zypper | ||
].freeze | ||
|
||
def initialize(repo, tag) | ||
@repo = repo | ||
@tag = tag | ||
@backends = [] | ||
|
||
BACKENDS.each { |b| @backends << b.new(repo, tag) if b.enabled? } | ||
end | ||
|
||
# Returns a hash with the results from all the backends. The results are a | ||
# list of hashes. | ||
# TODO: document format | ||
def vulnerabilities | ||
# First get all the layers composing the given image. | ||
client = Registry.get.client | ||
manifest = client.manifest(@repo, @tag) | ||
|
||
params = { | ||
layers: manifest.last["layers"].map { |l| l["digest"] }, | ||
token: client.token, | ||
registry_url: client.base_url | ||
} | ||
|
||
res = {} | ||
@backends.each do |b| | ||
name = b.class.name.to_s.demodulize.downcase.to_sym | ||
res[name] = b.vulnerabilities(params) | ||
end | ||
res | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
require "portus/http_helpers" | ||
|
||
module Portus | ||
module SecurityBackend | ||
# Base implements basic functionality that each security backend should | ||
# have. All security backends should subclass this one. | ||
class Base | ||
include ::Portus::HttpHelpers | ||
|
||
def initialize(repo, tag) | ||
@repo = repo | ||
@tag = tag | ||
@base_url = self.class.configuration["server"] | ||
end | ||
|
||
# Returns true if the given backend has been enabled, false otherwise. | ||
def self.enabled? | ||
cfg = configuration | ||
!cfg["server"].blank? | ||
end | ||
|
||
# Returns the configuration of the given backend. | ||
def self.configuration | ||
n = name.to_s.demodulize.downcase | ||
APP_CONFIG["security"][n] | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
require "portus/security_backends/base" | ||
|
||
# Docker images contain quite some empty blobs, and trying to upload them will | ||
# fail. | ||
EMPTY_LAYER_SHA = "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4".freeze | ||
|
||
module Portus | ||
module SecurityBackend | ||
# Clair implements all security-related methods by using CoreOS' Clair | ||
# (https://github.com/coreos/clair) | ||
class Clair < ::Portus::SecurityBackend::Base | ||
# Returns the vulnerabilities that can be found for the given layers. In | ||
# order to do so, this method needs an authentication token that will be | ||
# used to post this images into Clair. Moreover, this method also needs | ||
# the URL of the registry, since it needs to pass this information to | ||
# Clair. | ||
def vulnerabilities(params) | ||
@token = params[:token] | ||
@registry_url = params[:registry_url] | ||
|
||
# Filter out empty layers. | ||
@layers = params[:layers].reject { |digest| digest == EMPTY_LAYER_SHA } | ||
|
||
# We first post everything in reverse order, so parent layers are | ||
# available when inspecting vulnerabilities. | ||
@layers.reverse.each_index { |k| post_layer(k) } | ||
|
||
# Finally, according to Clair's documentation, requesting | ||
# vulnerabilities from the last child will give you all of them. | ||
layer_vulnerabilities(@layers.last) | ||
end | ||
|
||
protected | ||
|
||
# Returns an array with all the vulnerabilities found by Clair for the | ||
# given digest. | ||
def layer_vulnerabilities(digest) | ||
layer = fetch_layer(digest) | ||
return [] if layer.nil? | ||
|
||
res = [] | ||
known = [] | ||
layer["Features"].each do |f| | ||
vulns = f["Vulnerabilities"] | ||
next if vulns.nil? | ||
|
||
vulns.each do |v| | ||
if v && v["Name"] && !known.include?(v["Name"]) | ||
known << v["Name"] | ||
res << v | ||
end | ||
end | ||
end | ||
res | ||
end | ||
|
||
# Fetches the layer information from Clair for the given digest as a Hash. | ||
# If nothing could be extracted, then nil is returned. | ||
def fetch_layer(digest) | ||
# Now we fetch the vulnerabilities discovered by clair on that layer. | ||
uri, req = get_request("/v1/layers/#{digest}?features=false&vulnerabilities=true", "get") | ||
res = get_response_token(uri, req) | ||
|
||
# Parse the given response and return the result. | ||
msg = JSON.parse(res.body) | ||
if res.code.to_i == 200 | ||
msg["Layer"] | ||
else | ||
msg = error_message(msg) | ||
Rails.logger.tagged("clair.get") { Rails.logger.debug "Error for '#{digest}': #{msg}" } | ||
nil | ||
end | ||
end | ||
|
||
# Post the layer pointed by the given index to Clair. | ||
def post_layer(index) | ||
parent = index > 0 ? @layers.fetch(index - 1) : "" | ||
digest = @layers.fetch(index) | ||
|
||
uri, req = get_request("/v1/layers", "post") | ||
req.body = layer_body(digest, parent).to_json | ||
|
||
res = get_response_token(uri, req) | ||
if res.code.to_i != 200 && res.code.to_i != 201 | ||
msg = error_message(JSON.parse(res.body)) | ||
Rails.logger.tagged("clair.post") do | ||
Rails.logger.debug "Could not post '#{digest}': #{msg}" | ||
end | ||
end | ||
end | ||
|
||
# Returns a hash that has to be used as the body of a POST request. This | ||
# method requires the digest of the layer to be pushed, and the digest of | ||
# the parent layer. If this layer has no parent, then it should be an | ||
# empty string. | ||
def layer_body(digest, parent) | ||
path = URI.join(@registry_url.to_s, "/v2/#{@repo}/blobs/#{digest}").to_s | ||
|
||
{ | ||
"Layer" => { | ||
"Name" => digest, | ||
"NamespaceName" => "", | ||
"Path" => path, | ||
"Headers" => { "Authorization" => "Bearer #{@token}" }, | ||
"ParentName" => parent, | ||
"Format" => "Docker", | ||
"IndexedByVersion" => 0, | ||
"Features" => [] | ||
} | ||
} | ||
end | ||
|
||
# Returns a proper error message for the given JSON response. | ||
def error_message(msg) | ||
msg["Error"] && msg["Error"]["Message"] ? msg["Error"]["Message"] : msg | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
require "portus/security_backends/base" | ||
|
||
module Portus | ||
module SecurityBackend | ||
# Dummy implements a backend that simply returns fixture data. This backend | ||
# is meant to be used only for development/testing purposes. | ||
class Dummy < ::Portus::SecurityBackend::Base | ||
# Files stored in `lib/portus/security_backends/fixtures`. | ||
DUMMY_FIXTURE = "dummy.json".freeze | ||
|
||
# Whether the response from the `vulnerabilities` method should be as | ||
# "Working in Progress". | ||
WIP = true | ||
|
||
# Returns nil if the dummy backend is "working on it", otherwise it | ||
# returns a list of vulnerabilities as specified by the DUMMY_FIXTURE | ||
# constant. | ||
def vulnerabilities(_params) | ||
return nil if WIP | ||
|
||
path = Rails.root.join("lib", "portus", "security_backends", "fixtures", DUMMY_FIXTURE) | ||
JSON.parse(File.read(path)) | ||
end | ||
end | ||
end | ||
end |
Oops, something went wrong.