GitHub Actions から AWS へのアクセスに利用している OpenID Connect ID Provider の thumbprint について調査した

ROUTE06 でエンジニアリングマネージャ兼ソフトウェアエンジニアとして働いております海老沢 (@satococoa) と申します。

先日発生した GitHub Actions と AWS の OpenID Connect 連携におけるトラブルに関して調査を行い、対応方針を策定した件を共有したいと思います。

[2023/07/10 追記]

Thumbprint を明示的にユーザ側で設定しなくて良いように、AWS 側で対応されたそうです。 github.com

当面 Terraform のモジュール的には必須入力のままですが、任意の文字列で良いそうです。 (いずれ入力も不要になるのかと思います。)

https://github.com/aws-actions/configure-aws-credentials/issues/357#issuecomment-1626357333

The API still appears to require that some thumbprint is configured, since that will require other API and UI changes, but your thumbprint will be completely ignored and OIDC will still work. The message in the IAM console will persist to remind you of this. I successfully tested this out in my own AWS account by setting my configured thumbprint to afafafafafafafafafafafafafafafafafafafaf and was able to get authenticated.

[追記終わり]

事の発端

先日 2023/06/27 の午後に AWS 環境へのデプロイを行っている GitHub Actions のワークフローで以下のようなエラーが出るようになりました。

Error: OpenIDConnect provider's HTTPS certificate doesn't match configured thumbprint

該当箇所は AWS 公式の aws-actions/configure-aws-credentials にて OpenID Connect (以下 OIDC) を用いて AWS リソース操作のためのクレデンシャルを取得している部分です。

調べてみたところ、OpenIDConnect provider's HTTPS certificate doesn't match configured thumbprint の Issue がヒットし、GitHub Actions が提供する ID Provider の証明書が変更されたことにより AWS 側に設定している thumbprint がマッチしなくなってしまったことが原因であることがわかりました。

さらに夜には GitHub 公式のブログに GitHub Actions – Update on OIDC integration with AWS という記事がアップされ、以下の 2 つの thumbprint を両方とも AWS 側に設定するよう案内が出ました。

  • 6938fd4d98bab03faadb97b34396831e3780aea1
  • 1c58a3a8518e8759bf075b76b750d4f2df264fcd

(ここまでの情報は海老沢が調査したのではなく、社内の別チームが調査を行いソフトウェアエンジニアの定例を通じて共有してもらったものです。)

対応自体は単純に新しい thumbprint を AWS 側の ID プロバイダの設定に追加すれば良いだけではありますが、こちらを Terraform で管理するにはどのようにすれば良いのか、また同様の事象が発生したときに備えて何か対策はできないかどうか調査しました。

トラブル発生時の Terraform の実装

元々の Terraform の設定は以下の Zenn の記事を参考に作られていました。

Terraform だけを使って GitHub Actions OIDC ID プロパイダの thumbprint を計算する方法

data "http" "github_actions_openid_configuration" {
  url = "https://token.actions.githubusercontent.com/.well-known/openid-configuration"
}

data "tls_certificate" "github_actions" {
  url = jsondecode(data.http.github_actions_openid_configuration.body).jwks_uri
}

resource "aws_iam_openid_connect_provider" "github_actions" {
  url             = "https://token.actions.githubusercontent.com"
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = [data.tls_certificate.github_actions.certificates[0].sha1_fingerprint]
}

改善策を試す

上述の記事に寄せられている ラグさんのコメント をみると、このような記載もあります。

一つだけ改善点があるかもしれません。記事にも書いてあるように[0]だともしかして正しい証明書を参照できない可能性があると思います。

確かに GitHub Actions – Update on OIDC integration with AWS 記事中にも以下のように記載があるので、複数の証明書の thumbprint をセットするのが良さそうです。

There are two possible intermediary certificates for the Actions SSL certificate and either can be returned by our servers, requiring customers to trust both. This is a known behavior when the intermediary certificates are cross-signed by the CA.

Customers experiencing issues authenticating via OIDC with AWS should configure both thumbprints to be trusted in the AWS portal.

ということで、 aws_iam_openid_connect_provider.github_actions のリソースを以下のように変更して plan してみます。

resource "aws_iam_openid_connect_provider" "github_actions" {
  url             = "https://token.actions.githubusercontent.com"
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = data.tls_certificate.github_actions.certificates[*].sha1_fingerprint
}

これで実行すると thumprint_list には GitHub のエントリで指定がある以下の 2 つの文字列に変更されることを想定していました。

  • 6938fd4d98bab03faadb97b34396831e3780aea1
  • 1c58a3a8518e8759bf075b76b750d4f2df264fcd

するとなんという事でしょう、実行タイミングによって出てくる差分が違ってしまいました。その上、GitHub のエントリに記載されていない f879abce0008e4eb126e0097e46620f5aaae26ad という値も現れてしまいました。

差分パターン 1

~ resource "aws_iam_openid_connect_provider" "github_actions" {
    id              = "arn:aws:iam::xxxxx:oidc-provider/token.actions.githubusercontent.com"
    tags            = {}
  ~ thumbprint_list = [
      - "6938fd4d98bab03faadb97b34396831e3780aea1",
      + "1c58a3a8518e8759bf075b76b750d4f2df264fcd",
      + "f879abce0008e4eb126e0097e46620f5aaae26ad",
    ]
    # (4 unchanged attributes hidden)
}

差分パターン 2

