From b8c927fa4525d3145d04b52c4c5e88f3a67bc5a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miquel=20Sabat=C3=A9=20Sol=C3=A0?= Date: Fri, 1 Dec 2017 11:51:09 +0100 Subject: [PATCH] Fixed blocking issues with security scanning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unless you provided enough Puma workers, there was a problem with HTTP requests getting blocked. This is a similar issue as pointed out in #1353 with registry events. The solution has been to move security scanning in the background, in a different process. This is similar to #1353, but more polished (and if this proves to really work we might move #1353 to this background runner as well). Now the Tag model has two more columns: `vulnerabilities` and `scanned`. The former contains the cached result of the latest security check. The latter is an integer that can have three different values: 0 (scanning not performed), 1 (working on it) and 2 (scanning done). This has also been exposed on the API, so the client side can be updated. Signed-off-by: Miquel Sabaté Solà --- app/controllers/tags_controller.rb | 4 +- app/helpers/repositories_helper.rb | 2 +- app/models/repository.rb | 7 +- app/models/tag.rb | 35 +- bin/background.rb | 47 +++ ...1130095821_add_scanned_and_vulns_to_tag.rb | 6 + db/schema.rb | 22 +- lib/api/entities.rb | 5 + lib/portus/background/security_scanning.rb | 48 +++ lib/portus/db.rb | 6 +- lib/portus/security.rb | 13 +- lib/portus/security_backends/clair.rb | 46 ++- lib/portus/security_backends/dummy.rb | 4 + lib/portus/security_backends/zypper.rb | 4 + spec/controllers/tags_controller_spec.rb | 28 +- .../background/security_scanning_spec.rb | 91 +++++ spec/lib/portus/security/clair_spec.rb | 18 +- spec/lib/portus/security_spec.rb | 9 +- spec/models/tag_spec.rb | 54 ++- spec/vcr_cassettes/background/clair.yml | 338 ++++++++++++++++++ 20 files changed, 694 insertions(+), 93 deletions(-) create mode 100644 bin/background.rb create mode 100644 db/migrate/20171130095821_add_scanned_and_vulns_to_tag.rb create mode 100644 lib/portus/background/security_scanning.rb create mode 100644 spec/lib/portus/background/security_scanning_spec.rb create mode 100644 spec/vcr_cassettes/background/clair.yml diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index 798323a9c..bbee07b45 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -6,9 +6,7 @@ def show authorize @tag @names = Tag.where(digest: @tag.digest).sort.map(&:name) - - sec = ::Portus::Security.new(@tag.repository.full_name, @tag.name) - @vulnerabilities = sec.vulnerabilities + @vulnerabilities = @tag.fetch_vulnerabilities end # Removes all tags that match the digest of the tag with the given ID. diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb index 0b4dce1e1..9a8ba2b00 100644 --- a/app/helpers/repositories_helper.rb +++ b/app/helpers/repositories_helper.rb @@ -131,7 +131,7 @@ def name_and_link(tr, activity) # Returns if any security module is enabled def security_vulns_enabled? - ::Portus::Security.new(nil, nil).enabled? + ::Portus::Security.enabled? end # Returns true if any vulnerability is found diff --git a/app/models/repository.rb b/app/models/repository.rb index b1ad48a58..af3a5cb7d 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -136,10 +136,13 @@ def self.add_repo(event, namespace, repo, tag) if repository.nil? repository = Repository.create(namespace: namespace, name: repo) elsif repository.tags.exists?(name: tag) - # Update digest if the given tag already exists. + # Update digest and status if the given tag already exists. id, digest = Repository.id_and_digest_from_event(event, repository.full_name) tag = repository.tags.find_by(name: tag) - tag.update_columns(image_id: id, digest: digest, updated_at: Time.current) + tag.update_columns(image_id: id, + digest: digest, + scanned: Tag.statuses[:scan_none], + updated_at: Time.current) repository.create_activity(:push, owner: actor, recipient: tag) return end diff --git a/app/models/tag.rb b/app/models/tag.rb index 5277ff3d9..589e3d2cf 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -2,16 +2,18 @@ # # Table name: tags # -# id :integer not null, primary key -# name :string(255) default("latest"), not null -# repository_id :integer not null -# created_at :datetime not null -# updated_at :datetime not null -# user_id :integer -# digest :string(255) -# image_id :string(255) default("") -# marked :boolean default(FALSE) -# username :string(255) +# id :integer not null, primary key +# name :string(255) default("latest"), not null +# repository_id :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# user_id :integer +# digest :string(255) +# image_id :string(255) default("") +# marked :boolean default(FALSE) +# username :string(255) +# scanned :integer default(0) +# vulnerabilities :text(65535) # # Indexes # @@ -23,6 +25,11 @@ # name follows the format as defined in registry/api/v2/names.go from Docker's # Distribution project. The default name for a tag is "latest". class Tag < ActiveRecord::Base + serialize :vulnerabilities + + # NOTE: as a Hash because in Rails 5 we'll be able to pass a proper prefix. + enum status: { scan_none: 0, scan_working: 1, scan_done: 2 } + belongs_to :repository belongs_to :author, class_name: "User", foreign_key: "user_id" @@ -86,9 +93,11 @@ def delete_and_update!(actor) create_delete_activities!(actor) end - def vulnerabilities - sec = ::Portus::Security.new(repository.full_name, name) - sec.vulnerabilities + # Returns vulnerabilities if there are any available and security scanning is + # enabled. + def fetch_vulnerabilities + return unless ::Portus::Security.enabled? + vulnerabilities if scanned == Tag.statuses[:scan_done] end protected diff --git a/bin/background.rb b/bin/background.rb new file mode 100644 index 000000000..00be10a5b --- /dev/null +++ b/bin/background.rb @@ -0,0 +1,47 @@ +require "portus/db" + +# +# First of all, wait until the database is up and running. This is useful in +# containerized scenarios. +# + +count = 0 +TIMEOUT = 90 + +while ::Portus::DB.ping != :ready + if count >= TIMEOUT + puts "Timeout reached, exiting with error. Check the logs..." + exit 1 + end + + puts "Waiting for DB to be ready" + sleep 5 + count += 5 +end + +# +# The DB is up, now let's define the different background jobs as classes. +# + +require "portus/background/security_scanning" + +they = [::Portus::Background::SecurityScanning.new] +values = they.map { |v| "'#{v}'" }.join(", ") +Rails.logger.info "Running: #{values}" + +# +# Finally, we will loop infinitely like this: +# 1. Each background job will execute its task. +# 2. Then we will sleep until there's more work to be done. +# + +SLEEP_VALUE = 10 + +# Loop forever executing the given tasks. It will go to sleep for spans of +# `SLEEP_VALUE` seconds, if there's nothing else to be done. +loop do + they.each { |t| t.execute! if t.work? } + sleep SLEEP_VALUE until they.any?(&:work?) +end + +exit 0 diff --git a/db/migrate/20171130095821_add_scanned_and_vulns_to_tag.rb b/db/migrate/20171130095821_add_scanned_and_vulns_to_tag.rb new file mode 100644 index 000000000..ea9ec7545 --- /dev/null +++ b/db/migrate/20171130095821_add_scanned_and_vulns_to_tag.rb @@ -0,0 +1,6 @@ +class AddScannedAndVulnsToTag < ActiveRecord::Migration + def change + add_column :tags, :scanned, :integer, default: 0 + add_column :tags, :vulnerabilities, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index 0061db305..544a06f1d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171031112721) do +ActiveRecord::Schema.define(version: 20171130095821) do create_table "activities", force: :cascade do |t| t.integer "trackable_id", limit: 4 @@ -119,15 +119,17 @@ add_index "stars", ["user_id"], name: "index_stars_on_user_id", using: :btree create_table "tags", force: :cascade do |t| - t.string "name", limit: 255, default: "latest", null: false - t.integer "repository_id", limit: 4, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.integer "user_id", limit: 4 - t.string "digest", limit: 255 - t.string "image_id", limit: 255, default: "" - t.boolean "marked", default: false - t.string "username", limit: 255 + t.string "name", limit: 255, default: "latest", null: false + t.integer "repository_id", limit: 4, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "user_id", limit: 4 + t.string "digest", limit: 255 + t.string "image_id", limit: 255, default: "" + t.boolean "marked", default: false + t.string "username", limit: 255 + t.integer "scanned", limit: 4, default: 0 + t.text "vulnerabilities", limit: 65535 end add_index "tags", ["repository_id"], name: "index_tags_on_repository_id", using: :btree diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 164654e69..aebfe8ead 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -81,6 +81,11 @@ class Tags < Grape::Entity expose :digest, documentation: { type: String, desc: "The digest of the tag" } expose :image_id, documentation: { type: String, desc: "The internal image ID" } expose :created_at, :updated_at, documentation: { type: DateTime } + expose :scanned, documentation: { + type: Integer, + desc: "Whether vulnerabilities have been scanned or not. The values available are: " \ + "0 (not scanned), 1 (work in progress) and 2 (scanning done)." + } expose :vulnerabilities, documentation: { is_array: true, desc: "An array of vulnerabilities for this tag, or null if the feature is not enabled" diff --git a/lib/portus/background/security_scanning.rb b/lib/portus/background/security_scanning.rb new file mode 100644 index 000000000..41eef48bc --- /dev/null +++ b/lib/portus/background/security_scanning.rb @@ -0,0 +1,48 @@ +require "portus/security" + +module Portus + module Background + # SecurityScanning represents a task for checking vulnerabilities of tags that + # have not been scanned yet. + class SecurityScanning + def work? + ::Portus::Security.enabled? && Tag.exists?(scanned: Tag.statuses[:scan_none]) + end + + def execute! + Tag.where(scanned: Tag.statuses[:scan_none]).find_each do |tag| + # Mark as work in progress. This is important in case there is a push in + # progress. + tag.update_columns(scanned: Tag.statuses[:scan_working]) + + # Fetch vulnerabilities. + sec = ::Portus::Security.new(tag.repository.full_name, tag.name) + vulns = sec.vulnerabilities + + # Now it's time to update the columns and mark the scanning as done. That + # being said, it may have happened that during the scanning process a push + # or a delete action has been performed against this tag. For this reason, + # in a transaction we will reload it and check if any of these conditions + # changed. If not, then we will proceed with the change. + ActiveRecord::Base.transaction do + # If the tag no longer exists, then we need to raise a Rollback + # exception to leave early and cleanly from the transaction. + begin + tag = tag.reload + rescue ActiveRecord::RecordNotFound + raise ActiveRecord::Rollback + end + + if tag&.scanned != Tag.statuses[:scan_none] + tag.update_columns(vulnerabilities: vulns, scanned: Tag.statuses[:scan_done]) + end + end + end + end + + def to_s + "Security scanning" + end + end + end +end diff --git a/lib/portus/db.rb b/lib/portus/db.rb index 8701a3e10..abe3877bb 100644 --- a/lib/portus/db.rb +++ b/lib/portus/db.rb @@ -21,7 +21,11 @@ def self.ping # trivial, but this gives us a nice way to test this module. def self.migrations? ActiveRecord::Base.connection - ActiveRecord::Base.connection.table_exists? "schema_migrations" + return unless ActiveRecord::Base.connection.table_exists? "schema_migrations" + + # If db:migrate:status does not return a migration as "down", then all + # migrations are up and ready. + !`bundle exec rake db:migrate:status`.include?("down") end # Returns true if the given configured adapter is MariaDB. diff --git a/lib/portus/security.rb b/lib/portus/security.rb index c8aa054e9..f2bef8937 100644 --- a/lib/portus/security.rb +++ b/lib/portus/security.rb @@ -21,16 +21,23 @@ def initialize(repo, tag) BACKENDS.each { |b| @backends << b.new(repo, tag) if b.enabled? } end - # Returns true if security scanning is enabled, false otherwise. - def enabled? + # Returns true if there is backends that work available, false otherwise. + def available? @backends.present? end + # Returns true if security scanning is enabled, false otherwise. + def self.enabled? + ::Portus::Security::BACKENDS.any? do |b| + APP_CONFIG["security"][b.config_key]["server"].present? + end + end + # Returns a hash with the results from all the backends. The results are a # list of hashes. If security vulnerability checking is not enabled, then # nil is returned. def vulnerabilities - return unless enabled? + return unless available? # First get all the layers composing the given image. client = Registry.get.client diff --git a/lib/portus/security_backends/clair.rb b/lib/portus/security_backends/clair.rb index c94d1639d..f209dd9ff 100644 --- a/lib/portus/security_backends/clair.rb +++ b/lib/portus/security_backends/clair.rb @@ -30,6 +30,10 @@ def vulnerabilities(params) layer_vulnerabilities(@layers.last) end + def self.config_key + "clair" + end + protected # Returns an array with all the vulnerabilities found by Clair for the @@ -45,13 +49,15 @@ def layer_vulnerabilities(digest) next if vulns.nil? vulns.each do |v| - if v && v["Name"] && !known.include?(v["Name"]) - known << v["Name"] - res << v - end + name = Hash(v)["Name"] + next if name.blank? || known.include?(name) + + known << v["Name"] + # Skipping some fields that we don't use and can be very long... + res << v.except("Metadata", "Description") end end - res + res.sort_by { |el| el["Name"] } end # Fetches the layer information from Clair for the given digest as a Hash. @@ -59,6 +65,7 @@ def layer_vulnerabilities(digest) 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") + req["Accept"] = "application/json" begin res = get_response_token(uri, req) rescue SocketError, Errno::ECONNREFUSED => e @@ -67,13 +74,11 @@ def fetch_layer(digest) end # Parse the given response and return the result. - msg = JSON.parse(res.body) if res.code.to_i == 200 + msg = JSON.parse(res.body) msg["Layer"] else - msg = error_message(msg) - Rails.logger.tagged("clair.get") { Rails.logger.debug "Error for '#{digest}': #{msg}" } - nil + handle_response(res, digest, "clair.get") end end @@ -92,13 +97,7 @@ def post_layer(index) return end - code = res.code.to_i - return if code == 200 || code == 201 - - msg = error_message(JSON.parse(res.body)) - Rails.logger.tagged("clair.post") do - Rails.logger.debug "Could not post '#{digest}': #{msg}" - end + handle_response(res, digest, "clair.post") end # Returns a hash that has to be used as the body of a POST request. This @@ -122,6 +121,21 @@ def layer_body(digest, parent) } end + # Handle a response from a Clair request. The first parameter is the HTTP + # response itself, the `digest` holds a string with the digest ID, and + # finally `kind` is a string that identifies the kind of request. + def handle_response(response, digest, kind) + code = response.code.to_i + return if code == 200 || code == 201 + + msg = code == 404 ? response.body : error_message(JSON.parse(response.body)) + Rails.logger.tagged(kind) do + Rails.logger.debug "Could not post '#{digest}': #{msg}" + end + + nil + end + # Returns a proper error message for the given JSON response. def error_message(msg) msg["Error"] && msg["Error"]["Message"] ? msg["Error"]["Message"] : msg diff --git a/lib/portus/security_backends/dummy.rb b/lib/portus/security_backends/dummy.rb index 47c04873c..b2ebe605a 100644 --- a/lib/portus/security_backends/dummy.rb +++ b/lib/portus/security_backends/dummy.rb @@ -21,6 +21,10 @@ def vulnerabilities(_params) path = Rails.root.join("lib", "portus", "security_backends", "fixtures", DUMMY_FIXTURE) JSON.parse(File.read(path)) end + + def self.config_key + "dummy" + end end end end diff --git a/lib/portus/security_backends/zypper.rb b/lib/portus/security_backends/zypper.rb index 6efc5d114..4692d2474 100644 --- a/lib/portus/security_backends/zypper.rb +++ b/lib/portus/security_backends/zypper.rb @@ -27,6 +27,10 @@ def vulnerabilities(_params) end end + def self.config_key + "zypper" + end + protected def consume_response(response) diff --git a/spec/controllers/tags_controller_spec.rb b/spec/controllers/tags_controller_spec.rb index e41c724bb..686ce3980 100644 --- a/spec/controllers/tags_controller_spec.rb +++ b/spec/controllers/tags_controller_spec.rb @@ -2,16 +2,18 @@ # # Table name: tags # -# id :integer not null, primary key -# name :string(255) default("latest"), not null -# repository_id :integer not null -# created_at :datetime not null -# updated_at :datetime not null -# user_id :integer -# digest :string(255) -# image_id :string(255) default("") -# marked :boolean default(FALSE) -# username :string(255) +# id :integer not null, primary key +# name :string(255) default("latest"), not null +# repository_id :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# user_id :integer +# digest :string(255) +# image_id :string(255) default("") +# marked :boolean default(FALSE) +# username :string(255) +# scanned :integer default(0) +# vulnerabilities :text(65535) # # Indexes # @@ -37,16 +39,14 @@ enable_security_vulns_module! end - it "assigns the requested tag as @tag" do - allow_any_instance_of(::Portus::Security).to receive(:vulnerabilities) - .and_return([]) + it "assigns the requested tag as @tag", focus: true do get :show, { id: tag.to_param }, valid_session expect(assigns(:tag)).to eq(tag) expect(response.status).to eq 200 end it "assigns the tag's vulnerabilities as @vulnerabilities" do - allow_any_instance_of(::Portus::Security).to receive(:vulnerabilities) + allow_any_instance_of(Tag).to receive(:fetch_vulnerabilities) .and_return(["something"]) get :show, { id: tag.to_param }, valid_session expect(assigns(:vulnerabilities)).to eq(["something"]) diff --git a/spec/lib/portus/background/security_scanning_spec.rb b/spec/lib/portus/background/security_scanning_spec.rb new file mode 100644 index 000000000..563d99def --- /dev/null +++ b/spec/lib/portus/background/security_scanning_spec.rb @@ -0,0 +1,91 @@ +require "rails_helper" +require "portus/background/security_scanning" + +describe ::Portus::Background::SecurityScanning do + let!(:admin) { create(:admin) } + let!(:token) { create(:application_token, user: admin) } + let!(:registry) { create(:registry, hostname: "my.registry:5000", use_ssl: true) } + let!(:repository) { create(:repository, name: "repo", namespace: registry.global_namespace) } + + before :each do + APP_CONFIG["security"] = { + "clair" => { "server" => "http://localhost:6060" }, + "zypper" => { "server" => "" }, + "dummy" => { "server" => "" } + } + end + + describe "#work?" do + it "returns false if security scanning is not enabled" do + APP_CONFIG["security"]["clair"]["server"] = "" + expect(subject.work?).to be_falsey + end + + it "returns true if there are tags to be scanned" do + create(:tag, name: "tag", repository: repository, digest: "1", author: admin) + expect(subject.work?).to be_truthy + end + + it "returns false if no tags are to be scanned" do + create(:tag, name: "tag", repository: repository, digest: "1", + author: admin, scanned: Tag.statuses[:scan_done]) + expect(subject.work?).to be_falsey + end + end + + describe "#execute!" do + it "properly saves the vulnerabilities" do + VCR.turn_on! + + tag = create(:tag, name: "tag", repository: repository, digest: "1", author: admin) + + VCR.use_cassette("background/clair", record: :none) do + subject.execute! + end + + tag.reload + expect(tag.scanned).to eq Tag.statuses[:scan_done] + expect(tag.vulnerabilities).to_not be_empty + end + + it "ignores it when a push has happened while fetching vulnerabilities" do + tag = create(:tag, name: "tag", repository: repository, digest: "1", author: admin) + + allow_any_instance_of(::Portus::Security).to receive(:vulnerabilities) do + tag.reload + expect(tag.scanned).to eq Tag.statuses[:scan_working] + + # Faking a push + Tag.update_all(scanned: Tag.statuses[:scan_none]) + [] + end + + subject.execute! + + tag.reload + expect(tag.scanned).to eq Tag.statuses[:scan_none] + end + + it "ignores it when a delete has happened while fetching vulnerabilities" do + tag = create(:tag, name: "tag", repository: repository, digest: "1", author: admin) + + allow_any_instance_of(::Portus::Security).to receive(:vulnerabilities) do |_, *_args| + tag.reload + expect(tag.scanned).to eq Tag.statuses[:scan_working] + + # Faking a delete + Tag.delete_all + [] + end + + subject.execute! + expect { tag.reload }.to raise_error(ActiveRecord::RecordNotFound) + end + end + + describe "#to_s" do + it "works" do + expect(subject.to_s).to eq "Security scanning" + end + end +end diff --git a/spec/lib/portus/security/clair_spec.rb b/spec/lib/portus/security/clair_spec.rb index 5d924bd4a..a17a25f3d 100644 --- a/spec/lib/portus/security/clair_spec.rb +++ b/spec/lib/portus/security/clair_spec.rb @@ -40,14 +40,6 @@ def expect_cve_match(cves, given, expected) "NamespaceName" => "alpine:v3.4", "Link" => "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-8859", "Severity" => "High", - "Metadata" => { - "NVD" => { - "CVSSv2" => { - "Score" => 7.5, - "Vectors" => "AV:N/AC:L/Au:N/C:P/I:P" - } - } - }, "FixedBy" => "1.1.14-r13" }, { @@ -55,14 +47,6 @@ def expect_cve_match(cves, given, expected) "NamespaceName" => "alpine:v3.4", "Link" => "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-6301", "Severity" => "High", - "Metadata" => { - "NVD" => { - "CVSSv2" => { - "Score" => 7.8, - "Vectors" => "AV:N/AC:L/Au:N/C:N/I:N" - } - } - }, "FixedBy" => "1.24.2-r12" } ] @@ -114,7 +98,7 @@ def expect_cve_match(cves, given, expected) VCR.turn_on! res = {} - msg = "Error for "\ + msg = "Could not post "\ "'sha256:28c417e954d8f9d2439d5b9c7ea3dcb2fd31690bf2d79b94333d889ea26689d2':"\ " Something went wrong when fetching" expect(Rails.logger).to receive(:debug).with(msg) diff --git a/spec/lib/portus/security_spec.rb b/spec/lib/portus/security_spec.rb index 3a941106a..07640279a 100644 --- a/spec/lib/portus/security_spec.rb +++ b/spec/lib/portus/security_spec.rb @@ -14,7 +14,8 @@ } sec = ::Portus::Security.new("some", "tag") - expect(sec.enabled?).to be_falsey + expect(sec.available?).to be_falsey + expect(::Portus::Security.enabled?).to be_falsey end it "is enabled when at least one has been configured" do @@ -29,7 +30,8 @@ } sec = ::Portus::Security.new("some", "tag") - expect(sec.enabled?).to be_truthy + expect(sec.available?).to be_truthy + expect(::Portus::Security.enabled?).to be_truthy end it "is enabled when all has been configured" do @@ -44,7 +46,8 @@ } sec = ::Portus::Security.new("some", "tag") - expect(sec.enabled?).to be_truthy + expect(sec.available?).to be_truthy + expect(::Portus::Security.enabled?).to be_truthy end end end diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb index bc608b201..b495dd307 100644 --- a/spec/models/tag_spec.rb +++ b/spec/models/tag_spec.rb @@ -2,16 +2,18 @@ # # Table name: tags # -# id :integer not null, primary key -# name :string(255) default("latest"), not null -# repository_id :integer not null -# created_at :datetime not null -# updated_at :datetime not null -# user_id :integer -# digest :string(255) -# image_id :string(255) default("") -# marked :boolean default(FALSE) -# username :string(255) +# id :integer not null, primary key +# name :string(255) default("latest"), not null +# repository_id :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# user_id :integer +# digest :string(255) +# image_id :string(255) default("") +# marked :boolean default(FALSE) +# username :string(255) +# scanned :integer default(0) +# vulnerabilities :text(65535) # # Indexes # @@ -201,4 +203,36 @@ def fetch_digest_test expect(tag.owner).to eq "user" end end + + describe "#fetch_vulnerabilities" do + let!(:tag) do + create(:tag, name: "tag", user_id: user.id, repository: repository, + digest: "1", vulnerabilities: "something") + end + + it "returns nil if security scanning is not enabled" do + # Checking that even if scanning is done and there are vulnerabilities, it + # returns nil because security scanning is disabled. + tag.update_columns(scanned: Tag.statuses[:scan_done], vulnerabilities: "something") + + expect(tag.fetch_vulnerabilities).to be_nil + end + + it "returns nil if scan has not started" do + enable_security_vulns_module! + expect(tag.fetch_vulnerabilities).to be_nil + end + + it "returns nil if scan is work in progress" do + enable_security_vulns_module! + tag.update_columns(scanned: Tag.statuses[:scan_working]) + expect(tag.fetch_vulnerabilities).to be_nil + end + + it "returns the vulnerabilities when scan is over" do + enable_security_vulns_module! + tag.update_columns(scanned: Tag.statuses[:scan_done], vulnerabilities: "something") + expect(tag.fetch_vulnerabilities).to eq "something" + end + end end diff --git a/spec/vcr_cassettes/background/clair.yml b/spec/vcr_cassettes/background/clair.yml new file mode 100644 index 000000000..87d682e0a --- /dev/null +++ b/spec/vcr_cassettes/background/clair.yml @@ -0,0 +1,338 @@ +--- +http_interactions: +- request: + method: get + uri: https://my.registry:5000/v2/repo/manifests/tag + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/vnd.docker.distribution.manifest.v2+json + User-Agent: + - Ruby + response: + status: + code: 401 + message: Unauthorized + headers: + Content-Type: + - application/json; charset=utf-8 + Docker-Distribution-Api-Version: + - registry/2.0 + Www-Authenticate: + - Bearer realm="https://my.registry/v2/token",service="my.registry:5000",scope="repository:repo:pull" + Date: + - Thu, 30 Nov 2017 15:41:58 GMT + Content-Length: + - '147' + body: + encoding: UTF-8 + string: '{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"repository","Class":"","Name":"repo","Action":"pull"}]}]} + +' + http_version: + recorded_at: Thu, 30 Nov 2017 15:41:58 GMT +- request: + method: get + uri: https://my.registry/v2/token?account=portus&scope=repository:repo:pull&service=my.registry:5000 + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + Authorization: + - Basic cG9ydHVzOnBvcnR1czEyMzQ= + response: + status: + code: 200 + message: OK + headers: + Server: + - nginx + Date: + - Thu, 30 Nov 2017 15:41:58 GMT + Content-Type: + - application/json; charset=utf-8 + Transfer-Encoding: + - chunked + Connection: + - keep-alive + X-Frame-Options: + - DENY + - SAMEORIGIN + X-Xss-Protection: + - 1; mode=block + - 1; mode=block + X-Content-Type-Options: + - nosniff + - nosniff + Vary: + - Accept-Encoding + Etag: + - W/"0b88b3c2cbaea124c8f59ca61db8d270" + Cache-Control: + - max-age=0, private, must-revalidate + Set-Cookie: + - _portus_session=bHhTeVZwOTVWR0duaXQ2NW94ckdnemh2VnlQbjFDS1c3K0VYNlcwT0cza25NVDRHMkxodXlqU04rOURvb0RkYVBjbXQ2OWZNVVI0UFlPNEZVUzgrY3c9PS0tWFFUamRqeVpGTUFFdkNycEVxekxOdz09--3250d3646aa3af68a2d842a6e6a73fefed49795a; + path=/; HttpOnly + X-Request-Id: + - 3e9b6db6-f1ae-480a-a7bc-baea8299792b + X-Runtime: + - '0.138606' + X-Ua-Compatible: + - IE=edge + body: + encoding: ASCII-8BIT + string: '{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IkxGNFU6VFRHRjpWN0ZMOlFEQkY6Tk5CVTo2S0RIOjNONU46UlNXQTpWQVNVOkoyUVI6N0tGQjpRTEo0In0.eyJpc3MiOiJyZWdpc3RyeS5tc3NvbGEuY2F0Iiwic3ViIjoicG9ydHVzIiwiYXVkIjoicmVnaXN0cnkubXNzb2xhLmNhdDo1MDAwIiwiaWF0IjoxNTEyMDU2NTE4LCJuYmYiOjE1MTIwNTY1MTMsImV4cCI6MTUxMjA1NjgxOCwianRpIjoiZGFLNFdRUjh0czFXd0FKaEFXUGNIRkNma1F4UmZRd0R3QzczTllpcnZwIiwiYWNjZXNzIjpbeyJ0eXBlIjoicmVwb3NpdG9yeSIsIm5hbWUiOiJyZXBvIiwiYWN0aW9ucyI6WyJwdWxsIl19XX0.phzTxtkV2TVvqB-dWIGgXFB3Z4fu0M0N7kfKAJTWee1I3wqSO9KVf3Z9L73V4WgknVPsni3mibPuxoWZrdAS6h4vHoUQ8k92MdswIAMzxDYx3TahPnPXz1PXflqwsQD-SZ93hU_pJF06EWdJhXzWk-8pvayo7FkhGHS1XJD0W_ktlGZ2mxYib6tMxaIiVGoDwitHE9CxVvTNIAUTqmqvQrKmYk0y0BfdSD6J3dK_A3-t7XPa2J9ZmIBegBKvC7kvFI8ykB5JgLi2xIq0RzLDjb1ZTLIy3riAK46Crl_PwQiYbhNSQZmbF0TrmAtFM-telIwqDiY9Jz-FfjTRfZ3BrA","expires_in":300,"issued_at":"2017-11-30T15:41:58+00:00"}' + http_version: + recorded_at: Thu, 30 Nov 2017 15:41:58 GMT +- request: + method: get + uri: https://my.registry:5000/v2/repo/manifests/tag + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/vnd.docker.distribution.manifest.v2+json + User-Agent: + - Ruby + Authorization: + - Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IkxGNFU6VFRHRjpWN0ZMOlFEQkY6Tk5CVTo2S0RIOjNONU46UlNXQTpWQVNVOkoyUVI6N0tGQjpRTEo0In0.eyJpc3MiOiJyZWdpc3RyeS5tc3NvbGEuY2F0Iiwic3ViIjoicG9ydHVzIiwiYXVkIjoicmVnaXN0cnkubXNzb2xhLmNhdDo1MDAwIiwiaWF0IjoxNTEyMDU2NTE4LCJuYmYiOjE1MTIwNTY1MTMsImV4cCI6MTUxMjA1NjgxOCwianRpIjoiZGFLNFdRUjh0czFXd0FKaEFXUGNIRkNma1F4UmZRd0R3QzczTllpcnZwIiwiYWNjZXNzIjpbeyJ0eXBlIjoicmVwb3NpdG9yeSIsIm5hbWUiOiJyZXBvIiwiYWN0aW9ucyI6WyJwdWxsIl19XX0.phzTxtkV2TVvqB-dWIGgXFB3Z4fu0M0N7kfKAJTWee1I3wqSO9KVf3Z9L73V4WgknVPsni3mibPuxoWZrdAS6h4vHoUQ8k92MdswIAMzxDYx3TahPnPXz1PXflqwsQD-SZ93hU_pJF06EWdJhXzWk-8pvayo7FkhGHS1XJD0W_ktlGZ2mxYib6tMxaIiVGoDwitHE9CxVvTNIAUTqmqvQrKmYk0y0BfdSD6J3dK_A3-t7XPa2J9ZmIBegBKvC7kvFI8ykB5JgLi2xIq0RzLDjb1ZTLIy3riAK46Crl_PwQiYbhNSQZmbF0TrmAtFM-telIwqDiY9Jz-FfjTRfZ3BrA + response: + status: + code: 200 + message: OK + headers: + Content-Length: + - '1159' + Content-Type: + - application/vnd.docker.distribution.manifest.v2+json + Docker-Content-Digest: + - sha256:3b5d6016f31140d9be1af6c8fba1f70ed9fb3f095a4433754d3c8d3691372990 + Docker-Distribution-Api-Version: + - registry/2.0 + Etag: + - '"sha256:3b5d6016f31140d9be1af6c8fba1f70ed9fb3f095a4433754d3c8d3691372990"' + Date: + - Thu, 30 Nov 2017 15:41:58 GMT + body: + encoding: UTF-8 + string: |- + { + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { + "mediaType": "application/vnd.docker.container.image.v1+json", + "size": 3398, + "digest": "sha256:020daa9b782f8718b17ec544fc2b1dd143cb7221de24b4cb5e594d9d223a86b2" + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 2382139, + "digest": "sha256:28c417e954d8f9d2439d5b9c7ea3dcb2fd31690bf2d79b94333d889ea26689d2" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 1333241, + "digest": "sha256:faa8a552d0bddb391293b470084c91e7790c0d70fa17b69627a32729de105562" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 5144622, + "digest": "sha256:acdc6057f492499eac1f4bcabe30e712d6fdf46c3b161be91294e6d3acc90004" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 17390, + "digest": "sha256:6bda536f8dd6d360178e4e3871301bb9ed706eb2ed439be52d068aa3dec2fd00" + } + ] + } + http_version: + recorded_at: Thu, 30 Nov 2017 15:41:58 GMT +- request: + method: post + uri: http://localhost:6060/v1/layers + body: + encoding: UTF-8 + string: '{"Layer":{"Name":"sha256:28c417e954d8f9d2439d5b9c7ea3dcb2fd31690bf2d79b94333d889ea26689d2","NamespaceName":"","Path":"https://my.registry:5000/v2/repo/blobs/sha256:28c417e954d8f9d2439d5b9c7ea3dcb2fd31690bf2d79b94333d889ea26689d2","Headers":{"Authorization":"Bearer + eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IkxGNFU6VFRHRjpWN0ZMOlFEQkY6Tk5CVTo2S0RIOjNONU46UlNXQTpWQVNVOkoyUVI6N0tGQjpRTEo0In0.eyJpc3MiOiJyZWdpc3RyeS5tc3NvbGEuY2F0Iiwic3ViIjoicG9ydHVzIiwiYXVkIjoicmVnaXN0cnkubXNzb2xhLmNhdDo1MDAwIiwiaWF0IjoxNTEyMDU2NTE4LCJuYmYiOjE1MTIwNTY1MTMsImV4cCI6MTUxMjA1NjgxOCwianRpIjoiZGFLNFdRUjh0czFXd0FKaEFXUGNIRkNma1F4UmZRd0R3QzczTllpcnZwIiwiYWNjZXNzIjpbeyJ0eXBlIjoicmVwb3NpdG9yeSIsIm5hbWUiOiJyZXBvIiwiYWN0aW9ucyI6WyJwdWxsIl19XX0.phzTxtkV2TVvqB-dWIGgXFB3Z4fu0M0N7kfKAJTWee1I3wqSO9KVf3Z9L73V4WgknVPsni3mibPuxoWZrdAS6h4vHoUQ8k92MdswIAMzxDYx3TahPnPXz1PXflqwsQD-SZ93hU_pJF06EWdJhXzWk-8pvayo7FkhGHS1XJD0W_ktlGZ2mxYib6tMxaIiVGoDwitHE9CxVvTNIAUTqmqvQrKmYk0y0BfdSD6J3dK_A3-t7XPa2J9ZmIBegBKvC7kvFI8ykB5JgLi2xIq0RzLDjb1ZTLIy3riAK46Crl_PwQiYbhNSQZmbF0TrmAtFM-telIwqDiY9Jz-FfjTRfZ3BrA"},"ParentName":"","Format":"Docker","IndexedByVersion":0,"Features":[]}}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 201 + message: Created + headers: + Content-Type: + - application/json;charset=utf-8 + Server: + - clair + Date: + - Thu, 30 Nov 2017 15:41:58 GMT + Content-Length: + - '828' + body: + encoding: ASCII-8BIT + string: '{"Layer":{"Name":"sha256:28c417e954d8f9d2439d5b9c7ea3dcb2fd31690bf2d79b94333d889ea26689d2","Path":"https://my.registry:5000/v2/repo/blobs/sha256:28c417e954d8f9d2439d5b9c7ea3dcb2fd31690bf2d79b94333d889ea26689d2","Headers":{"Authorization":"Bearer + eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IkxGNFU6VFRHRjpWN0ZMOlFEQkY6Tk5CVTo2S0RIOjNONU46UlNXQTpWQVNVOkoyUVI6N0tGQjpRTEo0In0.eyJpc3MiOiJyZWdpc3RyeS5tc3NvbGEuY2F0Iiwic3ViIjoicG9ydHVzIiwiYXVkIjoicmVnaXN0cnkubXNzb2xhLmNhdDo1MDAwIiwiaWF0IjoxNTEyMDU2NTE4LCJuYmYiOjE1MTIwNTY1MTMsImV4cCI6MTUxMjA1NjgxOCwianRpIjoiZGFLNFdRUjh0czFXd0FKaEFXUGNIRkNma1F4UmZRd0R3QzczTllpcnZwIiwiYWNjZXNzIjpbeyJ0eXBlIjoicmVwb3NpdG9yeSIsIm5hbWUiOiJyZXBvIiwiYWN0aW9ucyI6WyJwdWxsIl19XX0.phzTxtkV2TVvqB-dWIGgXFB3Z4fu0M0N7kfKAJTWee1I3wqSO9KVf3Z9L73V4WgknVPsni3mibPuxoWZrdAS6h4vHoUQ8k92MdswIAMzxDYx3TahPnPXz1PXflqwsQD-SZ93hU_pJF06EWdJhXzWk-8pvayo7FkhGHS1XJD0W_ktlGZ2mxYib6tMxaIiVGoDwitHE9CxVvTNIAUTqmqvQrKmYk0y0BfdSD6J3dK_A3-t7XPa2J9ZmIBegBKvC7kvFI8ykB5JgLi2xIq0RzLDjb1ZTLIy3riAK46Crl_PwQiYbhNSQZmbF0TrmAtFM-telIwqDiY9Jz-FfjTRfZ3BrA"},"Format":"Docker","IndexedByVersion":3}} + +' + http_version: + recorded_at: Thu, 30 Nov 2017 15:41:58 GMT +- request: + method: post + uri: http://localhost:6060/v1/layers + body: + encoding: UTF-8 + string: '{"Layer":{"Name":"sha256:faa8a552d0bddb391293b470084c91e7790c0d70fa17b69627a32729de105562","NamespaceName":"","Path":"https://my.registry:5000/v2/repo/blobs/sha256:faa8a552d0bddb391293b470084c91e7790c0d70fa17b69627a32729de105562","Headers":{"Authorization":"Bearer + eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IkxGNFU6VFRHRjpWN0ZMOlFEQkY6Tk5CVTo2S0RIOjNONU46UlNXQTpWQVNVOkoyUVI6N0tGQjpRTEo0In0.eyJpc3MiOiJyZWdpc3RyeS5tc3NvbGEuY2F0Iiwic3ViIjoicG9ydHVzIiwiYXVkIjoicmVnaXN0cnkubXNzb2xhLmNhdDo1MDAwIiwiaWF0IjoxNTEyMDU2NTE4LCJuYmYiOjE1MTIwNTY1MTMsImV4cCI6MTUxMjA1NjgxOCwianRpIjoiZGFLNFdRUjh0czFXd0FKaEFXUGNIRkNma1F4UmZRd0R3QzczTllpcnZwIiwiYWNjZXNzIjpbeyJ0eXBlIjoicmVwb3NpdG9yeSIsIm5hbWUiOiJyZXBvIiwiYWN0aW9ucyI6WyJwdWxsIl19XX0.phzTxtkV2TVvqB-dWIGgXFB3Z4fu0M0N7kfKAJTWee1I3wqSO9KVf3Z9L73V4WgknVPsni3mibPuxoWZrdAS6h4vHoUQ8k92MdswIAMzxDYx3TahPnPXz1PXflqwsQD-SZ93hU_pJF06EWdJhXzWk-8pvayo7FkhGHS1XJD0W_ktlGZ2mxYib6tMxaIiVGoDwitHE9CxVvTNIAUTqmqvQrKmYk0y0BfdSD6J3dK_A3-t7XPa2J9ZmIBegBKvC7kvFI8ykB5JgLi2xIq0RzLDjb1ZTLIy3riAK46Crl_PwQiYbhNSQZmbF0TrmAtFM-telIwqDiY9Jz-FfjTRfZ3BrA"},"ParentName":"sha256:28c417e954d8f9d2439d5b9c7ea3dcb2fd31690bf2d79b94333d889ea26689d2","Format":"Docker","IndexedByVersion":0,"Features":[]}}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 201 + message: Created + headers: + Content-Type: + - application/json;charset=utf-8 + Server: + - clair + Date: + - Thu, 30 Nov 2017 15:41:58 GMT + Content-Length: + - '885' + body: + encoding: ASCII-8BIT + string: '{"Layer":{"Name":"sha256:faa8a552d0bddb391293b470084c91e7790c0d70fa17b69627a32729de105562","Path":"https://my.registry:5000/v2/repo/blobs/sha256:faa8a552d0bddb391293b470084c91e7790c0d70fa17b69627a32729de105562","Headers":{"Authorization":"Bearer + eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IkxGNFU6VFRHRjpWN0ZMOlFEQkY6Tk5CVTo2S0RIOjNONU46UlNXQTpWQVNVOkoyUVI6N0tGQjpRTEo0In0.eyJpc3MiOiJyZWdpc3RyeS5tc3NvbGEuY2F0Iiwic3ViIjoicG9ydHVzIiwiYXVkIjoicmVnaXN0cnkubXNzb2xhLmNhdDo1MDAwIiwiaWF0IjoxNTEyMDU2NTE4LCJuYmYiOjE1MTIwNTY1MTMsImV4cCI6MTUxMjA1NjgxOCwianRpIjoiZGFLNFdRUjh0czFXd0FKaEFXUGNIRkNma1F4UmZRd0R3QzczTllpcnZwIiwiYWNjZXNzIjpbeyJ0eXBlIjoicmVwb3NpdG9yeSIsIm5hbWUiOiJyZXBvIiwiYWN0aW9ucyI6WyJwdWxsIl19XX0.phzTxtkV2TVvqB-dWIGgXFB3Z4fu0M0N7kfKAJTWee1I3wqSO9KVf3Z9L73V4WgknVPsni3mibPuxoWZrdAS6h4vHoUQ8k92MdswIAMzxDYx3TahPnPXz1PXflqwsQD-SZ93hU_pJF06EWdJhXzWk-8pvayo7FkhGHS1XJD0W_ktlGZ2mxYib6tMxaIiVGoDwitHE9CxVvTNIAUTqmqvQrKmYk0y0BfdSD6J3dK_A3-t7XPa2J9ZmIBegBKvC7kvFI8ykB5JgLi2xIq0RzLDjb1ZTLIy3riAK46Crl_PwQiYbhNSQZmbF0TrmAtFM-telIwqDiY9Jz-FfjTRfZ3BrA"},"ParentName":"sha256:28c417e954d8f9d2439d5b9c7ea3dcb2fd31690bf2d79b94333d889ea26689d2","Format":"Docker","IndexedByVersion":3}} + +' + http_version: + recorded_at: Thu, 30 Nov 2017 15:41:58 GMT +- request: + method: post + uri: http://localhost:6060/v1/layers + body: + encoding: UTF-8 + string: '{"Layer":{"Name":"sha256:acdc6057f492499eac1f4bcabe30e712d6fdf46c3b161be91294e6d3acc90004","NamespaceName":"","Path":"https://my.registry:5000/v2/repo/blobs/sha256:acdc6057f492499eac1f4bcabe30e712d6fdf46c3b161be91294e6d3acc90004","Headers":{"Authorization":"Bearer + eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IkxGNFU6VFRHRjpWN0ZMOlFEQkY6Tk5CVTo2S0RIOjNONU46UlNXQTpWQVNVOkoyUVI6N0tGQjpRTEo0In0.eyJpc3MiOiJyZWdpc3RyeS5tc3NvbGEuY2F0Iiwic3ViIjoicG9ydHVzIiwiYXVkIjoicmVnaXN0cnkubXNzb2xhLmNhdDo1MDAwIiwiaWF0IjoxNTEyMDU2NTE4LCJuYmYiOjE1MTIwNTY1MTMsImV4cCI6MTUxMjA1NjgxOCwianRpIjoiZGFLNFdRUjh0czFXd0FKaEFXUGNIRkNma1F4UmZRd0R3QzczTllpcnZwIiwiYWNjZXNzIjpbeyJ0eXBlIjoicmVwb3NpdG9yeSIsIm5hbWUiOiJyZXBvIiwiYWN0aW9ucyI6WyJwdWxsIl19XX0.phzTxtkV2TVvqB-dWIGgXFB3Z4fu0M0N7kfKAJTWee1I3wqSO9KVf3Z9L73V4WgknVPsni3mibPuxoWZrdAS6h4vHoUQ8k92MdswIAMzxDYx3TahPnPXz1PXflqwsQD-SZ93hU_pJF06EWdJhXzWk-8pvayo7FkhGHS1XJD0W_ktlGZ2mxYib6tMxaIiVGoDwitHE9CxVvTNIAUTqmqvQrKmYk0y0BfdSD6J3dK_A3-t7XPa2J9ZmIBegBKvC7kvFI8ykB5JgLi2xIq0RzLDjb1ZTLIy3riAK46Crl_PwQiYbhNSQZmbF0TrmAtFM-telIwqDiY9Jz-FfjTRfZ3BrA"},"ParentName":"sha256:faa8a552d0bddb391293b470084c91e7790c0d70fa17b69627a32729de105562","Format":"Docker","IndexedByVersion":0,"Features":[]}}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 201 + message: Created + headers: + Content-Type: + - application/json;charset=utf-8 + Server: + - clair + Date: + - Thu, 30 Nov 2017 15:41:58 GMT + Content-Length: + - '884' + body: + encoding: ASCII-8BIT + string: '{"Layer":{"Name":"sha256:acdc6057f492499eac1f4bcabe30e712d6fdf46c3b161be91294e6d3acc90004","Path":"https://my.registry:5000/v2/repo/blobs/sha256:acdc6057f492499eac1f4bcabe30e712d6fdf46c3b161be91294e6d3acc90004","Headers":{"Authorization":"Bearer + eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IkxGNFU6VFRHRjpWN0ZMOlFEQkY6Tk5CVTo2S0RIOjNONU46UlNXQTpWQVNVOkoyUVI6N0tGQjpRTEo0In0.eyJpc3MiOiJyZWdpc3RyeS5tc3NvbGEuY2F0Iiwic3ViIjoicG9ydHVzIiwiYXVkIjoicmVnaXN0cnkubXNzb2xhLmNhdDo1MDAwIiwiaWF0IjoxNTEyMDU2NTE4LCJuYmYiOjE1MTIwNTY1MTMsImV4cCI6MTUxMjA1NjgxOCwianRpIjoiZGFLNFdRUjh0czFXd0FKaEFXUGNIRkNma1F4UmZRd0R3QzczTllpcnZwIiwiYWNjZXNzIjpbeyJ0eXBlIjoicmVwb3NpdG9yeSIsIm5hbWUiOiJyZXBvIiwiYWN0aW9ucyI6WyJwdWxsIl19XX0.phzTxtkV2TVvqB-dWIGgXFB3Z4fu0M0N7kfKAJTWee1I3wqSO9KVf3Z9L73V4WgknVPsni3mibPuxoWZrdAS6h4vHoUQ8k92MdswIAMzxDYx3TahPnPXz1PXflqwsQD-SZ93hU_pJF06EWdJhXzWk-8pvayo7FkhGHS1XJD0W_ktlGZ2mxYib6tMxaIiVGoDwitHE9CxVvTNIAUTqmqvQrKmYk0y0BfdSD6J3dK_A3-t7XPa2J9ZmIBegBKvC7kvFI8ykB5JgLi2xIq0RzLDjb1ZTLIy3riAK46Crl_PwQiYbhNSQZmbF0TrmAtFM-telIwqDiY9Jz-FfjTRfZ3BrA"},"ParentName":"sha256:faa8a552d0bddb391293b470084c91e7790c0d70fa17b69627a32729de105562","Format":"Docker","IndexedByVersion":3}} + +' + http_version: + recorded_at: Thu, 30 Nov 2017 15:41:58 GMT +- request: + method: post + uri: http://localhost:6060/v1/layers + body: + encoding: UTF-8 + string: '{"Layer":{"Name":"sha256:6bda536f8dd6d360178e4e3871301bb9ed706eb2ed439be52d068aa3dec2fd00","NamespaceName":"","Path":"https://my.registry:5000/v2/repo/blobs/sha256:6bda536f8dd6d360178e4e3871301bb9ed706eb2ed439be52d068aa3dec2fd00","Headers":{"Authorization":"Bearer + eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IkxGNFU6VFRHRjpWN0ZMOlFEQkY6Tk5CVTo2S0RIOjNONU46UlNXQTpWQVNVOkoyUVI6N0tGQjpRTEo0In0.eyJpc3MiOiJyZWdpc3RyeS5tc3NvbGEuY2F0Iiwic3ViIjoicG9ydHVzIiwiYXVkIjoicmVnaXN0cnkubXNzb2xhLmNhdDo1MDAwIiwiaWF0IjoxNTEyMDU2NTE4LCJuYmYiOjE1MTIwNTY1MTMsImV4cCI6MTUxMjA1NjgxOCwianRpIjoiZGFLNFdRUjh0czFXd0FKaEFXUGNIRkNma1F4UmZRd0R3QzczTllpcnZwIiwiYWNjZXNzIjpbeyJ0eXBlIjoicmVwb3NpdG9yeSIsIm5hbWUiOiJyZXBvIiwiYWN0aW9ucyI6WyJwdWxsIl19XX0.phzTxtkV2TVvqB-dWIGgXFB3Z4fu0M0N7kfKAJTWee1I3wqSO9KVf3Z9L73V4WgknVPsni3mibPuxoWZrdAS6h4vHoUQ8k92MdswIAMzxDYx3TahPnPXz1PXflqwsQD-SZ93hU_pJF06EWdJhXzWk-8pvayo7FkhGHS1XJD0W_ktlGZ2mxYib6tMxaIiVGoDwitHE9CxVvTNIAUTqmqvQrKmYk0y0BfdSD6J3dK_A3-t7XPa2J9ZmIBegBKvC7kvFI8ykB5JgLi2xIq0RzLDjb1ZTLIy3riAK46Crl_PwQiYbhNSQZmbF0TrmAtFM-telIwqDiY9Jz-FfjTRfZ3BrA"},"ParentName":"sha256:acdc6057f492499eac1f4bcabe30e712d6fdf46c3b161be91294e6d3acc90004","Format":"Docker","IndexedByVersion":0,"Features":[]}}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 201 + message: Created + headers: + Content-Type: + - application/json;charset=utf-8 + Server: + - clair + Date: + - Thu, 30 Nov 2017 15:41:58 GMT + Content-Length: + - '884' + body: + encoding: ASCII-8BIT + string: '{"Layer":{"Name":"sha256:6bda536f8dd6d360178e4e3871301bb9ed706eb2ed439be52d068aa3dec2fd00","Path":"https://my.registry:5000/v2/repo/blobs/sha256:6bda536f8dd6d360178e4e3871301bb9ed706eb2ed439be52d068aa3dec2fd00","Headers":{"Authorization":"Bearer + eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IkxGNFU6VFRHRjpWN0ZMOlFEQkY6Tk5CVTo2S0RIOjNONU46UlNXQTpWQVNVOkoyUVI6N0tGQjpRTEo0In0.eyJpc3MiOiJyZWdpc3RyeS5tc3NvbGEuY2F0Iiwic3ViIjoicG9ydHVzIiwiYXVkIjoicmVnaXN0cnkubXNzb2xhLmNhdDo1MDAwIiwiaWF0IjoxNTEyMDU2NTE4LCJuYmYiOjE1MTIwNTY1MTMsImV4cCI6MTUxMjA1NjgxOCwianRpIjoiZGFLNFdRUjh0czFXd0FKaEFXUGNIRkNma1F4UmZRd0R3QzczTllpcnZwIiwiYWNjZXNzIjpbeyJ0eXBlIjoicmVwb3NpdG9yeSIsIm5hbWUiOiJyZXBvIiwiYWN0aW9ucyI6WyJwdWxsIl19XX0.phzTxtkV2TVvqB-dWIGgXFB3Z4fu0M0N7kfKAJTWee1I3wqSO9KVf3Z9L73V4WgknVPsni3mibPuxoWZrdAS6h4vHoUQ8k92MdswIAMzxDYx3TahPnPXz1PXflqwsQD-SZ93hU_pJF06EWdJhXzWk-8pvayo7FkhGHS1XJD0W_ktlGZ2mxYib6tMxaIiVGoDwitHE9CxVvTNIAUTqmqvQrKmYk0y0BfdSD6J3dK_A3-t7XPa2J9ZmIBegBKvC7kvFI8ykB5JgLi2xIq0RzLDjb1ZTLIy3riAK46Crl_PwQiYbhNSQZmbF0TrmAtFM-telIwqDiY9Jz-FfjTRfZ3BrA"},"ParentName":"sha256:acdc6057f492499eac1f4bcabe30e712d6fdf46c3b161be91294e6d3acc90004","Format":"Docker","IndexedByVersion":3}} + +' + http_version: + recorded_at: Thu, 30 Nov 2017 15:41:58 GMT +- request: + method: get + uri: http://localhost:6060/v1/layers/sha256:6bda536f8dd6d360178e4e3871301bb9ed706eb2ed439be52d068aa3dec2fd00?features=false&vulnerabilities=true + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/json + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - application/json;charset=utf-8 + Server: + - clair + Date: + - Thu, 30 Nov 2017 15:41:58 GMT + Content-Length: + - '862' + body: + encoding: ASCII-8BIT + string: '{"Layer":{"Name":"sha256:6bda536f8dd6d360178e4e3871301bb9ed706eb2ed439be52d068aa3dec2fd00","NamespaceName":"alpine:v3.4","ParentName":"sha256:acdc6057f492499eac1f4bcabe30e712d6fdf46c3b161be91294e6d3acc90004","IndexedByVersion":3,"Features":[{"Name":"libcrypto1.0","NamespaceName":"alpine:v3.4","VersionFormat":"dpkg","Version":"1.0.2j-r0","AddedBy":"sha256:28c417e954d8f9d2439d5b9c7ea3dcb2fd31690bf2d79b94333d889ea26689d2"},{"Name":"busybox","NamespaceName":"alpine:v3.4","VersionFormat":"dpkg","Version":"1.24.2-r11","Vulnerabilities":[{"Name":"CVE-2016-6301","NamespaceName":"alpine:v3.4","Link":"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-6301","Severity":"High","Metadata":{"NVD":{"CVSSv2":{"Score":7.8,"Vectors":"AV:N/AC:L/Au:N/C:N/I:N"}}},"FixedBy":"1.24.2-r12"},{"Name":"CVE-2017-16544","NamespaceName":"alpine:v3.4","Link":"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16544","Severity":"Unknown","FixedBy":"1.24.2-r13"},{"Name":"CVE-2017-15873","NamespaceName":"alpine:v3.4","Link":"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15873","Severity":"Medium","Metadata":{"NVD":{"CVSSv2":{"Score":4.3,"Vectors":"AV:N/AC:M/Au:N/C:N/I:N"}}},"FixedBy":"1.24.2-r13"}],"AddedBy":"sha256:28c417e954d8f9d2439d5b9c7ea3dcb2fd31690bf2d79b94333d889ea26689d2"},{"Name":"zlib","NamespaceName":"alpine:v3.4","VersionFormat":"dpkg","Version":"1.2.8-r2","Vulnerabilities":[{"Name":"CVE-2016-9840","NamespaceName":"alpine:v3.4","Link":"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-9840","Severity":"Medium","Metadata":{"NVD":{"CVSSv2":{"Score":6.8,"Vectors":"AV:N/AC:M/Au:N/C:P/I:P"}}},"FixedBy":"1.2.11-r0"},{"Name":"CVE-2016-9841","NamespaceName":"alpine:v3.4","Link":"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-9841","Severity":"High","Metadata":{"NVD":{"CVSSv2":{"Score":7.5,"Vectors":"AV:N/AC:L/Au:N/C:P/I:P"}}},"FixedBy":"1.2.11-r0"},{"Name":"CVE-2016-9843","NamespaceName":"alpine:v3.4","Link":"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-9843","Severity":"High","Metadata":{"NVD":{"CVSSv2":{"Score":7.5,"Vectors":"AV:N/AC:L/Au:N/C:P/I:P"}}},"FixedBy":"1.2.11-r0"},{"Name":"CVE-2016-9842","NamespaceName":"alpine:v3.4","Link":"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-9842","Severity":"Medium","Metadata":{"NVD":{"CVSSv2":{"Score":6.8,"Vectors":"AV:N/AC:M/Au:N/C:P/I:P"}}},"FixedBy":"1.2.11-r0"}],"AddedBy":"sha256:28c417e954d8f9d2439d5b9c7ea3dcb2fd31690bf2d79b94333d889ea26689d2"},{"Name":"alpine-baselayout","NamespaceName":"alpine:v3.4","VersionFormat":"dpkg","Version":"3.0.3-r0","AddedBy":"sha256:28c417e954d8f9d2439d5b9c7ea3dcb2fd31690bf2d79b94333d889ea26689d2"},{"Name":"musl","NamespaceName":"alpine:v3.4","VersionFormat":"dpkg","Version":"1.1.14-r12","Vulnerabilities":[{"Name":"CVE-2016-8859","NamespaceName":"alpine:v3.4","Link":"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-8859","Severity":"High","Metadata":{"NVD":{"CVSSv2":{"Score":7.5,"Vectors":"AV:N/AC:L/Au:N/C:P/I:P"}}},"FixedBy":"1.1.14-r13"},{"Name":"CVE-2017-15650","NamespaceName":"alpine:v3.4","Link":"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650","Severity":"Medium","Metadata":{"NVD":{"CVSSv2":{"Score":5,"Vectors":"AV:N/AC:L/Au:N/C:N/I:N"}}},"FixedBy":"1.1.14-r16"}],"AddedBy":"sha256:28c417e954d8f9d2439d5b9c7ea3dcb2fd31690bf2d79b94333d889ea26689d2"},{"Name":"libc-utils","NamespaceName":"alpine:v3.4","VersionFormat":"dpkg","Version":"0.7-r0","AddedBy":"sha256:28c417e954d8f9d2439d5b9c7ea3dcb2fd31690bf2d79b94333d889ea26689d2"},{"Name":"libssl1.0","NamespaceName":"alpine:v3.4","VersionFormat":"dpkg","Version":"1.0.2j-r0","AddedBy":"sha256:28c417e954d8f9d2439d5b9c7ea3dcb2fd31690bf2d79b94333d889ea26689d2"},{"Name":"alpine-keys","NamespaceName":"alpine:v3.4","VersionFormat":"dpkg","Version":"1.1-r0","AddedBy":"sha256:28c417e954d8f9d2439d5b9c7ea3dcb2fd31690bf2d79b94333d889ea26689d2"},{"Name":"scanelf","NamespaceName":"alpine:v3.4","VersionFormat":"dpkg","Version":"1.1.6-r0","AddedBy":"sha256:28c417e954d8f9d2439d5b9c7ea3dcb2fd31690bf2d79b94333d889ea26689d2"},{"Name":"musl-utils","NamespaceName":"alpine:v3.4","VersionFormat":"dpkg","Version":"1.1.14-r12","AddedBy":"sha256:28c417e954d8f9d2439d5b9c7ea3dcb2fd31690bf2d79b94333d889ea26689d2"},{"Name":"apk-tools","NamespaceName":"alpine:v3.4","VersionFormat":"dpkg","Version":"2.6.7-r0","AddedBy":"sha256:28c417e954d8f9d2439d5b9c7ea3dcb2fd31690bf2d79b94333d889ea26689d2"},{"Name":"openssl","NamespaceName":"alpine:v3.4","VersionFormat":"dpkg","Version":"1.0.2k-r0","Vulnerabilities":[{"Name":"CVE-2017-3736","NamespaceName":"alpine:v3.4","Link":"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-3736","Severity":"Unknown","FixedBy":"1.0.2m-r0"},{"Name":"CVE-2017-3735","NamespaceName":"alpine:v3.4","Link":"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-3735","Severity":"Medium","Metadata":{"NVD":{"CVSSv2":{"Score":5,"Vectors":"AV:N/AC:L/Au:N/C:N/I:P"}}},"FixedBy":"1.0.2m-r0"}],"AddedBy":"sha256:faa8a552d0bddb391293b470084c91e7790c0d70fa17b69627a32729de105562"},{"Name":"ca-certificates","NamespaceName":"alpine:v3.4","VersionFormat":"dpkg","Version":"20161130-r0","AddedBy":"sha256:faa8a552d0bddb391293b470084c91e7790c0d70fa17b69627a32729de105562"}]}} + +' + http_version: + recorded_at: Thu, 30 Nov 2017 15:41:58 GMT +recorded_with: VCR 3.0.3