この記事は GRIPHONE Advent Calendar 2021 2日目の記事です。
SREの徳田です。
GKEのTokenRequestで発行されたID Tokenって外から検証できるんだっけ・・?という疑問から色々探していたところ、OpenID Connect Discoveryまで実装されており外部からID Tokenの検証が可能だったため、これを活用して今回はAWSのRoleにAssumeRoleしてみようと思います。
TL;DR
- TokenRequestProjectionでTokenをマウント
- AWSでOIDC Identity Providerの設定とそこからAssumeRoleできるIAM Roleの作成
- Provider URLには
https://container.googleapis.com/v1/projects/{{ PROJECT_ID }}/locations/{{ LOCATION }}/clusters/{{ CLUSTER_NAME}}
を指定
- Provider URLには
- 環境変数を適宜設定し、ID TokenにAudienceを設定したPodを作成
AWS_ROLE_ARN
AWS_WEB_IDENTITY_TOKEN_FILE
AWS_ROLE_SESSION_NAME
TokenRequestProjectionを使ってID Tokenをマウント
いきなりですが、TokenRequestProjectionを使うとshort-livedなTokenを動的に生成し、Projected Volumeを使ってPodのコンテナにマウントすることができます。
apiVersion: v1
kind: Pod
metadata:
name: token-request-projection
spec:
containers:
- name: main
image: alpine
command:
- ash
- -c
- |
apk add --no-cache curl > /dev/null 2>&1
curl -sk https://kubernetes/api
echo
curl -sk -H "Authorization: Bearer `cat /secret/token`" https://kubernetes/api
volumeMounts:
- name: token
mountPath: /secret
readOnly: true
volumes:
- name: token
projected:
sources:
- serviceAccountToken:
path: token
restartPolicy: Never
このとき生成されるTokenはOIDC準拠のID Tokenです。これをkube-apiserverに投げてAPIを呼び出すことができます。上記のManifestを適用してログを見てみると、呼び出せてることがわかると思います。
$ kubectl apply -f token-request-projection.yaml
pod/token-request-projection created
$ kubectl logs token-request-projection
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {
},
"status": "Failure",
"message": "forbidden: User \"system:anonymous\" cannot get path \"/api\"",
"reason": "Forbidden",
"details": {
},
"code": 403
}
{
"kind": "APIVersions",
"versions": [
"v1"
],
"serverAddressByClientCIDRs": [
{
"clientCIDR": "0.0.0.0/0",
"serverAddress": "34.84.124.238:443"
}
]
}
さて、このToken、OIDC準拠のID Tokenということで、Issuerはどうなっているのでしょうか?確認してみましょう。以下のManifestを作成します。
apiVersion: v1
kind: Pod
metadata:
name: decode-idtoken
spec:
containers:
- name: main
image: alpine
command:
- ash
- -c
- |
cat /secret/token | \
cut -d. -f2 | \
base64 -d
volumeMounts:
- name: token
mountPath: /secret
readOnly: true
volumes:
- name: token
projected:
sources:
- serviceAccountToken:
path: token
restartPolicy: Never
適用してログを見てみましょう。
$ kubectl apply -f decode-idtoken.yaml
pod/decode-idtoken created
$ kubectl logs decode-idtoken
{"aud":["https://container.googleapis.com/v1/projects/teak-catwalk-333611/locations/asia-northeast1-a/clusters/test-wi"],"exp":1638210931,"iat":1638207331,"iss":"https://container.googleapis.com/v1/projects/teak-catwalk-333611/locations/asia-northeast1-a/clusters/test-wi","kubernetes.io":{"namespace":"default","pod":{"name":"decode-idtoken","uid":"95e5a286-1037-4f30-9c7d-5e123f0a5d3d"},"serviceaccount":{"name":"default","uid":"67e14e43-91fb-4e1e-9ebb-9a927abbb2bd"}},"nbf":1638207331,"sub":"system:serviceaccount:default:default"}
見づらいですね、整形したものがこちら。
{
"aud": [
"https://container.googleapis.com/v1/projects/teak-catwalk-333611/locations/asia-northeast1-a/clusters/test-wi"
],
"exp": 1638210931,
"iat": 1638207331,
"iss": "https://container.googleapis.com/v1/projects/teak-catwalk-333611/locations/asia-northeast1-a/clusters/test-wi",
"kubernetes.io": {
"namespace": "default",
"pod": {
"name": "decode-idtoken",
"uid": "95e5a286-1037-4f30-9c7d-5e123f0a5d3d"
},
"serviceaccount": {
"name": "default",
"uid": "67e14e43-91fb-4e1e-9ebb-9a927abbb2bd"
}
},
"nbf": 1638207331,
"sub": "system:serviceaccount:default:default"
}
ID Tokenらしき情報が色々ありますが、iss
に注目してみましょう。
https://container.googleapis.com/v1/projects/teak-catwalk-333611/locations/asia-northeast1-a/clusters/test-wi
というこちらのリンクがこのID TokenのIssuerです。で、このIssuerがOpenID Connect DiscoveryをサポートしていればIssuerに関する情報がでてくるはず・・・。
おもむろにcurlを投げてみます。
$ curl https://container.googleapis.com/v1/projects/teak-catwalk-333611/locations/asia-northeast1-a/clusters/test-wi/.well-known/openid-configuration
{
"issuer": "https://container.googleapis.com/v1/projects/teak-catwalk-333611/locations/asia-northeast1-a/clusters/test-wi",
"jwks_uri": "https://container.googleapis.com/v1/projects/teak-catwalk-333611/locations/asia-northeast1-a/clusters/test-wi/jwks",
"response_types_supported": [
"id_token"
],
"subject_types_supported": [
"public"
],
"id_token_signing_alg_values_supported": [
"RS256"
],
"claims_supported": [
"iss",
"sub",
"kubernetes.io"
],
"grant_types": [
"urn:kubernetes:grant_type:programmatic_authorization"
]
}
さて、OpenID Connect Discoveryがサポートされていたようで、Issuerに関する情報がでてきました。 jwks_uri
もあります。
ということは、最近流行りのOIDCのID Tokenを使った認証ができるのでは!?ということでGKEからAWSの認証ができるように設定してみます。
AWSにOpenID Connect Identity Providerを設定
まず、先に jwks_uri
の証明書のCAのthumbprintを出しておきます。こちらを参考に出します。
jwks_uri
の証明書を表示させましょう。
$ openssl s_client -showcerts -connect container.googleapis.com:443
CONNECTED(00000005)
depth=3 C = BE, O = GlobalSign nv-sa, OU = Root CA, CN = GlobalSign Root CA
verify return:1
depth=2 C = US, O = Google Trust Services LLC, CN = GTS Root R1
verify return:1
depth=1 C = US, O = Google Trust Services LLC, CN = GTS CA 1C3
verify return:1
depth=0 CN = *.googleapis.com
verify return:1
---
Certificate chain
0 s:/CN=*.googleapis.com
i:/C=US/O=Google Trust Services LLC/CN=GTS CA 1C3
-----BEGIN CERTIFICATE-----
MIIa8TCCGdmgAwIBAgIRAOoWrQcm7qqTCgAAAAEZUxEwDQYJKoZIhvcNAQELBQAw
〜〜〜中略〜〜〜
f54xPmxU//OmW5wlNkF9Cx/Cei4HU20cObW3rqxsN5GAOxCkOw==
-----END CERTIFICATE-----
1 s:/C=US/O=Google Trust Services LLC/CN=GTS CA 1C3
i:/C=US/O=Google Trust Services LLC/CN=GTS Root R1
-----BEGIN CERTIFICATE-----
MIIFljCCA36gAwIBAgINAgO8U1lrNMcY9QFQZjANBgkqhkiG9w0BAQsFADBHMQsw
〜〜〜中略〜〜〜
1IXNDw9bg1kWRxYtnCQ6yICmJhSFm/Y3m6xv+cXDBlHz4n/FsRC6UfTd
-----END CERTIFICATE-----
2 s:/C=US/O=Google Trust Services LLC/CN=GTS Root R1
i:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA
-----BEGIN CERTIFICATE-----
MIIFYjCCBEqgAwIBAgIQd70NbNs2+RrqIQ/E8FjTDTANBgkqhkiG9w0BAQsFADBX
MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEQMA4GA1UE
CxMHUm9vdCBDQTEbMBkGA1UEAxMSR2xvYmFsU2lnbiBSb290IENBMB4XDTIwMDYx
OTAwMDA0MloXDTI4MDEyODAwMDA0MlowRzELMAkGA1UEBhMCVVMxIjAgBgNVBAoT
GUdvb2dsZSBUcnVzdCBTZXJ2aWNlcyBMTEMxFDASBgNVBAMTC0dUUyBSb290IFIx
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAthECix7joXebO9y/lD63
ladAPKH9gvl9MgaCcfb2jH/76Nu8ai6Xl6OMS/kr9rH5zoQdsfnFl97vufKj6bwS
iV6nqlKr+CMny6SxnGPb15l+8Ape62im9MZaRw1NEDPjTrETo8gYbEvs/AmQ351k
KSUjB6G00j0uYODP0gmHu81I8E3CwnqIiru6z1kZ1q+PsAewnjHxgsHA3y6mbWwZ
DrXYfiYaRQM9sHmklCitD38m5agI/pboPGiUU+6DOogrFZYJsuB6jC511pzrp1Zk
j5ZPaK49l8KEj8C8QMALXL32h7M1bKwYUH+E4EzNktMg6TO8UpmvMrUpsyUqtEj5
cuHKZPfmghCN6J3Cioj6OGaK/GP5Afl4/Xtcd/p2h/rs37EOeZVXtL0m79YB0esW
CruOC7XFxYpVq9Os6pFLKcwZpDIlTirxZUTQAs6qzkm06p98g7BAe+dDq6dso499
iYH6TKX/1Y7DzkvgtdizjkXPdsDtQCv9Uw+wp9U7DbGKogPeMa3Md+pvez7W35Ei
Eua++tgy/BBjFFFy3l3WFpO9KWgz7zpm7AeKJt8T11dleCfeXkkUAKIAf5qoIbap
sZWwpbkNFhHax2xIPEDgfg1azVY80ZcFuctL7TlLnMQ/0lUTbiSw1nH69MG6zO0b
9f6BQdgAmD06yK56mDcYBZUCAwEAAaOCATgwggE0MA4GA1UdDwEB/wQEAwIBhjAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTkrysmcRorSCeFL1JmLO/wiRNxPjAf
BgNVHSMEGDAWgBRge2YaRQ2XyolQL30EzTSo//z9SzBgBggrBgEFBQcBAQRUMFIw
JQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnBraS5nb29nL2dzcjEwKQYIKwYBBQUH
MAKGHWh0dHA6Ly9wa2kuZ29vZy9nc3IxL2dzcjEuY3J0MDIGA1UdHwQrMCkwJ6Al
oCOGIWh0dHA6Ly9jcmwucGtpLmdvb2cvZ3NyMS9nc3IxLmNybDA7BgNVHSAENDAy
MAgGBmeBDAECATAIBgZngQwBAgIwDQYLKwYBBAHWeQIFAwIwDQYLKwYBBAHWeQIF
AwMwDQYJKoZIhvcNAQELBQADggEBADSkHrEoo9C0dhemMXoh6dFSPsjbdBZBiLg9
NR3t5P+T4Vxfq7vqfM/b5A3Ri1fyJm9bvhdGaJQ3b2t6yMAYN/olUazsaL+yyEn9
WprKASOshIArAoyZl+tJaox118fessmXn1hIVw41oeQa1v1vg4Fv74zPl6/AhSrw
9U5pCZEt4Wi4wStz6dTZ/CLANx8LZh1J7QJVj2fhMtfTJr9w4z30Z209fOU0iOMy
+qduBmpvvYuR7hZL6Dupszfnw0Skfths18dG9ZKb59UhvmaSGZRVbNQpsg3BZlvi
d0lIKO2d1xozclOzgjXPYovJJIultzkMu34qQb9Sz/yilrbCgj8=
-----END CERTIFICATE-----
---
Server certificate
subject=/CN=*.googleapis.com
issuer=/C=US/O=Google Trust Services LLC/CN=GTS CA 1C3
---
No client certificate CA names sent
Server Temp Key: ECDH, X25519, 253 bits
---
SSL handshake has read 10395 bytes and written 281 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-CHACHA20-POLY1305
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-CHACHA20-POLY1305
Session-ID: 3974D98852FC6912236D830CDCAD6E3753002A63C1DEA136EB532930D69B5B49
Session-ID-ctx:
Master-Key: 2A722D19954EBFF022D1C064CCCAC6A746920544071D69F0F4EECD9F573DE81D2708E0B5336DAD08EEA3E817D169012B
TLS session ticket lifetime hint: 100800 (seconds)
TLS session ticket:
0000 - 01 10 60 ee 4e ac ad 1e-37 9d 1c 71 fc 29 2b a7 ..`.N...7..q.)+.
0010 - 31 d3 1f 64 03 5f 38 10-60 33 87 b9 7b 06 0d 2c 1..d._8.`3..{..,
0020 - 3f 89 9b fd 35 78 b5 84-d3 b3 1e 02 2a 25 3c 60 ?...5x......*%<`
0030 - 05 2a 21 67 81 31 e2 a1-81 b3 d6 bb c9 9e 9e b5 .*!g.1..........
0040 - 2e db d9 b9 bb 0e 9a 45-72 83 af d3 80 a1 4a 54 .......Er.....JT
0050 - 0b d8 fa d7 14 15 dc 59-7a 2d 62 c8 cb ad e4 55 .......Yz-b....U
0060 - 84 f6 10 04 a3 9e 93 15-70 3f a6 d6 14 7f ff 11 ........p?......
0070 - b4 38 5e d1 7c c1 a0 64-23 cf 04 01 2f 2c b1 9a .8^.|..d#.../,..
0080 - 2a dc 37 aa 30 57 37 a5-81 ee da bf 78 0e d5 77 *.7.0W7.....x..w
0090 - 2b 08 b4 9f cc 65 5b fa-c2 01 4a 78 4b 48 92 af +....e[...JxKH..
00a0 - 4a 1c 58 46 0d f0 f6 16-55 ee e1 48 c6 4f de ac J.XF....U..H.O..
00b0 - 70 d6 b5 52 91 9a 81 48-31 33 d1 af d6 bc cf 4e p..R...H13.....N
00c0 - 72 b0 8a 74 ca e8 a7 26-6c 72 16 d0 c7 1f 65 0a r..t...&lr....e.
00d0 - 5d 09 8e 98 28 3e 15 72-70 e5 28 d9 f7 ]...(>.rp.(..
Start Time: 1638209772
Timeout : 7200 (sec)
Verify return code: 0 (ok)
---
^C
表示された最後の証明書を保存しておき(今回は cert.pem
とした)、以下のコマンドでthumbprintを出します。
$ openssl x509 -in cert.pem -fingerprint -noout | cut -d= -f2 | tr -d 08745487E891C19E3078C1F2A07E452950EF36F6
それではOpenID Connect Identity Providerを作成します。
--url
にはIssuerのURL、 --client-id-list
にはID TokenのAudience、 --thumbprint-list
には先程出した値を入れます。
$ aws iam create-open-id-connect-provider \
--url "https://container.googleapis.com/v1/projects/teak-catwalk-333611/locations/asia-northeast1-a/clusters/test-wi" \
--client-id-list aws \
--thumbprint-list 08745487E891C19E3078C1F2A07E452950EF36F6
arn:aws:iam::152732720536:oidc-provider/container.googleapis.com/v1/projects/teak-catwalk-333611/locations/asia-northeast1-a/clusters/test-wi
委任するIAM Roleの作成
先程作成したIdentity ProviderからAssumeRoleできるIAM Roleを作成しましょう。
まず以下のようなPolicyファイルを作成します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::152732720536:oidc-provider/container.googleapis.com/v1/projects/teak-catwalk-333611/locations/asia-northeast1-a/clusters/test-wi"
},
"Action": "sts:AssumeRoleWithWebIdentity"
}
]
}
Federated
のところに先程作成したIdentity ProviderのARNを入力します。
そしてIAM Roleを作成します。今回特に権限付与は行いません。
$ aws iam create-role --role-name gke-pod --assume-role-policy-document file://policy.json
ROLE arn:aws:iam::152732720536:role/gke-pod 2021-11-29T18:40:44+00:00 / AROASHD4ULWMEWXJQ57F3 gke-pod
ASSUMEROLEPOLICYDOCUMENT 2012-10-17
STATEMENT sts:AssumeRoleWithWebIdentity Allow
PRINCIPAL arn:aws:iam::152732720536:oidc-provider/container.googleapis.com/v1/projects/teak-catwalk-333611/locations/asia-northeast1-a/clusters/test-wi
GKEのPodからAssumeRoleしてみる
それでは最後にGKEのPodからAssumeRoleしてみましょう。
以下のようなManifestを作成します。
apiVersion: v1
kind: Pod
metadata:
name: assume-role-from-gke
spec:
containers:
- name: main
image: amazon/aws-cli
command:
- aws
- sts
- get-caller-identity
- --output
- text
env:
- name: AWS_ROLE_ARN
value: arn:aws:iam::152732720536:role/gke-pod
- name: AWS_WEB_IDENTITY_TOKEN_FILE
value: /secret/token
- name: AWS_ROLE_SESSION_NAME
value: assume-role-from-gke
volumeMounts:
- name: token
mountPath: /secret
readOnly: true
volumes:
- name: token
projected:
sources:
- serviceAccountToken:
audience: aws
path: token
restartPolicy: Never
要点としては、3つの環境変数と audience
を設定することです。
AWS_ROLE_ARN
には委任するIAM Roleで作成したRoleのARNを指定AWS_WEB_IDENTITY_TOKEN_FILE
にはID Tokenのファイルパスを指定AWS_ROLE_SESSION_NAME
にはそのロールセッションの名前を指定。わかり易い名前だったら何でもいいかと。audience
にはOpenID Connect Identity Providerを作成した際に--client-id-list
で指定した値を入れる
それではこのManifestを適用してログを見てみましょう。
$ kubectl apply -f assume-role-from-gke.yaml
pod/assume-role-from-gke created
$ kubectl logs assume-role-from-gke
152732720536 arn:aws:sts::152732720536:assumed-role/gke-pod/assume-role-from-gke AROASHD4ULWMEWXJQ57F3:assume-role-from-gke
こんな感じでTokenRequestProjectionで発行されたID Tokenを使ってAWSのRoleにAssumeRoleすることができました🎉
ついでに
GKEのOpenID Connect Discoveryのドキュメントが見つからないな〜と思ったら、API Referenceにはありました。
This API is not yet intended for general use, and is not available for all clusters.
v1のAPIなのに・・・ふむ・・・🤔
という感じで紹介しておいてあれですが、使うにあたっては自己責任で😅
終わりに
GKEのクラスタからTokenRequestProjectionで発行されたのID Tokenを使ってAWSのRoleにAssumeRoleできました。
OIDCのID TokenなのでAWSに限らず色々なところで使えそうですね。
GCP内でもhostNetworkが有効になっているPodなどにも設定すると良さそうかな〜と思っています。
それでは!!