RenovateでGitHub成果物のチェックサムを更新する

開発やビルドに必要なツールをインストールするとき、特にビルド済みの実行ファイルをインターネットからダウンロードするとき、セキュリティインシデントを防ぐためにはアーティファクトを信頼しても良いか確認しなくてはいけません。 もしGnuPGで署名されていればそれを使うことになりますが、正直に言うと開発者にとってもユーザーにとっても手間がかかることが多いです。 一般によく取られている方法が、チェックサムファイルをアーティファクトと一緒に公開することです。 ユーザーは、sha256sumコマンドなどでアーティファクトチェックサムを公開されているチェックサムと比較し、改竄されていないことを確認できます。

中間者攻撃やソーシャルハッキングによるアーティファクトの改竄などのシナリオを想定すると、アーティファクトと同じドメインで公開されているチェックサムファイルを、同じスクリプトのなかでダウンロードして使うことはできません。 しかし、ビルドするスクリプトの中で人間が目でチェックサムを確認するわけにはいきませんから、チェックサムの確認コマンドを実装する時は公開されているチェックサムをそのままスクリプトに書くことになります。 Dockerfileを例にしますが、シェルスクリプトでも同様です。

ARG GRPC_HEALTH_PROBE_VERSION=v0.4.18
ARG GRPC_HEALTH_PROBE_SHA256SUM=0437836b49728bd2ca0da124a8cd005679dabf85a51e1ac429af7d968f932442
RUN curl -fsSL -o /usr/local/bin/grpc_health_probe "https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64" \
 && echo "${GRPC_HEALTH_PROBE_SHA256SUM} /usr/local/bin/grpc_health_probe" | sha256sum -c \
 && chmod +x /usr/local/bin/grpc_health_probe

このようにしておけば、中間者がアーティファクトを差し替えたとしてもビルドが落ちるだけですし、悪意を持つ者がバージョンとチェックサムを変更したとしても、改竄されたアーティファクトをダウンロードしたビルドを全てリストアップできます。

