Workload Identity を使ってGKEからAWSへセキュアにアクセスを行う

Workload Identityを用いてGKEからAWSのリソースにアクセスする

この記事で使用する手順は主に dotintl/gtoken に沿っています

Workload Identity とは

Workload Identity GKEアプリケーションが Google API 提供のサービスを使用するための推奨された方法です

GoogleサービスアカウントのメールアドレスがAnnotateされたKubernetesサービスアカウントを用いることで、サービスアカウントを使用してクラウドサービスへの認証を行うことができます

AWSにも似たような機能があります

今回は、Workload Identityを用いてAWSのサービスに対し認証情報を持たずにセキュアにアクセスを行う方法を紹介します。

アプローチ

AWS IAM Role を GKEのPodに割り当てる

  • AWS
    • OIDC IDプロバイダーのIAMロールを作成する
    • GoogleのサービスアカウントでAssumeRoleできるようにする
  • Google
    • サービスアカウントを作成する
    • Workload Identityを通じてPodに有効なOIDCトークンを付与する
    • OIDCトークンを用いてAWSに作成したIAMロールにAssumeRoleする

AWS SDKは、次の環境変数が設定されている場合、STSサービスから一時的なAWS認証情報をリクエストします

  • AWS_WEB_IDENTITY_TOKEN_FILE
  • AWS_ROLE_ARN
    • 引き受ける役割のARN
  • AWS_ROLE_SESSION_NAME
    • このセッションの名前

OSSであるdointl/gtoken を用いることで簡単にセットアップができます

gtoken

gtokenとは、OIDC ID tokenの期限が切れる前に更新する役割をもつコンテナ

gtoken-webhook のデプロイ

gtoken-webhook とは

特定のAnnotationがついたサービスアカウントで実行されるPodに対して、gtokenをinjectするサービスです

デプロイには、Webhookサービスとデプロイメントを作成する必要があります。

また、gtokenのデプロイには通常のサービスと違い、証明書を作成する必要があります。

git clone https://github.com/doitintl/gtoken

./deployment/webhook-create-signed-cert.sh

証明書の作成が完了したら、デプロイメントとサービスを作成します。

kubectl apply -f deployment/deployment.yaml

kubectl apply -f deployment/service.yaml

次にMutatingWebhookConfiguration をApplyします。

apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
  name: mutating-gtoken-webhook-cfg
  labels:
    app: gtoken-webhook
webhooks:
  - name: gtoken.doit-intl.com
    clientConfig:
      service:
        name: gtoken-webhook-svc
        namespace: default
        path: "/pods"
      caBundle: ${CA_BUNDLE}
    rules:
      - operations: [ "CREATE" ]
        apiGroups: ["*"]
        apiVersions: ["*"]
        resources: ["pods"]

ここで、${CA_BUNDLE} を適切に設定する必要があります。 CAを置き換えるスクリプトが同梱されているので、それを使います。

cat ./deployment/mutatingwebhook.yaml | ./deployment/webhook-patch-ca-bundle.sh > ./deployment/mutatingwebhook-bundle.yaml

kubectl apply -f deployment/mutatingwebhook-bundle.yaml

gtoken-webhook の RBACを構成する

gtoken-webhookが使用するサービスアカウントと、その権限を作成します。

# create a service account
kubectl create -f deployment/service-account.yaml

# create a cluster role
kubectl create -f deployment/clusterrole.yaml
# create a cluster role binding
kubectl create -f deployment/clusterrolebinding.yaml

GKE Workload Identity の設定

以下の設定で、いくつかの環境変数を使います。 .envrc などで定義しておくと便利です。

  • PROJECT_ID
    • GCP の project ID
  • CLUSTER_NAME
  • GSA_NAME
    • Google Cloud の サービスアカウントの名前
  • KSA_NAME
    • Kubernetes のサービスアカウントの名前
  • KSA_NAMESPACE
  • AWS_ROLE_NAME
    • AWSのIAM Role の名前
  • AWS_POLICY_NAME
    • $AWS_ROLE_NAME に紐付けたいポリシーの名前
  • 生成されるもの
    • GSA_ID
      • gcloud iam service-accounts describe --format json $GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com | jq -r '.uniqueId' で取得可能
    • AWS_ROLE_ARN
      • $AWS_ROLE_NAMEを 生成したときに割り当てられるARN
      • aws iam get-role --role-name $ROLE_NAME --query Role.Arn --output text で取得可能

Gcloud Workload Identity を 有効にする

Workload Identity

ノードプールに--workload-metadata-from-node=GKE_METADATA_SERVERを付加すると、そのノードでWorkload Identity を使うことができます

新しいクラスタの場合

gcloud beta container clusters create ${CLUSTER_NAME} --identity-namespace=${PROJECT_ID}.svc.id.goog

既存のクラスタの場合

gcloud beta container clusters update ${CLUSTER_NAME} --identity-namespace=${PROJECT_ID}.svc.id.goog

この場合、既存のノードプールへの影響はなく、新しいノードプールでは--workload-metadata-from-node=GKE_METADATA_SERVER が有効になります。 よって、Workload Identityを使用するにはノードプールを更新するか、新しく作成する必要があります。

# 既存のノードプールを更新する場合
gcloud beta container node-pools update ${NODEPOOL_NAME} \
  --cluster=${CLUSTER_NAME} \
  --workload-metadata-from-node=GKE_METADATA_SERVER
  
# 新しくノードプールを作成する場合
gcloud beta container node-pools create ${NODEPOOL_NAME} \
  --cluster=${CLUSTER_NAME} \
  --workload-metadata-from-node=GKE_METADATA_SERVER
  

Google Cloud Service Account を作成する

gcloud iam service-accounts create ${GSA_NAME}

GSA_ID=$(gcloud iam service-accounts describe --format json ${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com  | jq -r '.uniqueId')

GSA_NAME に以下の役割を与えます。 - roles/iam.workloadIdentityUser - GKE ワークロードのサービスアカウントを使用するため -roles/iam.serviceAccountTokenCreator` - サービス アカウント権限を使用するため

gcloud iam service-accounts add-iam-policy-binding \
  --role roles/iam.serviceAccountTokenCreator \
  --member "serviceAccount:${PROJECT_ID}.svc.id.goog[${K8S_NAMESPACE}/${KSA_NAME}]" \
  ${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com
  
gcloud iam service-accounts add-iam-policy-binding \
  --role roles/iam.workloadIdentityUser \
  --member "serviceAccount:${PROJECT_ID}.svc.id.goog[${K8S_NAMESPACE}/${KSA_NAME}]" \
  ${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com

参考にしたブログ では一行になってましたが、やったらできなかったので2つに分けてます

Google OIDC フェデレーション用のAWS IAM Roleを作成する

Google OIDC フェデレーション用のAWS IAM Roleを作成するためのトラストポリシーを作成しておきます。

cat > gcp-trust-policy.json << EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "accounts.google.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "accounts.google.com:sub": "${GSA_ID}"
        }
      }
    }
  ]
}
EOF

AWS IAM Roleを作成します

# assume-role-policy-documentでさっきのファイルを指定する
aws iam create-role --role-name ${AWS_ROLE_NAME} --assume-role-policy-document file://gcp-trust-policy.json

# policy を割り当てる
aws iam attach-role-policy --role-name ${AWS_ROLE_NAME} --policy-arn arn:aws:iam::aws:policy/${AWS_POLICY_NAME}

Kubernetes サービスアカウントのアノテーションに使用するため、ARNを取得しておきます

AWS_ROLE_ARN=$(aws iam get-role --role-name ${ROLE_NAME} --query Role.Arn --output text)

Kubernetes サービスアカウントの作成

Kubernetes namespace と サービスアカウントを作成します

# Kubernetes namespace の作成
kubectl create namespace ${K8S_NAMESPACE}

# Kubernetes service account の作成
kubectl create serviceaccount --namespace ${K8S_NAMESPACE} ${KSA_NAME}

# service account に GKE Workload Identity(GCP ServiceAccountのメールアドレス) を annotate
kubectl annotate serviceaccount --namespace ${K8S_NAMESPACE} ${KSA_NAME} \
  iam.gke.io/gcp-service-account=${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com

# service account に AWS Role ARN を annotate

kubectl annotate serviceaccount --namespace ${K8S_NAMESPACE} ${KSA_NAME} \
amazonaws.com/role-arn=${AWS_ROLE_ARN}

試す

GKE Workload Identity

kubectl run --rm -it \
  --generator=run-pod/v1 \
  --image google/cloud-sdk:slim \
  --serviceaccount $KSA_NAME \
  --namespace $K8S_NAMESPACE \
  workload-identity-test

root@workload-identity-test:/ gcloud auth list
                       Credentialed Accounts
ACTIVE  ACCOUNT
*       test-service-account@$PROJECT_ID.iam.gserviceaccount.com

To set the active account, run:
    $ gcloud config set account `ACCOUNT`

正しく設定できていた場合、Googleサービスアカウントのメールアドレスのみが表示されるはずです。

AWS

kubectl run -it --rm --generator=run-pod/v1 --image mikesir87/aws-cli --serviceaccount ${KSA_NAME} test-pod -n {$K8S_NAMESPACE}

If you don't see a command prompt, try pressing enter.
root@test-pod:/aws#
root@test-pod:/aws# aws sts get-caller-identity
{
    "UserId": "XXXXX:gtoken-webhook-vqbhasptyassltln",
    "Account": "XXXXXXXX",
    "Arn": "arn:aws:sts::XXXXXXX:assumed-role/test-gcp-service-account/gtoken-webhook-vqbhasptyassltln"
}
root@test-pod:/aws# exit

指定したARNが表示されていたら成功です。

クリーンアップ

# remove aws iam role
aws iam detach-role-policy --role-name $AWS_ROLE_NAME --policy-arn arn:aws:iam::aws:policy/$AWS_POLICY_NAME

aws iam delete-role --role-name $AWS_ROLE_NAME

# remove k8s sa, namespace
kubectl delete sa -n $K8S_NAMESPACE  $KSA_NAME

kubectl delete namespace $K8S_NAMESPACE

#remove Google Cloud IAM 
gcloud iam service-accounts remove-iam-policy-binding \
     --role roles/iam.serviceAccountTokenCreator \
    --member "serviceAccount:$PROJECT_ID.svc.id.goog[$K8S_NAMESPACE/$KSA_NAME]" \
      ${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com

gcloud iam service-accounts remove-iam-policy-binding \
    --role roles/iam.workloadIdentityUser \
    --member "serviceAccount:$PROJECT_ID.svc.id.goog[$K8S_NAMESPACE/$KSA_NAME]" \
      ${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com

gcloud iam service-accounts delete $GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com

# disable workload identity
# update node
gcloud beta container node-pools update $K8S_NODEPOOL --cluster=$K8S_CLUSTER   --workload-metadata-from-node=EXPOSED

# disable workload identity
gcloud beta container clusters update $CLUSTER_NAME  --disable-workload-identity

# delete resources
kubectl delete -f deployment/

おわりに

この記事は3月に株式会社オプティマインドで検証した記録です。