今回はWorkload Identityを利用してAWS Lambdaでスプレッドシートにアクセスする手順をまとめます。
今回のポイントは以下のとおり
- Workload Identityを利用して、認証のキーやシークレットを使わずに認証を通す
- サービスアカウントの権限を利用(借用)する
- 条件は特定のAssumeRole名とする
- コンソール画面で設定する
- Lambda側はPythonで実装する
- スプレッドシート操作はgspreadライブラリを使用する
なお、以下については記載していませんのであしからず。
- フェデレーションやWorkload Identityの仕組みについての解説
- GCPプロジェクトやLambda関数作成等の基礎的な手順
- Workload Identityを使ってスプレッドシートを取得できてからの処理
GCP内のサービスに直でアクセスする記事はあるのですが、ワークスペースのスプレッドシートへアクセスする手順などが書かれた記事がなかったので、同じ困難にぶつかった人の助けになれば嬉しいです。詳細は聞かれてもわからないかもしれない
準備
まずは準備段階として、以下を実施します。
- AWSアカウントID(12桁の番号)を控えておく
- Lambdaのロール名を決めておく(先に関数を作成してても大丈夫)
- GCPプロジェクトの用意
ロール名はGCP側で登録が必要なため、あらかじめ決めておくと楽です。
名称が長すぎるとエラーになるため、CloudFormationを利用している方はデフォルトの名称にしているとエラー率高いです。
また、GCPプロジェクトはWorkload Identity管理用に専用のプロジェクトを作成することが推奨されています。
詳しくはGoogle Cloud公式ドキュメントの 専用のプロジェクトを使用して Workload Identity プールとプロバイダを管理する を参照してください。
GCP側の設定
まずはGCP側の設定をしましょう。以下の順番で実施します。
- サービスアカウントの作成
- Workload Identity設定
- サービスアカウントの権限借用設定
- IAM設定
サービスアカウントの作成
今回はGoogleワークスペースにアクセスするのでサービスアカウントを使用します。
GCPのサービス(Cloud StrageやらBigQueryやら)を使うだけであれば、サービスアカウントをわざわざ介さなくても問題ないはずです。
まずはサービスアカウントを作成します。
サービスアカウントはIAM管理の中で設定できます。
サービスアカウントを作成し、Googleワークスペース(の中にスプレッドシート)にアクセスできるようアクセス権限を渡してください。
今回はWorkload Identityを利用するので、キーの作成は不要です。
ちなみに、Workload Identityの仕組みはサービスアカウントとしてアクセスするのではなく、サービスアカウントの権限を借用してアクセスする形になるそうです。
手順は解説するほどのことはないので割愛します。
Workload Identity設定
Workload Identityの設定画面はIAM設定の箇所から遷移できます。
遷移したらまずは”プールを作成”ボタンからプールを作成します。
プール名・説明は任意のものを入れてください。
“有効なプール”が有効状態になっていることを確認のうえ、”続行”ボタン。
以下のとおり入力してください
- プロバイダの選択: “AWS”を選択(選択すると以下2つの入力欄が表示されます)
- プロバイダ名: 任意
- AWSアカウントID: 準備のところで控えたAWSのアカウントID
入力したら”続行”で次に進みます。
デフォルトのままでOK
今回は特に”attribute.aws_role: assertion.arn.contents(‘assume-role’)?”が入っていればOKのはずです。
下にある”保存”を押して確定しましょう。
サービスアカウントの権限借用設定
作成したプール・プロバイダでサービスアカウントの権限借用の設定をします。
プールとプロバイダの作成が完了したら、Workload Identity 連携画面の一覧に作成したプールが表示されますので、リンクをクリックしてプールの詳細画面を開いてください。
上部にある”アクセスを許可”ボタンから、サービスアカウントの紐付け設定ができます。
以下のように設定します。
- サービス アカウントの権限借用を使用してアクセス権を付与するを選択
- サービスアカウントを選択する: 作成したサービスアカウント
- プリンシパル
- 属性名: aws_role
- 属性値: LambdaのAssumeRoleのArn(arn:aws:sts::{ID}:assumed-role/{AssumeRoleName})
“保存”を押してして次に進みます。
サービスアカウントは固定で指定されていると思いますので、プロバイダ(プール作成時に追加したもの)を指定して、”構成をダウンロード”を押して完了します。
ここでダウンロードしたものがLambdaから認証を通す際に使用するものなので、適切に管理しましょう。
追記: プリンシパルで指定するロール名について
サービスアカウントの紐付けのところでarn:aws:sts::{ID}:assumed-role/*
と記載すればロール名関係なく設定したAWS環境からアクセスできそうです。
当然セキュリティ的には弱くなるかと思いますので、おすすめはしません。
構成ファイルには機密情報が含まれていない
ここでファイルをダウンロードをするとなると、「結局、秘密鍵の含まれたcredentials.json的なものを管理することになるのでは?」と思うかもしれませんが、ここでダウンロードする構成ファイルには機密情報が含まれていません。
ここがサービスアカウントキーを使用する方法との決定的な違いになります。
GCPの公式ドキュメントでは、以下のように記載されています。
注: サービス アカウント キーとは異なり、認証情報の構成ファイルに秘密鍵が含まれていないため、機密情報として扱う必要はありません。認証情報の構成ファイルの詳細については、https://google.aip.dev/auth/4117 をご覧ください。
Google Cloud – AWS または Azure との Workload Identity 連携を構成する
IAM設定
GCP側最後の設定が、IAMの権限設定です。
“アクセス件を付与”で以下の設定をしてください
- プールの設定
- プリンシパル: principalSet://iam.googleapis.com/projects/{Google Project ID}/locations/global/workloadIdentityPools/{Pool Name}/*
- ロール: Workload Identity ユーザー
- サービスアカウントの設定
- プリンシパル: 作成したサービスアカウントのアドレス
- ロール: サービス アカウント トークン作成者(その他ワークスペース処理で必要な権限)
これでLambdaからWorkload Identityでアクセスした際にサービスアカウントからのトークンが返されるようになり、それをもとにワークスペースへアクセスできます。
Lambda側の設定
GCP側の設定が完了したので、AWS(Lambda)側を作っていきます。
Lambda環境設定
環境設定について解説すると長いので、必要最低限のtemplate.yamlを記載します。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
SampleFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_sample/
Handler: app.lambda_handler
Runtime: python3.9
Architectures:
- x86_64
Environment:
Variables:
GOOGLE_CLOUD_PROJECT: "workload-identity-sample" # Workload Identity管理用に作成したGCPプロジェクト
GOOGLE_APPLICATION_CREDENTIALS: "clientLibraryConfig-sample-pool.json" # ダウンロードしてきた構成ファイルのパス
Timeout: 10
Role: !GetAtt SampleFuncRole.Arn
SampleFuncRole:
Type: AWS::IAM::Role
Properties:
RoleName: "sample-role" # 事前に決めておいたAssumeRole名
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
Service: lambda.amazonaws.com
Policies:
- PolicyName: sample-func-policy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"
今回特に意識すべきはコメントを記載した箇所ぐらいで、プロジェクト名やパスなどを任意で調整してもらえればと思います。
次に、今回使うライブラリをrequirements.txtに記載します。
google-auth
gspread
requests
スクリプトを作成
最後にLambdaのスクリプトを作成します。
ファイル構成とかは何も考えず1ファイルで書いてしまいます。
import os
import json
import gspread
from google.auth import aws
def lambda_handler(event, context):
credentials = get_credentials()
client = gspread.authorize(credentials)
client.open_by_url("Spreadsheet URL") # スプレッドシートをURLから取得する
def get_credentials():
config_path = os.path.dirname(os.path.abspath(__file__)) + "/" + os.environ.get("GOOGLE_APPLICATION_CREDENTIALS")
with open(config_path, 'r') as cred_file:
json_config_info = json.loads(cred_file.read())
credentials = aws.Credentials.from_info(json_config_info)
scoped_credentials = credentials.with_scopes([
'https://spreadsheets.google.com/feeds',
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/spreadsheets'
])
return scoped_credentials
これでスプレッドシートが取得できれば、あとはgspreadライブラリのとおりに使用するだけです。
template.yamlで設定した環境変数部分やスプレッドシートのURLなどは、ご自身で調整してください。
ここまでできたら、あとはデプロイして実行です。
無事にスプレッドシートが取得できれば、完了となります。
まとめ
Workload Identityを利用したアクセスは、外部に漏らしたくないCredentialsファイルなどを持たせる必要がないため、比較的安心して運用できるようになります。
追記したとおりロール名に関係なくアクセスできるようにできましたが、安全性を考えて個別に設定した方が良いのは間違いありません。
プロジェクト毎に権限を分けてアクセスさせる方が一般的でしょうし、それに応じてプールを追加し、アクセスできるロールを個別指定するのが一番綺麗かと思います。
また、今回はGCP側をコンソール上で設定しましたが、CLIで操作すればもっと楽にできるかもしれません。
そのあたりのTipsについても溜まってきたら書いていきたいと思います。