跳至主要内容

[09] 為什麼 EKS 可以整合 IAM roles for service accounts(IRSA)(二)

上一篇,我們根據 token 內容得知 OIDC provider URL 設定為 EKS OICD provider,然而為什麼 IAM OIDC 與 EKS 關係為何?其 token 又是怎麼被掛載注入於 Pod 呢?

而根據 EKS IRSA 1 文件內提及以下內容:

  1. 2014 年,IAM 服務支援 OpenID Connect (OIDC) 的聯合身分。此功能可讓您向支援的身分提供者驗證 AWS API 呼叫,並接收有效的 OIDC JSON Web Token (JWT)。因此可以將 token 提供 AWS STS AssumeRoleWithWebIdentity 2 API ,並取得 IAM temporary security credentials,並藉由此 credentials 與 AWS 服務互動。
  2. Kubernetes 預設使用 Service Account 作為內部身份系統。Pods 可以使用由 Kubernetes API server 驗證自動掛載的 Token 訪問 API server。在 Kubernetes 1.12 版本支援了  ProjectedServiceAccountToken 功能。此功能是 OIDC JSON Web Token,同時包含服務帳戶身分,並支援可設定的對象。

故本文接續討論:

  • OIDC 流程
  • 此 token 是如何被產生的
  • AssumeRoleWithWebIdentity API

OIDC 流程

首先,OpenID Providers 必須提供提供一個 JSON 格式文件設定檔於路徑 /.well-known/openid-configuration 給 issuer。因此我們可以透過 curl 命令檢視 EKS OIDC endpoint 所提供設定,其詳細欄位定義查看官方 OIDC 文件欄位定義 3

$ curl -s https://oidc.eks.eu-west-1.amazonaws.com/id/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/.well-known/openid-configuration | jq .
{
"issuer": "https://oidc.eks.eu-west-1.amazonaws.com/id/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"jwks_uri": "https://oidc.eks.eu-west-1.amazonaws.com/id/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/keys",
"authorization_endpoint": "urn:kubernetes:programmatic_authorization",
"response_types_supported": [
"id_token"
],
"subject_types_supported": [
"public"
],
"claims_supported": [
"sub",
"iss"
],
"id_token_signing_alg_values_supported": [
"RS256"
]
}

其中我們關注於 jwks_uri :提供 JSON Web Key (JWK) set 的 openid-configuration 中指定的 endpoint,換言之,主要目的用於簽署來自 issuer 的 token。

$ curl -s https://oidc.eks.eu-west-1.amazonaws.com/id/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/keys | jq .
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "001bbf00f107017bc195419a1f47c4d3debad7d8",
"alg": "RS256",
"n": "qxowTumR7IVAaxNmMluNDliaZO-rRGnREr0s7oIFZf2iJzO31UW7W8a3MGOauHxrjMxhop0yO4ScKfP29OsCOAoG--_0mbVUnNYaa4aFbB5QqFhynXYGuITTi4AuRs4nMxdlViMA_AloC57q_dKGnfW2azdKpr8hFWv6y_NX_oauiJ1LVQdhpHXi5WJkev8yxsUH5L3Nzs3016j3t8VEQ6CjUt71BBcpeSLAOiEPM99EnNdF9oUmHttnpg09laK7dEuR0LkwVJd_MuNkAh8qn9GWl-sJOGqkvwCGu7i_p92BvvB3Uf0WdQRqadWBuWcdCR69Khp0DdHhhEv2blAzuw"
},
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "1e1d3732da4f678d29629aa8904535c354a50cc9",
"alg": "RS256",
"n": "s9nXfKxBjAY3akEnV5Enr76ZLkXCoeWjm0Ydr5bhubEAhWGszrPaLFVGskiddlYnGa_GGRDFFs-Lr_Gvb_Mxcvk0BfuylhAdfW6_slq6mmEJqw_HIekYnWmd2B6JIyGgmOFtebWEyKWgQd_U_WxPRxUqTh-wnEDP8Lb7V5F15lhGYtZzxKQMLxIDfbZVJmjQ2iruIUAMMXTgGMOSIq6dg4f4YE2-VnTr3_e35NHY83A0fr4_LnyMq7LX9LC5mBWH5yU4GFUvBo39eOMopZSBnEoytzNbHVNwUr7HFSRpKtXsUQCo5VqSRF-wq8cxT7FGpeETBoBGO5gdbvGjpCZJ-Q"
}
]
}

