Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Add Buildkite as a trusted publisher #5437

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

yob
Copy link
Contributor

@yob yob commented Feb 7, 2025

I'm still learning my way around this codebase, so my approach for exploring trusted publishers and adding Buildkite has been to hack-and-slash my way through to a point where in my local environment I can exchange a Buildkite OIDC token for a trusted publisher API token via /api/v1/oidc/trusted_publisher/exchange_token 💪

Now that I'm here, I thought I'd come up for air and check in with y'all before I start to polish it up. Here's a few thoughts and questions I have:

  • this is built on top of Add integration test for assuming an API Key Role using a Buildkite OIDC token #5416
  • Now that I've done this, I realise why the rubygems team and docs are promoting Trusted Publishers over OIDC API Key Roles. Trusted Publishers is just a better system, particularly removing the need for the key role ID. I'd still advocate for fixing the issue in New system test creating an API Key Role for Buildkite #5434 though - While OIDC API Key Roles are a supported approach, it'd be nice for them to work with multiple providers
  • I assume it'd be OK to ship Buildkite Trusted Publisher support without Sigstore and attestation support, and that we could follow up with that in a later PR?
  • Thanks to the polymorphic association (and the different shape of the OIDC tokens), the forms for creating a new trusted publisher for GitHub Actions and Buildkite are quite different. The current "new trusted publisher" form has a provider select with a single item, but unless we want to do javascript things it might be easier to ask the user to pick the provider prior to loading the new form. What do you think?

Copy link
Member

@segiddins segiddins left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yob you're a hero for getting this started!

This is generally what I'd expect.

I assume it'd be OK to ship Buildkite Trusted Publisher support without Sigstore and attestation support, and that we could follow up with that in a later PR?
Yes, that is OK, so long as it fails with a 400 with a clear message (and that code path is tested)
Thanks to the polymorphic association (and the different shape of the OIDC tokens), the forms for creating a new trusted publisher for GitHub Actions and Buildkite are quite different. The current "new trusted publisher" form has a provider select with a single item, but unless we want to do javascript things it might be easier to ask the user to pick the provider prior to loading the new form. What do you think?

If it's not too hard with a stimulus controller, I imagined using the selector to change -- it could even re-navigate the page, setting a query param. That way, it remains possible to link directly to a "create a trusted publisher" form


def change
create_table :oidc_trusted_publisher_buildkites do |t|
t.string :organization_slug, null: false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these two slugs both immutable, or is it possible for someone to take over an organization name that was previously owned by someone else?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Pipeline slug is not very immutable. Similar to a github repository in an account, they can be deleted, renamed and re-use slugs
  • Organization slug is unique for as long as the account exists, including if the customer cancels their account. However, if they opt into deleting their account then the slug does become available for others

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in that case, can we do what we do for GitHub and also store an organization_id that gets fetched on trusted publisher creation to prevent cross-organization name takeovers?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately we don't have a public API that would make this as easy as it is for the GitHub provider.

We do have a unique and immutable organization_id for each organization and it's available as a claim in our OIDC tokens. The value is a UUID. It's less readable to the user, but I can add it as a requirement here to avoid the risk of slug reuse attacks.

If we do that to prevent cross-organization risk, are you comfortable leaving pipeline_slug as a readable scope within an organization context?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's the right tradeoff on both counts, but also curious what @woodruffw & @sethmlarson as my collaborators on the trusted publishing guides from the openssf

@@ -14,22 +14,31 @@ def view_template
title_content

div(class: "t-body") do
p do
"New Trusted Publisher: #{rubygem_trusted_publisher.trusted_publisher.class.publisher_name}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i18n ?

@@ -18,7 +18,8 @@ def view_template
end

p do
button_to t(".create"), new_rubygem_trusted_publisher_path(rubygem.slug), class: "form__submit", method: :get
button_to t(".create") + " Buildkite", new_rubygem_trusted_publisher_path(rubygem.slug), params: {trusted_publisher: "buildkite"}, class: "form__submit", method: :get
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of multiple buttons here, what if the trusted publisher new view reloaded the form when the selector for trusted publisher type was changed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have to brush up on how stimulus works, but I'll give it a go!


def to_access_policy(jwt)
# TODO what to do with jwt here?
# TODO should we be checking the audience claim?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, audience claim should be checked, same as is done for the GHA publisher

end

def to_access_policy(jwt)
# TODO what to do with jwt here?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're OK to ignore it, since there are no cross-claim consistency checks we need to enforce, unlike with GitHub (where we make sure the workflow source matches up with the commit being built)

)
end

#class SigstorePolicy
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add a push test that uses a build kite trusted publisher and includes an attestation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll start by adding one and asserting that the push is rejected, just to keep this PR a manageable size. I'm keen to come back and fill in that functionality though, we have a few gems to get green on https://segiddins.github.io/are-we-attested-yet/ ;)

yob added 3 commits February 17, 2025 23:18
The form and POSTing to create works, but the resulting API Key Role has
a condition that expects a principal of "https://token.actions.githubusercontent.com"
When a gem has a source doe URI for GitHub, `add_default_params`
defaults the form to common settings for GitHub OIDC tokens.

That's a reasonable thing to do. However, that includes setting the
principal to a Github Actions shaped value. That value is rendered on
the form in hidden elements so the user doesn't have a chance to
edit it. After saving the created role has a provider of Buildkite
with an expected principal for GitHub Actions.

Its safe to remove the statement.principal assignment completely. It's
not required - when the form is submitted the
OIDC::ApiKeyRole#set_statement_principals callback will set the correct
principal for both GitHub Actions *and* Buildkite.
@yob yob force-pushed the buildkite-trusted-publisher branch from 50c8f01 to fb8d33b Compare February 17, 2025 12:23
Copy link

codecov bot commented Feb 17, 2025

Codecov Report

Attention: Patch coverage is 42.52874% with 50 lines in your changes missing coverage. Please review.

Project coverage is 93.78%. Comparing base (968beaf) to head (fb8d33b).

Files with missing lines Patch % Lines
...oidc/trusted_publisher/buildkite/form_component.rb 0.00% 18 Missing ⚠️
app/models/oidc/trusted_publisher/buildkite.rb 57.89% 16 Missing ⚠️
...idc/trusted_publisher/buildkite/table_component.rb 0.00% 11 Missing ⚠️
...lers/oidc/rubygem_trusted_publishers_controller.rb 60.00% 2 Missing ⚠️
.../views/oidc/rubygem_trusted_publishers/new_view.rb 80.00% 2 Missing ⚠️
app/models/oidc/provider.rb 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5437      +/-   ##
==========================================
- Coverage   97.06%   93.78%   -3.28%     
==========================================
  Files         451      454       +3     
  Lines        9392     9531     +139     
==========================================
- Hits         9116     8939     -177     
- Misses        276      592     +316     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Development

Successfully merging this pull request may close these issues.

2 participants