aws_iam_openid_connect_providerのthumbprint_listの計算方法

GitHub ActionsがOIDCプロバイダとして使えるようになりました。
これによりAWSのアクセスキーを埋めることなくロールベースでのアクセスができるようになりました。やったね。

https://github.blog/changelog/2021-10-27-github-actions-secure-cloud-deployments-with-openid-connect/

この話自体はなぜか公式リリース前にサンプルコードが出回ったりして、みんなサンプルコードコピペして動いた〜って話題になってたので今さら感があるかもですが、例えばTerraformでaws_iam_openid_connect_providerの設定をするならこんなかんじでしょうか。

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_openid_connect_provider

```.tf
resource "aws_iam_openid_connect_provider" "github" {
  url             = "https://token.actions.githubusercontent.com"
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = ["a031c46782e6e6c662c2c87c76da9aa62ccabd8e"]
}
```

ふむふむ〜ってかんじですが、いざ使おうとするとこの `thumbprint_list` がなんなのかが気になって仕方がなかったので調べました。

> thumbprint_list - (Required) A list of server certificate thumbprints for the OpenID Connect (OIDC) identity provider's server certificate(s).

terraform-provider-awsのドキュメントにはOIDCプロバイダの公開鍵のなんかのハッシュ値との記載があり、公式のGitHub ActionsにもCloudFormationのテンプレートの例が載っていますが、特に説明もなくマジックナンバーが埋まっています。

https://github.com/aws-actions/configure-aws-credentials/tree/v1.6.0#sample-iam-role-cloudformation-template

```YAML
  GithubOidc:
    Type: AWS::IAM::OIDCProvider
    Condition: CreateOIDCProvider
    Properties:
      Url: https://token.actions.githubusercontent.com
      ClientIdList: 
        - sts.amazonaws.com
      ThumbprintList:
        - a031c46782e6e6c662c2c87c76da9aa62ccabd8e
```

この `a031c46782e6e6c662c2c87c76da9aa62ccabd8e` はどこから来たのか？気になりますよね？え、気にならない？気にならない人は別にこれ以降は読む必要はありません。私は気になる方の人だったので調べました。

まずGitHub Actionsの公式ドキュメントを見ましたが、特に記載が見つかりませんでした。

https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services#adding-the-identity-provider-to-aws

AWSの公式ドキュメント見たら、以下の記載を見つけました。OIDCプロバイダのCAの証明書のハッシュ値です。

https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc_verify-thumbprint.html

> When you create an OpenID Connect (OIDC) identity provider in IAM, you must supply a thumbprint. IAM requires the thumbprint for the top intermediate certificate authority (CA) that signed the certificate used by the external identity provider (IdP). The thumbprint is a signature for the CA's certificate that was used to issue the certificate for the OIDC-compatible IdP.

よく使われそうなGitHubぐらい例として載せておいてくれよと思いつつ、計算したらたしかにそうなった。

まず、OIDCプロバイダのjwks_uriのドメインを調べます。

```
$ curl -s https://token.actions.githubusercontent.com/.well-known/openid-configuration | jq .
{
  "issuer": "https://token.actions.githubusercontent.com",
  "jwks_uri": "https://token.actions.githubusercontent.com/.well-known/jwks",
  "subject_types_supported": [
    "public",
    "pairwise"
  ],
  "response_types_supported": [
    "id_token"
  ],
  "claims_supported": [
    "sub",
    "aud",
    "exp",
    "iat",
    "iss",
    "jti",
    "nbf",
    "ref",
    "repository",
    "repository_owner",
    "run_id",
    "run_number",
    "run_attempt",
    "actor",
    "workflow",
    "head_ref",
    "base_ref",
    "event_name",
    "ref_type",
    "environment",
    "job_workflow_ref"
  ],
  "id_token_signing_alg_values_supported": [
    "RS256"
  ],
  "scopes_supported": [
    "openid"
  ]
}
```

