Flatt Security Blog

株式会社Flatt Securityの公式ブログです。プロダクト開発やプロダクトセキュリティに関する技術的な知見・トレンドを伝える記事を発信しています。

株式会社Flatt Securityの公式ブログです。
プロダクト開発やプロダクトセキュリティに関する技術的な知見・トレンドを伝える記事を発信しています。

Sigstore によるコンテナイメージの Keyless Signing

f:id:flattsecurity:20220228190410j:plain
This image includes the work that is distributed in the Apache License Version 2.0

こんにちは。株式会社Flatt SecurityでインターンをしているMarina (@marin_a___) です。本稿はソフトウェアサプライチェーン領域で注目を集める Sigstore プロジェクトについての記事です。

Sonatype 社の Sonatype's 2021 State of the Software Supply Chain によると、ソフトウェアサプライチェーン攻撃の観測件数は以下のようになっています。

f:id:flattsecurity:20220228045446p:plain
Sonatype 社の資料 より引用

2021年の1年間でソフトウェアサプライチェーン攻撃は12,000件以上観測され、前年比650%増となりました。これは由々しき事態でしょう。特に2021年末には Apache Log4j の脆弱性が発見され、より一層サプライチェーンのセキュリティの問題を痛感することになりました。

本稿では、ソフトウェアサプライチェーンのセキュリティ対策として開発が進んでいる sigstore というプロジェクトと、sigstore を使った鍵管理の必要がないイメージ署名(Keyless Signing)について取り上げます。

※ 本稿でこの後紹介する sigtore の Fulcio 等のプロジェクトや keyless signing を実現するための機能はどれも開発途中のものです。そのため、本稿で説明している内容から仕様や実装が大きく変化する可能性があります。また、本稿中のコード例やコマンド例は 2022年2月時点に検証を行ったものになります。

Sigstoreとは

sigstore はソフトウェアのサプライチェーンをより安全に・簡単に保護する方法を提供するオープンソースプロジェクトです。Let’s Encrypt が証明書発行・管理等を容易にすることで HTTPS の利用を強く支えているように、 sigstore は証明書発行・管理等の基盤を提供することで、コードやそこからの生成物への署名・検証を容易にすることを目指しています。なお、Linux Foundationによりプロジェクトの活動開始が発表されたのは比較的最近の2021年3⽉です。設⽴メンバーは Google、Red Hat、パデュー⼤学からなります。

上記のような目標の実現のため、現在sigstore では、以下の 3 つのソフトウェアの開発が進められています:

  1. Fulcio: ルート認証局として、OpenID Connect により外部から与えられたユーザーの Identity に応じて、署名用鍵を発行するサーバーソフトウェア
  2. Rekor: 証明書発行や署名の Transparency (後述) を担保するために、それらのログを安全に保管するためのサーバーソフトウェア
  3. Cosign: ローカルの鍵ペアや Fulcio が発行した署名用鍵を用いて、OCI イメージ(cf. Docker イメージ)に対する署名や検証を行うためのツール / Fulcio に証明書を要求したり、Rekor に署名を保存したりするツール

Cosign によるローカルの鍵ペアを用いた署名方法

まず Cosign の用法の一つである、ローカルの鍵ペアを用いたコンテナイメージへの署名の流れを見てみましょう。ここからは以下のドキュメントの流れに沿って署名してみます。

公開鍵と秘密鍵のペアの作成

まずは署名に必要な鍵を生成してみましょう。鍵ペアの生成には任意のツールを用いることが可能ですが、 cosign はパスワードによる鍵ペア作成機能も提供しているため、cosign を使って生成してみます。以下のコマンドを実行すると cosign.pub(公開鍵)と cosign.key(秘密鍵)が生成されます。

$ cosign generate-key-pair
Enter password for private key:
Enter again:
Private key written to cosign.key
Public key written to cosign.pub

作成した鍵を用いたコンテナイメージの署名

