鈴木たかのり
PEP 740の提案とその背景
この機能はPEP 740によって2024年1月に提案され、2024年7月に採択されました。
多くのPythonのパッケージはPyPI
なお、このPEPの採択によってPyPIへのアップロード時のデジタル証明書の添付や、pip
コマンドでの証明書の検証が必須になるわけではないことに注意してください。
このPEPの提案の動機
- Pythonパッケージには配布元などの情報がメタデータとして含まれているが、認証がされていない。暗号化されたデジタル証明書によって、Pythonパッケージが認証されたリポジトリから作成されたことを確認できるようにする。
- Pythonパッケージを乗っ取ろうとする攻撃者にとっては、プライベートな署名情報にアクセスする必要があるため難易度が上がる。
- 現在はPythonパッケージの検証手段はリリースファイルごとのPGP署名しかなく、インデックスページを証明する手段が存在しない。メタデータによってインデックスの証明書の有効性を確認できる。
なお、インデックス証明書の仕様は現在PyPA
デジタル証明書付きパッケージの例
実際にデジタル証明書が付いているパッケージと付いていないパッケージの例を示します。以下は、筆者が開発しているsphinx-nekochanというライブラリで、Sphinxにネコチャン絵文字を挿入する機能を追加します。このライブラリでは0.
sphinx-nekochan 0.

sphinx-nekochan 0.
- Publisher:どこからリリースされたか
(ここではGitHub Action) - Attestations:証明書情報
- Source repository:どのリポジトリのどのバージョンか、またリポジトリの所有者情報
- Publication detail:リリース用のトークンはどこで生成された物か、実行されたワークフローのコード
このような情報を出力することによって、正規のリポジトリからリリースされたパッケージであるということを証明しています。また、画面の左側を見てみると、Verified Details

本題とはそれますが、Sphinxについて本連載で以前紹介しているので興味のある方は参照してください。
PEP 740に対応したパッケージの一覧
「Are we PEP 740 yet? 🔏」
- Are we PEP 740 yet? 🔏
- https://
trailofbits. github. io/ are-we-pep740-yet/
現在68/
- 緑
(19%) :PEP 740に対応済み - 無色
(31%) :最新のパッケージは証明書が利用可能になる前にアップロードされた - 黄色
(44%) :証明書がアップロードされていない - マゼンタ
(6%) :証明書に対応していないリポジトリでホストされているもの(後述)
一覧を見てみると、著名なライブラリでも対応していないものがたくさんあることがわかります。これからPEP 740に対応したパッケージが増えることを期待します。

デジタル証明書付きでパッケージをリリース
では実際にパッケージをデジタル証明書付きでリリースする方法について説明します。ソースコードのリポジトリにGitHubを使用している場合は、GitHub Actionsでリリースを行います。
1. PyPIのプロジェクトにTrusted Publisherを設定する
まずPyPI上のプロジェクトでTrusted Publisher

サイドメニューで
- Owner:リポジトリの所有者であるGitHub OrganizationまたはGitHub username
- Repository name:リポジトリの名前
- Workflow name:リリースワークフローのファイル名
(このあと作成します) - Environment name:GitHub Actionsの環境名
(オプション)

ここで

