為什麼 kubectl 可以訪問 EKS cluster

本文將探討這兩個問題「為什麼原生的 kubectl 可以直接訪問 EKS cluster」以及「為什麼 AWS CLI 權限與訪問 EKS cluster」,希望理解在 kubectl 如何與 IAM 權限整合允許訪問 EKS cluster。

為什麼 kubectl 可以訪問 EKS cluster
Photo by Raoul Droog / Unsplash

根據 EKS 文件 Installing or updating kubectl [1],或是 Kubernetes 官方安裝 kubectl 文件 [2] 皆是直接安裝 kubectl binary 並設置相應 Linux 路徑及權限即可以直接使用。

而 EKS 前置作業則需要設置 AWS CLI 權限,那「為什麼原生的 kubectl 可以直接訪問 EKS cluster」以及「為什麼 AWS CLI 權限與訪問 EKS cluster」,本文將探討這兩個問題,希望理解在 kubectl 如何與 IAM 權限整合允許訪問 EKS cluster。

AWS IAM Authenticator

EKS 文件並未提及 kubectl 所需最低版本,而在檢視 eksctl [3] GitHub 頁面說明提及需搭配 AWS CLI(aws eks get-token)或是使用 AWS IAM Authenticator for Kubernetes [4]。

AWS IAM Authenticator for Kubernetes 提供使用 AWS IAM credentials 可以向 Kubernetes cluster 進行身份驗證(authenticate)的工具。

若希望 Kubernetes 在 AWS 環境上支援 IAM Authenticator,需要以下五個步驟:

  1. 建立 IAM 角色(role)
  2. 以 DaemonSet 方式執行 Authenticator server
  3. 設定 Kubernetes API  Server 與 Authenticator server 整合
  4. 創建 IAM role/user 到 Kubernetes user/group 對照
  5. 設定 kubectl 使用 AWS IAM Authenticator 所提供的 authentication tokens

有趣的是,其中在對照 IAM role/user 致 Kubernetes user/group 所使用的正是 EKS 文件 [5] 所提及使用的 EKS-style kube-system/aws-auth ConfigMap。以下為預設 eksctl 建立時會自動將 EKS worker node IAM role 關聯至此 kube-system/aws-auth ConfigMap。

$ kubectl -n kube-system get cm aws-auth -o yaml
apiVersion: v1
data:
  mapRoles: |
    - groups:
      - system:bootstrappers
      - system:nodes
      rolearn: arn:aws:iam::111111111111:role/eksctl-ironman-2022-nodegroup-ng1-publ-NodeInstanceRole-HN27OZ18JS6U
      username: system:node:{{EC2PrivateDNSName}}
kind: ConfigMap
metadata:
  creationTimestamp: "2022-09-14T09:56:19Z"
  name: aws-auth
  namespace: kube-system
  resourceVersion: "2011"
  uid: f4cb3fae-17ef-421d-871c-5be15efbe73f

進一步查看 EKS control plane logging [6] 文件,我們也可以更近一步確認 EKS 除了以下原生元件 logs 之外,提供了

  • Kubernetes API server
  • Audit
  • Controller manager
  • Scheduler
  • Authenticator (authenticator) – Authenticator logs 為 EKS 環境獨有。這些 logs 代表 EKS 使用 IAM 憑證進行 Kubernetes RBAC 身分驗證的 control plane 元件。

驗證

kubectl

一般來說,kubectl 命令設定文建會儲存於 ~/.kube/config,透過 eksctl 命令建立 cluster 時也會自動生成此文件。在 kubeconfig 設定檔,使用了 users.user.exec 命令並調用外部 AWS CLI 命令。

$ cat ~/.kube/config
apiVersion: v1
clusters:
... 
... SKIPPING certificate-authority-data INFORMATION ... 
...
current-context: arn:aws:eks:eu-west-1:111111111111:cluster/ironman-2022
kind: Config
preferences: {}
users:
- name: cli@ironman-2022.eu-west-1.eksctl.io
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1alpha1
      args:
      - eks
      - get-token
      - --cluster-name
      - ironman-2022
      - --region
      - eu-west-1
      command: aws

在 Kubernetes 1.10 alpha k8s.io/client-go library 支援了 exec-based credential providers [7]。而 kubectlkubectl 命令正式使用相同 k8s.io/client-go library。

同時,我們也可以得知若 client 端使用 client-go credential plugins 方式與 API server 進行認證,有以下流程:

  • 使用者發出 kubectl 命令
  • 調用外部 credentials 並與外部服務取得 token
  • Credential plugin 取得 token 後返回給 client-go client,並使用 token 訪問 API server
  • API Server 使用  webhook token authenticator 元件向外部服務發出 TokenReview 請求。
  • 外部服務驗證 token 上的 signature 後,返回 Kubernetes user name 及 group

因此我們也可以透過 aws eks get-token 命令查看此 token:

$ aws eks get-token --cluster ironman-2022 | jq .
{
  "kind": "ExecCredential",
  "apiVersion": "client.authentication.k8s.io/v1beta1",
  "spec": {},
  "status": {
    "expirationTimestamp": "2022-09-14T23:04:55Z",
    "token": "k8s-aws-v1.aHR0cHM6Ly9zdHMuZXUtd2VzdC0xLmFtYXpvbmF3cy5jb20vP0FjdGlvbj1HZXRDYWxsZXJJZGVudGl0eSZWZXJzaW9uPTIwMTEtMDYtMTUmWC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBWUZNUVNOU0U1SDVaVEpERSUyRjIwMjIwOTE0JTJGZXUtd2VzdC0xJTJGc3RzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyMjA5MTRUMjI1MDU1WiZYLUFtei1FeHBpcmVzPTYwJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCUzQngtazhzLWF3cy1pZCZYLUFtei1TaWduYXR1cmU9MDU5ZjQwNzEzNTY0OGYwNjdlMWZjOThjZjhhYmY3YjdkMmIzNDgxMjE1ZWEzNGE1NTI4YzcyODczMmU1YjJkYw"
  }
}

每個 token 皆是 AWS IAM Authenticator 定義以 k8s-aws-v1. 字串為前綴,並接續使用 base64 encoded string,故透過 base64 命令解析出 STS presigned URL[9]:

$ aws eks get-token --cluster ironman-2022 | jq -r .status.token | awk -F. '{print $2}' | base64 -d
https://sts.eu-west-1.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAYFMQSNSE5H5ZTJDE%2F20220914%2Feu-west-1%2Fsts%2Faws4_request&X-Amz-Date=20220914T225747Z&X-Amz-Expires=60&X-Amz-SignedHeaders=host%3Bx-k8s-aws-id&X-Amz-Signature=9688c99e7abd33807bc7b3af3542a59235e501a0d9c807100707410fc3da5d33

Control plane

透過以下 CloudWatch Log Syntax 可查看 kube-apiserver 所使用 Flag:

filter @logStream not like /^kube-apiserver-audit/
 | filter @logStream like /^kube-apiserver-/
 | fields @timestamp, @message
 | sort @timestamp asc
 | filter @message like "--authentication"
 | limit 10000

kube-apiserver logs 定義了 --authentication-token-webhook-config-file 則代表使用了 Webhook Token Authentication [10] ,但非常遺憾的是我們無法查看到 API Server 元件內定義目錄查看細節。/etc/kubernetes/authenticator/apiserver-webhook-kubeconfig.yaml

I0914 09:46:06.295581 10 flags.go:59] FLAG: --authentication-token-webhook-cache-ttl="7m0s"
I0914 09:46:06.295687 10 flags.go:59] FLAG: --authentication-token-webhook-config-file="/etc/kubernetes/authenticator/apiserver-webhook-kubeconfig.yaml"
I0914 09:46:06.295703 10 flags.go:59] FLAG: --authentication-token-webhook-version="v1beta1"

此外,一樣透過  CloudWatch Log Syntax 查看 authenticator log,也能觀察到相同的設置及 logs:

filter @logStream like /^authenticator/
 | fields @timestamp, @message
 | sort @timestamp asc
 | filter @message like "--authentication-token-webhook-config-file"
 | limit 10000
time="2022-09-14T09:46:01Z" level=info msg="reconfigure your apiserver with `--authentication-token-webhook-config-file=/etc/kubernetes/authenticator/apiserver-webhook-kubeconfig.yaml` to enable (assuming default hostPath mounts)"

總結

原生 kubectl 使用了 k8s.io/client-go library,並在 Kubernetes 1.10 版本開始支援 exec 命令使用外部 credentials,而在 API server 則設定了使用  webhook token authenticator 方式設定 AWS IAM Authenticator 整合 IAM STS 服務驗證 IAM 使用者,STS endpoint 驗證後交由 Kubernetes API server 讀取 kube-system/aws-auth ConfigMap 對照 Kubernetes 使用 RBAC groups,最終返回結果。

了解上述 aws eks get-token 為 Kubernetes Bearer Token,故我們也可以直接透過 curl 命令搭配此 token 訪問 EKS cluster endpoint。

$ TOKEN=$(aws eks get-token --cluster ironman-2022 | jq -r .status.token)
$ APISERVER=$(aws eks describe-cluster --name ironman-2022 | jq -r .cluster.endpoint) 
$ curl $APISERVER/api --header "Authorization: Bearer $TOKEN" --insecure
{
  "kind": "APIVersions",
  "versions": [
    "v1"
  ],
  "serverAddressByClientCIDRs": [
    {
      "clientCIDR": "0.0.0.0/0",
      "serverAddress": "ip-10-0-51-57.eu-west-1.compute.internal:443"
    }
  ]

參考文件

  1. Installing or updating kubectl - https://docs.aws.amazon.com/eks/latest/userguide/install-kubectl.html
  2. Install and Set Up kubectl on Linux  | Kubernetes Documentation -  https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/
  3. eksctl - The official CLI for Amazon EKS -  https://github.com/weaveworks/eksctl
  4. AWS IAM Authenticator for Kubernetes - https://github.com/kubernetes-sigs/aws-iam-authenticator.
  5. Enabling IAM user and role access to your cluster - Add IAM users or roles to your Amazon EKS cluster - https://docs.aws.amazon.com/eks/latest/userguide/add-user-role.html#aws-auth-users
  6. Amazon EKS control plane logging - https://docs.aws.amazon.com/eks/latest/userguide/control-plane-logs.html
  7. client-go: add an exec-based client auth provider #59495 - https://github.com/kubernetes/kubernetes/pull/59495
  8. client-go credential plugins  | Authenticating  - https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins
  9. https://github.com/kubernetes-sigs/aws-iam-authenticator#api-authorization-from-outside-a-cluster
  10. Webhook Token Authentication  | Authenticating - https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication