Amazon EKSのService Accountをawscliやkubectl使ったスクリプトで作成する

この記事はBeeX Advent Calendar 2020の12/3の記事です。

Service Accountの仕組みを理解するために、Step By Stepでスクリプト化してみました。

スクリプト

#!/bin/bash
set -eu

CLUSTER_NAME=$1
SA_NAMESPACE=$2
SA_NAME=$3
ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
AWS_REGION=ap-northeast-1
ROLE_NAME=eks-role-${CLUSTER_NAME}-${SA_NAMESPACE}-${SA_NAME}
TEMP_FILE=./tmp_${ROLE_NAME}_doc.json

#######
#  1.OIDC Providerの証明書のThumbprint取得
#    ブラウザから取得します。(後述)
OIDC_THUMBPRINT=9e99a48a9960b14926bb7f3b02e22da2b0ab7280

#######
# 2.OIDC ProviderをIAMに登録
#   OIDC ProviderがIdPとしてIAMに登録されていることを確認。登録されていない場合は新規登録
OIDC_PROVIDER_URL=$(aws eks describe-cluster --region ${AWS_REGION} --name ${CLUSTER_NAME} --query 'cluster.identity.oidc.issuer' --output text)
CLUSTER_ID=$(echo ${OIDC_PROVIDER_URL} | awk -F"/" '{print $5}')

IS_OIDC_PROVIDER_EXIST=$(aws iam list-open-id-connect-providers --query 'OpenIDConnectProviderList[].Arn' --output text | grep ${CLUSTER_ID} | wc -l)
if [ "${IS_OIDC_PROVIDER_EXIST}" == "0" ];then
    OIDC_PROVIDER_ARN=$(aws iam create-open-id-connect-provider --url ${OIDC_PROVIDER_URL} --thumbprint-list ${OIDC_THUMBPRINT} --query 'OpenIDConnectProviderArn' --output text)
    # AudienceとしてSTSを追加
    aws iam add-client-id-to-open-id-connect-provider --open-id-connect-provider-arn ${OIDC_PROVIDER_ARN} --client-id sts.amazonaws.com
fi

#######
# 3 EKSで認証されたSAがロールをAssumeできるように設定
cat <<EOF > ${TEMP_FILE}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::${ACCOUNT_ID}:oidc-provider/oidc.eks.${AWS_REGION}.amazonaws.com/id/${CLUSTER_ID}"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.${AWS_REGION}.amazonaws.com/id/${CLUSTER_ID}:sub": "system:serviceaccount:${SA_NAMESPACE}:${SA_NAME}"
        }
      }
    }
  ]
}
EOF
aws iam create-role --role-name ${ROLE_NAME} --assume-role-policy-document file://${TEMP_FILE}


#######
# 4. ロールを紐付けたServiceAccountの作成
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: ${SA_NAME}
  namespace: ${SA_NAMESPACE}
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::${ACCOUNT_ID}:role/${ROLE_NAME}
EOF

使ってみる

名前空間の作成

Namespaceを作成して、そこにService Accountを作成してみます。

# 先に名前空間作成しておきます
$ kubectl create ns my-namespace

スクリプトの実行

スクリプトを実行します。IAMの設定、IAM Roleの作成、Service Accountの作成と紐付けまで行ってくれます。

$ /bin/bash ./create_service_account.sh my-cluster my-namespace my-account
# -- 省略 --
serviceaccount/my-account created

ServiceAccountの確認

以下のイメージをPodでデプロイして、Service Accountが紐付けられたロールをAssumeしていることを確認します。

cat <<EOF > Dockerfile
FROM amazon/aws-cli
CMD ["sts", "get-caller-identity"]
EOF

上記のDockerfileのイメージをECRにPushしておき、以下のようにJobから呼び出してみます。 - serviceAccountName に先程作成したService Accountが指定されてます - image はECRのイメージ名に変更します。

cat <<EOF | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
  name: get-caller-identity
  namespace: my-namespace
spec:
  completions: 1
  parallelism: 1
  template:
    spec:
      serviceAccountName: my-account
      containers:
      - name: get-caller-identity
        image: 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/my-image:latest
      restartPolicy: Never
EOF

kubectl apply してみるとジョブが一つ走ります。その際に aws sts get-caller-identity が実行されAssumeしているロールが確認できますが、先程作成したロールにService Accountが紐付いていることがわかります。

$ kubectl get po -n my-namespace
NAME                        READY   STATUS      RESTARTS   AGE
get-caller-identity-dfvx8   0/1     Completed   0          15s
$ kubectl logs -n my-namespace get-caller-identity-dfvx8
{
    "UserId": "xxxxxxxxx:botocore-session-xxxxxxxx",
    "Account": "123456789012",
    "Arn": "arn:aws:sts::123456789012:assumed-role/eks-role-my-cluster-my-namespace-my-account/botocore-session-xxxxxxxx"
}

やっていることを少し解説

1.OIDC Providerの証明書のThumbprint取得

IAMの画面から取得できます。 f:id:yomon8:20201222201835p:plain

または、ブラウザから以下のURL叩いて取得してください。

https://oidc.eks.ap-northeast-1.amazonaws.com

2.OIDC ProviderをIAMに登録

登録されているのは以下の画面の部分です。IDの発行元としてAWSのIAMに登録します。

f:id:yomon8:20201201201208p:plain

3 EKSで認証されたSAがロールをAssumeできるように設定

OIDC Providerにて発行されたIDを信頼します。

今回は system:serviceaccount:${SA_NAMESPACE}:${SA_NAME} と言う風にService Account名まで固定してIAMロールを紐付けてますが、

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::${ACCOUNT_ID}:oidc-provider/oidc.eks.${AWS_REGION}.amazonaws.com/id/${CLUSTER_ID}"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.${AWS_REGION}.amazonaws.com/id/${CLUSTER_ID}:sub": "system:serviceaccount:${SA_NAMESPACE}:${SA_NAME}"
        }
      }
    }
  ]
}

以下のように名前空間にIAMロールを紐付けることも可能です。

      "Condition": {
        "StringLike": {
          "oidc.eks.${AWS_REGION}.amazonaws.com/id/${CLUSTER_ID}:sub": "system:serviceaccount:${SA_NAMESPACE}:*"
        }

参考URL

4. ロールを紐付けたServiceAccountの作成

以下のService Accoutが作成されます。

$ kubectl get serviceaccounts -n my-namespace my-account
NAME         SECRETS   AGE
my-account   1         40m

このServiceAccountを紐付けると内部的に様々な設定がされます。先程のシンプルなPodの定義を出力してみると面白いです。

$ kubectl get pod -n my-namespace get-caller-identity-dfvx8 -oyaml