作成した鍵を cosign sign コマンドのオプションで指定することで、鍵を用いて任意のコンテナイメージに署名することができます。以下の例では、架空のイメージ marina810/hoge に署名しています。

$ cosign sign --key cosign.key marina810/hoge
Enter password for private key:
Pushing signature to: index.docker.io/marina810/hoge:sha256-<snip>.sig

これでコンテナイメージへの署名は完了し、署名に関する情報が marina810/hoge:sha256-<snip>.sig として公開されました。このように cosign により OCI レジストリ上にあるコンテナイメージに署名するとき、生成された署名は OCI レジストリに保管できます。

公開鍵を用いた署名検証

署名に使った公開鍵(上記の例の場合 cosign.pub)を誰でも見れる場所に公開しておけば、公開鍵を取得した人は誰でも、それを用いてイメージを検証することができます。以下は先程作成した marina810/hoge に対する署名の検証を行ってみたときの出力の例です:

$ cosign verify --key cosign.pub marina810/hoge
The following checks were performed on these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
{"Critical":{"Identity":{"docker-reference":""},"Image":{"Docker-manifest-digest":"sha256:<snip>"},"Type":"cosign container image signature"},"Optional":null}

ここでは "The signatures were verified against the specified public key" などと言われているように、実際に先程公開された署名情報が、公開鍵(cosign.pub)を用いて検証されています。この検証により、marina810/hoge:latest に関して公開されている署名情報が、たしかにその公開鍵に対応する秘密鍵の持ち主により作成されたものであることが分かります。

小まとめ: 全体の流れ

ここまでを少し抽象化してまとめると、全体の流れは以下の図のようになります。

f:id:flattsecurity:20220228052547p:plain
鍵ペアを用いた署名の全体の流れ

それぞれ対応するコマンドは以下になります。

  • 1: cosign generate-key-pair
  • 2: cosign sign
  • 4: cosign verify

鍵管理に関する課題

しかし、このようなローカルの鍵ペアを用いるような伝統的な署名方法には、以下のような課題があるのも事実です:

  • 秘密鍵の管理が大変である: この方法で署名する場合は、秘密鍵を自分で管理する必要があります。KMSなどを使ってシステマティックに署名鍵を管理することは可能とはいえ、鍵を保有する限りは、流出のリスクを抱え続けることにはなります。
  • 公開鍵の配布が面倒である: ソフトウェア開発者側からすれば、公開鍵を安全に配布するのは難しいものです(公開鍵自体が悪意のある人物により置き換えられたら困りものです)。ソフトウェアの利用者側から見ても、ソフトウェアごとに公開鍵を拾ってくるのは面倒ですし、公開鍵の配送経路での攻撃の余地がある場合には公開鍵を信用できるかも問題になります。

OpenID Connect を活用した署名(Keyless Signing)

Sigstore プロジェクトの各種ツールを利用すると、上述のように自分で鍵管理を行わずにコンテナイメージへの署名を行うこともできます。具体的には 「OpenID Connect を活用して得られる署名者の認証情報に紐づけて短命の署名用鍵を発行し、それで署名する」 というようなことができるのです。これが本稿のテーマである Keyless Signing と呼ばれる手法です。

Keyless Signing の仕組み

cosign sign コマンドにより Keyless Signing を行うときの全体像を図にすると、大まかには以下のようになります。Let’s Encrypt が TLS のために行ったこと(= Root CA や Certificate Transparency の提供)と近い構図です。

f:id:flattsecurity:20220228053110p:plain
Keyless Signing(OpenID Connect を用いた署名)の全体像

