attestation: handle bad configurations better

This commit is contained in:
Bo Anderson 2024-07-18 16:11:25 +01:00
parent 4aae003a1a
commit 16d547b030
No known key found for this signature in database
3 changed files with 39 additions and 13 deletions

View File

@ -44,6 +44,12 @@ module Homebrew
# @api private
class GhAuthNeeded < RuntimeError; end
# Raised if attestation verification cannot continue due to invalid
# credentials.
#
# @api private
class GhAuthInvalid < RuntimeError; end
# Returns whether attestation verification is enabled.
#
# @api private
@ -53,6 +59,7 @@ module Homebrew
return true if Homebrew::EnvConfig.verify_attestations?
return false if GitHub::API.credentials.blank?
return false if ENV.fetch("CI", false)
return false if OS.unsupported_configuration?
Homebrew::EnvConfig.developer? || Homebrew::EnvConfig.devcmdrun?
end
@ -117,11 +124,14 @@ module Homebrew
raise GhAuthNeeded, "missing credentials" if credentials.blank?
begin
result = system_command!(gh_executable, args: cmd, env: { "GH_TOKEN" => credentials },
result = system_command!(gh_executable, args: cmd,
env: { "GH_TOKEN" => credentials, "GH_HOST" => "github.com" },
secrets: [credentials], print_stderr: false, chdir: HOMEBREW_TEMP)
rescue ErrorDuringExecution => e
# Even if we have credentials, they may be invalid or malformed.
raise GhAuthNeeded, "invalid credentials" if e.status.exitstatus == 4
if e.status.exitstatus == 4 || e.stderr.include?("HTTP 401: Bad credentials")
raise GhAuthInvalid, "invalid credentials"
end
raise InvalidAttestationError, "attestation verification failed: #{e}"
end

View File

@ -1284,6 +1284,22 @@ on_request: installed_on_request?, options:)
ohai "Verifying attestation for #{formula.name}"
begin
Homebrew::Attestation.check_core_attestation formula.bottle
rescue Homebrew::Attestation::GhAuthInvalid
# Only raise an error if we explicitly opted-in to verification.
raise CannotInstallFormulaError, <<~EOS if Homebrew::EnvConfig.verify_attestations?
The bottle for #{formula.name} could not be verified.
This typically indicates an invalid GitHub API token.
If you have `HOMEBREW_GITHUB_API_TOKEN` set, check it is correct
or unset it and instead run:
gh auth login
EOS
# If we didn't explicitly opt-in, then quietly opt-out in the case of invalid credentials.
# Based on user reports, a significant number of users are running with stale tokens.
ENV["HOMEBREW_NO_VERIFY_ATTESTATIONS"] = "1"
rescue Homebrew::Attestation::GhAuthNeeded
raise CannotInstallFormulaError, <<~EOS
The bottle for #{formula.name} could not be verified.

View File