一方で、私たちは依存するソフトウェアを短いサイクルで更新していかなければなりません。 そうしなければ、ソフトウェアはたちまち古くなってしまい、バージョンアップに多大な労力が必要になるだけでなく、セキュリティリスクも高まってしまいます。 そこでRenovatedependabotのようなツールが役に立ちます。 実際、RenovateでDockerfileの中で依存しているソフトウェアのバージョンを更新するのは、regexManagers:dockerfileVersionsプリセットを使って簡単に実現できます。 個人的にはこのプリセットの名前がとても紛らわしいと感じるのですが、Dockerfileの中のバージョンぽいENVやARGを更新してくれるというものです。

  "extends": [
    "config:base",
    "regexManagers:dockerfileVersions",
  ],
  "enabledManagers": ["regex", etc...
# renovate: datasource=github-releases depName=grpc-ecosystem/grpc-health-probe
ARG GRPC_HEALTH_PROBE_VERSION=v0.4.18

RenovateはGitHubの該当するリポジトリのreleasesを確認して、バージョンを上げるPRを作成してくれます。

しかし、ここであなたはそのPRを手で修正しないといけないことに気が付きます。

  # renovate: datasource=github-releases depName=grpc-ecosystem/grpc-health-probe
- ARG GRPC_HEALTH_PROBE_VERSION=v0.4.18
+ ARG GRPC_HEALTH_PROBE_VERSION=v0.4.19
  ARG GRPC_HEALTH_PROBE_SHA256SUM=0437836b49728bd2ca0da124a8cd005679dabf85a51e1ac429af7d968f932442

チェックサムが更新されていません。 これは困りました。 人間がいちいちチェックサムファイルをダウンロードして更新してコミットするのは面倒です。 かと言って、これまでに説明してきたように、チェックサムファイルをビルドスクリプトの中でダウンロードするのは何のセキュリティ対策にもなっていません。

こういうシナリオのために、Renovateは github-release-attachments というデータソースを持っています。 このデータソースは、digestとしてアーティファクトチェックサムを想定しています (github-releasesのdigestはコミットのハッシュです)。

# renovate: datasource=github-release-attachments depName=grpc-ecosystem/grpc-health-probe
ARG GRPC_HEALTH_PROBE_VERSION=v0.4.18
ARG GRPC_HEALTH_PROBE_SHA256SUM=0437836b49728bd2ca0da124a8cd005679dabf85a51e1ac429af7d968f932442

残念ながらプリセットの regexManagers:dockerfileVersions は、現在はdigestまでは更新してくれません。 仕方がないのでプリセットのパターンを参考にしながら、以下のような設定を書くことになります。

  "regexManagers": [
    {
      "fileMatch": ["(^|/)Dockerfile$"],
      "matchStrings": [
        "# renovate: datasource=(?<datasource>[a-z-]+?) depName=(?<depName>[^\\s]+?)(?: versioning=(?<versioning>[^\\s]+?))?\\s+(?:ENV|ARG)\\s+[A-Za-z0-9_]+?_VERSION[ =]\"?(?<currentValue>.+?)\"?\\s+(?:(?:ENV|ARG)\\s+[A-Za-z0-9_]+?_SHA256SUM[ =]\"?(?<currentDigest>[a-f0-9]+?)\"?\\s)?"
      ]
    },

これでようやく、Renovateはバージョンとともにチェックサムも更新してくれるようになりました。

  # renovate: datasource=github-release-attachments depName=grpc-ecosystem/grpc-health-probe
- ARG GRPC_HEALTH_PROBE_VERSION=v0.4.18
- ARG GRPC_HEALTH_PROBE_SHA256SUM=0437836b49728bd2ca0da124a8cd005679dabf85a51e1ac429af7d968f932442
+ ARG GRPC_HEALTH_PROBE_VERSION=v0.4.19
+ ARG GRPC_HEALTH_PROBE_SHA256SUM=0f46d50fb7220dcf4cbf8861b394be9ebdfba5d7712f3900f0f4d6c38843cbcf

さて、Renovateはどのように新しいチェックサムを知るのでしょうか。 後続のコマンドからアーティファクトのURLを調べて、実際にダウンロードしてチェックサムを計算しているわけではありません。 それは複雑すぎますし、Renovateの更新する処理で攻撃のリスクが上がります。 そもそもソフトウェアの提供者が公開していないものをチェックサムとして使うのはナンセンスです。

Renovateのgithub-release-attachmentsは、チェックサムファイルがアーティファクトとして公開されていることを想定しています。 そして GitHubのreleasesのアーティファクトの中から"チェックサムファイルっぽい"ファイルを探し、今のチェックサムに該当するファイル名を探します (詳しくは実装を参照ください。ヒューリスティックな方法に見えるかもしれませんが、そもそもRenovate自体がヒューリスティックの塊だと思います)。 ユーザーが設定しているチェックサムに対応するファイルの名前が分かれば、新しいチェックサムファイルをダウンロードして新しいチェックサムを知ることができます。

依存するソフトウェアをダウンロードするとき、アーティファクトが改竄されていないことを確認するのは必要なことです。 どのような攻撃に対して有効な対策なのか、そして仮にビルドを許したとしてもインシデントが起きた時に追跡できるか、こういうことを普段から意識するのは大切なことです。 一方で、依存するソフトウェアは常に更新していくべきですし、そのために割く労力は必要最低限にしたいものです。 この記事では、アーティファクトチェックサムの確認をしながら、Renovateにより自動で更新していく方法を紹介しました。

最後にjqをDockerイメージにインストールする設定を置いておきます。 きっと誰かの役に立つことでしょう。

# renovate: datasource=github-release-attachments depName=jqlang/jq versioning=regex:^jq-(?<major>\d+)\.(?<minor>\d+)(\.(?<patch>\d+))?$
ARG JQ_VERSION=jq-1.7
ARG JQ_SHA256SUM=2f312b9587b1c1eddf3a53f9a0b7d276b9b7b94576c85bda22808ca950569716
RUN curl -fsSL -o /usr/local/bin/jq "https://github.com/jqlang/jq/releases/download/${JQ_VERSION}/jq-linux-amd64" \
 && echo "${JQ_SHA256SUM} /usr/local/bin/jq" | sha256sum -c \
 && chmod +x /usr/local/bin/jq