先述の通り、これは大まかに言えば 「OpenID Connect により認証を行い、その認証情報と別途生成した短命の署名用鍵を紐づけて記録しておき、それで署名する」 ということをするようなステップになっています。より詳しく説明すると、この図の各ステップでは以下のようなことが行われています。

  • ステップ 1, 2, 3
    • OpenID Connect を用いて署名者の認証を行い ID Token を取得する
  • ステップ 4
    • 短命の鍵ペアを作成する
  • ステップ 5, 6, 7, 8
    • ステップ 3 で取得した ID Token とステップ 4 で作成した短命の公開鍵を用いて、ルート認証局の役割を持つ Fulcio に証明書発行のリクエストを送る
    • それを受け取った Fulcio が、その公開鍵に関する証明書の発行ログを Key Transparency Log に記録しつつ、証明書を発行する
  • ステップ 9, 10
    • ステップ 8 の証明書とステップ 4 の秘密鍵を用いて署名を行う
    • 署名の発行ログを Rekor の Signature Transparency Log に記録する

これらのステップはどれも一見複雑に見えますが、cosign sign というコマンドによって多くは自動化されています。実のところ、署名を行いたい人(図中の Developers にあたる人)が行う必要があるのはステップ 2(認証)の部分のみです。

また、Sigstore のソフトウェアはログ管理に Google の開発した Trillian を用いることで、ログの改ざんを困難にしています。その上で証明書や署名に関する様々なログがログサーバーに保存されるので、署名のために作成した鍵ペアを破棄しても証明書を revoke する必要はありません。これもこのような署名方式の利点の一つです。

上述のような仕組みにより、開発者一人ひとりの鍵ペアの管理が不要になり、負担が大幅に減ることになります。"keyless" とはいっても署名の際には鍵が用いられないわけではなく、 「開発者が鍵を管理する必要がない」「短命の鍵と証明書が使用される」というところが "keyless" という言葉が使われるゆえんなわけです。

Keyless Signing の具体的な利用方法

Keyless Signining を行う際に必要な ID Token を取得するための方法としては、大きく分けて以下の 2 つの方法が存在します:

  1. 人力で認証を行い ID Token を取得する方法
    • cosign sign コマンドで認証画面へのリンクを生成し、好きな OpenID Connect プロバイダを選択して認証することで、ID Token が取得できる
    • ターミナルが利用できない非対話環境の場合でも、stdout に出力された認証用のリンクを回収することができれば、同様に手動で認証・ID Token の取得が進められる
  2. 機械的に ID Token を取得する方法
    • GitHub Actions のワークフロー内や、SPIFFE や Workload Identity のような機能が利用できる場合など、機械的に ID Token を取得できる場合は、それを利用して Fulcio が利用できる

ここからは 1 と 2 のそれぞれの方法で認証を行いながら Keyless Signing を実践する手順を紹介します。

方法1: 人力で認証を行って署名する

まずは OpenID Connect プロバイダでの認証を人の手で行うような形で Docker Hub 上のイメージに署名してみます。以降例示では個人のイメージ名 marina810/hoge を使用しているので、自分で再現する際には適宜置き換えてください。

まずは cosign sign コマンドを COSIGN_EXPERIMENTAL=1 という環境変数をセットして実行しましょう。

$ COSIGN_EXPERIMENTAL=1 cosign sign marina810/hoge
Generating ephemeral keys...
Retrieving signed certificate...
Your browser will now be opened to:
https://oauth2.sigstore.dev/auth/auth?access_type=online&client_id=sigstore&code_challenge=<snip>&code_challenge_method=S256&nonce=<snip>&redirect_uri=http%3A%2F%2Flocalhost%3A54542%2Fauth%2Fcallback&response_type=code&scope=openid+email&state=<snip>

すると、以下のようなページが Web ブラウザ上で開かれます。この画面では認証に使用する OpenID Connect プロバイダを選択することができます。

f:id:flattsecurity:20220228053640p:plain
認証に使用できるプロバイダの一覧

今回は GitHub を選択してみます。

f:id:flattsecurity:20220228053709p:plain
GitHub を選択した場合

GitHub 上での操作が完了すると、ターミナルに以下のようなメッセージが表示されます。このメッセージからは、署名が作成され、ローカルの鍵ペアを使用して署名した場合と同様にリモートに署名情報がアップロードされていることが分かります。