AWS STS 藉由在 jwks_uri 定義的 endpoint https://oidc.eks.eu-west-1.amazonaws.com/id/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/keys 檢查此 token 已經由信任來源(Identity Provider defined in IAM,如 EKS)簽署並驗證其 signature。以下整理 OIDC 流程:

  1. client 提供了一個由信任 provider 簽署 JWT token 至 AWS STS endpoint 進行身份驗證(authenticate)。
  2. AWS STS endpoint 接收此 JWT 並確認 AWS STS endpoint 接收 JWT 並確定其是否具有由 JWT header 中的 kid(key ID)attribute 的 public key。 如果它不識別 key,則會有以下流程:
    • 透過 /.well-known/openid-configuration 路徑取得 OIDC 設定檔。
    • 並基於上述 URL 路徑中的 jwks_uri 欄位取得 provider 的 published public keys (the JWKS)。
  3. 一旦 AWS STS endpoint 取得 provider 的 public key,將會驗證(validates)token 簽名、到期時間(expiration)、audience claims
  4. 驗證完畢後,API 則可以確保 token 有效性。

在 IAM 中建立了 OIDC Provider 後,我們可以在 AWS IAM 上設定為信任由它產生的 token,因此需要於 IAM role trust policy 4上達到這件事。而在昨天使用了 eksctl create iamserviceaccount 命令建立 Kubernetes Service Account,我們可以檢視一下此 IAM role 建立過程中是否有調整對應的 IAM role trust policy。

透過 aws iam get-role命令檢視 Role.AssumeRolePolicyDocument 。在此範例中:我們授權(authorize)client 使用帶有 system:serviceaccount:default:my-s3-sasub 聲明 JWT token 進行 assumed role,其中此為 default namespace 中 Service Account my-s3-sa

$ aws iam get-role --role-name eksctl-ironman-2022-addon-iamserviceaccount-defau-Role1-KXGCZM9EIHAA --query Role.AssumeRolePolicyDocument
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::111111111111:oidc-provider/oidc.eks.eu-west-1.amazonaws.com/id/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.eu-west-1.amazonaws.com/id/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX:aud": "sts.amazonaws.com",
"oidc.eks.eu-west-1.amazonaws.com/id/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX:sub": "system:serviceaccount:default:my-s3-sa"
}
}
}
]
}

Token 是怎麼產生的

在了解 OIDC 流程之後,所以提供驗證的 token 到底是怎麼來的。讓我們再次複習一下 Pod 資訊:

$ kubectl describe po aws-cli
Name: aws-cli
Namespace: default
...
...
Containers:
aws-cli:
...
Command:
sleep
infinity
State: Running
Started: Fri, 23 Sep 2022 10:59:13 +0000
Ready: True
Restart Count: 0
Environment:
AWS_STS_REGIONAL_ENDPOINTS: regional
AWS_DEFAULT_REGION: eu-west-1
AWS_REGION: eu-west-1
AWS_ROLE_ARN: arn:aws:iam::111111111111:role/eksctl-ironman-2022-addon-iamserviceaccount-defau-Role1-KXGCZM9EIHAA
AWS_WEB_IDENTITY_TOKEN_FILE: /var/run/secrets/eks.amazonaws.com/serviceaccount/token
Mounts:
/var/run/secrets/eks.amazonaws.com/serviceaccount from aws-iam-token (ro)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-cvtwf (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
aws-iam-token:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 86400
kube-api-access-cvtwf:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>

其中有兩筆 Projected Volumes5

  • kube-api-access-cvtwf 掛載於 /var/run/secrets/kubernetes.io/serviceaccount目錄。此為 ServiceAccount Admission Controller 6 更新 Pod Spec,倘若 Pod Spec 沒有指定 Service Account 則會設定該 namespace 中 default Service Account。 而在 Kubernetes 1.22 版本正式啟用 BoundServiceAccountTokenVolume 7,改善了 Service Account Token 安全性問題,並設定過期時間為一小時。
  • aws-iam-token 掛載於 /var/run/secrets/eks.amazonaws.com/serviceaccount目錄。此由 Amazon EKS Pod Identity Webhook8 定義的 projected volume,以下為部分原始碼9定義:
// NewModifier returns a Modifier with default values
func NewModifier(opts ...ModifierOpt) *Modifier {
mod := &Modifier{
AnnotationDomain: "eks.amazonaws.com",
MountPath: "/var/run/secrets/eks.amazonaws.com/serviceaccount",
volName: "aws-iam-token",
tokenName: "token",
}
for _, opt := range opts {
opt(mod)
}

return mod
}

接續,為瞭解 EKS 於 API server 上是否有設定相對應 Service Account 設定。透過 CloudWatch Logs insight syntax 語法檢視 kube-apiserver logs 內啟用與 Service Account 所關聯的 flag:

filter @logStream not like /^kube-apiserver-audit/
| filter @logStream like /^kube-apiserver-/
| fields @timestamp, @message
| sort @timestamp asc
| filter @message like "--service-account"
| limit 10000
I0914 09:46:06.296376 10 flags.go:59] FLAG: --service-account-api-audiences="[https://kubernetes.default.svc]"
I0914 09:46:06.296386 10 flags.go:59] FLAG: --service-account-extend-token-expiration="true"
I0914 09:46:06.296391 10 flags.go:59] FLAG: --service-account-issuer="[https://oidc.eks.eu-west-1.amazonaws.com/id/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX]"
I0914 09:46:06.296402 10 flags.go:59] FLAG: --service-account-jwks-uri=""
I0914 09:46:06.296407 10 flags.go:59] FLAG: --service-account-key-file="[/etc/kubernetes/pki/sa.pub]"
I0914 09:46:06.296417 10 flags.go:59] FLAG: --service-account-lookup="true"
I0914 09:46:06.296422 10 flags.go:59] FLAG: --service-account-max-token-expiration="24h0m0s"
I0914 09:46:06.296428 10 flags.go:59] FLAG: --service-account-signing-key-file=""

