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

Dm/attestations doc #2827

Draft
wants to merge 1 commit into
base: ww/attestations-docs
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
146 changes: 146 additions & 0 deletions docs/user/attestations/getting-started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
---
title: Getting started
---

# Signing release with Attestations

!!! note

Signing a release with attestations requires setting up [Trusted Publishers]


!!! warning

The following document only describes the procedure for GitHub Actions.


## They easy way

If you are already using the PyPA's `pypi-publish` action to publish
your packages, the only change you need to also sign them is to add the
following yaml :

```yaml
attestations: true
```

So, in the end, your action might look like something like this :

<!-- TODO(dm): Change the version in the following snippet once the action
has been updated -->

```yaml hl_lines="14-15"
jobs:
pypi-publish:
name: upload release to PyPI
runs-on: ubuntu-latest
# Specifying a GitHub environment is optional, but strongly encouraged
environment: release
permissions:
# IMPORTANT: this permission is mandatory for trusted publishing
id-token: write
steps:
# retrieve your distributions here
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/vXXXX
with:
attestations: true
```

## The manual way

!!! warning

**STOP! You probably don't need this section; it exists only to provide
some internal details about how GitHub Actions and PyPI coordinate using
OIDC. If you're an ordinary user, it is strongly recommended that you use
the PyPA's pypi-publish action instead. **


The process for signing an artifact requires an OIDC token. This procedure
is described in the [Trusted Publishers] section.

The process is then as followed :

1. Configure the environment
2. Lists every artifact that needs to be signed
3. Generate an attestation for each of them
4. Publish the artifact along their attestations

It is not recommended to generate attestations by hand, this process is
performed by the [pypi-attestation-models] package and needs to be installed.

### Configure the environment

```shell
# Generate a Python environment
python -m venv env-attestations
# Activate it
source env-attestations/bin/activate
# Install dependencies
python -m pip install pypi-attestation-models \ # For generating Attestations
twine \ # For uploading packages
sigstore # For generating the signatures and the OIDC tokens
```

### List every artifact

```python
from pathlib import Path

packages_dir = Path(".").absolute()
dists = [sdist.absolute() for sdist in packages_dir.glob('*.tar.gz')]
dists.extend(whl.absolute() for whl in packages_dir.glob('*.whl'))

# Check that we found some artifacts
assert len(dists) > 1, "Missing artifacts"
```

### Generate an attestation

First, we need to generate a signing token.

```python
from sigstore.oidc import Issuer

issuer = Issuer.production()

# Generates a Token using an OAuth-2 flow
# Warning: Opens a browser
oidc_token = issuer.identity_token()
assert oidc_token is not None, "Failure in generating the token"
```

Then, use this token

```python
from pathlib import Path
from pypi_attestation_models import Attestation

from sigstore.sign import SigningContext, Signer


def attest_dist(package: Path, signer: Signer):
"""Generates an PEP 740 Attestation for a package."""
attestation_path = Path(f'{package}.publish.attestation')
attestation = Attestation.sign(signer, package)
attestation_path.write_text(attestation.model_dump_json(), encoding='utf-8')

signer: Signer = SigningContext.production().signer(identity_token=oidc_token)
for dist in dists:
attest_dist(dist, signer)
```

### Upload the artifacts generated

Finally, with the generated attestations, it is straightforward to upload
the packages along their attestations to PyPI (or Test PyPI) using `twine`.

<!-- TODO(dm): Find a solution to pass the OIDC token for the upload. -->

```shell
twine upload -i __token__ dist/* --attestations
```

[Trusted Publishers]: /trusted-publishers/
[pypi-attestation-models]: https://github.com/trailofbits/pypi-attestation-models
24 changes: 22 additions & 2 deletions docs/user/attestations/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ title: Introduction

<!--[[ preview('index-attestations') ]]-->

These pages document PyPI's implementation of index attestations ([PEP 740]),
including in-toto attestation predicates specific to PyPI itself.
These pages document PyPI's implementation of digital attestations as
defined in [PEP 740].

These attestations are generated by package publishers, stored and
distributed by the Index, and used by downstream users. For brevity, we call
them as *index attestations*.

## Quick background

Expand All @@ -32,6 +36,22 @@ Currently, PyPI allows the following attestation predicates:
* [SLSA Provenance]
* [PyPI Publish]

## Security Model

Index attestations are only a building block in the Software Security
Ecosystem. They attest to that a software arqtifact has been built by a
specific workflow.

They prevent attacks such as :

- tampering with a software artifact after its release
-

On the contrary, they provide no guarantee on the following :

- software quality
- maintainer compromise

[in-toto Attestation Framework]: https://github.com/in-toto/attestation/blob/main/spec/README.md

[PEP 740]: https://peps.python.org/pep-0740/
Expand Down