Successfully verified SCT...
tlog entry created with index: 1412065
Pushing signature to: index.docker.io/marina810/hoge

先程紹介した Keyless Signing の流れをもう一度見てみましょう。いま署名時に手動で行う必要のあったブラウザ上での認証は、この中だとステップ 2(認証)の部分にあたります。逆にいえば、それ以外は全て cosign が自動化してくれているということです。

f:id:flattsecurity:20220228053747p:plain
Keyless Signing の流れ(再掲)

続いて、付与した署名の検証をしてみましょう。この場合は Fulcio に公開鍵が登録されているので、コマンドを打つ際に --key オプションで公開鍵を指定する必要はありません。

$ COSIGN_EXPERIMENTAL=1 cosign verify marina810/hoge:1.0 | jq .
Verification for index.docker.io/marina810/hoge:1.0 --
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - Existence of the claims in the transparency log was verified offline
  - Any certificates were verified against the Fulcio roots.
[
  {
    "critical": {
      "identity": {
        "docker-reference": "index.docker.io/marina810/hoge"
      },
      "image": {
        "docker-manifest-digest": "sha256:7d6b646dfc486feb3d30e4b566c64bb73716e08e523b74e6370c92f5f29d2fd3"
      },
      "type": "cosign container image signature"
    },
    "optional": {
      "Bundle": {
        "SignedEntryTimestamp": "MEUCIQDQMQxmUCAVUmsSSoAcl870TF4lODV6MJ8yuk48AcbbgwIgeFCRPfWCgZXX8zlEvv6YqyGMtBf9scVt9B89Sxip30A=",
        "Payload": {
          "body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJkODBiMmJjYmY4ODg1NDFhNWZlZDQ3NTRjZDU2YTAzOGY5MDlhMzEzYTI1YmI3NmY4MjNlMTY5MWU4MjU0NGRjIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJSDRGSnRMN0IzK0Zsc29haUFGemYrNTFwb2xBN0NRVG8xVVA5YWRnbXF6TkFpRUF2enVybFpMYW9BNHNYQ2Nob0xGK21jYytXV3k2cHNTYy8wamE0VjFLSEZBPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTktWRU5EUVdGMVowRjNTVUpCWjBsVlFVbG5ZMFpOYUVsd1YySnlTVlJGVEdWeVEwOXphWGN2VkZOWmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1MycEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWtWM1JIZFpSRlpSVVVSRmQyaDZZVmRrZW1SSE9YbGFWRUZsUm5jd2VRcE5ha0Y1VFZSWmQwNVVTWHBPVkd4aFJuY3dlVTFxUVhsTlZGbDNUbFJOZWs1VWFHRk5RazE0UlZSQlVFSm5UbFpDUVc5VVEwaE9jRm96VGpCaU0wcHNDazFHYTNkRmQxbElTMjlhU1hwcU1FTkJVVmxKUzI5YVNYcHFNRVJCVVdORVVXZEJSV3hVT0RFdlowaDBVMHBFYVZWWlVWbHdhell6V0VsbU5FMTNjbUlLVG1Sc2J6VjFObUZ1ZEdsTlJrazNTbk5uY1haT1RISXpPV1YzVlV0U05rSTNUWEEwTlc0eFpsZDNUV1F3V20wMk5qRjVUUzl5WVROTllVOUNlRlJEUWdwM2FrRlBRbWRPVmtoUk9FSkJaamhGUWtGTlEwSTBRWGRGZDFsRVZsSXdiRUpCZDNkRFoxbEpTM2RaUWtKUlZVaEJkMDEzUkVGWlJGWlNNRlJCVVVndkNrSkJTWGRCUkVGa1FtZE9Wa2hSTkVWR1oxRlZlWEpGVlhCbmExZFBWM0IyTkdWWFZISlpjRU0yTm10SWFpdGpkMGgzV1VSV1VqQnFRa0puZDBadlFWVUtWMDFCWlZnMVJrWndWMkZ3WlhONVVXOWFUV2t3UTNKR2VHWnZkMGgzV1VSV1VqQlNRa0puZDBadlJWVmhlbWQ0VFVjeGFHTnRiSFZaVlVKdVlsZEdjQXBpUXpWcVlqSXdkMHhCV1V0TGQxbENRa0ZIUkhaNlFVSkJVVkZsWVVoU01HTklUVFpNZVRsdVlWaFNiMlJYU1hWWk1qbDBUREo0ZGxveWJIVk1NamxvQ21SWVVtOU5RVzlIUTBOeFIxTk5ORGxDUVUxRVFUSm5RVTFIVlVOTlVVTktSbnBDVkhsMWVDdEpZWEZpUVZSWlFXOTJVMkUxVG01SmRXWnpXVFpQZVVJS05XNHpZU3RoV0c5ck1HdHRhMW8xY1Zjck5UVnhRekJHVjNkUlNTOWhjME5OUTA1cUwwbzNhblZrWjFsb1VIRkJTRzlqTWtSS1FtVXJPRm92VDFJNGFncDBlVzVxY0dvMFdWbFJkRXRrWWtwR2RqUkthRk0yYVc4NGEyNTZhWGh5TW05blBUMEtMUzB0TFMxRlRrUWdRMFZTVkVsR1NVTkJWRVV0TFMwdExRbz0ifX19fQ==",
          "integratedTime": 1644989046,
          "logIndex": 1412065,
          "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"
        }
      },
      "Issuer": "https://github.com/login/oauth",
      "Subject": "自分のメールアドレス"
    }
  }
]

