Workload Identity連携でAWS(EC2/ECS/EKS)からサービスアカウントキーなしでBigQueryにアクセスする
背景
オンプレミスやAWSなどのクラウドからGoogle Cloud StorageやBigQueryなどのGCPサービス(API)を利用したい場合、サービスアカウントキーを使用していました。サービスアカウントキーはサービスアカウントから払い出された秘密鍵ファイルであり、使用する場合は環境変数 GOOGLE_APPLICATION_CREDENTIALS
にサービスアカウントキーのファイルパスを指定します。
$ export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service_account_key.json
サービスアカウントキーの利用について次の懸念があるため、システム提供者側は発行を極力避けたいと考えます。
-
キーファイルの漏洩リスク:サービスアカウントキーを使用する権限を持っていることを、サービスアカウントキーを利用される側は知ることができない。つまり、外部に漏洩すると誰でもサービスアカウントに付与されている権限が実行できてしまう
-
管理の煩雑さ:サービスアカウントキーには有効期限がないため、発行者側でローテートする必要がある。GCPドキュメントにはキーファイルのローテートを推奨されているが面倒くさい
この問題に対して、Workload Identity連携を用いることでサービスアカウントキーを使うことなく、オンプレミスやAWSなどのクラウドからGCPサービスを利用できます。
Workload Identity連携についてはGoogle公式ブログで紹介されています。
本記事では、Workload Identity連携を設定して、AWS EC2/Amazon ECS/Amazon EKSからBigQueryにアクセスする方法について紹介します。
Workload Identity連携とは
有効期限がないサービスアカウントキーをトークンとして一時的な認証情報に置き換えることができる機能です。GCPリソースにアクセスできるまでの流れは以下のようになります。
YouTuebe「What is Workload Identity Federation?」より
- AWSなどGCP外の環境で、アプリケーション(Workload)は自環境のIDプロバイダ(Identity Provider : IdP)に対して認証しクレデンシャルを取得する
- アプリケーションはGoogleのSecurity Token Service(STS)に1のクレデンシャルを渡して妥当性をチェックし、問題がなければアクセストークンが発行される
- STSはWorkload Identity Poolにクレデンシャルを渡して信頼できるIdPからであることを確認してからアクセストークンを発行する
- 2のアクセストークンを使って、GCPのサービスアカウントになりすましてGCPリソースにアクセスできるようになる
GCP公式動画に詳しく説明されています。
設定の流れ
EC2/ECS/EKSいずれの場合も以下のように設定を行っていきます。
- AWS側:IAMロールを作成する。ECSの場合はECSタスクロール、EKSの場合はPod単位のアクセス制御をするために、IAMロールとKubernetesのサービスアカウント(KSA : Kubernetes Service Account)を紐づける(IRSA : IAM Roles for Service Accounts)
-
GCP側:Workload Identity PoolとWorkload Identity Providerを作成する
- Workload Identity Providerは、Workload Identity Poolが外部IdPを信頼するための設定
- GCP側:サービスアカウントを作成し、Workload Identity Poolの権限を借用するための権限とGCPリソースを利用するために必要な権限を付与(今回の場合はBigQuery関連)
- GCP側:認証の構成ファイルを取得する
-
AWS側:5で取得したファイルのファイルパスを環境変数
GOOGLE_APPLICATION_CREDENTIALS
に指定してアプリケーションを実行
EC2インスタンスからアクセスする場合
(AWS) IAMロールの作成
access2bq
というIAMロールを作成します。必要に応じてAWSリソースにアクセスするためのIAMポリシーをアタッチしてください。
$ cat << EOF > assume.json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
$ IAM_ROLE="access2bq"
$ aws iam create-role --role-name $IAM_ROLE \
--assume-role-policy-document file://assume.json
# インスタンスプロファイルの追加とロールのアタッチ
$ aws iam create-instance-profile --instance-profile-name access2bq
$ aws iam add-role-to-instance-profile \
--instance-profile-name access2bq \
--role-name access2bq
(GCP)Workload Identity Poolの作成
作成するために必要なAPI(IAM、Resource Manager、Service Account Credentials、Security Token Service APIを)有効にします。GCPドキュメントにあるAPI有効化リンクをクリックすると一気に有効化できます。
bq-pool
というWorkload Identity Poolを作成します。
$ POOL="bq-pool"
$ gcloud iam workload-identity-pools create $POOL --location="global"
(GCP)Workload Identity Providerの作成
aws-provider
というWorkload Identity Providerを作成します。属性マッピング(--attribute-mapping
)のattribute.aws_role
は特定のIAMロールからしか許可しないように指定します。今回はドキュメントに記載されているものを使います。
$ AWS_ACCOUND_ID="<AWSアカウントID>"
$ POOL="bq-pool"
$ PROVIDER="aws-provider"
$ gcloud iam workload-identity-pools providers create-aws $PROVIDER \
--location="global" \
--workload-identity-pool=$POOL \
--account-id=$AWS_ACCOUND_ID \
--attribute-mapping='google.subject=assertion.arn,attribute.aws_role=assertion.arn.contains("assumed-role") ? assertion.arn.extract("{account_arn}assumed-role/") + "assumed-role/" + assertion.arn.extract("assumed-role/{role_name}/") : assertion.arn'
(GCP)サービスアカウントの作成
access-from-aws
というサービスアカウントを作成します。
$ SERVICE_ACCOUNT="access-from-aws"
$ gcloud iam service-accounts create $SERVICE_ACCOUNT \
--description="Access from AWS with workload identity federation"
今回はBigQueryにクエリ実行するため、以下の権限も付与します。BigQueryを利用しない場合はこの作業は不要です。
$ PROJECT_ID="foo-project"
$ SERVICE_ACCOUNT="access-from-aws"
# BigQueryデータ閲覧者(BigQuery Data Viewer)の権限を付与する
$ gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:${SERVICE_ACCOUNT}@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/bigquery.dataViewer"
# クエリ実行するための権限(BigQueryジョブユーザー、BigQuery読み取りセッションユーザー)を付与する
$ gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:${SERVICE_ACCOUNT}@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/bigquery.jobUser"
$ gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:${SERVICE_ACCOUNT}@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/bigquery.readSessionUser"
(GCP)Workload Identity Poolとサービスアカウントの連携
サービスアカウントに対して、Workload Identity Poolの権限を借用するための権限を付与します。プロジェクト番号(PROJECT_NUMBER
)はGCPの「IAM & Adminの設定画面」から確認できます(プロジェクトID、プロジェクト名とは異なるので注意)。
$ SERVICE_ACCOUNT="access-from-aws"
$ PROJECT_NUMBER="123456789012"
$ PROJECT_ID="foo-project"
$ POOL="bq-pool"
$ AWS_ACCOUND_ID="<AWSアカウントID>"
$ IAM_ROLE="access2bq"
$ gcloud iam service-accounts add-iam-policy-binding "${SERVICE_ACCOUNT}@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL}/attribute.aws_role/arn:aws:sts::${AWS_ACCOUND_ID}:assumed-role/${IAM_ROLE}"
(GCP)認証構成ファイルの取得
認証に必要な構成ファイルを取得します。
$ SERVICE_ACCOUNT="access-from-aws"
$ PROJECT_ID="foo-project"
$ PROJECT_NUMBER="123456789012"
$ POOL="bq-pool"
$ PROVIDER="aws-provider"
$ gcloud iam workload-identity-pools create-cred-config \
"projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL}/providers/${PROVIDER}" \
--service-account="${SERVICE_ACCOUNT}@${PROJECT_ID}.iam.gserviceaccount.com" \
--aws \
--output-file="config-aws-provider.json"
--output-file
で指定したファイル名(config-aws-provider.json
)に生成されます。このファイルの中身は機密情報が含まれていないため、サービスアカウントキーよりも管理を厳密にする必要はありません。
$ cat config-aws-provider.json
{
"type": "external_account",
"audience": "//iam.googleapis.com/projects/123456789012/locations/global/workloadIdentityPools/bq-pool/providers/aws-provider",
"subject_token_type": "urn:ietf:params:aws:token-type:aws4_request",
"token_url": "https://sts.googleapis.com/v1/token",
"credential_source": {
"environment_id": "aws1",
"region_url": "http://169.254.169.254/latest/meta-data/placement/availability-zone",
"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials",
"regional_cred_verification_url": "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
},
"service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/access-from-aws@foo-project.iam.gserviceaccount.com:generateAccessToken"
アプリケーションの実行
実行するためのEC2インスタンスがなれけば作ります。IAMロールは最初に作成したロール(access2bq
)を指定します。OSは何でも良いですが今回はAmazon Linux 2にしました。
EC2インスタンスにSSHログイン後、認証構成ファイル(config-aws-provider.json
)を配置します。今回はPythonからBigQueryにクエリ実行するので、必要なパッケージを入れておきます。
$ pip3 install google-cloud-bigquery
$ pip3 install pandas db-dtypes
サンプルコード(query_bq_ec2.py)
認証構成ファイル(config-aws-provider.json
)と同じフォルダで記載します。
import os
from google.cloud import bigquery
# 認証構成ファイルのパスを環境変数に入れる。ソースコードの外から入れても良い
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = './config-aws-provider.json'
# Cloud Identity連携が設定されているプロジェクトIDを指定する
project_id = 'foo-project'
bqclient = bigquery.Client(project=project_id)
dryrun = False
sql = """
SELECT *
FROM `foo-project.hoge_dataset.fuga_table`
LIMIT 10
"""
job_config = bigquery.QueryJobConfig(dry_run=dryrun)
query_job = bqclient.query(sql, job_config)
assert query_job.errors is None, f'errors: {query_job.errors}'
# debug
print(f"Bytes processed: {query_job.total_bytes_processed} B")
# 先頭5行分のみ出力
df = query_job.result().to_dataframe()
print(df.head())
クエリ実行してBigQueryからクエリ実行できることを確認します。
$ python ./query_bq_ec2.py
異なるIAMロールからアクセスしてみる
今回作成したものと異なるIAMロールで実行した場合は以下のエラーとなり、アクセスできません。
Traceback (most recent call last):
...(省略)...
google.auth.exceptions.RefreshError: ('Unable to acquire impersonated credentials', '{\n "error": {\n "code": 403,\n "message": "The caller does not have permission",\n "status": "PERMISSION_DENIED"\n }\n}\n')
Amazon ECSからアクセスする場合
基本的な流れはEC2インスタンスと同じです。ECSの場合、GCPリソースへのアクセス制御は以下の2パターンがあります。
- IAMロールを用いる(データプレーンがEC2の場合)
- ECSタスクロールを用いる
1はEC2インスタンスと同じですが、データプレーンがEC2のみ、Fargateでは使えません。また、EC2インスタンス上に動いているECSタスクすべてがGCPリソースにアクセスできてしまいます。
今回は2のECSタスクロールを用いることで、特定のECSタスクのみアクセスできるようにします。
(AWS)ECSタスクロールの作成
ecs-task-role
というECSタスクロールを作成します。
$ cat << EOF > assume.json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
$ IAM_ROLE="ecs-task-role"
$ aws iam create-role --role-name $IAM_ROLE \
--assume-role-policy-document file://assume.json
(GCP)Workload Identity Pool、Workload Identity Provider、サービスアカウントの作成
EC2インスタンスと同じです。今回はEC2インスタンスで作ったものを使います。
(GCP)Workload Identity Poolとサービスアカウントの連携
EC2インスタンスと同じです。今回はIAMロールがECSタスクロールに変わっています。
$ SERVICE_ACCOUNT="access-from-aws"
$ PROJECT_NUMBER="123456789012"
$ PROJECT_ID="foo-project"
$ POOL="bq-pool"
$ AWS_ACCOUND_ID="<AWSアカウントID>"
$ IAM_ROLE="ecs-task-role"
$ gcloud iam service-accounts add-iam-policy-binding "${SERVICE_ACCOUNT}@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL}/attribute.aws_role/arn:aws:sts::${AWS_ACCOUND_ID}:assumed-role/${IAM_ROLE}"
アプリケーションの実行
ECSタスクからBigQueryにアクセスする場合、EC2インスタンスのときと同じにできません。なぜなら、EC2インスタンスとECSタスクロールでクレデンシャルを取得するためのメタデータURLが異なるからです。
-
EC2インスタンス:
http://169.254.169.254/latest/meta-data/iam/security-credentials
-
ECSタスクロール:
http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
認証構成ファイルの中身をみるとEC2インスタンスのメタデータを見にいくようになっています。この箇所をECSタスクロールの認証エンドポイントに置き換えたとしても、google-auth-library-pythonが対応していないため動作しません。
そのため、こちらで提示されているようにECSタスクロールのクレデンシャルを取得して環境変数に入れる必要があります。
平たく書くと以下のようになります。
サンプルコード(query_bq_ecs1.py)
認証構成ファイルのパスはコンテナイメージに同梱し、ECSタスク定義を作成する際に環境変数 GOOGLE_APPLICATION_CREDENTIALS
に認証構成ファイルのファイルパスを指定します。
import os
import requests
from google.cloud import bigquery
# ECSタスクロールのクレデンシャルを環境変数に入れている
url_path = os.environ.get('AWS_CONTAINER_CREDENTIALS_RELATIVE_URI')
url = urljoin('http://169.254.170.2', url_path)
res = requests.get(url, timeout=3).json()
os.environ['AWS_ACCESS_KEY_ID'] = res['AccessKeyId']
os.environ['AWS_SECRET_ACCESS_KEY'] = res['SecretAccessKey']
os.environ['AWS_SESSION_TOKEN'] = res['Token']
# Cloud Identity連携が設定されているプロジェクトIDを指定する
project_id = 'foo-project'
bqclient = bigquery.Client(project=project_id)
dryrun = False
sql = """
SELECT *
FROM `foo-project.hoge_dataset.fuga_table`
LIMIT 10
"""
job_config = bigquery.QueryJobConfig(dry_run=dryrun)
query_job = bqclient.query(sql, job_config)
assert query_job.errors is None, f'errors: {query_job.errors}'
# debug
print(f"Bytes processed: {query_job.total_bytes_processed} B")
# 先頭5行分のみ出力
df = query_job.result().to_dataframe()
print(df.head())
サンプルコード(query_bq_ecs2.py)
先ほどのサンプルコード(query_bq_ecs1.py
)では、冒頭でクレデンシャルを環境変数に入れるところを毎回書くのが面倒です。また、ECS、EC2、ローカル環境といった複数の環境でアプリケーション実行するためにソースコードの差分をなくしたいので、以下のPyPIを使います(自作しました)。
これをimportするだけでECS、EC2といった環境によらず認証を通すことができ、コードがスッキリします。
import aws_ecs_gcp_workload_identity # 追加
from google.cloud import bigquery
# Cloud Identity連携が設定されているプロジェクトIDを指定する
project_id = 'foo-project'
bqclient = bigquery.Client(project=project_id)
dryrun = False
sql = """
SELECT *
FROM `foo-project.hoge_dataset.fuga_table`
LIMIT 10
"""
job_config = bigquery.QueryJobConfig(dry_run=dryrun)
query_job = bqclient.query(sql, job_config)
assert query_job.errors is None, f'errors: {query_job.errors}'
# debug
print(f"Bytes processed: {query_job.total_bytes_processed} B")
# 先頭5行分のみ出力
df = query_job.result().to_dataframe()
print(df.head())
Amazon EKSからアクセスする場合
以下の記事のように、Pod単位で権限付与するためにOIDC プロバイダを用いる必要があります。
Workload Identityを用いてEKSクラスタからGoogle Cloudへアクセスする
(AWS)EKSクラスタのセットアップ、準備
すでにEKSクラスタがあるならこの節は不要です。EKSクラスタがない人はQuick Startで作成します。CloudFormation一発で作成してくれるので楽ですが40分ぐらいかかりました。
- デプロイ方法:
新規の VPC にデプロイする
- リージョン:
オハイオ(us-east-2)
構築した後、自動作成されて踏み台サーバで作業するため、SSHログインし以下の準備をします。また、踏み台サーバのIAMロールにAdministratorAccessポリシーをアタッチしておきます。
$ aws configure
AWS Access Key ID [None]:
AWS Secret Access Key [None]:
Default region name [None]: us-east-2
Default output format [None]:
# eksctlのインストール
$ curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
$ sudo mv /tmp/eksctl /usr/local/bin
$ eksctl version
0.115.0
(AWS)EKSクラスタのIAM OIDCプロバイダーの確認
Quick Startで構築した場合はすでに作成されているため確認作業のみです。
# CloudFormationで自動作成されているため、確認しておく
$ CLUSTER="EKS-HOGEHOGE"
$ OIDC_ID=$(aws eks describe-cluster \
--name $CLUSTER \
--query "cluster.identity.oidc.issuer" --output text | cut -d '/' -f 5)
$ aws iam list-open-id-connect-providers | grep $OIDC_ID
"Arn": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.us-east-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E"
(AWS)IAMロールの作成、Kubernetesのサービスアカウントとの関連付け
eksctlコマンドで簡単につくれます。今回は検証のためAmazonS3FullAccess
ポリシーを付与します。Kubernetesのサービスアカウント(KSA)の名前はeks-access-to-gcp
としています。
$ CLUSTER="EKS-HOGEHOGE"
$ KSA_NAME="eks-access-to-gcp"
$ eksctl create iamserviceaccount \
--name $KSA_NAME \
--cluster $CLUSTER \
--attach-policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess \
--approve
KSAの中身を確認します。
$ kubectl get serviceaccounts
NAME SECRETS AGE
default 1 78m
eks-access-to-gcp 1 3m1s
$ KSA_NAME="eks-access-to-gcp"
$ kubectl get serviceaccounts $KSA_NAME --output yaml
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/eksctl-EKS-HOGEHOGE-addon-iamserviceaccount-Role1-1LMY230X5R55Z
creationTimestamp: "2022-10-27T12:16:57Z"
labels:
app.kubernetes.io/managed-by: eksctl
name: eks-access-to-gcp
namespace: default
resourceVersion: "15574"
uid: 619f3b0c-61a9-4996-9b9e-2d2b4672a450
secrets:
- name: eks-access-to-gcp-token-2tdsg
eksctl-${CLUSTER}-addon-iamserviceaccount-Role1-(ランダム文字列)
という命名規則でIAMロールが作成されます。今回の場合はeksctl-EKS-HOGEHOGE-addon-iamserviceaccount-Role1-1LMY230X5R55Z
が作成されました。
IAMロールのポリシーを見ると、ロールとKSAがマッピングされていることが確認できます。
$ ROLE_NAME="eksctl-EKS-HOGEHOGE-addon-iamserviceaccount-Role1-1LMY230X5R55Z"
$ aws iam get-role \
--role-name $ROLE_NAME \
--query Role.AssumeRolePolicyDocument
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRoleWithWebIdentity",
"Effect": "Allow",
"Condition": {
"StringEquals": {
"oidc.eks.us-east-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:aud": "sts.amazonaws.com",
"oidc.eks.us-east-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:sub": "system:serviceaccount:default:eks-access-to-gcp"
}
},
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.us-east-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E"
}
}
]
確認作業
KSAに付与された権限が正しく機能しているかを確認します。AWS CLI用のPodを立ててS3 APIを実行してみます。awscli-pod.yaml
というYAMLファイルを作成します。
apiVersion: v1
kind: Pod
metadata:
name: awscli
labels:
app: awscli
spec:
serviceAccountName: eks-access-to-gcp
containers:
- image: amazon/aws-cli
command:
- "sleep"
- "604800"
imagePullPolicy: IfNotPresent
name: awscli
restartPolicy: Always
Podを作成した後、Podに入ってAWS CLIを実行してみます。
$ kubectl apply -f ./awscli-pod.yaml -n default
# Podに環境変数がセットされていることが確認できる
$ kubectl exec -n default awscli env | grep AWS
AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token
AWS_DEFAULT_REGION=us-east-2
AWS_REGION=us-east-2
AWS_ROLE_ARN=arn:aws:iam::123456789012:role/eksctl-EKS-HOGEHOGE-addon-iamserviceaccount-Role1-1LMY230X5R55Z
# Podに入ってAWS CLIを実行、S3 APIが叩けていることを確認
$ kubectl exec -it awscli -n default -- /bin/bash
bash-4.2# aws s3 ls
2022-10-27 10:45:41 foo-bucket
2022-10-26 08:27:36 bar-bucket
(GCP)Workload Identity Poolの作成
EC2インスタンスと同じです。今回はEC2インスタンスで作ったものを使います。
(GCP)Workload Identity Providerの作成
EKSクラスタのIAM OIDC プロバイダのURLを取得しておきます。
$ CLUSTER="EKS-HOGEHOGE"
$ aws eks describe-cluster \
--name $CLUSTER \
--query "cluster.identity.oidc.issuer" --output text
https://oidc.eks.us-east-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E
aws-oidc-provider
というWorkload Identity Providerを作成します。属性マッピング(--attribute-mapping
)のattribute.ksa_name
は特定のKSAからしか許可しないように指定しています。
$ PROVIDER="aws-oidc-provider"
$ POOL="bq-pool"
$ ISSUER_URL="https://oidc.eks.us-east-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E"
$ gcloud iam workload-identity-pools providers create-oidc $PROVIDER \
--location="global" \
--workload-identity-pool=$POOL \
--attribute-mapping="google.subject=assertion.sub,attribute.ksa_name=assertion.sub.extract('system:serviceaccount:{ksa_name}')" \
--issuer-uri=$ISSUER_URL \
--allowed-audiences=sts.amazonaws.com
(GCP)サービスアカウントの作成
access-from-aws-eks-pod
というサービスアカウントを作成します。
$ SERVICE_ACCOUNT="access-from-aws-eks-pod"
$ gcloud iam service-accounts create $SERVICE_ACCOUNT \
--description="Access from Amazon EKS with workload identity federation"
今回はBigQueryにクエリ実行するため、以下の権限も付与します。BigQueryを利用しない場合はこの作業は不要です。
$ PROJECT_ID="foo-project"
$ SERVICE_ACCOUNT="access-from-aws-eks-pod"
# BigQueryデータ閲覧者(BigQuery Data Viewer)の権限を付与する
$ gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:${SERVICE_ACCOUNT}@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/bigquery.dataViewer"
# クエリ実行するための権限(BigQueryジョブユーザー、BigQuery読み取りセッションユーザー)を付与する
$ gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:${SERVICE_ACCOUNT}@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/bigquery.jobUser"
$ gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:${SERVICE_ACCOUNT}@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/bigquery.readSessionUser"
(GCP)Workload Identity Poolとサービスアカウントの連携
EC2のときと同じですが、--member
オプションが少し異なっている点に注意してください。
$ PROJECT_NUMBER="123456789012"
$ PROJECT_ID="foo-project"
$ SERVICE_ACCOUNT="access-from-aws-eks-pod"
$ POOL="bq-pool"
$ KSA_NAME="default:eks-access-to-gcp"
$ gcloud iam service-accounts add-iam-policy-binding "${SERVICE_ACCOUNT}@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL}/attribute.ksa_name/${KSA_NAME}"
(GCP)認証構成ファイルの取得
認証に必要な構成ファイルを取得します。
$ PROJECT_ID="foo-project"
$ PROJECT_NUMBER="123456789012"
$ SERVICE_ACCOUNT="access-from-aws-eks-pod"
$ POOL="bq-pool"
$ PROVIDER="aws-oidc-provider"
$ gcloud iam workload-identity-pools create-cred-config \
"projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL}/providers/${PROVIDER}" \
--subject-token-type="urn:ietf:params:oauth:token-type:jwt" \
--service-account="${SERVICE_ACCOUNT}@${PROJECT_ID}.iam.gserviceaccount.com" \
--credential-source-file="/var/run/secrets/eks.amazonaws.com/serviceaccount/token" \
--credential-source-type="text" \
--output-file="config-aws-eks-provider.json"
--output-fileで指定したファイル名(config-aws-eks-provider.json
)に生成されます。このファイルの中身は機密情報が含まれていないため、サービスアカウントキーよりも管理を厳密にする必要はありません。生成されたconfig-aws-eks-provider.json
は以下のようになります。
{
"type": "external_account",
"audience": "//iam.googleapis.com/projects/123456789012/locations/global/workloadIdentityPools/bq-pool/providers/aws-oidc-provider",
"subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
"token_url": "https://sts.googleapis.com/v1/token",
"credential_source": {
"file": "/var/run/secrets/eks.amazonaws.com/serviceaccount/token",
"format": {
"type": "text"
}
},
"service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/access-from-aws-eks-pod@foo-project.iam.gserviceaccount.com:generateAccessToken"
}
アプリケーションの実行
サンプルコード(query_bq_eks.py)
query_bq_ec2.py
と同じです。
import os
from google.cloud import bigquery
project_id = 'ohsawa0515-cloud-identity'
bqclient = bigquery.Client(project=project_id)
dryrun = False
sql = """
SELECT
*
FROM
`ohsawa0515-bq-nyumon.my_open_data.covid19_open_data_japan`
WHERE
date = "2022-08-11"
LIMIT
1000
"""
job_config = bigquery.QueryJobConfig(dry_run=dryrun)
query_job = bqclient.query(sql, job_config)
assert query_job.errors is None, f'errors: {query_job.errors}'
# debug
print(f"Bytes processed: {query_job.total_bytes_processed} B")
# 先頭5行分のみ出力
df = query_job.result().to_dataframe()
print(df.head())
config-aws-eks-provider.json
とサンプルコード(query_bq_eks.py
)を同梱したコンテナイメージ(bq-test
)をAmazon ECRにプッシュしておきます。
以下のようなマニフェストファイルを作ります(oidc-test.yaml
)。serviceAccountName
に作成したKSA、環境変数GOOGLE_APPLICATION_CREDENTIALS
に認証構成ファイルのパスを指定します。
apiVersion: v1
kind: Pod
metadata:
name: oidc-test
labels:
app: oidc-test
spec:
serviceAccountName: eks-access-to-gcp
containers:
- image: 123456789012.dkr.ecr.us-east-2.amazonaws.com/bq-test:latest
env:
- name: GOOGLE_APPLICATION_CREDENTIALS
value: "/app/config-aws-eks-provider.json"
imagePullPolicy: IfNotPresent
name: bq-test
restartPolicy: Always
kubectl apply
で実行した後、実行結果からBigQueryにアクセスできていればOKです。
$ kubectl apply -f ./oidc-test.yaml -n default
異なるKubernetes Service Accountからアクセスしてみる
今回作成したKSAのと別のを作成し、BigQueryへのアクセスを試みてアクセスできないことを確認します。
$ CLUSTER="EKS-HOGEHOGE"
$ KSA_NAME="hogehoge-ksa"
$ eksctl create iamserviceaccount \
--name $KSA_NAME \
--cluster $CLUSTER \
--attach-policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess \
--approve
# 確認
$ kubectl get serviceaccounts
NAME SECRETS AGE
default 1 2d
eks-access-to-gcp 1 47h
hogehoge-ksa 1 37s
$ kubectl get serviceaccounts hogehoge-ksa --output yaml
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/eksctl-EKS-HOGEHOGE-addon-iamserviceaccount-Role1-JK04WG7AF9ZK
creationTimestamp: "2022-10-29T11:24:16Z"
labels:
app.kubernetes.io/managed-by: eksctl
name: hogehoge-ksa
namespace: default
resourceVersion: "563691"
uid: 574c9f9e-57ee-4a22-9b62-a845d92caea4
secrets:
先ほど作成したマニフェストファイル(oidc-test.yaml
)のserviceAccountName
をhogehoge-ksa
に差し替えてkubectl apply
するとPermission Deniedになることが確認できます。
Traceback (most recent call last):
...(省略)
google.auth.exceptions.RefreshError: ('Unable to acquire impersonated credentials', '{\n "error": {\n "code": 403,\n "message": "The caller does not have permission",\n "status": "PERMISSION_DENIED"\n }\n}\n')
参考URLリンク
- Workload identity federation | IAM Documentation | Google Cloud
- Obtaining short-lived credentials with identity federation | IAM Documentation | Google Cloud
- Enable keyless access to GCP with workload Identity Federation | Google Cloud Blog
- AWS - GCP の ID 連携を使い、 AWS CodeBuild で Terraform を使って GCP を管理 - スタディサプリ Product Team Blog
- Workload Identityを用いてEKSクラスタからGoogle Cloudへアクセスする|クラウドテクノロジーブログ|ソフトバンク
- Workload Identity Federationを図で理解する - Carpe Diem
- GCP Service Account を AWS から STS 認証で利用する設定例 - Qoosky
- AWS の IAM ロールと GCP のサービスアカウントを紐付けて鍵なしでアクセスする方法 | yuu26-memo
- aws fargate - connection error from aws fargete to gcp bigquery by using Workload Identity - Stack Overflow
- Incorrect AWS metadata server path when running on Fargate · Issue #1099 · googleapis/google-auth-library-python
- Workload Identity連携を使ってAWS LambdaからCloud Spannerにパスワードレスでアクセスしてみた | DevelopersIO
- サービスアカウントの IAM ロール - Amazon EKS
Discussion