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] User docs #13285

Merged
merged 34 commits into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
480595b
docs/user: OIDC doc skeleton
woodruffw Mar 23, 2023
75ef2ce
docs/user: dump the OIDC docs in
woodruffw Mar 23, 2023
8f78f48
OIDC publishing: retitle
woodruffw Mar 23, 2023
73aaf16
docs: recase
woodruffw Mar 23, 2023
33f20df
docs: remove misbehaving directory
woodruffw Mar 23, 2023
475c070
docs: fixup
woodruffw Mar 23, 2023
dd1438f
docs: fix images
woodruffw Mar 23, 2023
9d3d9b9
user/main: remove unused macro
woodruffw Mar 23, 2023
23a7a45
docs: subpages!
woodruffw Mar 23, 2023
717b5c8
mkdocs: add nav
woodruffw Mar 23, 2023
0a310a4
docs: fix headers, use breakout
woodruffw Mar 23, 2023
0c5ec4a
docs: add missing preview breakout
woodruffw Mar 23, 2023
7355811
Merge remote-tracking branch 'upstream/main' into tob-oidc-user-docs
woodruffw Mar 23, 2023
d710805
Losslessly squash assets by ~40%
hugovk Mar 23, 2023
2bf8a48
Apply suggestions from code review
woodruffw Mar 23, 2023
e90802a
Update docs/user/OIDC-publishing/adding-a-publisher.md
woodruffw Mar 23, 2023
7e4c4d5
Merge pull request #1623 from hugovk/13285-squash-pngs
woodruffw Mar 23, 2023
69d8437
Apply suggestions from code review
woodruffw Mar 24, 2023
8a417bf
Apply suggestions from code review
woodruffw Mar 24, 2023
e408cb7
Apply suggestions from code review
woodruffw Mar 24, 2023
e172c99
Update docs/user/main.py
woodruffw Mar 24, 2023
ab13e65
docs: rename entire tree
woodruffw Mar 24, 2023
77fdaf5
docs: remove "above" ref
woodruffw Mar 24, 2023
2ac579a
docs: OIDC -> trusted
woodruffw Mar 24, 2023
83790a8
docs: emphasize permission preference
woodruffw Mar 24, 2023
62d8aaa
docs: add a diff
woodruffw Mar 24, 2023
c9e37a0
docs: document risks with untrusted committers
woodruffw Mar 24, 2023
c69eac5
docs: remove ref to beta
woodruffw Mar 24, 2023
ae693ba
Apply suggestions from code review
di Mar 24, 2023
f21748b
docs: use a canonical PyPI project name
woodruffw Mar 25, 2023
a7f63b9
docs: more picture updates
woodruffw Mar 25, 2023
3051803
Merge remote-tracking branch 'upstream/main' into tob-oidc-user-docs
woodruffw Mar 25, 2023
508db84
Apply suggestions from code review
di Mar 28, 2023
514a25b
Merge branch 'main' into tob-oidc-user-docs
di Mar 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions docs/mkdocs-user-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ site_dir: user-site
plugins:
- macros:
module_name: user/main
markdown_extensions:
- admonition
- pymdownx.details
markdown_extensions:
- admonition
- pymdownx.details
- pymdownx.superfences
- tables
theme:
Expand Down Expand Up @@ -38,3 +38,22 @@ extra:
link: https://twitter.com/pypi
repo_url: https://github.com/pypi/warehouse
edit_uri: blob/main/docs/user/

nav:
- "index.md"
- "Organization Accounts":
- "organization-accounts/index.md"
- "organization-accounts/org-acc-faq.md"
- "organization-accounts/roles-entities.md"
- "Actions":
- "organization-accounts/actions/billing-actions.md"
- "organization-accounts/actions/org-actions.md"
- "organization-accounts/actions/project-actions.md"
- "organization-accounts/actions/team-actions.md"
- "Trusted Publishers":
- "trusted-publishers/index.md"
- "trusted-publishers/adding-a-publisher.md"
- "trusted-publishers/creating-a-project-through-oidc.md"
- "trusted-publishers/using-a-publisher.md"
- "trusted-publishers/security-model.md"
- "trusted-publishers/troubleshooting.md"
Binary file added docs/user/assets/dropdown.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/user/assets/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/user/assets/manage-link.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/user/assets/pending-publisher-form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
woodruffw marked this conversation as resolved.
Show resolved Hide resolved
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
woodruffw marked this conversation as resolved.
Show resolved Hide resolved
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/user/assets/project-publishing-form.png
woodruffw marked this conversation as resolved.
Show resolved Hide resolved
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/user/assets/project-publishing-link.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/user/assets/project-publishing.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/user/assets/publishing-link.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/user/assets/required-reviewers.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 20 additions & 3 deletions docs/user/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
from pathlib import Path