このログを見て分かるように、Keyless Signing を行う場合には、optional の部分に「どのようなアイデンティティにより署名されたか」を示す情報が記録されることになります1。例えば今回は OpenID Connect プロバイダとして GitHub を選択したので、issuer が https://github.com/login/oauth になっています。また、Subject には GitHub が連携時に発行した ID Token の subクレームの値(= GitHub に登録しているメールアドレス)が入っているのが確認できます。

方法2: 機械的に認証を行って署名する

方法 1 では Web ブラウザ上で署名者が認証を行いました。しかし本質的には、ID Token が何かしらの形で手に入りさえすれば、Fulcio によりそれに対応する署名鍵を払い出すことができます2。例えば、GitHub Actions のワークフロー内や、SPIFFE や Workload Identity のような機能が利用できる場合など、機械的に ID Token を取得できる場合は、それを利用して認証が行えるということです。

ここでは容易に・機械的に ID Token が取得できるような OpenID Connect プロバイダを提供している GitHub Actions を用いて Keyless Signing を試してみます。以下にサンプルリポジトリを用意したので、以降はこれを元に説明を進めます。

GitHub Actions の「容易に・機械的に ID Token が取得できるような OpenID Connect プロバイダ」とは、簡単に述べると「GitHub Actions のワークフローが実行されるたびに短命の ID Token を発行してくれる」ような存在です。詳しい話はさておき、この仕組みは以下のワークフロー(.github/workflows/keyless-sign.yml)のように permissionsid-token: write を指定することによって有効化することができます3

name: Publish Signed Container Image

on:
  workflow_dispatch: {}

jobs:
  release_job:
    runs-on: ubuntu-latest
    permissions:
      packages: write
      id-token: write
    steps:
      - name: install cosign
        uses: sigstore/cosign-installer@main

      - name: Setup Docker buildx
        uses: docker/setup-buildx-action@v1

      - name: Log into ghcr.io
        uses: docker/login-action@v1
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push container image
        id: push-step
        uses: docker/build-push-action@v2
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:latest

      - name: Sign the container image
        env:
          COSIGN_EXPERIMENTAL: "true"
        run: cosign sign --force ghcr.io/${{ github.repository }}@${{ steps.push-step.outputs.digest }}

      - name: Verify the container image
        env:
          COSIGN_EXPERIMENTAL: "true"
        run: cosign verify ghcr.io/${{ github.repository }}@${{ steps.push-step.outputs.digest }}