其中我們關注以下三個 flag:

  • --service-account-issuer: Service Account issuer 名稱,可以觀察到預設定義為 EKS OIDC Provider URL。
  • --service-account-key-file:用來驗證 ServiceAccount tokens key,設置為 /etc/kubernetes/pki/sa.pub。代表了 EKS 在建立 cluster 時預設建立此 key pair。
  • --service-account-api-audiences:設置 API audiences 為 https://kubernetes.default.svc

AssumeRoleWithWebIdentity

當 AWS API 被呼叫時,AWS SDK 呼叫 STS AssumeRoleWithWebIdentity API。在驗證過 token signature 後,IAM 將 Kubernetes issue 的 token 交換為臨時 AWS role credential。

根據 Java SDK Credential Provider Chain10 流程為:

  1. 環境變數 Environment variables:AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY
  2. Java system properties:aws.accessKeyIdaws.secretAccessKey
  3. Web Identity Token credentials:來自於環境變數或 container
  4. 預設 credential profiles 設定檔:預設為 ~/.aws/credentials 多半由 AWS SDKs 或是 AWS CLI 設定。
  5. Amazon ECS container credentials:ECS 環境變數 AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
  6. Instance profile credentials:EC2 關聯設定 Instance profile 後,可以透過 EC2 metadata service 載入 profile credentials。

其中 Web Identity Token credentials 所提及的環境變數為 AWS_WEB_IDENTITY_TOKEN_FILE,我們也可以根據不同語言文件來進行確認,如 Java SDK11js SDK12

總結

EKS cluster 在建立時,預設設定了 Project Service Account 為 OIDC Provider 及設定對應的 key。而 IAM 服務也支援作為 OIDC provider,故能在 issue token 時驗證授權 token。同時,在 Pod 建立階段透過 Amazon EKS Pod Identity Webhook 更新 token 於環境變數及掛載目錄 。最後,AWS SDK 藉由 STS AssumeRoleWithWebIdentity API 取得對應 IAM role 暫時 credential 調用對應的 AWS 服務 API。

參考文件

Footnotes

  1. IAM roles for service accounts - https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html

  2. AssumeRoleWithWebIdentity - https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html

  3. OpenID Connect Discovery 1.0 - https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata

  4. Creating a role for web identity or OpenID Connect Federation (console) - https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-idp_oidc.html

  5. Projected Volumes - https://kubernetes.io/docs/concepts/storage/projected-volumes

  6. https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/#serviceaccount-admission-controller

  7. TokenRequest API and Kubelet integration #542 - https://github.com/kubernetes/enhancements/issues/542

  8. Amazon EKS Pod Identity Webhook - https://github.com/aws/amazon-eks-pod-identity-webhook

  9. https://github.com/aws/amazon-eks-pod-identity-webhook/blob/master/pkg/handler/handler.go#L74-L96

  10. Working with AWS Credentials - Using the Default Credential Provider Chain - https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html#credentials-default

  11. https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/auth/credentials/WebIdentityTokenFileCredentialsProvider.html

  12. https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/TokenFileWebIdentityCredentials.html