`jwks_uri` の証明書を取得します。

```
$ openssl s_client -servername token.actions.githubusercontent.com -showcerts -connect token.actions.githubusercontent.com:443
CONNECTED(00000005)
depth=2 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert High Assurance EV Root CA
verify return:1
depth=1 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert SHA2 High Assurance Server CA
verify return:1
depth=0 C = US, ST = California, L = San Francisco, O = "GitHub, Inc.", CN = *.actions.githubusercontent.com
verify return:1
---
Certificate chain
 0 s:/C=US/ST=California/L=San Francisco/O=GitHub, Inc./CN=*.actions.githubusercontent.com
   i:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 High Assurance Server CA
-----BEGIN CERTIFICATE-----
MIIHEDCCBfigAwIBAgIQDydo1w3Zr+2V8IN+I3bC+TANBgkqhkiG9w0BAQsFADBw
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMS8wLQYDVQQDEyZEaWdpQ2VydCBTSEEyIEhpZ2ggQXNz
dXJhbmNlIFNlcnZlciBDQTAeFw0yMDA0MDMwMDAwMDBaFw0yMjA0MDgxMjAwMDBa
MHsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T
YW4gRnJhbmNpc2NvMRUwEwYDVQQKEwxHaXRIdWIsIEluYy4xKDAmBgNVBAMMHyou
YWN0aW9ucy5naXRodWJ1c2VyY29udGVudC5jb20wggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQDICQluw+gpsxgWSHEbyqwoTKV29C0CyU11Dc/sVMEXsLw+
DjIgxYTxKum1xkhOoewajpBKE9Xe+EWlOOLpsn8i+rU7HXBeSnTYZeh8Z5lzZk0H
iu9HeKNdfmpbzlszFdY1wJwQLOBSL+/8eQJfdAgvWxWTsONxsq5dzV/rtSfhEx8G
dG5ZJDSIUvjsf/xDRQeFFE+SvmooC/RzAL8Q1BXGfh8lWbfgnPylte3wZ8RSPeh8
XEEGxKUE9kgAjS7B1APZxBPGg0EWkEvbkMFjD0b4EhWHN98Fw2iaZpwOiH+NF4Bu
q2EYK3Rq/Vq/YMjYqWlpvDNUIsGcCwCQqj3wM7krAgMBAAGjggOZMIIDlTAfBgNV
HSMEGDAWgBRRaP+QrwIHdTzM2WVkYqISuFlyOzAdBgNVHQ4EFgQUlWwX+zmXWLbh
Nzfku+3tBl9hdCAwSQYDVR0RBEIwQIIfKi5hY3Rpb25zLmdpdGh1YnVzZXJjb250
ZW50LmNvbYIdYWN0aW9ucy5naXRodWJ1c2VyY29udGVudC5jb20wDgYDVR0PAQH/
BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjB1BgNVHR8EbjBs
MDSgMqAwhi5odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1oYS1zZXJ2ZXIt
ZzYuY3JsMDSgMqAwhi5odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1oYS1z
ZXJ2ZXItZzYuY3JsMEwGA1UdIARFMEMwNwYJYIZIAYb9bAEBMCowKAYIKwYBBQUH
AgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCAYGZ4EMAQICMIGDBggr
BgEFBQcBAQR3MHUwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNv
bTBNBggrBgEFBQcwAoZBaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lD
ZXJ0U0hBMkhpZ2hBc3N1cmFuY2VTZXJ2ZXJDQS5jcnQwDAYDVR0TAQH/BAIwADCC
AX4GCisGAQQB1nkCBAIEggFuBIIBagFoAHYAKXm+8J45OSHwVnOfY6V35b5XfZxg
Cvj5TV0mXCVdx4QAAAFxQK0GWgAABAMARzBFAiEAtOsN2nTCsYy3vcEOmue5jALN
QT3PSbyBo1CrfqB7PZcCIHYxknrxHm79ozyK99Ywp1fzkOvcDQwsDspAz3eEGC4i
AHYAIkVFB1lVJFaWP6Ev8fdthuAjJmOtwEt/XcaDXG7iDwIAAAFxQK0GkQAABAMA
RzBFAiEA6zg3TWCuOMEVFfCDGqQ6DIFdKmvQUu1qifvtTnkgBF8CIE8Ge6Emyx35
AjPxzImIcQLmKK2/m4hCxlQ7J0Pqgf5WAHYAUaOw9f0BeZxWbbg3eI8MpHrMGyfL
956IQpoN/tSLBeUAAAFxQK0G2gAABAMARzBFAiEAwOa3wkbpA5PngKBLno5LFylS
KcFI63Do1hZFDCMH7U0CIEv4HZ0BXmVoFoS4k1AH3ZR1zLv1Suddppgeien5nGYA
MA0GCSqGSIb3DQEBCwUAA4IBAQCZkidhrGI4fWaTGPzelZEYo9SA3ZfdKowMJwgc
pmGS6x9GxhkxlT/ziGMUsTkKJc61yXv0tbKr0H4ZfG4CvxefS6YRIYR4Laum5YWB
siXCVdL1hnDDvWI9wg14zKJiRUjv2RpM8JpkWxvGzi0SsdKJBUkFGUWgY3Dns4/v
YrpNQkfrBKVZLRz70yXvbdegSHxGe9WivMB+ac7lAbUjcMy8o93HDZqoCgw0PreJ
tHw2YwfG+tj0P6m/HqPK6TMjkQQysaQ3YTkGIIe3HeU52z4RQW0YcC/EnWDwPF++
l74SjPAIXDU8ApIKs1EW9b/8WkDNIjynDaFe4BmOJMkBK51Q
-----END CERTIFICATE-----
 1 s:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 High Assurance Server CA
   i:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV Root CA
-----BEGIN CERTIFICATE-----
MIIEsTCCA5mgAwIBAgIQBOHnpNxc8vNtwCtCuF0VnzANBgkqhkiG9w0BAQsFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBFViBSb290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowcDEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
LmRpZ2ljZXJ0LmNvbTEvMC0GA1UEAxMmRGlnaUNlcnQgU0hBMiBIaWdoIEFzc3Vy
YW5jZSBTZXJ2ZXIgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2
4C/CJAbIbQRf1+8KZAayfSImZRauQkCbztyfn3YHPsMwVYcZuU+UDlqUH1VWtMIC
Kq/QmO4LQNfE0DtyyBSe75CxEamu0si4QzrZCwvV1ZX1QK/IHe1NnF9Xt4ZQaJn1
itrSxwUfqJfJ3KSxgoQtxq2lnMcZgqaFD15EWCo3j/018QsIJzJa9buLnqS9UdAn
4t07QjOjBSjEuyjMmqwrIw14xnvmXnG3Sj4I+4G3FhahnSMSTeXXkgisdaScus0X
sh5ENWV/UyU50RwKmmMbGZJ0aAo3wsJSSMs5WqK24V3B3aAguCGikyZvFEohQcft
bZvySC/zA/WiaJJTL17jAgMBAAGjggFJMIIBRTASBgNVHRMBAf8ECDAGAQH/AgEA
MA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw
NAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy
dC5jb20wSwYDVR0fBEQwQjBAoD6gPIY6aHR0cDovL2NybDQuZGlnaWNlcnQuY29t
L0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9vdENBLmNybDA9BgNVHSAENjA0MDIG
BFUdIAAwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQ
UzAdBgNVHQ4EFgQUUWj/kK8CB3U8zNllZGKiErhZcjswHwYDVR0jBBgwFoAUsT7D
aQP4v0cB1JgmGggC72NkK8MwDQYJKoZIhvcNAQELBQADggEBABiKlYkD5m3fXPwd
aOpKj4PWUS+Na0QWnqxj9dJubISZi6qBcYRb7TROsLd5kinMLYBq8I4g4Xmk/gNH
E+r1hspZcX30BJZr01lYPf7TMSVcGDiEo+afgv2MW5gxTs14nhr9hctJqvIni5ly
/D6q1UEL2tU2ob8cbkdJf17ZSHwD2f2LSaCYJkJA69aSEaRkCldUxPUd1gJea6zu
xICaEnL6VpPX/78whQYwvwt/Tv9XBZ0k7YXDK/umdaisLRbvfXknsuvCnQsH6qqF
0wGjIChBWUMo0oHjqvbsezt3tkBigAVBRQHvFwY+3sAzm2fTYS5yh+Rp/BIAV0Ae
cPUeybQ=
-----END CERTIFICATE-----
---
Server certificate
subject=/C=US/ST=California/L=San Francisco/O=GitHub, Inc./CN=*.actions.githubusercontent.com
issuer=/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 High Assurance Server CA
---
No client certificate CA names sent
Server Temp Key: ECDH, P-384, 384 bits
---
SSL handshake has read 3540 bytes and written 398 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES256-GCM-SHA384
    Session-ID: 7720000046A9ADA2FA16E4BAB2BE93AED2816789B890B48CB89CB7BDAB4F9600
    Session-ID-ctx:
    Master-Key: 3FC5B951311CE6DBD68371558BD84270B92AC1D9DC17232EF1C30AEFCA1889348DC7F5A77F50D19989B34FA2E7145041
    Start Time: 1638148905
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
---
^C
```