なお、執筆時点ではPyPIではTrusted PublisherとしてGitHub以外にGoogle Cloud、ActiveState、GitLab CI/
2. GitHub Actionsにワークフローを設定する
次に先ほどTrusted Publisherとして設定したGitHub Actionsを作成します。先ほどの設定画面では以下の値を入力していました。
- Owner:
takanory
- Repository name:
sphinx-nekochan
- Workflow name:
workflow.
yml
この場合はhttps://.github/
ディレクトリにworkflow.
という名前で、ワークフロー用のファイルを作成します。コードの内容は以下で参照できます。
上記のコードは、以下のページで提供されているコードを元にしています。コメントを追加して解説します。
パッケージのビルド
GitHubにpushすると、GitHub Actionが実行されます。最初のbuild
ジョブでbuildライブラリをインストールし、このライブラリを使用してパッケージをビルドします。
name: build
on: push # pushすると実行される
jobs:
build:
name: Build distribution 📦
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Install pypa/build # build用のライブラリをインストール
run: >-
python3 -m
pip install
build
--user
- name: Build a binary wheel and a source tarball # パッケージをビルド
run: python3 -m build
- name: Store the distribution packages # ビルドしたパッケージを保存
uses: actions/upload-artifact@v4
with:
name: python-package-distributions
path: dist/
パッケージのリリース
次のジョブpublish-to-pypi
では、先ほど作成したパッケージをPyPIにリリースします。最後に実行しているpypa/
のGithub Actionによって、デジタル証明書付きでパッケージがリリースされます。
PyPA
publish-to-pypi:
name: Publish Python 🐍 distribution 📦 to PyPI
if: startsWith(github.ref, 'refs/tags/') # タグがプッシュされたときのみリリースを行う
needs:
- build
runs-on: ubuntu-latest
environment:
name: pypi # PyPIで設定したEnvironment nameと合わせる
url: https://pypi.org/p/sphinx-nekochan # 自分のパッケージのURLを設定
permissions:
id-token: write # OpenID Connectトークンを取得し、信頼された公開処理とするために必要な設定
steps:
- name: Download all the dists # さきほど保存したパッケージをダウンロード
uses: actions/download-artifact@v4
with:
name: python-package-distributions
path: dist/
- name: Publish distribution 📦 to PyPI # パッケージをPyPIにリリース
uses: pypa/gh-action-pypi-publish@release/v1
GitHubリリースの作成
最後のジョブgithub-release
はGitHubのリリースを作成します。リリースにはSigstoreの署名が含まれます。
github-release:
name: Sign the Python 🐍 distribution 📦 with Sigstore and upload them to GitHub Release
needs:
- publish-to-pypi
runs-on: ubuntu-latest
permissions:
contents: write # GitHubリリースの作成に必要
id-token: write # sigstoreに必要
steps:
- name: Download all the dists
uses: actions/download-artifact@v4
with:
name: python-package-distributions
path: dist/
- name: Sign the dists with Sigstore # Sigstoreでパッケージに署名をする
uses: sigstore/gh-action-sigstore-python@v3.0.0
with:
inputs: >-
./dist/*.tar.gz
./dist/*.whl
- name: Create GitHub Release # GitHubリリースを作成
env:
GITHUB_TOKEN: ${{ github.token }}
run: >-
gh release create
"$GITHUB_REF_NAME"
--repo "$GITHUB_REPOSITORY"
--notes ""
- name: Upload artifact signatures to GitHub Release # パッケージと署名をアップロード
env:
GITHUB_TOKEN: ${{ github.token }}
# Upload to GitHub Release using the `gh` CLI.
# `dist/` contains the built packages, and the
# sigstore-produced signatures and certificates.
run: >-
gh release upload
"$GITHUB_REF_NAME" dist/**
--repo "$GITHUB_REPOSITORY"
GitHub以外でのリリース方法
GitHub Actions以外を使用したデジタル証明書付きのパッケージリリース方法については、以下のドキュメントを参照してください。Google Cloud、ActiveState、GitLab CI/
gh-action-pypi-publishの解説
先述のとおり、デジタル証明書付きのパッケージのリリースはpypa/
というGitHub Actionによって行われます。ここでは、このGitHub Actionの中でどういった処理が行われているかを解説します。ソースコードは以下のリポジトリで管理されています。
以下で主要なファイルについて説明します。
Dockerfile:Dockerの設定ファイル
設定に従ってDockerイメージが作成されます。
Pythonの環境を構築したあとに各種スクリプトがDocker環境にコピーされ、twine-upload.
が実行されます。
twine-upload.sh :処理全体のスクリプト
メインの処理となるスクリプトで、デジタル証明書の作成、PyPIへのアップロードなどを行います。
このコードの中でoidc-exchange.
スクリプトでPyPIにアップロードするためのトークンを取得し、INPUT_
環境変数に保存します。
次にattestations.
スクリプトでデジタル証明書を作成します。最後に、twine upload
コマンドの引数に--attestations
を追加することで、PyPIにデジタル証明書付きでパッケージをアップロードしています。このときにINPUT_
環境変数に保存された一時的なトークンを使用することで、安全にパッケージのアップロードが行われます。
twine-upload.sh
の一部コードに日本語コメントを付加if "${TRUSTED_PUBLISHING}" ; then
# PyPIのトークンを取得してINPUT_PASSWORDに設定する
INPUT_PASSWORD="$(python /app/oidc-exchange.py)"
if [[ ${INPUT_ATTESTATIONS,,} != "false" ]] ; then
# デジタル証明書を生成してアップロード
python /app/attestations.py "${INPUT_PACKAGES_DIR%%/}"
# --attestations引数を追加
TWINE_EXTRA_ARGS="--attestations $TWINE_EXTRA_ARGS"
# twine uploadコマンドでPythonパッケージとデジタル証明書をアップロード
exec twine upload ${TWINE_EXTRA_ARGS} ${INPUT_PACKAGES_DIR%%/}/*
oidc-exchange.py :OpenID ConnectでPyPIからトークンを取得
このPythonスクリプトではOIDC
最初に、idモジュールのdetect_
関数により、PyPIとやりとりするためのトークンを取得してoidc_
に格納します。内部ではdetect_$ACTIONS_
にアクセスしてトークンを取得しています。
次に、PyPIのトークン交換用URLtoken_
)oidc_
)mint_
に代入します。
mint_
はJSON形式なのでPythonオブジェクトに変換し、その中にあるtoken
キーの値を取得してpypi_
に代入します。この値がPyPIにアクセスするための一時的なトークンとなります。
oidc-exchange.py
の一部コードに日本語コメントを付加try:
# GitHubのOIDCトークンを取得
oidc_token = id.detect_credential(audience=oidc_audience)
...
# PyPIのトークン交換用URLにアクセス
mint_token_resp = requests.post(
token_exchange_url,
json={'token': oidc_token},
timeout=5, # S113 wants a timeout
)
try:
# レスポンスのJSONを変換
mint_token_payload = mint_token_resp.json()
...
# PyPIにアップロードするためのトークンを取得
pypi_token = mint_token_payload.get('token')
# PyPIトークンを出力してtwine-upload.shに渡す
print(pypi_token)
attestations.py :デジタル証明書を作成
デジタル証明書ファイルを作成するPythonスクリプトです。
main()
関数の中でattest_
関数を呼び出して、デジタル証明書ファイルを作成しています。
attest_
関数では、pypi-attestationsモジュールのDistribution.
メソッドでファイルからPythonパッケージのディストリビューションを表すインスタンスを生成して、dist
に代入します。そして、Attestation.
メソッドでデジタル証明書を作成し、attestation_
にJSON形式で書き出します。
このattestation_
に保存されたJSON形式のファイルがtwine upload
コマンドでパッケージと一緒にアップロードされることで、デジタル証明書付きのリリースとなります。
attestations.py
の一部コードに日本語コメントを付加from pypi_attestations import Attestation, Distribution
def attest_dist(
dist_path: Path,
attestation_path: Path,
signer: Signer,
) -> None:
# Pythonディストリビューションファイルのインスタンスを生成
dist = Distribution.from_file(dist_path)
# デジタル証明書を作成
attestation = Attestation.sign(signer, dist)
attestation_path.write_text(attestation.model_dump_json(), encoding='utf-8')
def main() -> None:
# attest_dict()関数でデジタル証明書を作成
with SigningContext.production().signer(identity, cache=True) as signer:
for dist_path, attestation_path in dist_to_attestation_map.items():
attest_dist(dist_path, attestation_path, signer)
まとめ
今回はPEP 740により、デジタル証明書付きでPyPIにパッケージをリリースするという提案と、GitHub Actionsを使用してリリースする方法について紹介しました。デジタル証明書付きのリリースが一般的になれば、より安全にPyPIにアップロードされているパッケージが利用できるようになると思われます。
後半ではgh-action-pypi-publish
の中で具体的にどのような処理が行われているかを解説し、トークンを交換する手順やデジタル証明書の作成手順について説明しました。
自身でPythonパッケージをメンテナンスしている方は、ぜひデジタル証明書付きでのリリースに挑戦してみてください。
参考資料
- PEP 740 – Index support for digital attestations | peps.
python. org - Index hosted attestations - Python Packaging User Guide
- PyPI - Digital Attestations
- PyPI now supports digital attestations - The Python Package Index Blog
- Publishing package distribution releases using GitHub Actions CI/
CD workflows - Python Packaging User Guide - Adding a Trusted Publisher to an Existing PyPI Project - PyPI Docs
- pypa/
gh-action-pypi-publish: The blessed GitHub Action, for publishing your distribution files to PyPI, the tokenless way: https:// github. com/ marketplace/ actions/ pypi-publish - pypi-publish · Actions · GitHub Marketplace