~ resource "aws_iam_openid_connect_provider" "github_actions" {
    id              = "arn:aws:iam::xxxxx:oidc-provider/token.actions.githubusercontent.com"
    tags            = {}
  ~ thumbprint_list = [
        "6938fd4d98bab03faadb97b34396831e3780aea1",
      + "f879abce0008e4eb126e0097e46620f5aaae26ad",
    ]
    # (4 unchanged attributes hidden)
}

なぜ想定と違う値が取得されるのか調査

AWS 公式の OpenID Connect ID プロバイダーのサムプリントの取得 を読み、thumbprint の計算方法を確認します。

すると以下の記述が気になります。

複数の証明書が表示される場合は、表示される最後 (コマンド出力の最後) の証明書を見つけます。これには認証機関チェーンの最上位中間 CA の証明書が含まれています。

つまり前述の対応だと「表示される最後 (コマンド出力の最後) の証明書を見つけます」ができておらず、取得できた証明書全てに対して thumbprint を計算しているのでは?と仮説を立てました。

以下、この仮説を検証していきます。

次のようなシェルスクリプトを書いて複数回実行することにより、取得できる全ての証明書の thumbprint を出力してみました。(なお、このシェルスクリプトは ChatGPT 先生に書いていただきました。3 回ほどのリテイクでバッチリ仕上げてくださいました。)

#!/bin/bash

OIDC_CONFIGURE_URL=https://token.actions.githubusercontent.com/.well-known/openid-configuration

jwks_uri=$(curl $OIDC_CONFIGURE_URL | jq '.jwks_uri')
domain_name=$(echo $jwks_uri | sed -n 's/"https:\/\/\([^/]*\).*/\1/p')

# OpenSSL s_client コマンドを実行し、出力を一時ファイルに保存
openssl s_client -servername ${domain_name} -showcerts -connect ${domain_name}:443 </dev/null 2>/dev/null | awk '
  /-----BEGIN CERTIFICATE-----/ { cert = 1 }
  cert { print > "cert.pem" }
  /-----END CERTIFICATE-----/ { cert = 0 }
'

# 証明書のFingerprintを出力
cert_count=$(awk '/-----BEGIN CERTIFICATE-----/{count++}END{print count}' cert.pem)
i=0
while [ $i -lt $cert_count ]; do
  fingerprint=$(openssl x509 -in cert.pem -fingerprint -sha1 -noout | awk -F= '{print $2}')
  fingerprint_formatted=$(echo $fingerprint | tr ':' ' ' | tr -d ' ' | tr '[:upper:]' '[:lower:]')
  echo $fingerprint_formatted
  echo "----------------------"
  sed -i '' '1,/-----END CERTIFICATE-----/d' cert.pem
  ((i++))
done

# 一時ファイルを削除
rm cert.pem

何度か実行しましたが出力は以下のどちらかになるようでした。(2023/06/29 の夜ごろ実行)

パターン 1

f879abce0008e4eb126e0097e46620f5aaae26ad
----------------------
6938fd4d98bab03faadb97b34396831e3780aea1
-----------------------

パターン 2

f879abce0008e4eb126e0097e46620f5aaae26ad
----------------------
1c58a3a8518e8759bf075b76b750d4f2df264fcd
----------------------

この値は先ほど Terraform 実行時に差分として出てきた値と一致しますね。 つまり、仮説とした

この PR の対応だと「表示される最後 (コマンド出力の最後) の証明書を見つけます」ができておらず、取得できた証明書全てに対して thumbprint を計算しているのでは?と予想しました。

↑ の認識が正しいことがわかりました。

また、上記出力のパターン 1, 2 の最後の証明書の出力、つまりは「認証機関チェーンの最上位中間 CA の証明書」の thumbprint は以下のどちらかになります。

  • 6938fd4d98bab03faadb97b34396831e3780aea1
  • 1c58a3a8518e8759bf075b76b750d4f2df264fcd

これは GitHub のエントリで案内されている値と同じです。

最終的な対応策

証明書の取得タイミングによってどちらの証明書が取得できるかが不定であるためいっぺんに両方の値を取得することは難しく、 Terraform でここの値の自動更新は難しそうです。よってハードコーディングする方針しかなさそうです。

https://github.com/aws-actions/configure-aws-credentials/issues/357 のコメントを追うとハードコーディングに加えて data.tls_certificate.github.certificates[0].sha1_fingerprint の値も組み合わせる方法(以下のコード参照)が提示されています。

https://github.com/aws-actions/configure-aws-credentials/issues/357#issuecomment-1610974303 より抜粋

thumbprint_list = distinct([
  "6938fd4d98bab03faadb97b34396831e3780aea1",
  "1c58a3a8518e8759bf075b76b750d4f2df264fcd",
  data.tls_certificate.github.certificates[0].sha1_fingerprint,
])

上記のようにすることにより将来的な GitHub 側の証明書の変更は追従しやすくなりそうな見込みはありましたが、 thumbprint の役割としては想定外の証明書を持つサーバで発行された OIDC トークンによる AWS リソースへのアクセス許可を防いで安全にするという目的があると思っており、今回は安全側に倒して動的には更新せずハードコーディングするという対応を採用しました。

結果、シンプルに以下のようなコードとなっております。

resource "aws_iam_openid_connect_provider" "github_actions" {
  url            = "https://token.actions.githubusercontent.com"
  client_id_list = ["sts.amazonaws.com"]
  thumbprint_list = [
    "6938fd4d98bab03faadb97b34396831e3780aea1",
    "1c58a3a8518e8759bf075b76b750d4f2df264fcd"
  ]
}

参考サイト