中間証明書が見えますが、一番下の証明書の `-----BEGIN CERTIFICATE-----` から `-----END CERTIFICATE-----` のところをファイルとして保存して、fingerprintを計算します。

```
$ openssl x509 -in certificate.crt -fingerprint -noout
SHA1 Fingerprint=A0:31:C4:67:82:E6:E6:C6:62:C2:C8:7C:76:DA:9A:A6:2C:CA:BD:8E
```

シェル芸で `=` の右側の値から `:` を取り除き、小文字にするとたしかに一致しました。

```
$ openssl x509 -in certificate.crt -fingerprint -noout | cut -f2 -d'=' | tr -d ':' | tr '[:upper:]' '[:lower:]'
a031c46782e6e6c662c2c87c76da9aa62ccabd8e
```

ちなみにこの証明書の有効期限は2028年のよう。

```
$ openssl x509 -noout -dates -in certificate.crt
notBefore=Oct 22 12:00:00 2013 GMT
notAfter=Oct 22 12:00:00 2028 GMT
```

マジックナンバーの計算方法がわかってスッキリしました。
これで突然証明書がなんらかの理由でローテーションされてハッシュ値が変わっても自分で再計算できて安心ですね。おしまい。

---
(2022/01/14追記)
2022/01/13に新しい証明書に切り替わった影響でthumbprintが `6938fd4d98bab03faadb97b34396831e3780aea1` に変わりました。 
https://github.blog/changelog/2022-01-13-github-actions-update-on-oidc-based-deployments-to-aws/

