From 89645ad45069e3726e8dfd2dc462e0364d64da0b Mon Sep 17 00:00:00 2001 From: Carlo Cabrera <30379873+carlocab@users.noreply.github.com> Date: Mon, 14 Oct 2024 19:21:03 +0800 Subject: [PATCH 1/2] Cache bottle attestations This will help limit the number of times we need to query GitHub for the attestation in CI. --- Library/Homebrew/attestation.rb | 21 +++++++++++++++++++++ Library/Homebrew/cleanup.rb | 13 ++++++++++++- Library/Homebrew/software_spec.rb | 5 +++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/Library/Homebrew/attestation.rb b/Library/Homebrew/attestation.rb index d7479057ebea5..2476771ea8f5c 100644 --- a/Library/Homebrew/attestation.rb +++ b/Library/Homebrew/attestation.rb @@ -189,6 +189,13 @@ def self.check_attestation(bottle, signing_repo, signing_workflow = nil, subject attestation end + ATTESTATION_CACHE_DIRECTORY = T.let((HOMEBREW_CACHE/"attesttion").freeze, Pathname) + + sig { params(bottle: Bottle).returns(Pathname) } + def self.cached_attestation_path(bottle) + ATTESTATION_CACHE_DIRECTORY/bottle.filename.attestation_json(bottle.resource.checksum.hexdigest) + end + ATTESTATION_MAX_RETRIES = 5 # Verifies the given bottle against a cryptographic attestation of build provenance @@ -203,6 +210,16 @@ def self.check_attestation(bottle, signing_repo, signing_workflow = nil, subject # @api private sig { params(bottle: Bottle).returns(T::Hash[T.untyped, T.untyped]) } def self.check_core_attestation(bottle) + cached_attestation = cached_attestation_path(bottle) + + if cached_attestation.exist? + begin + return JSON.parse(cached_attestation.read) + rescue JSON::ParserError + cached_attestation.unlink + end + end + begin # Ideally, we would also constrain the signing workflow here, but homebrew-core # currently uses multiple signing workflows to produce bottles @@ -216,6 +233,8 @@ def self.check_core_attestation(bottle) # workflow, which would then be our sole identity. However, GitHub's # attestations currently do not include reusable workflow state by default. attestation = check_attestation bottle, HOMEBREW_CORE_REPO + ATTESTATION_CACHE_DIRECTORY.mkpath + cached_attestation.atomic_write attestation.to_json return attestation rescue MissingAttestationError odebug "falling back on backfilled attestation for #{bottle}" @@ -257,6 +276,8 @@ def self.check_core_attestation(bottle) end end + ATTESTATION_CACHE_DIRECTORY.mkpath + cached_attestation.atomic_write backfill_attestation.to_json backfill_attestation rescue InvalidAttestationError @attestation_retry_count ||= T.let(Hash.new(0), T.nilable(T::Hash[Bottle, Integer])) diff --git a/Library/Homebrew/cleanup.rb b/Library/Homebrew/cleanup.rb index 99d2fa3311f1f..d4d8361d3ca1b 100644 --- a/Library/Homebrew/cleanup.rb +++ b/Library/Homebrew/cleanup.rb @@ -62,6 +62,8 @@ def stale?(entry, scrub: false) stale_cask?(pathname, scrub) when :gh_actions_artifact stale_gh_actions_artifact?(pathname, scrub) + when :attestation + stale_attestation?(pathname, scrub) else stale_formula?(pathname, scrub) end @@ -76,6 +78,13 @@ def stale_gh_actions_artifact?(pathname, scrub) scrub || prune?(pathname, GH_ACTIONS_ARTIFACT_CLEANUP_DAYS) end + ATTESTATION_CLEANUP_DAYS = 3 + + sig { params(pathname: Pathname, scrub: T::Boolean).returns(T::Boolean) } + def stale_attestation?(pathname, scrub) + scrub || prune?(pathname, ATTESTATION_CLEANUP_DAYS) + end + sig { params(pathname: Pathname, scrub: T::Boolean).returns(T::Boolean) } def stale_api_source?(pathname, scrub) return true if scrub @@ -381,11 +390,13 @@ def cache_files cask_files = (cache/"Cask").directory? ? (cache/"Cask").children : [] api_source_files = (cache/"api-source").glob("*/*/*/*/*") # `////.rb` gh_actions_artifacts = (cache/"gh-actions-artifact").directory? ? (cache/"gh-actions-artifact").children : [] + attestations = (cache/"attestation").directory? ? (cache/"attestation").children : [] files.map { |path| { path:, type: nil } } + cask_files.map { |path| { path:, type: :cask } } + api_source_files.map { |path| { path:, type: :api_source } } + - gh_actions_artifacts.map { |path| { path:, type: :gh_actions_artifact } } + gh_actions_artifacts.map { |path| { path:, type: :gh_actions_artifact } } + + attestations.map { |path| { path:, type: :attestation } } end def cleanup_empty_api_source_directories(directory = cache/"api-source") diff --git a/Library/Homebrew/software_spec.rb b/Library/Homebrew/software_spec.rb index cb7969a7dab90..72494c060ad12 100644 --- a/Library/Homebrew/software_spec.rb +++ b/Library/Homebrew/software_spec.rb @@ -332,6 +332,11 @@ def json "#{name}--#{version}.#{tag}.bottle.json" end + sig { params(checksum: String).returns(String) } + def attestation_json(checksum) + "#{checksum}-#{name}--#{version}.#{tag}.attestation.json" + end + def url_encode ERB::Util.url_encode("#{name}-#{version}#{extname}") end From 67351defe9a14eb78efd4088c630790f123c7baf Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Wed, 16 Oct 2024 21:56:37 +0100 Subject: [PATCH 2/2] Update Library/Homebrew/attestation.rb Co-authored-by: Carlo Cabrera --- Library/Homebrew/attestation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Library/Homebrew/attestation.rb b/Library/Homebrew/attestation.rb index 2476771ea8f5c..4be0a95d51c0f 100644 --- a/Library/Homebrew/attestation.rb +++ b/Library/Homebrew/attestation.rb @@ -189,7 +189,7 @@ def self.check_attestation(bottle, signing_repo, signing_workflow = nil, subject attestation end - ATTESTATION_CACHE_DIRECTORY = T.let((HOMEBREW_CACHE/"attesttion").freeze, Pathname) + ATTESTATION_CACHE_DIRECTORY = T.let((HOMEBREW_CACHE/"attestation").freeze, Pathname) sig { params(bottle: Bottle).returns(Pathname) } def self.cached_attestation_path(bottle)