OIDC_PUBLISHING = """
!!! info

OpenID Connect publishing functionality is currently in closed beta.

You can request access to the closed beta using
[this form](https://forms.gle/XUsRT8KTKy66TuUp7).

**NOTE**: Access to the OIDC beta is provided on a *per-user* basis: users
can register OIDC publishers against projects once added to the beta, but
other maintainers/owners of the project can't modify OIDC settings unless
they're *also* in the beta.
"""

ORG_ACCOUNTS = """
!!! info

Expand All @@ -8,9 +25,9 @@
to be one of the first to know how you can begin using them.
"""

PREVIEW_FEATURES = {
'org-accounts': ORG_ACCOUNTS
}
PREVIEW_FEATURES = {"oidc-publishing": OIDC_PUBLISHING, "org-accounts": ORG_ACCOUNTS}

_HERE = Path(__file__).parent.resolve()


def define_env(env):
Expand Down
48 changes: 48 additions & 0 deletions docs/user/trusted-publishers/adding-a-publisher.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
title: Adding a Trusted Publisher to an Existing PyPI Project
---

{{ preview('oidc-publishing') }}

# Adding a trusted publisher to an existing PyPI project

Adding a trusted publisher to a PyPI project only requires a single setup step.

On the ["Your projects" page](https://pypi.org/manage/projects/), click "Manage" on any project you'd like to
configure:

![](/assets/manage-link.png)

Then, click on "Publishing" in the project's sidebar:

![](/assets/project-publishing-link.png)

That link will take you to the publisher configuration page for the project:

![](/assets/project-publishing.png)

To enable a publisher, you need to tell PyPI how to trust it. For
GitHub Actions (the only currently supported publisher), you do this by
providing the repository owner's name, the repository's name, and the
filename of the GitHub Actions workflow that's authorized to upload to
PyPI.

For example, if you have a project at `https://github.com/pypa/sampleproject`
that uses a publishing workflow defined in `.github/workflows/release.yml`,
then you'd do the following:

![](/assets/project-publishing-form.png)

Once you click "Add", your publisher will be registered and will appear
at the top of the page:

![](/assets/project-publisher-registered.png)

From this point onwards, the `release.yml` workflow on `pypa/sampleproject` will
be able to generate short-lived API tokens from PyPI for the project you've registered
it against.

A publisher can be registered against multiple PyPI projects (e.g. for a
multi-project repository), and a single PyPI project can have multiple
publishers (e.g. for multiple workflows on different architectures, operating
systems).
45 changes: 45 additions & 0 deletions docs/user/trusted-publishers/creating-a-project-through-oidc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
title: Creating a PyPI Project with a Trusted Publisher
---

{{ preview('oidc-publishing') }}

# Creating a PyPI project with a trusted publisher

Trusted publishers are not just for pre-existing PyPI projects: you can also use
them to *create* a PyPI project!

This again reduces the number of steps needed to set up a fully automated PyPI
publishing workflow: rather than having to manually upload a first release
to "prime" the project on PyPI, you can configure a "pending" publisher
that will *create* the project when used for the first time. "Pending"
publishers are converted into "normal" publishers on first use, meaning that
no further configuration is required.

The process for configuring a "pending" publisher are similar to those for
a normal publisher, except that the page is under your account sidebar
instead of any project's sidebar (since the project doesn't exist yet):

![](/assets/publishing-link.png)

Clicking on "publishing" will bring you to the following form:

![](/assets/pending-publisher-form.png)

This form behaves the same as with publishers for existing projects, except that you
also need to provide the name of the PyPI project that will be created.

For example, if you have a repository at
`https://github.com/octo-org/sampleproject` with a release workflow at
`release.yml` and you'd like to publish it to PyPI as `sampleproject`, you'd do
the following:

![](/assets/pending-publisher-form-filled.png)

Clicking "Add" will register the "pending" publisher, and show it to you:

![](/assets/pending-publisher-registered.png)

From this point on, the "pending" publisher can be used exactly like a
"normal" publisher, and after first use it will convert it into a "normal"
publisher.
60 changes: 60 additions & 0 deletions docs/user/trusted-publishers/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
title: Getting Started
---

{{ preview('oidc-publishing') }}

# Publishing to PyPI with a Trusted Publisher

## Confirming that you're in the beta

Before we do anything else: let's confirm that you're actually in the
beta! You can do this by [logging into PyPI](https://pypi.org/account/login/)
and clicking on "Account settings" in the dropdown under your profile:

![](/assets/dropdown.png)

On the "Account settings" page, you should see a right-side menu that
contains a "Publishing" link:

![](/assets/publishing-link.png)

If you see that link and can click on it, then you're in the beta group!

## Quick background: Publishing with OpenID Connect

OpenID Connect (OIDC) publishing is a mechanism for uploading packages to PyPI, *complementing*
existing methods (username/password combinations, API tokens).

You don't need to understand OIDC to use OIDC publishing with PyPI, but here's
the TL;DR:

1. Certain CI services (like GitHub Actions) are OIDC *identity providers*, meaning that
they can issue short-lived credentials ("OIDC tokens") that a third party
can **strongly** verify came from the CI service (as well as which user,
repository, etc. actually executed);
1. Projects on PyPI can be configured to trust a particular configuration on
a particular CI service, making that configuration an OIDC publisher
for that project;
1. Release automation (like GitHub Actions) can submit an OIDC token
to PyPI. The token will be matched against configurations trusted by
different projects; if any projects trust the token's configuration,
then PyPI will mint a *short-lived API token* for those projects and
return it;
1. The short-lived API token behaves exactly like a normal project-scoped API
token, except that it's only valid for 15 minutes from time of creation
(enough time for the CI to use it to upload packages).

This confers significant usability and security advantages when compared
to PyPI's traditional authentication methods:

* Usability: with trusted publishing, users no longer need to manually create
API tokens on PyPI and copy-paste them into their CI provider. The only
manual step is configuring the publisher on PyPI.
* Security: PyPI's normal API tokens are long-lived, meaning that an attacker
who compromises a package's release can use it until its legitimate user
notices and manually revokes it. Similarly, uploading with a password means
that an attacker can upload to *any* project associated with the account.
Trusted publishing avoids both of these problems: the tokens minted expire
automatically, and are scoped down to only the packages that they're
authorized to upload to.
121 changes: 121 additions & 0 deletions docs/user/trusted-publishers/security-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
---
title: Security Model and Considerations
---

{{ preview('oidc-publishing') }}

# Security model and considerations

## Security model

GitHub Actions' own security model for OpenID Connect tokens is a little subtle:

* Any workflow defined in a repository can request an OIDC token,
*with any audience*, **so long as it has the `id-token: write` permission**.

* The claims defined in an OIDC token are *bound to the workflow*, meaning
that a workflow defined at `foo.yml` in `org/repo` **cannot impersonate**
a workflow defined at `bar.yml` in `org/repo`. However, if `foo.yml` is
*renamed* to `bar.yml`, then the *new* `bar.yml` will be indistinguishable
from the old `bar.yml` **except** for claims that reflect the repository's
state (e.g. `git` ref, branch, etc.).

* *Generally speaking*, "third party" events **cannot** request an OIDC
token: even if they can trigger the workflow that requests the token,
the actual token retrieval step will fail. For example: PRs issued from forks
of a repository **cannot** access the OIDC tokens in the "upstream"
repository's workflows.

* The exception to this is `pull_request_target` events, which are
**[fundamentally dangerous] by design** and should not be used without
careful consideration.

## Considerations

While more secure than passwords and long-lived API tokens, OIDC publishing
is not a panacea. In particular:

* Short-lived API tokens are still sensitive material, and should not be
disclosed (ideally not at all, but certainly not before they expire).

* OIDC tokens themselves are sensitive material, and should not be disclosed.
OIDC tokens are also short-lived, but an attacker who successfully intercepts
one can mint API tokens against it for as long as it lives.

* Configuring an OIDC publisher means establishing trust in a particular piece
of external state; that state **must not** be controllable by untrusted
parties. In particular, for OIDC publishing with GitHub Actions, you **must**:

* Trust the correct username and repository: if you trust a repository
other than one you control and trust, that repository can upload to your
PyPI project.

* Trust the correct workflow: you shouldn't trust every workflow
to upload to PyPI; instead, you should isolate responsibility to the
smallest (and least-privileged) possible separate workflow. We recommend
naming this workflow `release.yml`.

* Take care when merging third-party changes to your code: if you trust
`release.yml`, then you must make sure that third-party changes to that
workflow (or code that runs within that workflow) are not malicious.

* Take care when adding repository contributors, members, and administrators:
by default, anybody who can unconditionally commit to your repository can
also modify your publishing workflow to make it trigger on events you
may not intend (e.g., a manual `workflow_dispatch` trigger).

This particular risk can be mitigated by using a dedicated environment
with manual approvers, as described below.

PyPI has protections in place to make some attacks against OIDC more difficult
(like account resurrection attacks). However, like all forms of authentication,
the end user is **fundamentally responsible** for applying it correctly.

In addition to the requirements above, you can do the following to
"ratchet down" the scope of your OIDC publishing workflows:

* **Use per-job permissions**: The `permissions` key can be defined on the
workflow level or the job level; the job level is **always more secure**
because it limits the number of jobs that receive elevated `GITHUB_TOKEN`
credentials.

* **[Use a dedicated environment]**: GitHub Actions supports "environments,"
which can be used to isolate secrets to specific workflows. OIDC publishing
doesn't use any pre-configured secrets, but a dedicated `publish` or `deploy`
environment is a general best practice.

Dedicated environments allow for additional protections like
[required reviewers], which can be used to require manual approval for a
workflow using the environment.

For example, here is how `pypa/pip-audit`'s `release` environment
restricts reviews to members of the maintenance and admin teams:

![](/assets/required-reviewers.png)

* **[Use tag protection rules]**: if you use a tag-based publishing workflow
(e.g. triggering on tags pushed), then you can limit tag creation and
modification to maintainers and higher (or custom roles) for any tags
that match your release pattern. For example, `v*` will prevent
non-maintainers from creating or modifying tags that match version
strings like `v1.2.3`.

* **Limit the scope of your publishing job**: your publishing job should
(ideally) have only two steps:

1. Retrieve the publishable distribution files from **a separate
build job**;

2. Publish the distributions using `pypa/gh-action-pypi-publish@release/v1`.

By using a separate build job, you keep the number of steps that can
access the OIDC token to a bare minimum. This prevents both accidental
and malicious disclosure.

[fundamentally dangerous]: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/

[Use a dedicated environment]: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment

[Use tag protection rules]: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/managing-repository-settings/configuring-tag-protection-rules

[required reviewers]: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#required-reviewers
35 changes: 35 additions & 0 deletions docs/user/trusted-publishers/troubleshooting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
title: Troubleshooting
---

{{ preview('oidc-publishing') }}

# Troubleshooting

Here's a quick enumeration of errors you might see from the `mint-token`
endpoint:

* `not-enabled`: this indicates that PyPI's backend has
disabled OIDC entirely. You should not see this message during normal
operation, **unless** PyPI's admins have decided to disable OIDC support.
* `invalid-payload`: the OIDC token payload submitted to the `mint-token`
endpoint is not formatted correctly. The payload **must** be a JSON serialized
object, with the following layout:

```json
{
"token": "oidc-token-here"
}
```

No other layouts are supported.

* `invalid-token`: the OIDC token itself is either formatted incorrectly,
has an invalid signature, is expired, etc. This encompasses pretty much
any failure mode that can occur with an OIDC token (which is just a JWT)
*before* it's actually matched against a publisher.
* `invalid-pending-publisher` and `invalid-publisher`: the OIDC token itself
is well-formed (and has a valid signature), but doesn't match any known
(pending) OIDC publisher. This likely indicates a mismatch between the
OIDC publisher specified in the user/project settings and the claims
represented in the actual OIDC token. Check for typos!
Loading