ちなみにTerraformでthumbprintの算出まで自動化したい場合は、tls_certificateというdata sourceを使うとできます。
https://registry.terraform.io/providers/hashicorp/tls/latest/docs/data-sources/tls_certificate

```.tf
data "tls_certificate" "github" {
  url = "https://token.actions.githubusercontent.com/.well-known/openid-configuration"
}

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

このためだけに依存のproviderを増やすかは検討の余地がありますが。
また、無条件に新しい値を受け入れるとCA証明書のthumbprintをpinしている意味がなくなるので、突然変わった場合は公式の発表を待つのがよいかと思います。

今回の突然のローテーションの件は計画作業ではなく意図しない変更だったようですが、セキュリティインシデントで合った場合を考慮すると、

(1) GitHubが計画作業で新しい証明書を追加した場合: 古いthumbprintを残し、新しい証明書を追加すべき
(2) GitHubが古い証明書のセキュリティインシデントを認識し、意図的に新しい証明書にローテーションした場合: 古いthumbprintを削除し、新しいthumbprintを追加すべき
(3) GitHubではない悪意のある第三者がなんらかの方法で不正にCA証明書の発行に成功した場合: 新しいthumbprintを追加せず、最悪ケースを想定すると古いCA証明書も侵害されている可能性も考慮し、古いthumbprintも一旦削除すべき

という可能性が考慮できるので、公式の発表なく突然thumbprint変わった場合は、一旦すべてのthumbprint_listを削除して、公式の発表を待つのが正しい対応に思います。現代はサーバを冗長化してもCI/CDがSPOFになってしまいますね。。。

---
(2022/01/19追記)

ローカルにファイルを保存せずにシェル芸だけで計算する方法

https://stackoverflow.com/questions/69247498/how-can-i-calculate-the-thumbprint-of-an-openid-connect-server/69247499#69247499

上記を参考にしつつ、ローカル変数にも副作用のないようにワンライナーに閉じ込めとこんなかんじ。

```badh
$ (jwks_uri=$(curl -sL https://token.actions.githubusercontent.com/.well-known/openid-configuration \
    | jq -r '.jwks_uri | split("/")[2]') && \
  echo | openssl s_client -servername $jwks_uri -showcerts -connect $jwks_uri:443 2> /dev/null \
  | sed -n -e '/BEGIN/h' -e '/BEGIN/,/END/H' -e '$x' -e '$p' | tail +2 \
  | openssl x509 -fingerprint -noout \
  | sed -e "s/.*=//" -e "s/://g" \
  | tr "ABCDEF" "abcdef")