上記のワークフローはコンテナイメージを生成し、GitHub Container Registry に push した後に cosign による Keyless Signing を実際に行い、テストとしてその署名の検証も行うというものです4

とりわけ cosign による署名は、以下の箇所において、このワークフロー内で push したコンテナイメージの digest に対して行われています5。なお --force フラグが指定されているのは、push 先のリポジトリが private なときであっても Rekor に署名ログを残すためです 6

      - name: Sign the container image
        env:
          COSIGN_EXPERIMENTAL: "true"
        run: cosign sign --force ghcr.io/${{ github.repository }}@${{ steps.push-step.outputs.digest }}

さて、このワークフローを実際に実行すると、Sign the container image のステップまでは問題なく進みます。また、Verify the container image も以下のような出力を残しながら正常終了します。

Verification for ghcr.io/flatt-security/keyless-signing-example@sha256:69d82a28f81cc57ce307eeb2760c83418a64e192f76e01403357239983476139 --
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - Existence of the claims in the transparency log was verified offline
  - Any certificates were verified against the Fulcio roots.

[
  {
    "critical": {
      "identity": {
        "docker-reference": "ghcr.io/flatt-security/keyless-signing-example"
      },
      "image": {
        "docker-manifest-digest": "sha256:69d82a28f81cc57ce307eeb2760c83418a64e192f76e01403357239983476139"
      },
      "type": "cosign container image signature"
    },
    "optional": {
      "Bundle": {
        "SignedEntryTimestamp": "MEQCIDhaIliBW4lt+Oifkz7OGwXRp7dz/1gNsyybq4GQE12fAiBNk7+piH9TinW5uyiMWXGT5xPmmmOf+8wNL40A/gaNBA==",
        "Payload": {
          "body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiIxYjc1MmU3ODgxYThjMzJlOWZmNTc3MjA2NjE1YWYwMTA1OTA0Y2VhMmYzNWU5NTE3NTkzOTdkYzVmZmUyOWQyIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FWUNJUUNjVlZzcVcrV041WThhakszS2pVeWNvSDRqT2N4S1pFNFVOcVpCeitDdnBRSWhBT1hFY1ZDalY4SnRXejc0NVovYUR0ZDZGcDd5OHlvWmxQWjVIT2xFT0tLRyIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVUlpSRU5EUVhWbFowRjNTVUpCWjBsVlFVdGxRamMwVFRWWGNHUXpSR3BRVWtSUE1qSmlRMWxOTnpWRmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1MycEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWtWM1JIZFpSRlpSVVVSRmQyaDZZVmRrZW1SSE9YbGFWRUZsUm5jd2VRcE5ha0Y1VFZSamQwNUVSWGRPVkVaaFJuY3dlVTFxUVhsTlZHTjNUa1JKZDA1VVFtRk5RazE0UlZSQlVFSm5UbFpDUVc5VVEwaE9jRm96VGpCaU0wcHNDazFHYTNkRmQxbElTMjlhU1hwcU1FTkJVVmxKUzI5YVNYcHFNRVJCVVdORVVXZEJSVUUzYjNsdVMweGpTV2xQV0hWVGVqRkZORGxaUTJRMFNYRXlaV0lLWkdFclVUTnlVbmR1T0dkSE1FaHhXazFSUzAxbVpYWTRaM05GVjJndlptOWhZV3R3VTNkRFZFbHBhRkZaZVZkdk9HTkNNalZSYjI4MU5rOURRV2RCZHdwblowZzRUVUUwUjBFeFZXUkVkMFZDTDNkUlJVRjNTVWhuUkVGVVFtZE9Wa2hUVlVWRVJFRkxRbWRuY2tKblJVWkNVV05FUVhwQlRVSm5UbFpJVWsxQ0NrRm1PRVZCYWtGQlRVSXdSMEV4VldSRVoxRlhRa0pVZG00M1dWbHFjMDB2V1hCaFRpdEZRMGxJUlRkTVRGRlNPVTU2UVdaQ1owNVdTRk5OUlVkRVFWY0taMEpTV1hkQ05XWnJWVmRzV25Gc05ucEtRMmhyZVV4UlMzTllSaXRxUW5kQ1owNVdTRkpGUldGVVFtNW9iVlp2WkVoU2QyTjZiM1pNTW1Sd1pFZG9NUXBaYVRWcVlqSXdkbHB0ZUdoa1NGRjBZekpXYW1SWVNuQmtTR3QyWVRKV05XSkhWbnBqZVRGNllWZGtkV0ZYTlc1TVYxWTBXVmN4ZDJKSFZYWk1iV1J3Q21SSGFERlphVGt6WWpOS2NscHRlSFprTTAxMlpFZFdlbVJETlRWWlZ6RnpVVWhLYkZwdVRYWmhSMVpvV2toTmRtSlhSbkJpYWtFMVFtZHZja0puUlVVS1FWbFBMMDFCUlVKQ1EzUnZaRWhTZDJONmIzWk1NMUoyWVRKV2RVeHRSbXBrUjJ4MlltNU5kVm95YkRCaFNGWnBaRmhPYkdOdFRuWmlibEpzWW01UmRRcFpNamwwVFVOM1IwTnBjMGRCVVZGQ1p6YzRkMEZSVVVWSWJFSXhXVzE0Y0dNeVoyZFZNbXh1WW0xV2EwbEZUblppYmxKb1lWYzFiR05wUWtwaVYwWnVDbHBVUVRKQ1oyOXlRbWRGUlVGWlR5OU5RVVZFUWtObk1scHFWbWhOUkdocVQxZE5kMDlVV1RSYVZFSnFUbFJyTkUxVVdUUk5SMVpzV2xkUmVGcFhTWGtLVG5wc2EwNVhSbXhQVjFwclRVSXdSME5wYzBkQlVWRkNaemM0ZDBGUldVVkVNMHBzV201TmRtRkhWbWhhU0UxMllsZEdjR0pxUVRCQ1oyOXlRbWRGUlFwQldVOHZUVUZGUmtKRFdtMWlSMFl3WkVNeGVscFhUakZqYld3d1pWTTVjbHBZYkhOYVdFNTZURmhPY0ZveU5YQmliV04wV2xob2FHSllRbk5hVkVGbUNrSm5iM0pDWjBWRlFWbFBMMDFCUlVOQ1FrWXpZak5LY2xwdGVIWmtNVGxyWVZoT2QxbFlVbXBoUkVGTFFtZG5jV2hyYWs5UVVWRkVRWGRPYmtGRVFtc0tRV3BCTDNCTloxZDBPVlYxSzNaSlJqbGlZbFowTUhKblVHZEtabXhTU0dJcmVscG9hVkJKZUV0d1pFNWhTR2x2YVdNd1RXVlNWVEowYVdwNVdFSnhhUXBVWkVGRFRVRjFVV0pUVTNKQ1JEZHFOak5HWlZBeFpsbEJWa1pDTUhBck5TdHRUazVOVVdSU2EwUXpPV2RhWWxjeFYzbGtUMGQyVmpOQ1YwVkRXVmMzQ21wNVJVMUtaejA5Q2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLIn19fX0=",
          "integratedTime": 1645071060,
          "logIndex": 1426690,
          "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"
        }
      },
      "Issuer": "https://token.actions.githubusercontent.com",
      "Subject": "https://github.com/flatt-security/keyless-signing-example/.github/workflows/test.yaml@refs/heads/main"
    }
  }
]

