Skip to content

Commit 9e85cb1

Browse files
authored
Attest by default when using trusted publishing (#11)
* Attest by default when using trusted publishing Fallback to pushing without attestations on any failure Signed-off-by: Samuel Giddins <[email protected]> * Unpin rubygems/configure-rubygems-credentials --------- Signed-off-by: Samuel Giddins <[email protected]>
1 parent 612653d commit 9e85cb1

File tree

2 files changed

+58
-0
lines changed

2 files changed

+58
-0
lines changed

action.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ inputs:
99
description: "Whether to setup the trusted publisher for the gem"
1010
required: false
1111
default: "true"
12+
attestations:
13+
description: >-
14+
[EXPERIMENTAL]
15+
Enable experimental support for sigstore attestations.
16+
Only works with RubyGems.org via Trusted Publishing.
17+
required: false
18+
default: "true"
1219
outputs: {}
1320
branding:
1421
color: "red"
@@ -29,6 +36,8 @@ runs:
2936
- name: Run release rake task
3037
run: bundle exec rake release
3138
shell: bash
39+
env:
40+
RUBYOPT: "${{ inputs.attestations == 'true' && format('-r{0}/rubygems-attestation-patch.rb {1}', github.action_path, env.RUBYOPT) || env.RUBYOPT }}"
3241
- name: Wait for release to propagate
3342
if: ${{ inputs.await-release == 'true' }}
3443
run: gem exec rubygems-await pkg/*.gem

rubygems-attestation-patch.rb

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# frozen_string_literal: true
2+
3+
return unless defined?(Gem)
4+
5+
require "rubygems/commands/push_command"
6+
7+
Gem::Commands::PushCommand.prepend(Module.new do
8+
def send_push_request(name, args)
9+
return super if options[:attestations]&.any? || @host != "https://rubygems.org"
10+
11+
begin
12+
send_push_request_with_attestation(name, args)
13+
rescue StandardError => e
14+
alert_warning "Failed to push with attestation, retrying without attestation.\n#{e.full_message}"
15+
super
16+
end
17+
end
18+
19+
def send_push_request_with_attestation(name, args)
20+
attestation = attest!(name)
21+
if options[:attestations]
22+
options[:attestations] << attestation
23+
send_push_request(name, args)
24+
else
25+
rubygems_api_request(*args, scope: get_push_scope) do |request|
26+
request.set_form([
27+
["gem", Gem.read_binary(name), { filename: name, content_type: "application/octet-stream" }],
28+
["attestations", "[#{Gem.read_binary(attestation)}]", { content_type: "application/json" }]
29+
], "multipart/form-data")
30+
request.add_field "Authorization", api_key
31+
end
32+
end
33+
end
34+
35+
def attest!(name)
36+
require "open3"
37+
bundle = "#{name}.sigstore.json"
38+
env = defined?(Bundler.unbundled_env) ? Bundler.unbundled_env : ENV.to_h
39+
out, st = Open3.capture2e(
40+
env,
41+
Gem.ruby, "-S", "gem", "exec",
42+
"sigstore-cli:0.2.1", "sign", name, "--bundle", bundle,
43+
unsetenv_others: true
44+
)
45+
raise Gem::Exception, "Failed to sign gem:\n\n#{out}" unless st.success?
46+
47+
bundle
48+
end
49+
end)

0 commit comments

Comments
 (0)