【Python】requirements.txtをハッシュピン+wheelsでpip installをオフラインで実行する方法
概要
今回はPythonツールを業務端末やエアギャップ環境で安全に配布するための、requirements.txtのハッシュピンとwheelsを使ったオフラインインストールについて解説していきたいと思います。
社内ツールを配布していると「この端末、インターネットにつながりません」といわれることありませんか(- -;
情シス管理下の端末だとプロキシでpip接続がブロックされてたり、そもそもインターネット非接続のエアギャップ環境だったりします。
さらに最近はサプライチェーン攻撃(PyPIにマルウェア混入パッケージが上がる事案)も増えていて、「社内ツールだから適当にpip installでOK」という時代ではなくなってきました。
そこで本記事では、SHA256ハッシュで依存パッケージを検証しつつ、ローカルのwheelファイルだけで完結するオフラインインストールの手順を、実プロジェクトで使っている構成そのままで紹介します。
これで少しは安心してPythonを使用できると思います!
それではやっていきましょう!
目次
この構成でできるようになること
- PyPIに接続できない端末でもpip installが完結する
- 配布時にサプライチェーン改ざんを検知できる(SHA256不一致で失敗する)
- 配布物から何が入るかを「requirements.txt」と「wheels/」だけで完全に把握できる
逆にこの方式では依存関係の自動解決は捨てることになります。
新しいパッケージを足すには、手動でwhlを拾いにいってハッシュを取り直す手間が発生するので、頻繁に依存を足し引きしたいプロジェクトには向きません。
社内で「バージョン固定で、ずっと同じ組み合わせで動かしたい」業務ツールと相性が良い方式です。
ディレクトリ構成
配布時のプロジェクト構成はこうなります。
- src/
- アプリ本体
- requirements.txt
- 依存パッケージとSHA256ハッシュ
- wheels/
.whlファイルを全て同梱
- README.md
- インストール手順
PyPIから落とした.whlをそのままwheels/に入れて、一緒にZIPで渡すイメージです。
手順1:パッケージバージョンを固定する
まず依存パッケージのバージョンを== で厳密に固定します。
>= や ~= は使いません。マイナーバージョンが変わるだけで挙動が変わる可能性があり、オフライン検証の意味が薄れるためです。
requirements.txt(バージョン固定のみ)nonum
1 | pymupdf==1.27.2.2 |
このあと、このバージョンにハッシュを付けていきます。
具体例で使うライブラリ
今回はPDFを画像化するツールで使っているPyMuPDF 1.27.2.2を例にします。
MuPDF本体はwheelに同梱されるので追加のDLLは不要で、Windows向けに配るときも楽な構成です。
社外配布やSaaS化する場合はライセンス再評価が必要なので、別記事もあわせて確認してください。
手順2:wheelファイルをダウンロードする
インターネット接続のある端末で、PyPIから.whlを直接ダウンロードします。
wheelダウンロード
1 | python -m pip download pymupdf==1.27.2.2 -d wheels --only-binary=:all: |
-d wheels: ダウンロード先ディレクトリ--only-binary=:all:: ソース配布(.tar.gz)を禁止、wheelのみ取得
これでwheels/pymupdf-1.27.2.2-cp39-abi3-win_amd64.whl(Windows 64bit版の場合)が取得できます。
手順3:SHA256ハッシュを取得する
次に、ダウンロードした.whlのSHA256ハッシュを取得します。
PowerShellならGet-FileHashコマンドで取得可能です(^^/
SHA256取得
1 | Get-FileHash wheels\pymupdf-1.27.2.2-cp39-abi3-win_amd64.whl -Algorithm SHA256 |
出力のHash欄に64文字の16進数が出てきます。これを後でrequirements.txtに貼り付けます。
その後取得したハッシュがPyPI側に記載された正規の値と一致するかを手動で照合してください。
照合用の公式ハッシュは、以下のいずれかの方法で取得できます。
方法A:PyPIのDownload filesページ(GUI)
PyPIのパッケージページから確認する方法です。
- 左サイドバー「Download files」タブをクリック
- 各 .whl ファイル名の下にある「view hashes」リンクをクリック
- ダイアログに SHA256 / MD5 / BLAKE2 が表示される
「view hashes」が見当たらない場合は「view details」や「ℹ️」アイコンを探してみてください。
方法B:PyPI JSON API(確実)
PyPIはパッケージメタデータをJSONで公開しているので、ブラウザでURLを叩けば全wheelのハッシュが一覧で取れます。
GUIが見つからない・バージョンが古い場合はこちらの方が確実です。
アクセスURL
1 | https://pypi.org/pypi/PyMuPDF/1.27.2.2/json |
ブラウザで開いたら Ctrl + F で sha256 を検索すると、各wheelのハッシュが見つかります。
JSON構造はこうなっています。
JSONレスポンス抜粋
1 | { |
filename が自分のダウンロードした .whl と一致する要素の digests.sha256 が照合対象です。
方法C:コマンドで一発取得
PowerShellから直接JSONを取得して、自分のwhlのハッシュだけ抜き出すこともできます。
いきなり特定ファイル名で絞り込むと、ファイル名が1文字違っただけで何も出力されません。
まずは全wheelのファイル名とハッシュを一覧表示して、自分のwhlと同じ行を探す手順が安全です。
全wheelの一覧表示
1 | $json = Invoke-RestMethod "https://pypi.org/pypi/PyMuPDF/1.27.2.2/json" |
こうするとこんな一覧が出ます。
出力イメージ
1 | filename sha256 |
自分がダウンロードした .whl と同じファイル名の行のsha256を、手順3で取得した Get-FileHash の結果と比較してください。
ダウンロード経路でファイルが差し替えられた可能性があります。
そのwheelは破棄して、別経路で取り直してください。
手順4:requirements.txtにハッシュを書き込む
取得したハッシュを--hash=sha256:<値>の形式でrequirements.txtに追記します。
requirements.txt(ハッシュピン版)
1 | pymupdf==1.27.2.2 \ |
- 行末の
\で改行継続 --hash=sha256:の直後にスペースなしでハッシュ値- 複数プラットフォーム対応なら、同じパッケージ行に
--hash=を複数重ねる
手順5:オフラインインストールを実行する
配布先端末では、--no-index と --find-links と --require-hashes を組み合わせてpip installします。
オフラインインストール
1 | python -m pip install -r requirements.txt ^ |
--no-index: PyPIを見に行かず、ローカルだけで完結--find-links wheels: 指定フォルダをパッケージリポジトリ扱い--require-hashes: requirements.txt内のハッシュ必須モード
--require-hashesが今回の要です。
これを付けるとrequirements.txt内の全パッケージにハッシュ指定が必須になり、1行でもハッシュ未指定があればエラーで止まります。
ハッシュが一致しなければ当然インストールも失敗するので、結果的に「ローカルのwheelフォルダが正規の物か」が毎回自動で検証される形になります。
失敗する例
ハッシュを変更して再実行すると、こんなエラーが出ます。
ハッシュ不一致エラー
1 | ERROR: THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS FILE. |
これが出た時点でファイルが改ざんされているか、記載ミスのどちらかなので、インストール作業をストップして原因を調べる運用にすると安全です。
運用フロー(依存パッケージを更新するとき)
バージョンアップやセキュリティパッチ適用の際は、次の順で進めます。
更新フロー
1 | 1. 外部接続可能な端末で新バージョンを pip download |
この流れを手順として README に明文化しておくと、担当者が変わっても供給チェーン管理のレベルを維持できます。
参考:なぜ –require-hashes を付けるのか
ハッシュ指定はpip installのデフォルトでは任意です。
requirements.txtにハッシュが書いてあっても、--require-hashesなしならpipは「ハッシュ指定が無いパッケージは普通にネット経由で取りに行く」挙動をします。
つまりpipはrequirements.txtを「ハッシュ付きの行だけ厳密に、そうでない行は普通に」と解釈してしまうので、1行でも書き漏らすとサプライチェーン検知の網に穴が空きます。
--require-hashesを付けておけば「全行ハッシュ必須、無ければエラーで停止」になり、抜け漏れを自動で検出できるようになります。
締め
今回はハッシュピン+wheelsフォルダでPythonツールをオフライン配布する方法を紹介しました。
ポイントをおさらいすると以下のとおりです。
- バージョンは
==で厳密固定 - wheelはPyPIから事前ダウンロードして同梱
- SHA256ハッシュは公式値と手動照合
pip install --no-index --find-links wheels --require-hashesでオフライン化- 更新フローをREADMEに明記して属人化を避ける
情シスや法務が厳しめの組織なら、「このツールが何をダウンロードするか説明できない」と配布許可が下りないケースも多いです。
この構成にしておけばrequirements.txtとwheelsフォルダだけ監査してもらえばOKな状態になるので、社内配布のハードルが一気に下がります(^^b
業務用のPythonツールを作る人は、最初からこの形式で書いておくのがオススメです。
以上となります。
厳しい制限の中でもエンジニアとして最大効率を目指し頑張りましょう!
それではお疲れさまでした!