EKSのIRSAについて改めて調べた

## はじめに

EKSクラスタを作成する時に大体セットで作成していたIAMのIDプロバイダ。認証周りでこれが必要なことは何となく理解しているものの、具体的な必要性については曖昧だったので自分なりに調べた事柄のアウトプット。

## 結論

- EKSクラスターに組み込まれているOIDCプロバイダと信頼関係を確立し、Kubernetes上のユーザ（=ServiceAccount）に対してAWSリソースへの許可を与えるために使用する
- 対象者（Audience）はIDトークンを使用する対象であり、IAMロールの引き受けであれば「sts.amazonaws.com」を指定する
- IAM Roles for Service Accounts(IRSA)と呼称されることがある

## 検証

[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/iam-roles-for-service-accounts.html)に沿っていきます。※既にIAMにIDプロバイダは設定済みのものとします。

### IAMロールとサービスアカウントの設定

#### IAMポリシーの作成

```bash
# IAMポリシードキュメントの作成
[root@ip-192-168-0-50 ~]# cat >my-policy.json <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::eks-handson-2024"
        }
    ]
}
EOF

# ドキュメントをインプットとしてIAMポリシーを作成
[root@ip-192-168-0-50 oidc]# aws iam create-policy --policy-name my-policy --policy-document file://my-policy.json
{
    "Policy": {
        "PolicyName": "my-policy",
        "PolicyId": "ANPA273BYGZP5GR6YWTGF",
        "Arn": "arn:aws:iam::XXXXXXXXXXXX:policy/my-policy",
        "Path": "/",
        "DefaultVersionId": "v1",
        "AttachmentCount": 0,
        "PermissionsBoundaryUsageCount": 0,
        "IsAttachable": true,
        "CreateDate": "2024-01-13T06:02:28+00:00",
        "UpdateDate": "2024-01-13T06:02:28+00:00"
    }
}

```

#### IAMロールとサービスアカウントの作成

eksctlを使うとコマンド一発でやってくれるのでこちらを採用します。

```bash
[root@ip-192-168-0-50 oidc]# eksctl create iamserviceaccount --name my-service-account --namespace default --clustereks-work-cluster --role-name my-role     --attach-policy-arn arn:aws:iam::XXXXXXXXXXXX:policy/my-policy --approve --region ap-northeast-1
2024-01-13 06:06:17 [ℹ]  2 existing iamserviceaccount(s) (kube-system/ebs-csi-controller-sa,kube-system/s3-csi-driver-sa) will be excluded
2024-01-13 06:06:17 [ℹ]  1 iamserviceaccount (default/my-service-account) was included (based on the include/excluderules)
2024-01-13 06:06:17 [!]  serviceaccounts that exist in Kubernetes will be excluded, use --override-existing-serviceaccounts to override
2024-01-13 06:06:17 [ℹ]  1 task: {
    2 sequential sub-tasks: {
        create IAM role for serviceaccount "default/my-service-account",
        create serviceaccount "default/my-service-account",
    } }2024-01-13 06:06:17 [ℹ]  building iamserviceaccount stack "eksctl-eks-work-cluster-addon-iamserviceaccount-default-my-service-account"
2024-01-13 06:06:17 [ℹ]  deploying stack "eksctl-eks-work-cluster-addon-iamserviceaccount-default-my-service-account"
2024-01-13 06:06:17 [ℹ]  waiting for CloudFormation stack "eksctl-eks-work-cluster-addon-iamserviceaccount-default-my-service-account"
2024-01-13 06:06:47 [ℹ]  waiting for CloudFormation stack "eksctl-eks-work-cluster-addon-iamserviceaccount-default-my-service-account"
```

#### 確認

まずは作成したIAMロールの信頼関係ポリシーを確認します。

```bash
[root@ip-192-168-0-50 oidc]# aws iam get-role --role-name my-role --query Role.AssumeRolePolicyDocument
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::XXXXXXXXXXXX:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "oidc.eks.ap-northeast-1.amazonaws.com/id/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY:aud": "sts.amazonaws.com",
                    "oidc.eks.ap-northeast-1.amazonaws.com/id/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY:sub": "system:serviceaccount:default:my-service-account"
                }
            }
        }
    ]
}
```

- 信頼されたエンティティとして、IAMのIDプロバイダが設定されました。つまり、このエンティティのユーザが対象であることを示しています。
- Conditionに2つの条件が指定されています。
  - OIDCトークンのaudienceが「sts.amazonaws.com」であること
  - OIDCトークンのsubjectフィールドが今回使用するServiceAccountに対応していること

つまり、このポリシーはOIDCトークンが特定のAudienceとServiceAccountに対応している場合に、一時的なセキュリティクレデンシャルの取得を許可することを意味しているようです。