先程 Keyless Signing を利用する場合には「どのようなアイデンティティにより署名が行われたか」が記録されると説明しましたが、今回の場合も例に漏れず ID Token の iss クレームと sub クレームが記録されています。具体的には、Issuer として https://token.actions.githubusercontent.com が、Subject として https://github.com/flatt-security/keyless-signing-example/.github/workflows/test.yaml@refs/heads/main が記録されています。

また、このページ を見ると、署名情報である ghcr.io/flatt-security/keyless-signing-example:sha256-69d82a28f81cc57ce307eeb2760c83418a64e192f76e01403357239983476139.sig が GitHub Container Registry にアップロードされていることも確認できます。

f:id:flattsecurity:20220228055407p:plain
GitHub Container Registry 上に push された Artifacts

[雑談] Github Actions での Keyless Signing の使用状況について

Celebrating 1,000,000 entries in Rekor によると、Rekor のログのうち、 issuerが https://token.actions.githubusercontent.com のものが17,179件あると述べられています。ログ全体で約1,000,000件あるため、約1%を占めることになります。

まとめ

sigstore は、鍵の管理の自動化や透明性のあるログ管理のためのサービスの開発・提供を、OpenID Connect のような仕様を組み合わせつつ、ソフトウェアサプライチェーンのセキュリティ対策に取り組んでいます。

