bake-gem-github 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bake/gem/github/setup.rb +22 -2
- data/bake/gem/github.rb +1 -3
- data/context/getting-started.md +22 -4
- data/context/preparing-releases.md +1 -1
- data/context/verifying-releases.md +1 -1
- data/lib/bake/gem/github/project.rb +12 -6
- data/lib/bake/gem/github/publisher.rb +3 -33
- data/lib/bake/gem/github/setup.rb +2 -4
- data/lib/bake/gem/github/trusted_publisher.rb +124 -0
- data/lib/bake/gem/github/version.rb +1 -1
- data/readme.md +10 -4
- data/releases.md +10 -0
- data/templates/release-publish.yaml.erb +0 -3
- metadata +3 -28
- checksums.yaml.gz.sig +0 -1
- data/release.cert +0 -24
- data.tar.gz.sig +0 -0
- metadata.gz.sig +0 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6806efc908e7db5189295eb013a2d29bc3f82a20b9749eafc9464baa898a251a
|
|
4
|
+
data.tar.gz: 19143737be1d10805c3204064234880fe49b2b33ba107cb55676c522a83fd5e1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7ec509f5bc2921c554352950a9c469dcbe8dccc8f64bfd0e71fcfdb237855c68c1d0a232fcbea6e816204cb37868d952a11b8b0bf97d1ca3e6c0b9e7cfb22f8d
|
|
7
|
+
data.tar.gz: 8bfb262d1cfcb69db13aa8075fb354d1dbf70826c03a04747bd7cba1e80b4c04a18d16ff147c150727c78a2c88deb4ff8a8a63498a024bfcff31d62adddbb572
|
data/bake/gem/github/setup.rb
CHANGED
|
@@ -4,9 +4,29 @@
|
|
|
4
4
|
# Copyright, 2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
# Inspect external settings before applying the generated policy.
|
|
7
|
+
# @parameter publisher [Boolean] Authenticate to RubyGems and check the expected publisher registration.
|
|
8
|
+
# @parameter key [Symbol] An explicitly selected RubyGems API key name.
|
|
9
|
+
# @parameter otp [String] An optional RubyGems MFA code.
|
|
7
10
|
# @returns [Hash] Desired and observed settings for review.
|
|
8
|
-
def plan
|
|
9
|
-
context.lookup("gem:github:doctor").call
|
|
11
|
+
def plan(publisher: false, key: nil, otp: nil)
|
|
12
|
+
result = context.lookup("gem:github:doctor").call
|
|
13
|
+
if publisher
|
|
14
|
+
require "bake/gem/github/trusted_publisher"
|
|
15
|
+
|
|
16
|
+
result[:trusted_publisher_status] = Bake::Gem::GitHub::TrustedPublisher.load(context.root, key: key, otp: otp).status
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
return result
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Register the configured release workflow as a trusted publisher for an existing gem on RubyGems.org.
|
|
23
|
+
# @parameter key [Symbol] An explicitly selected RubyGems API key name.
|
|
24
|
+
# @parameter otp [String] An optional RubyGems MFA code.
|
|
25
|
+
# @returns [Hash] Whether the publisher was created and its API record.
|
|
26
|
+
def publisher(key: nil, otp: nil)
|
|
27
|
+
require "bake/gem/github/trusted_publisher"
|
|
28
|
+
|
|
29
|
+
Bake::Gem::GitHub::TrustedPublisher.load(context.root, key: key, otp: otp).register
|
|
10
30
|
end
|
|
11
31
|
|
|
12
32
|
# Apply the four managed rulesets and configured environment reviewers using the current gh administrator credentials.
|
data/bake/gem/github.rb
CHANGED
|
@@ -9,10 +9,9 @@
|
|
|
9
9
|
# @parameter branch [String] Default branch name; discovered through GitHub when omitted.
|
|
10
10
|
# @parameter approvals [Integer] Number of approving reviews.
|
|
11
11
|
# @parameter reviewers [Array(String)] Publishing environment reviewers: user logins or organization/team names. Omit to leave environment settings unmanaged.
|
|
12
|
-
# @parameter signing [Boolean] Require certificate signing; when omitted, enable it if `release.cert` exists.
|
|
13
12
|
# @parameter ruby [String] Ruby version for release workflows.
|
|
14
13
|
# @returns [Array(String)] Generated paths relative to the repository root.
|
|
15
|
-
def setup(checks:, repository: nil, branch: nil, approvals: 2, reviewers: nil,
|
|
14
|
+
def setup(checks:, repository: nil, branch: nil, approvals: 2, reviewers: nil, ruby: "3.4")
|
|
16
15
|
require "bake/gem/github/setup"
|
|
17
16
|
require "bake/gem/shell"
|
|
18
17
|
|
|
@@ -31,7 +30,6 @@ def setup(checks:, repository: nil, branch: nil, approvals: 2, reviewers: nil, s
|
|
|
31
30
|
reviewers: reviewers,
|
|
32
31
|
ruby: ruby,
|
|
33
32
|
}
|
|
34
|
-
options[:signing] = signing unless signing.nil?
|
|
35
33
|
|
|
36
34
|
Bake::Gem::GitHub::Setup.new(context.root).generate(**options)
|
|
37
35
|
end
|
data/context/getting-started.md
CHANGED
|
@@ -48,13 +48,29 @@ The rules require two approvals by default, allow explicit administrator bypass,
|
|
|
48
48
|
|
|
49
49
|
## Configure publishing credentials
|
|
50
50
|
|
|
51
|
-
Create a `rubygems` GitHub environment restricted to the default branch.
|
|
51
|
+
Create a `rubygems` GitHub environment restricted to the default branch. For an existing gem on RubyGems.org, run this task from the repository root as a gem owner:
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
``` bash
|
|
54
|
+
bundle exec bake gem:github:setup:publisher
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The task reads the gem name from its gemspec and the owner/repository and environment from `config/release.yaml`. It registers the **`release-publish.yaml`** workflow with the configured environment (**`rubygems`** by default). An exact existing match is reused, so reruns do not create duplicate registrations. Other publishers are preserved; the task does not remove or replace them.
|
|
54
58
|
|
|
55
|
-
|
|
59
|
+
Without an explicitly supplied API key, the task prompts for RubyGems credentials and any required MFA verification. It requests a key with only the `configure_trusted_publishers` scope, expiring after 15 minutes, and keeps it in memory. It does not read or overwrite your default saved push key. To use an existing key with this scope, supply `GEM_HOST_API_KEY` or select a named RubyGems credential with `key=publisher`. Registration and the optional plan check also accept `otp=123456` or `GEM_HOST_OTP_CODE` when needed.
|
|
56
60
|
|
|
57
|
-
|
|
61
|
+
To check registration without creating a publisher:
|
|
62
|
+
|
|
63
|
+
``` bash
|
|
64
|
+
bundle exec bake gem:github:setup:plan publisher=true
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
This adds `trusted_publisher_status` to the plan, including `configured` and the matching registration. The default plan only reports expected RubyGems values and does not authenticate to RubyGems. Authentication, ownership, and API failures stop the task rather than being reported as a missing registration.
|
|
68
|
+
|
|
69
|
+
For a gem that has never been published, configure a pending publisher manually. Account ownership, MFA enrollment, and the GitHub environment remain manual setup steps. See [RubyGems Trusted Publishing](https://guides.rubygems.org/trusted-publishing/) for account setup. Trusted Publishing supplies the publishing credential for each release, so a long-lived RubyGems API key is unnecessary.
|
|
70
|
+
|
|
71
|
+
Publishing builds gems without RubyGems certificate signatures. Sigstore and GitHub provenance attestations bind the package to its source and release workflow; no separate certificate or private signing key is required.
|
|
72
|
+
|
|
73
|
+
Ensure another maintainer can administer the repository and recover its RubyGems account.
|
|
58
74
|
|
|
59
75
|
## Authorize publishing
|
|
60
76
|
|
|
@@ -117,6 +133,8 @@ git diff
|
|
|
117
133
|
|
|
118
134
|
This updates managed files in the working tree and returns their changed paths. Review the diff and selectively retain repository customizations before committing. The task does not stage, commit, or change remote settings. Repeated updates produce no further changes unless customizations differ from the templates. Apply changed rulesets after the corresponding workflows are running.
|
|
119
135
|
|
|
136
|
+
Updating removes the obsolete `signing` configuration and `GEM_SIGNING_KEY` workflow reference. The publisher ignores legacy certificate-signing settings and always builds unsigned packages. Remove `cert_chain`, `signing_key`, and unused public certificates from your gem project when migrating. Attestation verification and RubyGems Trusted Publishing remain required.
|
|
137
|
+
|
|
120
138
|
To enable rebase merging on an existing installation, regenerate and merge `release-validate.yaml` so it runs `gem:github:release:validate`, which requires single-commit release PRs. Then review `gem:github:setup:plan` and run `gem:github:setup:apply` to allow rebase merging in the managed rules. Enable rebase merging in repository settings as well. Upgrading the gem alone does not update workflows or live rules.
|
|
121
139
|
|
|
122
140
|
Regenerate existing workflows to adopt publishing on `push` instead of `pull_request_target`. The workflow filename and `rubygems` environment remain the same, so the RubyGems Trusted Publisher configuration does not change. No exception to GitHub's `pull_request_target` execution policy is needed. The resolve task continues to accept PR numbers from older workflows while you migrate.
|
|
@@ -36,7 +36,7 @@ Merging into the configured default branch triggers `release-publish.yaml` throu
|
|
|
36
36
|
|
|
37
37
|
Each release must land as the tip of its own push, as it does when merging a PR through GitHub. Later pushes do not change a pending release's source: inspection and publishing remain pinned to the original commit, and reruns use the same event. Do not combine a release and later changes into one direct push. If you automate merging, use a GitHub App or personal access token; pushes made using a workflow's `GITHUB_TOKEN` do not trigger another workflow.
|
|
38
38
|
|
|
39
|
-
Only validated releases enter the publishing queue and request approval from the `rubygems` environment when required. Ordinary pushes cannot replace them in that queue. The publishing job retains
|
|
39
|
+
Only validated releases enter the publishing queue and request approval from the `rubygems` environment when required. Ordinary pushes cannot replace them in that queue. The publishing job retains attestation and artifact recovery checks.
|
|
40
40
|
|
|
41
41
|
## Resume interrupted preparation
|
|
42
42
|
|
|
@@ -6,7 +6,7 @@ Use this when checking a completed release or confirming which source commit pro
|
|
|
6
6
|
|
|
7
7
|
## What publishing verifies
|
|
8
8
|
|
|
9
|
-
After squash, rebase, or merge, the publishing workflow verifies GitHub's merged PR record and ancestry, then checks out the exact resulting commit. Each release PR contains one commit, so the resulting commit's **first parent** is the default branch immediately before the release landed. Later development on the default branch is allowed. Publishing regenerates against that first parent, builds in a clean worktree,
|
|
9
|
+
After squash, rebase, or merge, the publishing workflow verifies GitHub's merged PR record and ancestry, then checks out the exact resulting commit. Each release PR contains one commit, so the resulting commit's **first parent** is the default branch immediately before the release landed. Later development on the default branch is allowed. Publishing regenerates against that first parent, builds an unsigned gem in a clean worktree, and creates two attestations over the final bytes:
|
|
10
10
|
|
|
11
11
|
- A Sigstore bundle submitted explicitly with `gem push --attestation` using RubyGems 4.0.21.
|
|
12
12
|
- GitHub's native SLSA provenance covering both the gem and `release.json`. This signed receipt binds the gem digest to the exact release commit, even when the workflow's own default-branch revision is newer.
|
|
@@ -181,12 +181,18 @@ module Bake
|
|
|
181
181
|
existing_rules: api("rulesets?per_page=100"),
|
|
182
182
|
environments: api("environments"),
|
|
183
183
|
environment_changes: environment_changes,
|
|
184
|
-
trusted_publisher:
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
184
|
+
trusted_publisher: trusted_publisher,
|
|
185
|
+
}
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Describe the RubyGems publisher for the reviewed release workflow.
|
|
189
|
+
# @returns [Hash] Repository, workflow filename, and environment settings.
|
|
190
|
+
def trusted_publisher
|
|
191
|
+
{
|
|
192
|
+
repository_owner: @repository.split("/").first,
|
|
193
|
+
repository_name: @repository.split("/").last,
|
|
194
|
+
workflow_filename: "release-publish.yaml",
|
|
195
|
+
environment: @config.fetch("environment"),
|
|
190
196
|
}
|
|
191
197
|
end
|
|
192
198
|
|
|
@@ -8,7 +8,6 @@ require_relative "backup"
|
|
|
8
8
|
require_relative "registry"
|
|
9
9
|
require "bake/releases"
|
|
10
10
|
require "digest"
|
|
11
|
-
require "openssl"
|
|
12
11
|
|
|
13
12
|
module Bake
|
|
14
13
|
module Gem
|
|
@@ -28,7 +27,7 @@ module Bake
|
|
|
28
27
|
|
|
29
28
|
# Build or restore this workflow run's artifact, after validating the actual merged commit.
|
|
30
29
|
# @parameter number [String | Integer] The merged release PR number.
|
|
31
|
-
# @returns [Hash] The release receipt. Extends {Project#inspect_release} metadata with `file`, `sha256`,
|
|
30
|
+
# @returns [Hash] The release receipt. Extends {Project#inspect_release} metadata with `file`, `sha256`, and `run_id`; writes the receipt to `pkg/release.json` and Actions outputs when configured.
|
|
32
31
|
# @raises [RuntimeError] If the workflow identity, source, or retained artifact is invalid, or published bytes cannot be recovered.
|
|
33
32
|
def build(number)
|
|
34
33
|
guard_environment
|
|
@@ -165,7 +164,6 @@ module Bake
|
|
|
165
164
|
file: filename,
|
|
166
165
|
sha256: Digest::SHA256.file(package).hexdigest,
|
|
167
166
|
run_id: run,
|
|
168
|
-
signing: @config.fetch("signing"),
|
|
169
167
|
)
|
|
170
168
|
File.write(File.join(path, "release.json"), JSON.pretty_generate(receipt) + "\n")
|
|
171
169
|
|
|
@@ -198,10 +196,8 @@ module Bake
|
|
|
198
196
|
end
|
|
199
197
|
end
|
|
200
198
|
|
|
201
|
-
# Verify
|
|
199
|
+
# Verify both attestation formats.
|
|
202
200
|
def verify_artifact(package, bundle)
|
|
203
|
-
verify_certificate(package) if @config.fetch("signing")
|
|
204
|
-
|
|
205
201
|
identity = "https://github.com/#{@repository}/.github/workflows/release-publish.yaml@refs/heads/#{@config.fetch('branch')}"
|
|
206
202
|
gem_command(
|
|
207
203
|
"exec", "sigstore-cli:0.2.3", "verify", package, "--bundle", bundle,
|
|
@@ -347,34 +343,8 @@ module Bake
|
|
|
347
343
|
raise "Publishing requires the default branch workflow." unless ENV["GITHUB_REF"] == "refs/heads/#{@config.fetch('branch')}"
|
|
348
344
|
end
|
|
349
345
|
|
|
350
|
-
def verify_certificate(path)
|
|
351
|
-
policy = ::Gem::Security::Policy.new("Release", only_trusted: false)
|
|
352
|
-
package = ::Gem::Package.new(path, policy)
|
|
353
|
-
package.verify
|
|
354
|
-
|
|
355
|
-
signer = OpenSSL::X509::Certificate.new(package.spec.cert_chain.last)
|
|
356
|
-
expected = OpenSSL::X509::Certificate.new(File.read(File.join(@root, "release.cert")))
|
|
357
|
-
raise "Package signer differs from release.cert." unless signer.to_der == expected.to_der
|
|
358
|
-
end
|
|
359
|
-
|
|
360
346
|
def build_package(path)
|
|
361
|
-
|
|
362
|
-
return @release.worktree("HEAD"){|source| @release.bake(source, "gem:build", root: path, signing_key: false)}
|
|
363
|
-
end
|
|
364
|
-
|
|
365
|
-
certificate = OpenSSL::X509::Certificate.new(File.read(File.join(@root, "release.cert")))
|
|
366
|
-
key = OpenSSL::PKey.read(ENV.fetch("GEM_SIGNING_KEY"))
|
|
367
|
-
raise "Signing key does not match release.cert." unless certificate.check_private_key(key)
|
|
368
|
-
raise "Signing certificate is not currently valid." unless (certificate.not_before..certificate.not_after).cover?(Time.now)
|
|
369
|
-
|
|
370
|
-
return Tempfile.create("gem-signing-key") do |file|
|
|
371
|
-
file.chmod(0600)
|
|
372
|
-
file.write(ENV.fetch("GEM_SIGNING_KEY"))
|
|
373
|
-
file.flush
|
|
374
|
-
package = @release.worktree("HEAD"){|source| @release.bake(source, "gem:build", root: path, signing_key: file.path)}
|
|
375
|
-
verify_certificate(package)
|
|
376
|
-
package
|
|
377
|
-
end
|
|
347
|
+
return @release.worktree("HEAD"){|source| @release.bake(source, "gem:build", root: path, signing_key: false)}
|
|
378
348
|
end
|
|
379
349
|
|
|
380
350
|
def output(receipt, restored:)
|
|
@@ -24,11 +24,10 @@ module Bake
|
|
|
24
24
|
# @parameter checks [Array(String)] Required CI job names; release validation is added automatically.
|
|
25
25
|
# @parameter approvals [Integer] Required approvals, between one and six.
|
|
26
26
|
# @parameter reviewers [Array(String) | Nil] Publishing environment reviewers, as user logins or organization/team names. Nil leaves environment settings unmanaged.
|
|
27
|
-
# @parameter signing [Boolean] Whether publishing requires the certificate and matching private key.
|
|
28
27
|
# @parameter ruby [String] The Ruby version used by release workflows.
|
|
29
28
|
# @returns [Array(String)] Generated paths relative to the repository root.
|
|
30
29
|
# @raises [RuntimeError] If configuration is invalid or an existing generated file differs.
|
|
31
|
-
def generate(repository:, branch: "main", checks:, approvals: 2, reviewers: nil,
|
|
30
|
+
def generate(repository:, branch: "main", checks:, approvals: 2, reviewers: nil, ruby: "3.4")
|
|
32
31
|
raise "Expected owner/repository." unless repository.match?(/\A[\w.-]+\/[\w.-]+\z/)
|
|
33
32
|
raise "Unsupported branch name." unless branch.match?(/\A[\w.\/-]+\z/)
|
|
34
33
|
raise "Select the required CI check names." if checks.empty?
|
|
@@ -40,7 +39,6 @@ module Bake
|
|
|
40
39
|
"branch" => branch,
|
|
41
40
|
"checks" => (checks + ["Release validation"]).uniq,
|
|
42
41
|
"approvals" => approvals,
|
|
43
|
-
"signing" => signing,
|
|
44
42
|
"ruby" => ruby,
|
|
45
43
|
"environment" => "rubygems",
|
|
46
44
|
}
|
|
@@ -145,10 +143,10 @@ module Bake
|
|
|
145
143
|
end
|
|
146
144
|
|
|
147
145
|
def render(config)
|
|
146
|
+
config.delete("signing")
|
|
148
147
|
self.class.validate_reviewers(config["reviewers"]) if config.key?("reviewers")
|
|
149
148
|
branch = config.fetch("branch")
|
|
150
149
|
ruby = config.fetch("ruby")
|
|
151
|
-
signing = config.fetch("signing")
|
|
152
150
|
|
|
153
151
|
templates = File.expand_path("../../../../templates", __dir__)
|
|
154
152
|
files = {"config/release.yaml" => YAML.dump(config)}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require "json"
|
|
7
|
+
require "rubygems/gemcutter_utilities"
|
|
8
|
+
require "rubygems/user_interaction"
|
|
9
|
+
require "uri"
|
|
10
|
+
require_relative "project"
|
|
11
|
+
|
|
12
|
+
module Bake
|
|
13
|
+
module Gem
|
|
14
|
+
module GitHub
|
|
15
|
+
# Registers the reviewed release workflow with RubyGems.org using its owner API.
|
|
16
|
+
class TrustedPublisher
|
|
17
|
+
include ::Gem::UserInteraction
|
|
18
|
+
include ::Gem::GemcutterUtilities
|
|
19
|
+
|
|
20
|
+
# The RubyGems API scope for managing trusted publishers.
|
|
21
|
+
SCOPE = :configure_trusted_publishers
|
|
22
|
+
|
|
23
|
+
# The provider type accepted by the RubyGems trusted publisher API.
|
|
24
|
+
TYPE = "OIDC::TrustedPublisher::GitHubAction"
|
|
25
|
+
|
|
26
|
+
# Load the gem identity and reviewed publisher settings from the current project.
|
|
27
|
+
# @parameter root [String] The repository root, which must also be the working directory.
|
|
28
|
+
# @parameter options [Hash] Authentication options passed to {initialize}.
|
|
29
|
+
# @returns [TrustedPublisher] The configured RubyGems client.
|
|
30
|
+
def self.load(root, **options)
|
|
31
|
+
project = Project.new(root)
|
|
32
|
+
name = Helper.new(root).gemspec.name
|
|
33
|
+
|
|
34
|
+
new(name, project.trusted_publisher, **options)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Configure an existing gem's expected GitHub Actions publisher.
|
|
38
|
+
# @parameter name [String] The gem name.
|
|
39
|
+
# @parameter settings [Hash] Repository owner/name, workflow filename, and environment.
|
|
40
|
+
# @parameter key [Symbol | Nil] An explicitly selected key in RubyGems credentials.
|
|
41
|
+
# @parameter otp [String | Nil] An optional MFA code; RubyGems can prompt when required.
|
|
42
|
+
def initialize(name, settings, key: nil, otp: nil)
|
|
43
|
+
@path = "api/v1/gems/#{URI.encode_www_form_component(name)}/trusted_publishers"
|
|
44
|
+
@settings = settings.transform_keys(&:to_s)
|
|
45
|
+
@options = {key: key, otp: otp}
|
|
46
|
+
@host = "https://rubygems.org"
|
|
47
|
+
@api_key = nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @attribute [Hash] Options used by RubyGems authentication and MFA support.
|
|
51
|
+
attr_reader :options
|
|
52
|
+
|
|
53
|
+
# Read the matching publisher without changing publisher configuration.
|
|
54
|
+
# @returns [Hash] Whether the publisher is configured and its existing API record, if any.
|
|
55
|
+
# @raises [RuntimeError] If RubyGems cannot list the gem's publishers.
|
|
56
|
+
def status
|
|
57
|
+
sign_in(scope: SCOPE) unless api_key
|
|
58
|
+
publishers = request(:get, expected: "200")
|
|
59
|
+
raise "Invalid RubyGems trusted publishers response." unless publishers.is_a?(Array)
|
|
60
|
+
publisher = publishers.find{|record| matching?(record)}
|
|
61
|
+
|
|
62
|
+
return {configured: !publisher.nil?, publisher: publisher}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Register a missing publisher, preserving all existing publisher registrations.
|
|
66
|
+
# @returns [Hash] Whether registration was created and the matching API record.
|
|
67
|
+
# @raises [RuntimeError] If RubyGems rejects registration or returns different settings.
|
|
68
|
+
def register
|
|
69
|
+
current = status
|
|
70
|
+
return {created: false, publisher: current.fetch(:publisher)} if current.fetch(:configured)
|
|
71
|
+
|
|
72
|
+
publisher = request(:post, expected: "201", payload: {trusted_publisher_type: TYPE, trusted_publisher: @settings})
|
|
73
|
+
raise "RubyGems returned unexpected trusted publisher settings." unless matching?(publisher)
|
|
74
|
+
|
|
75
|
+
return {created: true, publisher: publisher}
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Use the session key or an explicitly supplied credential, leaving the default push key alone.
|
|
79
|
+
# @returns [String | Nil] The API key used for this session.
|
|
80
|
+
def api_key
|
|
81
|
+
@api_key || ENV["GEM_HOST_API_KEY"] || (options[:key] && verify_api_key(options[:key]))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Keep newly issued credentials in memory rather than changing the user's credentials file.
|
|
85
|
+
# @parameter host [String] The authenticated RubyGems host.
|
|
86
|
+
# @parameter key [String] The newly issued API key.
|
|
87
|
+
def set_api_key(host, key)
|
|
88
|
+
@api_key = key
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def get_mfa_params(profile)
|
|
94
|
+
super.merge(expires_at: (Time.now.utc + 900).strftime("%Y-%m-%d %H:%M:%S UTC"))
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def matching?(record)
|
|
98
|
+
return false unless record.fetch("trusted_publisher_type") == TYPE
|
|
99
|
+
settings = record.fetch("trusted_publisher")
|
|
100
|
+
|
|
101
|
+
# Generated publishing workflows run in the configured repository, not a reusable workflow repository:
|
|
102
|
+
@settings.all?{|key, value| settings[key] == value} &&
|
|
103
|
+
settings["workflow_repository_owner"].nil? && settings["workflow_repository_name"].nil?
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def request(method, expected:, payload: nil)
|
|
107
|
+
response = rubygems_api_request(method, @path, scope: SCOPE) do |request|
|
|
108
|
+
request["Authorization"] = api_key
|
|
109
|
+
request["Accept"] = "application/json"
|
|
110
|
+
if payload
|
|
111
|
+
request["Content-Type"] = "application/json"
|
|
112
|
+
request.body = JSON.generate(payload)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
unless response.code == expected
|
|
116
|
+
raise "RubyGems trusted publisher #{method.to_s.upcase} failed (HTTP #{response.code}): #{clean_text(response.body)}"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
JSON.parse(response.body)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
data/readme.md
CHANGED
|
@@ -24,6 +24,16 @@ Please see the [project documentation](https://socketry.github.io/bake-gem-githu
|
|
|
24
24
|
|
|
25
25
|
Please see the [project releases](https://socketry.github.io/bake-gem-github/releases/index) for all releases.
|
|
26
26
|
|
|
27
|
+
### v0.7.0
|
|
28
|
+
|
|
29
|
+
- Remove RubyGems certificate signing and its private-key requirement. Publishing always builds unsigned gems and verifies Sigstore and GitHub provenance attestations.
|
|
30
|
+
- Remove the setup `signing` option. Regenerate workflows with `gem:github:setup:update` to remove legacy configuration and signing-secret references.
|
|
31
|
+
|
|
32
|
+
### v0.6.0
|
|
33
|
+
|
|
34
|
+
- Register RubyGems trusted publishers from the gemspec and release configuration with `gem:github:setup:publisher`, reusing existing registrations and RubyGems authentication/MFA support.
|
|
35
|
+
- Check publisher registration explicitly with `gem:github:setup:plan publisher=true`.
|
|
36
|
+
|
|
27
37
|
### v0.5.0
|
|
28
38
|
|
|
29
39
|
- Support rebase merging by requiring each release PR to contain exactly one commit, while leaving ordinary PRs unrestricted. Generated rules allow merge, squash, and rebase methods according to repository settings.
|
|
@@ -62,10 +72,6 @@ Please see the [project releases](https://socketry.github.io/bake-gem-github/rel
|
|
|
62
72
|
- Preserve verified release files in a draft GitHub release before uploading to RubyGems, so reruns can recover when Actions artifacts disappear.
|
|
63
73
|
- Wait for RubyGems registry propagation before finalizing releases.
|
|
64
74
|
|
|
65
|
-
### v0.0.1
|
|
66
|
-
|
|
67
|
-
- Initial implementation.
|
|
68
|
-
|
|
69
75
|
## Contributing
|
|
70
76
|
|
|
71
77
|
We welcome contributions to this project.
|
data/releases.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v0.7.0
|
|
4
|
+
|
|
5
|
+
- Remove RubyGems certificate signing and its private-key requirement. Publishing always builds unsigned gems and verifies Sigstore and GitHub provenance attestations.
|
|
6
|
+
- Remove the setup `signing` option. Regenerate workflows with `gem:github:setup:update` to remove legacy configuration and signing-secret references.
|
|
7
|
+
|
|
8
|
+
## v0.6.0
|
|
9
|
+
|
|
10
|
+
- Register RubyGems trusted publishers from the gemspec and release configuration with `gem:github:setup:publisher`, reusing existing registrations and RubyGems authentication/MFA support.
|
|
11
|
+
- Check publisher registration explicitly with `gem:github:setup:plan publisher=true`.
|
|
12
|
+
|
|
3
13
|
## v0.5.0
|
|
4
14
|
|
|
5
15
|
- Support rebase merging by requiring each release PR to contain exactly one commit, while leaving ordinary PRs unrestricted. Generated rules allow merge, squash, and rebase methods according to repository settings.
|
|
@@ -68,9 +68,6 @@ jobs:
|
|
|
68
68
|
id: build
|
|
69
69
|
env:
|
|
70
70
|
GITHUB_TOKEN: ${{ github.token }}
|
|
71
|
-
<% if signing -%>
|
|
72
|
-
GEM_SIGNING_KEY: ${{ secrets.GEM_SIGNING_KEY }}
|
|
73
|
-
<% end -%>
|
|
74
71
|
run: bundle exec bake gem:github:release:build
|
|
75
72
|
- name: Sign RubyGems attestation
|
|
76
73
|
if: steps.build.outputs.restored != 'true'
|
metadata
CHANGED
|
@@ -1,37 +1,12 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bake-gem-github
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
8
8
|
bindir: bin
|
|
9
|
-
cert_chain:
|
|
10
|
-
- |
|
|
11
|
-
-----BEGIN CERTIFICATE-----
|
|
12
|
-
MIIEFDCCAnygAwIBAgIBATANBgkqhkiG9w0BAQsFADAwMREwDwYDVQQKDAhTb2Nr
|
|
13
|
-
ZXRyeTEbMBkGA1UEAwwSU29ja2V0cnkgUnVieSBHZW1zMB4XDTI2MDkyMTA3NTgx
|
|
14
|
-
NFoXDTI3MDkyMTA3NTgxNFowMDERMA8GA1UECgwIU29ja2V0cnkxGzAZBgNVBAMM
|
|
15
|
-
ElNvY2tldHJ5IFJ1YnkgR2VtczCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoC
|
|
16
|
-
ggGBAM/QgjVgDzDo/xJEQoFvAzcVFP7msnswkhPJB2UDsbxXCZmers5jV512TM5s
|
|
17
|
-
NwLHfzJiC4DcI5ax9ZYKM9Q+dS21YYagNqjtg3YPyDqR6phoibEA0VoMuInUQW68
|
|
18
|
-
i5OkCJzYKGD2pYYVZnuFNqyM2ECUvXh/fBmvPoHbncAHhWPaCBH8mQJ8sRNc6+RV
|
|
19
|
-
SoZZu1Yo/aW1zQ+SpZwad7s1ZmigfDNHKhJ22KwZHk+/5Zw1QXVfcajjswV0Mm2P
|
|
20
|
-
irgjKN3fyNVkwlly63EBlR4VydT+g9QtJtqh7ee0ThhI+v4wYovf4dbLNcEBTHu1
|
|
21
|
-
x1pGbXGx/2fBAST5eVwaXIx1V8VU22AJcBk1H2o5/1F0HfbD2HQjmFOA112KUwBW
|
|
22
|
-
9TtspZD3g6rCQFX53XvL9h6j2y1ukl7s5AdEzOe/x6r1MO8tSDtgcYoF89wgF/88
|
|
23
|
-
Q/Eski0FckQ+znfESZcFcpzs2sPDoYW6SNVX4tZcNty85Y6WBHfHKio14x63PeyC
|
|
24
|
-
CG1gMwIDAQABozkwNzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQU
|
|
25
|
-
SuMc0x24Xshtaa8SQVkBr2Z5WzIwDQYJKoZIhvcNAQELBQADggGBABI62ey5lDWm
|
|
26
|
-
j8jsTQaNBMBb7LbKS3XkBwyL1UNGEwlicp399rJDeuNgVSHqBCZ/nE4yleNjgW9Y
|
|
27
|
-
N6DKdxXCvXmFUoHvUINeAvThHxmlEGhSfXl1x55xHEig0rEP4jgdjnYdQI4fqeOM
|
|
28
|
-
zIDN0F3ruArke4Y62SQgWVN7vspdAA41hBIRl1vsvs0KWG8DIVQ8cmR+TG6zjOmK
|
|
29
|
-
iUiUZGNPnStV1O1xW83c+Ba4Am0krP6/forxDPhRvTehqkVYvMdPfYcICNb5IM58
|
|
30
|
-
5vaYvuj5AtfZuZWN0F/0KtSphhNC17rh6h8QmuYdMJ1clvlrkiEaO1UjPVwjgtxC
|
|
31
|
-
x/ARh5X6q0/YTXl4r7EfSJEeqW7qhgu9EUj3mGIPHYdX0Dqih/NgK20F3GvQtTfB
|
|
32
|
-
7sse0duKBHXONYVwKAsceUVvqtCfyF608bCilCkbhw3QaInRj+BkRQKJ5Tuj2PZR
|
|
33
|
-
76a/hwaV2wWn5RtAFBaUQzQdo/xTqJ7IkwaFjOZe/QyvyoRjCK9Rdg==
|
|
34
|
-
-----END CERTIFICATE-----
|
|
9
|
+
cert_chain: []
|
|
35
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
36
11
|
dependencies:
|
|
37
12
|
- !ruby/object:Gem::Dependency
|
|
@@ -107,10 +82,10 @@ files:
|
|
|
107
82
|
- lib/bake/gem/github/publisher.rb
|
|
108
83
|
- lib/bake/gem/github/registry.rb
|
|
109
84
|
- lib/bake/gem/github/setup.rb
|
|
85
|
+
- lib/bake/gem/github/trusted_publisher.rb
|
|
110
86
|
- lib/bake/gem/github/version.rb
|
|
111
87
|
- license.md
|
|
112
88
|
- readme.md
|
|
113
|
-
- release.cert
|
|
114
89
|
- releases.md
|
|
115
90
|
- templates/release-prepare.yaml.erb
|
|
116
91
|
- templates/release-publish.yaml.erb
|
checksums.yaml.gz.sig
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
�?f�h�
|
data/release.cert
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
-----BEGIN CERTIFICATE-----
|
|
2
|
-
MIIEFDCCAnygAwIBAgIBATANBgkqhkiG9w0BAQsFADAwMREwDwYDVQQKDAhTb2Nr
|
|
3
|
-
ZXRyeTEbMBkGA1UEAwwSU29ja2V0cnkgUnVieSBHZW1zMB4XDTI2MDkyMTA3NTgx
|
|
4
|
-
NFoXDTI3MDkyMTA3NTgxNFowMDERMA8GA1UECgwIU29ja2V0cnkxGzAZBgNVBAMM
|
|
5
|
-
ElNvY2tldHJ5IFJ1YnkgR2VtczCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoC
|
|
6
|
-
ggGBAM/QgjVgDzDo/xJEQoFvAzcVFP7msnswkhPJB2UDsbxXCZmers5jV512TM5s
|
|
7
|
-
NwLHfzJiC4DcI5ax9ZYKM9Q+dS21YYagNqjtg3YPyDqR6phoibEA0VoMuInUQW68
|
|
8
|
-
i5OkCJzYKGD2pYYVZnuFNqyM2ECUvXh/fBmvPoHbncAHhWPaCBH8mQJ8sRNc6+RV
|
|
9
|
-
SoZZu1Yo/aW1zQ+SpZwad7s1ZmigfDNHKhJ22KwZHk+/5Zw1QXVfcajjswV0Mm2P
|
|
10
|
-
irgjKN3fyNVkwlly63EBlR4VydT+g9QtJtqh7ee0ThhI+v4wYovf4dbLNcEBTHu1
|
|
11
|
-
x1pGbXGx/2fBAST5eVwaXIx1V8VU22AJcBk1H2o5/1F0HfbD2HQjmFOA112KUwBW
|
|
12
|
-
9TtspZD3g6rCQFX53XvL9h6j2y1ukl7s5AdEzOe/x6r1MO8tSDtgcYoF89wgF/88
|
|
13
|
-
Q/Eski0FckQ+znfESZcFcpzs2sPDoYW6SNVX4tZcNty85Y6WBHfHKio14x63PeyC
|
|
14
|
-
CG1gMwIDAQABozkwNzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQU
|
|
15
|
-
SuMc0x24Xshtaa8SQVkBr2Z5WzIwDQYJKoZIhvcNAQELBQADggGBABI62ey5lDWm
|
|
16
|
-
j8jsTQaNBMBb7LbKS3XkBwyL1UNGEwlicp399rJDeuNgVSHqBCZ/nE4yleNjgW9Y
|
|
17
|
-
N6DKdxXCvXmFUoHvUINeAvThHxmlEGhSfXl1x55xHEig0rEP4jgdjnYdQI4fqeOM
|
|
18
|
-
zIDN0F3ruArke4Y62SQgWVN7vspdAA41hBIRl1vsvs0KWG8DIVQ8cmR+TG6zjOmK
|
|
19
|
-
iUiUZGNPnStV1O1xW83c+Ba4Am0krP6/forxDPhRvTehqkVYvMdPfYcICNb5IM58
|
|
20
|
-
5vaYvuj5AtfZuZWN0F/0KtSphhNC17rh6h8QmuYdMJ1clvlrkiEaO1UjPVwjgtxC
|
|
21
|
-
x/ARh5X6q0/YTXl4r7EfSJEeqW7qhgu9EUj3mGIPHYdX0Dqih/NgK20F3GvQtTfB
|
|
22
|
-
7sse0duKBHXONYVwKAsceUVvqtCfyF608bCilCkbhw3QaInRj+BkRQKJ5Tuj2PZR
|
|
23
|
-
76a/hwaV2wWn5RtAFBaUQzQdo/xTqJ7IkwaFjOZe/QyvyoRjCK9Rdg==
|
|
24
|
-
-----END CERTIFICATE-----
|
data.tar.gz.sig
DELETED
|
Binary file
|
metadata.gz.sig
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
���y0�a�tq��ok��̤�ebَk��z�3�!�:ւ/�đ�U����кq�-�e���V��<ӽ�0["� n���xϼ�ڄd�eS_��;��J��1#����4 D��w�0��P�t܁G)!�����ș?Ǔ[䱭&�^O@���wƁ}E�x��x����F�#U3w�.D2��ϒ\�,m����O��pV�u�!&�M]�Z��4�}jv���s2����W�=I���~�|�RhE��5��LK����l^v���3ӏ�:�,&z-��MNs�7���4� �X�թ��Fbrjst6P�����b�"���֝�S�D��h��I�@����+P�ߗ�
|