6938fd4d98bab03faadb97b34396831e3780aea1
```

---
(2023/06/28追記)

GitHub OIDCのthumbprintsが変更ではなく追加されたとのこと。
https://github.blog/changelog/2023-06-27-github-actions-update-on-oidc-integration-with-aws/

試してみるとたしかにランダムに2種類の値が返ってくる。

```
$ (jwks_uri=$(curl -sL https://token.actions.githubusercontent.com/.well-known/openid-configuration \
    | jq -r '.jwks_uri | split("/")[2]') && echo | openssl s_client -servername $jwks_uri -showcerts -connect $jwks_uri:443 2> /dev/null | sed -n -e '/BEGIN/h' -e '/BEGIN/,/END/H' -e '$x' -e '$p' | tail +2 | openssl x509 -fingerprint -noout | sed -e "s/.*=//" -e "s/://g" | tr "ABCDEF" "abcdef")
6938fd4d98bab03faadb97b34396831e3780aea1

$ (jwks_uri=$(curl -sL https://token.actions.githubusercontent.com/.well-known/openid-configuration \
    | jq -r '.jwks_uri | split("/")[2]') && echo | openssl s_client -servername $jwks_uri -showcerts -connect $jwks_uri:443 2> /dev/null | sed -n -e '/BEGIN/h' -e '/BEGIN/,/END/H' -e '$x' -e '$p' | tail +2 | openssl x509 -fingerprint -noout | sed -e "s/.*=//" -e "s/://g" | tr "ABCDEF" "abcdef")
1c58a3a8518e8759bf075b76b750d4f2df264fcd
```

複数の値が返ってくる可能性があるので、両方ともthumbprint_listで許可してあげる必要がある。
前述のTerraformのtls_certificateデータソースを使う方法だと読み込みタイミングによって結果が不定になるので、残念ながら値をハードコードするしかなさそうです。

---
(2023/07/12追記)

2023/07/06以降、GitHubとAWSの間でよしなにやるのでユーザ側で証明書をpinする必要はなくなったとのこと。厳密にはAPIとしては引き続き必須のままだが、適当なダミーを埋めても無視される状態になったとのこと。
https://github.com/aws-actions/configure-aws-credentials/issues/357#issuecomment-1626357333