sigstore の Vision には以下のようにも書かれていますが、OSS の利用が普通になってきた今、開発者がより簡単かつ安全に開発できる状態を作るのにはこのようなプロジェクトが非常に大切になっていくでしょう。

sigstore was started to improve supply chain technology for anyone using open source projects. It's for open source maintainers, by open source maintainers.

本稿で紹介したように、現在 Sigstore プロジェクトの取り組みは OCI イメージの署名に関することが中心ですが、npm などのパッケージシステムに関しての各方面の対応も気になるところですね。

Flatt Securityのセキュリティ診断

Flatt Securityではセキュリティエンジニアの手動検査とツールを組み合わせたセキュリティ診断サービスを提供しています。 技術スタックや構成に応じて、柔軟に診断プランを提案いたします。

過去に診断を実施したが不安や課題がある、予算やスケジュールに制約がありどのように診断を進めるべきか悩んでいる等、お困り事にあわせて対応策をご提案いたしますので、まずはお気軽にお問い合わせください。

お問い合わせは下記リンクよりどうぞ。

https://flatt.tech/assessment/contact

Flatt Securityはセキュリティに関する様々な発信を行っています。

最新情報を見逃さないよう、公式Twitterのフォローをぜひお願いします!

twitter.com

ここまでお読みいただきありがとうございました!


  1. それ以外のパラメータにも多くの情報が含まれています。例えば logIDlogIndex は Rekor に記録された log の ID や index を指しており、rekor get --log-index [対象のlogIndex] というコマンドを使って対象のログを確認するのに利用できます。

  2. もちろん Sigstore 公式が運営する Fulcio インスタンスは、全ての OpenID Connect プロバイダからの ID Token を受理するわけではありません。同インスタンスがどの OpenID Connect プロバイダと連携しているかは、現時点では sigstore/fulcio の federation ディレクトリ内にある設定 から確認できます。

  3. 今回は説明を簡単にするため GitHub Container Registry を利用しており、ワークフローから GitHub Container Registry に push するために packages: write も指定しています。もちろん DockerHub でも同様に GitHub Actions を用いた Keyless Signing を利用できますが、その場合は事前に GitHub Actions の Secret などにアクセス用の情報を保存しておく必要があります。

  4. こちらに Sigstore 公式によるワークフロー例があるので参考にしました: https://github.com/sigstore/cosign-installer

  5. Docker イメージのタグに対して署名することも可能ですが、非推奨です。なぜなら、タグの中身は変わりうるからです。Docker は同名のタグで push することが可能であり、その場合は中身が上書きされることになります。つまり、同じタグだからといって中身が同じである保証はありません。

  6. ここで --force フラグが必要なのは、cosign sign コマンドが Sigstore の運営している Rekor の公開インスタンスにログが無意識に登録されてしまうことを防ぐためです。Certificate Transparency Logs がドメイン名を探索するための OSINTにも用いられるように、プライベートなイメージへの署名ログも OSINT 的に利用される可能性はあります。