1クール続けるブログ

とりあえず1クール続けるエンジニアの備忘録

EKSでのAWSユーザorロール認証の流れを追う

記事一覧はこちら

要約

下記の記事で、k8s内のリソースからAWSのリソースを操作する時の権限のマッピングについて書いたけど、今回はIAMロールorユーザがk8sのリソースを操作する時の権限のマッピングについての話です。
k8sのwebhook認証を利用して、EKSはaws-iam-authentificatorとやり取りしてIAMの情報からk8sのsubjectを取得しています。そのフローの中で利用されるのがkube-system/aws-authのConfigMap。k8sのRoleBindingで定義されるユーザやグループにIAMロール/ユーザをマッピングできます。

44smkn.hatenadiary.com

EKSにおけるAuthentification(認証)

参考にさせていただいたのは下記の記事です。

itnext.io

全体の流れは、Kubernetes RBAC and IAM Integration in Amazon EKS using a Java-based Kubernetes Operator | Containers」に記載されている図がとてもわかり易いので、そちらを引用させていただきます。

https://d2908q01vomqb2.cloudfront.net/fe2ef495a1152561572949784c16bf23abb28057/2020/06/07/RBAC-IAM.png

起点となるのはkubectlとなります。もし直接k8s APIを叩くとしても流れとして大きくは変わらないのでkubectlで話を勧めていきます。
kubectlには、client-go credential pluginsという機能があります。k8s.io/client-goがネイティブにサポートしていない認証プロトコルとクライアントを統合してくれるものです。サービスアカウントトークン等とは違い、IAMでの認証はネイティブにサポートされていないので、このプラグインを利用することになります。
EKSの場合に用いるプラグインaws-iam-authenticatorとなります。kubeconfigには下記のように宣言します。

# [...]
users:
- name: ops
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1alpha1
      command: aws-iam-authenticator
      args:
        - "token"
        - "-i"
        - "<CLUSTER_ID>"
        - "-r"
        - "<ROLE_ARN>"
  # no client certificate/key needed here!

aws-iam-authenticator token -i <CLUSTER_ID> -r <ROLE_ARN>が実行され、標準出力に下記のように出力します。これがBearer Tokenとして、AuthorizationHTTPヘッダとして付与され、k8sAPIサーバにリクエストされます。このトークンの中身は署名付きURLをbase64エンコードされたものになっています(該当コード)。URLを署名するためにはもちろん認証情報が必要になります。ARNを指定した場合には、そのロールにAssumeRoleを行うことになります。
この辺の流れは、Pythonのコードの方が分かりやすいかも。
ここまでが冒頭で引用させていただいた図の①より前の部分になります。

{
  "apiVersion": "client.authentication.k8s.io/v1beta1",
  "kind": "ExecCredential",
  "status": {
    "token": "my-bearer-token",
    "expirationTimestamp": "2018-03-05T17:30:20-08:00"
  }
}

Bearer Tokenの付与されたリクエストはk8sAPIサーバに到達します。APIサーバではそのトークンが正しいかどうかを検証する必要があります。IAMの認証情報であるため、k8sだけでは検証することは不可能です。k8sBearer Tokenを検証するためにWebhook認証という機能があります。別のコンポーネントにPOSTリクエストを投げて、下記のようなレスポンスを返却してもらうことで、k8sでのユーザ/グループを特定します。

{
  "apiVersion": "authentication.k8s.io/v1beta1",
  "kind": "TokenReview",
  "status": {
    "authenticated": true,
    "user": {
      "username": "janedoe@example.com",
      "uid": "42",
      "groups": [
        "developers",
        "qa"
      ],
      "extra": {
        "extrafield1": [
          "extravalue1",
          "extravalue2"
        ]
      }
    }
  }

トークンを検証する別のコンポーネントというのがaws-iam-authenticatorのサーバです。aws-iam-authenticator serverのサブコマンドで起動します。EKSの(おそらく)マスターノードで動いています。k8sAPIサーバはこのエンドポイントをどう知りうるのかというと、aws-iam-authenticatorサーバ起動時に生成される設定ファイルを--authentication-token-webhook-config-fileの引数に渡してあげることで認識できます。

冒頭で引用した①に当たる部分で、k8sAPIサーバからトークンがaws-iam-authenticatorのエンドポイント/authenticateにPOSTリクエストで渡されます。②に当たる部分でトークンの有効性を確認します(該当コード)。その後は、③に当たる部分で、ユーザ名とグループ名をaws-authというConfigMapと照らし合わせてAPIサーバに返す(該当コード)。

k8sAPIサーバは渡されたユーザ名とグループ名に基づいて、Authorization(認可)を行うという流れになります。

aws-authの定義の仕方

ほとんどこのドキュメントに書いてあること。

docs.aws.amazon.com

IAMロール or IAMユーザをKubernetesのユーザにマッピングするための定義

mapRoles:
  - rolearn: arn:aws:iam::xxxxxxxxxxx:role/admin
    username: admin  # k8sが扱うときの名前になる(※1)。
    groups: 
      - system:node  # RoleBindingで指定する、subjectのkindがGroupのものと対応する
mapUsers:
  - userarn: arn:aws:iam::xxxxxxxxxxx:user/ops
    username: ops    # subjectのkindがUserのものと名称が一致すれば、その権限を得る(※2)。opsというユーザが既に存在。
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: ops
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: edit
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: ops

※1: エラーメッセージとかもError from server (Forbidden): services is forbidden: User "admin" cannot list resource "services" in API group "" in the namespace "default"のように表示される