Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Also check security workflow for scans #552

Merged
merged 1 commit into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions lib/github_fetcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -158,31 +158,44 @@ def rails_app?(repo)
false
end

def fetch_ci_file_content(repo)
github.contents("#{organisation}/#{repo}", path: ".github/workflows/ci.yml").content.unpack1("m")
def fetch_workflows(repo)
ci_yml = fetch_file_content(repo, ".github/workflows/ci.yml")
security_yml = fetch_file_content(repo, ".github/workflows/security.yml")

return nil if ci_yml.nil? && security_yml.nil?

{ ci: ci_yml, security: security_yml }
end

def fetch_file_content(repo, path)
github.contents("#{organisation}/#{repo}", path:).content.unpack1("m")
rescue Octokit::NotFound
nil
end

def has_required_scans?(repo)
return true if ignored_ci_repos.include?(repo)

ci_file = fetch_ci_file_content(repo)
return true if ci_file.nil? # if a CI file is not present assume no scans are needed
workflows = fetch_workflows(repo)
return true if workflows.nil? # if workflows are not present assume no scans are needed

scans_needed = rails_app?(repo) ? %i[sca sast brakeman] : %i[sca sast]
scans_needed.all? { |scan_type| has_scan?(ci_file, scan_type) }
scans_needed.all? do |scan_type|
workflows.values.any? { |workflow| has_scan?(workflow, scan_type) }
end
end

def has_scan?(ci_file, scan_type)
def has_scan?(workflow, scan_type)
return false if workflow.nil?

case scan_type
when :sca
ci_file.include?("uses: alphagov/govuk-infrastructure/.github/workflows/dependency-review.yml@main")
workflow.include?("uses: alphagov/govuk-infrastructure/.github/workflows/dependency-review.yml@main")
when :sast
ci_file.include?("uses: alphagov/govuk-infrastructure/.github/workflows/codeql-analysis.yml@main")
workflow.include?("uses: alphagov/govuk-infrastructure/.github/workflows/codeql-analysis.yml@main")
when :brakeman
ci_file.include?("uses: alphagov/govuk-infrastructure/.github/workflows/brakeman.yml@main") ||
ci_file.include?("bundle exec brakeman")
workflow.include?("uses: alphagov/govuk-infrastructure/.github/workflows/brakeman.yml@main") ||
workflow.include?("bundle exec brakeman")
end
end
end
40 changes: 29 additions & 11 deletions spec/github_fetcher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -279,32 +279,36 @@
end
end

describe "CI checks" do
let(:bad_ci_file) { double(Sawyer::Resource, content: "rubbish") }
let(:good_ci_file) { double(Sawyer::Resource, content: "dXNlczogYWxwaGFnb3YvZ292dWstaW5mcmFzdHJ1Y3R1cmUvLmdpdGh1Yi93\nb3JrZmxvd3MvZGVwZW5kZW5jeS1yZXZpZXcueW1sQG1haW4KdXNlczogYWxw\naGFnb3YvZ292dWstaW5mcmFzdHJ1Y3R1cmUvLmdpdGh1Yi93b3JrZmxvd3Mv\nY29kZXFsLWFuYWx5c2lzLnltbEBtYWluCnVzZXM6IGFscGhhZ292L2dvdnVr\nLWluZnJhc3RydWN0dXJlLy5naXRodWIvd29ya2Zsb3dzL2JyYWtlbWFuLnlt\nbEBtYWlu\n") }
let(:use_labels) { false }
describe "Workflow checks" do
let(:bad_workflow_content) { "rubbish" }
let(:good_workflow_content) { "dXNlczogYWxwaGFnb3YvZ292dWstaW5mcmFzdHJ1Y3R1cmUvLmdpdGh1Yi93\nb3JrZmxvd3MvZGVwZW5kZW5jeS1yZXZpZXcueW1sQG1haW4KdXNlczogYWxw\naGFnb3YvZ292dWstaW5mcmFzdHJ1Y3R1cmUvLmdpdGh1Yi93b3JrZmxvd3Mv\nY29kZXFsLWFuYWx5c2lzLnltbEBtYWluCnVzZXM6IGFscGhhZ292L2dvdnVr\nLWluZnJhc3R1Y3R1cmUvLmdpdGh1Yi93b3JrZmxvd3MvYnJha2VtYW4ueW1s\nQG1haW4\n".unpack1("m") }
let(:repos) { %w[repo1] }
let(:use_labels) { false }

before do
allow(github_fetcher).to receive(:ignored_ci_repos).and_return([])
allow(github_fetcher).to receive(:rails_app?).with("repo1").and_return(false)
end

context "when ci file exists and checks are present" do
context "when workflow file exists and checks are present" do
it "returns true" do
allow(fake_octokit_client).to receive(:contents).and_return(good_ci_file)
allow(github_fetcher).to receive(:fetch_workflows).with("repo1").and_return({ ci: good_workflow_content, security: nil })

expect(github_fetcher.check_team_repos_ci).to match({ "repo1" => true })
end
end

context "when ci file exists and checks are not present" do
context "when workflow file exists and checks are not present" do
it "returns false" do
allow(fake_octokit_client).to receive(:contents).and_return(bad_ci_file)
allow(github_fetcher).to receive(:fetch_workflows).with("repo1").and_return({ ci: bad_workflow_content, security: nil })

expect(github_fetcher.check_team_repos_ci).to match({ "repo1" => false })
end
end

context "when ci file does not exist" do
context "when neither ci file nor security file exist" do
it "returns true" do
allow(fake_octokit_client).to receive(:contents)
.and_raise(Octokit::NotFound.new)
allow(github_fetcher).to receive(:fetch_workflows).with("repo1").and_return(nil)

expect(github_fetcher.check_team_repos_ci).to match({ "repo1" => true })
end
Expand All @@ -317,5 +321,19 @@
expect(github_fetcher.check_team_repos_ci).to match({ "repo1" => true })
end
end

context "when ci and security files exist and checks are mixed" do
it "returns true if any file contains required checks" do
allow(github_fetcher).to receive(:fetch_workflows).with("repo1").and_return({ ci: bad_workflow_content, security: good_workflow_content })

expect(github_fetcher.check_team_repos_ci).to match({ "repo1" => true })
end

it "returns false if no file contains required checks" do
allow(github_fetcher).to receive(:fetch_workflows).with("repo1").and_return({ ci: bad_workflow_content, security: bad_workflow_content })

expect(github_fetcher.check_team_repos_ci).to match({ "repo1" => false })
end
end
end
end