Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
244 additions
and
146 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
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,42 @@ | ||
# frozen_string_literal: true | ||
module Samson | ||
# Hyperclair will pull the image from registry and run scan with Clair scanner | ||
# TODO: should check based on docker_repo_digest not tag | ||
# TODO: this should be a plugin instead and use hooks | ||
module Clair | ||
class << self | ||
def append_job_with_scan(job, docker_tag) | ||
return unless clair = ENV['HYPERCLAIR_PATH'] | ||
|
||
Thread.new do | ||
sleep 0.1 if Rails.env.test? # in test we reuse the same connection, so we cannot use it at the same time | ||
success, output, time = scan(clair, job.project, docker_tag) | ||
status = (success ? "success" : "errored or vulnerabilities found") | ||
output = "### Clair scan: #{status} in #{time}s\n#{output}" | ||
job.reload | ||
job.update_column(:output, job.output + output) | ||
end | ||
end | ||
|
||
private | ||
|
||
def scan(executable, project, docker_ref) | ||
with_time do | ||
Samson::CommandExecutor.execute( | ||
executable, | ||
*project.docker_repo.split('/', 2), | ||
docker_ref, | ||
whitelist_env: ['DOCKER_REGISTRY_USER', 'DOCKER_REGISTRY_PASS', 'PATH'], | ||
timeout: 60 * 60 | ||
) | ||
end | ||
end | ||
|
||
def with_time | ||
result = [] | ||
time = Benchmark.realtime { result = yield } | ||
result << time | ||
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,46 @@ | ||
# frozen_string_literal: true | ||
module Samson | ||
# TODO: reuse in git_repo ? | ||
# safe command execution that makes sure to use timeouts for everything and cleans up dead sub processes | ||
module CommandExecutor | ||
class << self | ||
# timeout could be done more reliably with timeout(1) from gnu coreutils ... but that would add another dependency | ||
def execute(*command, timeout:, whitelist_env: []) | ||
raise ArgumentError, "Positive timeout required" if timeout <= 0 | ||
output = "ABORTED" | ||
pid = nil | ||
|
||
wait = Thread.new do | ||
begin | ||
IO.popen(ENV.to_h.slice(*whitelist_env), command, unsetenv_others: true, err: [:child, :out]) do |io| | ||
pid = io.pid | ||
output = io.read | ||
end | ||
$?&.success? || false | ||
rescue Errno::ENOENT | ||
output = "No such file or directory - #{command.first}" | ||
false | ||
end | ||
end | ||
success = Timeout.timeout(timeout) { wait.value } # using timeout in a blocking thread never interrupts | ||
|
||
return success, output | ||
rescue Timeout::Error | ||
kill_process pid if pid | ||
return false, $!.message | ||
end | ||
|
||
private | ||
|
||
# timeout or parent process interrupted by user with Interrupt or SystemExit | ||
def kill_process(pid) | ||
Process.kill :INT, pid # tell it to stop | ||
sleep 1 # give it a second to clean up | ||
Process.kill :KILL, pid # kill it | ||
Process.wait pid # prevent zombie processes | ||
rescue Errno::ESRCH, Errno::ECHILD # kill or wait failing because pid was already gone | ||
nil | ||
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
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,57 @@ | ||
# frozen_string_literal: true | ||
require_relative '../../test_helper' | ||
|
||
SingleCov.covered! | ||
|
||
describe Samson::Clair do | ||
def execute! | ||
Samson::Clair.append_job_with_scan(job, 'latest') | ||
end | ||
|
||
let(:job) { jobs(:succeeded_test) } | ||
|
||
before { ActiveRecord::Base.stubs(connection: ActiveRecord::Base.connection) } # we update in another thread | ||
|
||
around do |t| | ||
Tempfile.open('clair') do |f| | ||
f.write("#!/bin/bash\necho HELLO\nexit 0") | ||
f.close | ||
File.chmod 0o755, f.path | ||
with_env(HYPERCLAIR_PATH: f.path, DOCKER_REGISTRY: 'my.registry', &t) | ||
end | ||
end | ||
|
||
it "runs clair and reports success to the database" do | ||
execute! | ||
|
||
wait_for_threads | ||
|
||
job.reload | ||
job.output.must_include "Clair scan: success" | ||
job.output.must_include "HELLO" | ||
end | ||
|
||
it "runs clair and reports missing script to the database" do | ||
File.unlink ENV['HYPERCLAIR_PATH'] | ||
|
||
execute! | ||
|
||
wait_for_threads | ||
|
||
job.reload | ||
job.output.must_include "Clair scan: errored" | ||
job.output.must_include "No such file or directory" | ||
end | ||
|
||
it "runs clair and reports failed script to the database" do | ||
File.write ENV['HYPERCLAIR_PATH'], "#!/bin/bash\necho WORLD\nexit 1" | ||
|
||
execute! | ||
|
||
wait_for_threads | ||
|
||
job.reload | ||
job.output.must_include "Clair scan: errored" | ||
job.output.must_include "WORLD" | ||
end | ||
end |
Oops, something went wrong.