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

OIDC: Threat model for Warehouse #10644

Closed
woodruffw opened this issue Jan 25, 2022 · 9 comments
Closed

OIDC: Threat model for Warehouse #10644

woodruffw opened this issue Jan 25, 2022 · 9 comments

Comments

@woodruffw
Copy link
Member

woodruffw commented Jan 25, 2022

This is a summary of my own notes from conversations with @di concerning Warehouse's threat model around OIDC (and GitHub, in particular, as an OIDC provider).

JWT considerations

JWT reuse

Warehouse needs well-defined and well-documented behavior around handling of a JWT seen more than once. For example, a GitHub action might do the following (pseudocode):

mint a JWT

for each dist in dist/* {
    use the JWT to mint an access token
    upload dist with the access token
}

In this case, the JWT gets used N times: once for each access token minting. This is probably not what we want; instead, we want this:

mint a JWT
mint an access token using the JWT

for each dist in dist/* { ... }

In this use pattern, we invalidate the JWT in Warehouse's backend after its first use. The process for that is probably as simple as performing uniqing on the jti claim after JWT verification, and then adding (jti, exp) to some table, where exp is the JWT's expiration claim (which gives us the ability to automatically clear out old JWTs once they expire).

Non-JWT considerations

Access token ephemerality

We should determine exactly how long-lived our ephemeral access tokens/API keys will be: how long is a reasonable period to allow uploads for? Should we cap the number of individual requests at some high number (e.g., is there any legitimate packaging workflow that creates more than 100 separate distribution files and uploads them)?

Provider-specific considerations

GitHub: account resurrection/reuse

GitHub allows a deleted user's username to be reused. When this happens, JWTs minted by the new user are indistinguishable from JWTs minted by the old one. This is a potential problem if PyPI is configured to accept JWTs from user/repo @ workflow, where user changes hands on GitHub -- the new (malicious) user would still be able to authenticate with PyPI as if they were the old user.

We probably need a mitigation for this before we fully enable support for GitHub as an OIDC provider. Two potential solutions:

  1. Verify the repository_owner claim in the JWT with some sidecar information: on trusted setup, retrieve the GitHub User ID (which is hopefully unique) for the user and cross-check it against the current ID. This is not the preferred solution.
  2. Get GitHub to include a repository_owner_id claim in the JWT, or something similar (like a repository_owner_epoch, if they don't want to guarantee the ID's uniqueness).

Neither of these is a great solution, because both still require some amount of sidecar state: we still have to either initially store the GitHub user's ID during trusted setup or during TOFU, so that we can check for changes during subsequent authentications. That complicates our DB schema, which we'd like to keep generic.

GitHub: JWTs minted for other consumers on the same workflow

GitHub allows any action to mint a JWT. Warehouse's consumer will only accept JWTs with acceptable claims (e.g., a matching ref), but that might not be sufficient.

In particular, it's conceivable for a publish workflow to have two jobs: publish-pypi and publish-rubygems, both of which mint JWTs for their respective services. Currently, there is no way for Warehouse to distinguish between these two JWTs: both originate from the same workflow. We could distinguish them with the aud claim, which Warehouse would then filter on, but that claim does not provide authenticity: an attacker who manages to take over the publish-rubygems job could mint a JWT with aud=pypi. We should determine whether this is a situation we need to handle on Warehouse's side.

GitHub: access token leakage

We should make sure that any (official) actions that mint access tokens via an OIDC JWT add the access token as a "mask value" so that it does not appear in CI logs: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#masking-a-value-in-log

GitHub: trusted workflows

Warehouse has no visibility into workflow access: a user could misconfigure their GitHub Actions such that anybody can run a publish/release action and thereby publish malicious distributions. We can't prevent this, but we should probably provide documentation/guidance to steer users in the right direction.

This is true also for trusted branches/tags: anybody who can push a new tag to the repo could conceivably authenticate with PyPI if the action is e.g. configured to match against tags like v*.

@woodruffw
Copy link
Member Author

Summarizing:

  • Warehouse will attempt to prevent JWT reuse by enforcing jti as a nonce.
  • Warehouse will produce only ephemeral access tokens from OIDC JWTs, and never long-lived tokens.
  • Subject to feasibility, Warehouse will attempt to defend against account reuse/squatting on the GitHub OIDC provider.
  • Warehouse will not distinguish between JWT "intent", i.e. a valid workflow can produce arbitrarily many JWTs, some of which might be intended for use on other consumers.
  • Warehouse will not protect the user from misconfiguring their own CI.

@di
Copy link
Member

di commented Feb 18, 2022

Neither of these is a great solution, because both still require some amount of sidecar state: we still have to either initially store the GitHub user's ID during trusted setup or during TOFU, so that we can check for changes during subsequent authentications. That complicates our DB schema, which we'd like to keep generic.

I think we can move forward under the assumption that we will eventually have a unique user ID available in the claim.

@woodruffw
Copy link
Member Author

I think we can move forward under the assumption that we will eventually have a unique user ID available in the claim.

Yep! I'm working under that assumption in #10753.

@woodruffw
Copy link
Member Author

I think we've fully fleshed out the threat/security considerations here, so I'm inclined to close this for now. The only thing that's currently missing is a way to verify a GitHub user's unique ID, which is blocked on GitHub rather than us (and is more of a development action item than a threat model design item).

Is that good with you @di?

@di
Copy link
Member

di commented Mar 17, 2022

Yep. Let's open an issue to capture the eventual migration of the user ID check from the API call to an OIDC claim, which is blocked on the token having this claim.

@woodruffw
Copy link
Member Author

Yep. Let's open an issue to capture the eventual migration of the user ID check from the API call to an OIDC claim, which is blocked on the token having this claim.

We're still going to need the API call for the trusted setup, unfortunately -- we still need to establish the initial ID to trust when verifying actual JWTs. But yeah, I can make a sub-issue for tracking GitHub's progress on this + adding it to our verification impl.

@di
Copy link
Member

di commented Mar 17, 2022

Ah, yep, I meant for verification.

@woodruffw
Copy link
Member Author

woodruffw commented Apr 22, 2022

#11239 will more or less round out the intended threat model here, by preventing "account resurrection" attacks.

To summarize:

  • Warehouse will not attempt to prevent JWT reuse, since JWTs are short-lived.
  • Warehouse will produce only ephemeral access tokens from OIDC JWTs, and never long-lived tokens.
  • Warehouse will attempt to defend against account reuse/squatting on the GitHub OIDC provider.
  • Warehouse will distinguish JWT via the aud claim.
  • Warehouse will not protect the user from misconfiguring their own CI.

@woodruffw
Copy link
Member Author

@di assuming that #10644 (comment) matches your mental model of the threat model, I think this is safe to close.

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

No branches or pull requests

2 participants