Skip to content

Commit

Permalink
Add integration test for assuming an API Key Role using a Buildkite O…
Browse files Browse the repository at this point in the history
…IDC token

Until recently, Buildkite OIDC tokens did not contain a `jti` claim. At
some point in early 2024 it was possible to assume an API Key Role using
Buildkite OIDC tokens, but when testing in January 2025 we found the
assume role request was failing with an error:

> Missing/invalid jti

Buildkite has addressed that by adding a `jti` claim to tokens - it's a
good claim to include. However, to reduce the risk of regressions in the
future, this proposes adding an integration test with a Buildkite-shaped
OIDC token.

The trait added to the OIDC::Provider factory is based on a real token
that I generated then anonymized. I only test the happy path with this
token - there's a buncha existing tests for various unhappy paths
(expired token, etc) using the Github Actions shaped OIDC token and
there's little value in replicating them.

Most of the added test is copy-pasted from the happy-path Github Actions
test further up the file.

Fixes rubygems#5412
  • Loading branch information
yob committed Feb 2, 2025
1 parent 55c07ac commit 0098d67
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 0 deletions.
51 changes: 51 additions & 0 deletions test/factories/oidc/provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,56 @@
transient do
pkey { OpenSSL::PKey::RSA.generate(2048) }
end

trait :buildkite do
sequence(:issuer) { |n| "https://#{n}.agent.buildkite.com" }
configuration do
{
issuer: issuer,
jwks_uri: "#{issuer}/.well-known/jwks",
id_token_signing_alg_values_supported: [
"RS256"
],
response_types_supported: [
"id_token"
],
scopes_supported: [
"openid"
],
subject_types_supported: %w[
public
pairwise
],
claims_supported: %w[
sub
aud
exp
iat
iss
nbf
jti
organization_id
organization_slug
pipeline_id
pipeline_slug
build_number
build_branch
build_tag
build_commit
build_source
step_key
job_id
agent_id
cluster_id
cluster_name
queue_id
queue_key
runner_environment
]
}
end
end

factory :oidc_provider_buildkite, traits: [:buildkite]
end
end
65 changes: 65 additions & 0 deletions test/integration/api/v1/oidc/api_key_roles_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -383,5 +383,70 @@ def jwt(claims = @claims, key: @pkey)
)
end
end

context "with a Buildkite OIDC token" do
setup do
@role = create(:oidc_api_key_role, provider: build(:oidc_provider_buildkite, issuer: "https://agent.buildkite.com", pkey: @pkey))
@user = @role.user

@claims = {
"aud" => "rubygems.org",
"exp" => 1_680_020_837,
"iat" => 1_680_020_537,
"iss" => "https://agent.buildkite.com",
"jti" => "0194b014-8517-7cef-b232-76a827315f08",
"nbf" => 1_680_019_937,
"sub" => "organization:example-org:pipeline:example-pipeline:ref:refs/heads/main:commit:b5ffe3aeea51cec6c41aef16e45ee6bce47d8810:step:",
"organization_slug" => "example-org",
"pipeline_slug" => "example-pipeline",
"build_number" => 5,
"build_branch" => "main",
"build_tag" => nil,
"build_commit" => "b5ffe3aeea51cec6c41aef16e45ee6bce47d8810",
"step_key" => nil,
"job_id" => "01945ecf-80f0-41e8-9b83-a2970a9305a1",
"agent_id" => "01945ecf-8bcf-40a6-9d70-a765db9a0928",
"build_source" => "ui",
"runner_environment" => "buildkite-hosted"
}

travel_to Time.zone.at(1_680_020_830) # after the JWT iat, before the exp
end

context "with matching conditions" do
should "return API key" do
# remove the Github Actions shaped condition in the default fixture
@role.access_policy.statements.first.conditions.clear
@role.access_policy.statements.first.conditions << OIDC::AccessPolicy::Statement::Condition.new(
operator: "string_equals",
claim: "organization_slug",
value: "example-org"
)
@role.save!

post assume_role_api_v1_oidc_api_key_role_path(@role.token),
params: {
jwt: jwt.to_s
},
headers: {}

assert_response :created

resp = response.parsed_body

assert_match(/^rubygems_/, resp["rubygems_api_key"])
assert_equal_hash(
{ "rubygems_api_key" => resp["rubygems_api_key"],
"name" => "#{@role.name}-0194b014-8517-7cef-b232-76a827315f08",
"scopes" => ["push_rubygem"],
"expires_at" => 30.minutes.from_now },
resp
)
hashed_key = @user.api_keys.sole.hashed_key

assert_equal hashed_key, Digest::SHA256.hexdigest(resp["rubygems_api_key"])
end
end
end
end
end

0 comments on commit 0098d67

Please sign in to comment.