|
| 1 | +--- |
| 2 | +title: Security Model and Considerations |
| 3 | +--- |
| 4 | + |
| 5 | +{{ preview('oidc-publishing') }} |
| 6 | + |
| 7 | +# Security model and considerations |
| 8 | + |
| 9 | +## Security model |
| 10 | + |
| 11 | +GitHub Actions' own security model for OpenID Connect tokens is a little subtle: |
| 12 | + |
| 13 | +* Any workflow defined in a repository can request an OIDC token, |
| 14 | + *with any audience*, **so long as it has the `id-token: write` permission**. |
| 15 | + |
| 16 | +* The claims defined in an OIDC token are *bound to the workflow*, meaning |
| 17 | + that a workflow defined at `foo.yml` in `org/repo` **cannot impersonate** |
| 18 | + a workflow defined at `bar.yml` in `org/repo`. However, if `foo.yml` is |
| 19 | + *renamed* to `bar.yml`, then the *new* `bar.yml` will be indistinguishable |
| 20 | + from the old `bar.yml` **except** for claims that reflect the repository's |
| 21 | + state (e.g. `git` ref, branch, etc.). |
| 22 | + |
| 23 | +* *Generally speaking*, "third party" events **cannot** request an OIDC |
| 24 | + token: even if they can trigger the workflow that requests the token, |
| 25 | + the actual token retrieval step will fail. For example: PRs issued from forks |
| 26 | + of a repository **cannot** access the OIDC tokens in the "upstream" |
| 27 | + repository's workflows. |
| 28 | + |
| 29 | + * The exception to this is `pull_request_target` events, which are |
| 30 | + **[fundamentally dangerous] by design** and should not be used without |
| 31 | + careful consideration. |
| 32 | + |
| 33 | +## Considerations |
| 34 | + |
| 35 | +While more secure than passwords and long-lived API tokens, OIDC publishing |
| 36 | +is not a panacea. In particular: |
| 37 | + |
| 38 | +* Short-lived API tokens are still sensitive material, and should not be |
| 39 | + disclosed (ideally not at all, but certainly not before they expire). |
| 40 | + |
| 41 | +* OIDC tokens themselves are sensitive material, and should not be disclosed. |
| 42 | + OIDC tokens are also short-lived, but an attacker who successfully intercepts |
| 43 | + one can mint API tokens against it for as long as it lives. |
| 44 | + |
| 45 | +* Configuring an OIDC publisher means establishing trust in a particular piece |
| 46 | + of external state; that state **must not** be controllable by untrusted |
| 47 | + parties. In particular, for OIDC publishing with GitHub Actions, you **must**: |
| 48 | + |
| 49 | + * Trust the correct username and repository: if you trust a repository |
| 50 | + other than one you control and trust, that repository can upload to your |
| 51 | + PyPI project. |
| 52 | + |
| 53 | + * Trust the correct workflow: you shouldn't trust every workflow |
| 54 | + to upload to PyPI; instead, you should isolate responsibility to the |
| 55 | + smallest (and least-privileged) possible separate workflow. We recommend |
| 56 | + naming this workflow `release.yml`. |
| 57 | + |
| 58 | + * Take care when merging third-party changes to your code: if you trust |
| 59 | + `release.yml`, then you must make sure that third-party changes to that |
| 60 | + workflow (or code that runs within that workflow) are not malicious. |
| 61 | + |
| 62 | + * Take care when adding repository contributors, members, and administrators: |
| 63 | + by default, anybody who can unconditionally commit to your repository can |
| 64 | + also modify your publishing workflow to make it trigger on events you |
| 65 | + may not intend (e.g., a manual `workflow_dispatch` trigger). |
| 66 | + |
| 67 | + This particular risk can be mitigated by using a dedicated environment |
| 68 | + with manual approvers, as described below. |
| 69 | + |
| 70 | +PyPI has protections in place to make some attacks against OIDC more difficult |
| 71 | +(like account resurrection attacks). However, like all forms of authentication, |
| 72 | +the end user is **fundamentally responsible** for applying it correctly. |
| 73 | + |
| 74 | +In addition to the requirements above, you can do the following to |
| 75 | +"ratchet down" the scope of your OIDC publishing workflows: |
| 76 | + |
| 77 | +* **Use per-job permissions**: The `permissions` key can be defined on the |
| 78 | + workflow level or the job level; the job level is **always more secure** |
| 79 | + because it limits the number of jobs that receive elevated `GITHUB_TOKEN` |
| 80 | + credentials. |
| 81 | + |
| 82 | +* **[Use a dedicated environment]**: GitHub Actions supports "environments," |
| 83 | + which can be used to isolate secrets to specific workflows. OIDC publishing |
| 84 | + doesn't use any pre-configured secrets, but a dedicated `publish` or `deploy` |
| 85 | + environment is a general best practice. |
| 86 | + |
| 87 | + Dedicated environments allow for additional protections like |
| 88 | + [required reviewers], which can be used to require manual approval for a |
| 89 | + workflow using the environment. |
| 90 | + |
| 91 | + For example, here is how `pypa/pip-audit`'s `release` environment |
| 92 | + restricts reviews to members of the maintenance and admin teams: |
| 93 | + |
| 94 | +  |
| 95 | + |
| 96 | +* **[Use tag protection rules]**: if you use a tag-based publishing workflow |
| 97 | + (e.g. triggering on tags pushed), then you can limit tag creation and |
| 98 | + modification to maintainers and higher (or custom roles) for any tags |
| 99 | + that match your release pattern. For example, `v*` will prevent |
| 100 | + non-maintainers from creating or modifying tags that match version |
| 101 | + strings like `v1.2.3`. |
| 102 | + |
| 103 | +* **Limit the scope of your publishing job**: your publishing job should |
| 104 | + (ideally) have only two steps: |
| 105 | + |
| 106 | + 1. Retrieve the publishable distribution files from **a separate |
| 107 | + build job**; |
| 108 | + |
| 109 | + 2. Publish the distributions using `pypa/gh-action-pypi-publish@release/v1`. |
| 110 | + |
| 111 | + By using a separate build job, you keep the number of steps that can |
| 112 | + access the OIDC token to a bare minimum. This prevents both accidental |
| 113 | + and malicious disclosure. |
| 114 | + |
| 115 | +[fundamentally dangerous]: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ |
| 116 | + |
| 117 | +[Use a dedicated environment]: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment |
| 118 | + |
| 119 | +[Use tag protection rules]: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/managing-repository-settings/configuring-tag-protection-rules |
| 120 | + |
| 121 | +[required reviewers]: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#required-reviewers |
0 commit comments