次に、ServiceAccountが作成されていることと、IAMロールが関連付けられている(Annotationに設定されている)ことを確認します。

```bash
[root@ip-192-168-0-50 oidc]# kubectl describe serviceaccount my-service-account -n default
Name:                my-service-account
Namespace:           default
Labels:              app.kubernetes.io/managed-by=eksctl
Annotations:         eks.amazonaws.com/role-arn: arn:aws:iam::XXXXXXXXXXXX:role/my-role
Image pull secrets:  <none>
Mountable secrets:   <none>
Tokens:              <none>
Events:              <none>
```

EKSクラスター上にServiceAccountが作成されていること、また、IAMロールが関連付けられていることを確認できました。

### Podの起動

#### Deployment作成と適用

```bash
# マニフェスト作成
[root@ip-192-168-0-50 oidc]# cat >my-deployment.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      serviceAccountName: my-service-account
      containers:
      - name: my-app
        image: public.ecr.aws/nginx/nginx:latest
EOF

# 適用
[root@ip-192-168-0-50 oidc]# k apply -f my-deployment.yaml
deployment.apps/my-app created
[root@ip-192-168-0-50 oidc]# k get po
NAME                      READY   STATUS    RESTARTS   AGE
my-app-56b4947986-tf8h9   1/1     Running   0          22s
```

#### 確認

```bash
[root@ip-192-168-0-50 oidc]# kubectl describe pod my-app-56b4947986-tf8h9
Name:             my-app-56b4947986-tf8h9
Namespace:        default
Priority:         0
Service Account:  my-service-account
Node:             ip-192-168-1-213.ap-northeast-1.compute.internal/192.168.1.213

---一部省略---

    Environment:
      AWS_STS_REGIONAL_ENDPOINTS:   regional
      AWS_DEFAULT_REGION:           ap-northeast-1
      AWS_REGION:                   ap-northeast-1
      AWS_ROLE_ARN:                 arn:aws:iam::XXXXXXXXXXXX:role/my-role # IAMロール
      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-hf9sw (ro)

```

実際にIDトークンが格納されているかcatしてみましょう。

```bash
[root@ip-192-168-0-50 oidc]# k exec my-app-56b4947986-tf8h9 -- cat /var/run/secrets/eks.amazonaws.com/serviceaccount/token
eyJhbGciOiJSUzI1NiIsImtpZCI6ImU3YTY2~~~以下省略
```

どうやらそれっぽいものが入っていますね。

ちなみにこれをデコードしたものがこちらです。

```json
{
  "aud": [
    "sts.amazonaws.com"
  ],
  "exp": 1705213372,
  "iat": 1705126972,
  "iss": "https://oidc.eks.ap-northeast-1.amazonaws.com/id/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY",
  "kubernetes.io": {
    "namespace": "default",
    "pod": {
      "name": "my-app-56b4947986-tf8h9",
      "uid": "uid"
    },
    "serviceaccount": {
      "name": "my-service-account",
      "uid": "uid"
    }
  },
  "nbf": 1705126972,
  "sub": "system:serviceaccount:default:my-service-account"
}
```

IAMロールの信頼関係ポリシーで指定した情報が入っていますね。

実際にPodに入ってIAMロールで許可された権限が使用できるかを確認します。

```bash
[root@ip-192-168-0-50 oidc]# k exec my-app-56b4947986-tf8h9 -it -- bash

# my-roleをAssumeRole出来ているようです
root@my-app-56b4947986-tf8h9:/# aws sts get-caller-identity --region ap-northeast-1
{
    "UserId": "AROA273BYGZPZICKEYFG2:botocore-session-1705127795",
    "Account": "XXXXXXXXXXXX",
    "Arn": "arn:aws:sts::XXXXXXXXXXXX:assumed-role/my-role/botocore-session-1705127795"
}

# S3バケットへアクセス
root@my-app-56b4947986-tf8h9:/# aws s3 ls s3://eks-handson-2024
2024-01-13 06:43:32          4 hoge.txt

# 与えていない操作に関してはエラーとなります
root@my-app-56b4947986-tf8h9:/# aws s3 ls
An error occurred (AccessDenied) when calling the ListBuckets operation: Access Denied

```

## 感想

IAMのIDプロバイダの設定は、KubernetesのリソースがAWSリソースを操作するために必要だったんですね。スッキリしました。一方で、やはり認証の難しさ・知識不足を実感したのでもっと理解を深めていこうと思います。

## 参考

[詳解: IAM Roles for Service Accounts](https://aws.amazon.com/jp/blogs/news/diving-into-iam-roles-for-service-accounts/)　やはり公式ブログが最強ですね。