@ -115,7 +115,7 @@ RSpec.describe Homebrew::Attestation do
expect(described_class).to receive(:system_command!)
.with(fake_gh, args: ["attestation", "verify", cached_download, "--repo",
described_class::HOMEBREW_CORE_REPO, "--format", "json"],
env: { "GH_TOKEN" => fake_gh_creds }, secrets: [fake_gh_creds],
env: { "GH_TOKEN" => fake_gh_creds, "GH_HOST" => "github.com" }, secrets: [fake_gh_creds],
print_stderr: false, chdir: HOMEBREW_TEMP)
.and_raise(ErrorDuringExecution.new(["foo"], status: fake_error_status))
@ -132,14 +132,14 @@ RSpec.describe Homebrew::Attestation do
expect(described_class).to receive(:system_command!)
.with(fake_gh, args: ["attestation", "verify", cached_download, "--repo",
described_class::HOMEBREW_CORE_REPO, "--format", "json"],
env: { "GH_TOKEN" => fake_gh_creds }, secrets: [fake_gh_creds],
env: { "GH_TOKEN" => fake_gh_creds, "GH_HOST" => "github.com" }, secrets: [fake_gh_creds],
print_stderr: false, chdir: HOMEBREW_TEMP)
.and_raise(ErrorDuringExecution.new(["foo"], status: fake_auth_status))
expect do
described_class.check_attestation fake_bottle,
described_class::HOMEBREW_CORE_REPO
end.to raise_error(described_class::GhAuthNeeded)
end.to raise_error(described_class::GhAuthInvalid)
end
it "raises when gh returns invalid JSON" do
@ -149,7 +149,7 @@ RSpec.describe Homebrew::Attestation do
expect(described_class).to receive(:system_command!)
.with(fake_gh, args: ["attestation", "verify", cached_download, "--repo",
described_class::HOMEBREW_CORE_REPO, "--format", "json"],
env: { "GH_TOKEN" => fake_gh_creds }, secrets: [fake_gh_creds],
env: { "GH_TOKEN" => fake_gh_creds, "GH_HOST" => "github.com" }, secrets: [fake_gh_creds],
print_stderr: false, chdir: HOMEBREW_TEMP)
.and_return(fake_result_invalid_json)
@ -166,7 +166,7 @@ RSpec.describe Homebrew::Attestation do
expect(described_class).to receive(:system_command!)
.with(fake_gh, args: ["attestation", "verify", cached_download, "--repo",
described_class::HOMEBREW_CORE_REPO, "--format", "json"],
env: { "GH_TOKEN" => fake_gh_creds }, secrets: [fake_gh_creds],
env: { "GH_TOKEN" => fake_gh_creds, "GH_HOST" => "github.com" }, secrets: [fake_gh_creds],
print_stderr: false, chdir: HOMEBREW_TEMP)
.and_return(fake_json_resp_wrong_sub)
@ -183,7 +183,7 @@ RSpec.describe Homebrew::Attestation do
expect(described_class).to receive(:system_command!)
.with(fake_gh, args: ["attestation", "verify", cached_download, "--repo",
described_class::HOMEBREW_CORE_REPO, "--format", "json"],
env: { "GH_TOKEN" => fake_gh_creds }, secrets: [fake_gh_creds],
env: { "GH_TOKEN" => fake_gh_creds, "GH_HOST" => "github.com" }, secrets: [fake_gh_creds],
print_stderr: false, chdir: HOMEBREW_TEMP)
.and_return(fake_result_json_resp)
@ -204,7 +204,7 @@ RSpec.describe Homebrew::Attestation do
expect(described_class).to receive(:system_command!)
.with(fake_gh, args: ["attestation", "verify", cached_download, "--repo",
described_class::HOMEBREW_CORE_REPO, "--format", "json"],
env: { "GH_TOKEN" => fake_gh_creds }, secrets: [fake_gh_creds],
env: { "GH_TOKEN" => fake_gh_creds, "GH_HOST" => "github.com" }, secrets: [fake_gh_creds],
print_stderr: false, chdir: HOMEBREW_TEMP)
.and_return(fake_result_json_resp)
@ -215,7 +215,7 @@ RSpec.describe Homebrew::Attestation do
expect(described_class).to receive(:system_command!)
.with(fake_gh, args: ["attestation", "verify", cached_download, "--repo",
described_class::HOMEBREW_CORE_REPO, "--format", "json"],
env: { "GH_TOKEN" => fake_gh_creds }, secrets: [fake_gh_creds],
env: { "GH_TOKEN" => fake_gh_creds, "GH_HOST" => "github.com" }, secrets: [fake_gh_creds],
print_stderr: false, chdir: HOMEBREW_TEMP)
.once
.and_raise(described_class::InvalidAttestationError)
@ -223,7 +223,7 @@ RSpec.describe Homebrew::Attestation do
expect(described_class).to receive(:system_command!)
.with(fake_gh, args: ["attestation", "verify", cached_download, "--repo",
described_class::BACKFILL_REPO, "--format", "json"],
env: { "GH_TOKEN" => fake_gh_creds }, secrets: [fake_gh_creds],
env: { "GH_TOKEN" => fake_gh_creds, "GH_HOST" => "github.com" }, secrets: [fake_gh_creds],
print_stderr: false, chdir: HOMEBREW_TEMP)
.and_return(fake_result_json_resp_backfill)
@ -234,7 +234,7 @@ RSpec.describe Homebrew::Attestation do
expect(described_class).to receive(:system_command!)
.with(fake_gh, args: ["attestation", "verify", cached_download, "--repo",
described_class::HOMEBREW_CORE_REPO, "--format", "json"],
env: { "GH_TOKEN" => fake_gh_creds }, secrets: [fake_gh_creds],
env: { "GH_TOKEN" => fake_gh_creds, "GH_HOST" => "github.com" }, secrets: [fake_gh_creds],
print_stderr: false, chdir: HOMEBREW_TEMP)
.once
.and_raise(described_class::InvalidAttestationError)
@ -242,7 +242,7 @@ RSpec.describe Homebrew::Attestation do
expect(described_class).to receive(:system_command!)
.with(fake_gh, args: ["attestation", "verify", cached_download, "--repo",
described_class::BACKFILL_REPO, "--format", "json"],
env: { "GH_TOKEN" => fake_gh_creds }, secrets: [fake_gh_creds],
env: { "GH_TOKEN" => fake_gh_creds, "GH_HOST" => "github.com" }, secrets: [fake_gh_creds],
print_stderr: false, chdir: HOMEBREW_TEMP)
.and_return(fake_result_json_resp_too_new)