つい最近Lambda関数の実装をしていまして、その中で困ったことと解決したことがあったので、自分への備忘も兼ねて共有します。
一応エチケットシートとして。この記事は投稿時点(2021/6/20時点)において検証した結果に基づいており、今後Amazon Web ServicesのBoto3ライブラリの仕様が変更された場合には、必ずしもこの検証通りの結果にならない可能性があることだけ、ご了承ください。
やろうとしていたこと
- Amazon S3バケットからS3.PutObjectを行う処理のみを、Lambda関数のレイヤーとして切り出す
- 上記で作成したレイヤーをimportしているビジネスロジックの部分をLambda関数上で実装する
- レイヤー、Lambda関数ともにPython3.8で実装
- Lambda関数を実行すると、S3バケットにファイルが格納される
ちなみにレイヤーのコードはざっくりこんな感じです。もうほとんどBoto3のドキュメントのお作法そのまんまです。
import boto3
def put_object(fileObject, bucketName, filePrefix, Key):
if filePrefix != '':
key = filePrefix + Key
s3 = boto3.resource('s3')
object = s3.Object(bucketName, Key)
response = object.put(
Body=fileObject,
ContentEncoding='utf-8',
ContentType='text/plain'
)
困ったこと
このレイヤーを含むLambda関数をリリースして、同じAWSアカウント内にS3バケットを作成。ブロックパブリックアクセスは有効にしています。
ブロックパブリックアクセスを有効にしている場合でも、デフォルトでは特に作成したS3バケットへのファイルの格納は普通にできる、と思っていたのですが、いざLambda関数を実行してみると、ファイルが格納されないわけです。で、デバッグのためにCloudWatch Logsの該当ロググループを確認すると、
botocore.exceptions.ClientError: An error occurred (AccessDenied) when calling the PutObject operation: Access Denied
お決まりのAccess Deniedが出力されているわけで。
危ないと知りながらも、一旦S3バケットのブロックパブリックアクセスを無効にしてみたり、それでも解決しないので、再度ブロックパブリックアクセスを有効にして、Lambdaの実行ロールに対してPutObjectとPutObjectACLを有効にしてあげるバケットポリシーを書いてみたりしてもどうにも解決せず、途方に暮れていました。
解決方法
鍵はバケットポリシーの書き方に違いない! というところまではなんとなく勘がついていたので、試しに以下のようにバケットポリシーを書き換えてあげたら、普通に該当するS3バケットにファイルが格納されるようになったではないですか!
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:sts::(AWSアカウント):assumed-role/(レイヤー名)",
"arn:aws:iam::(AWSアカウント):role/(Lambda関数の実行ロール)",
"arn:aws:sts::(AWSアカウント):assumed-role/(Lambda関数の実行ロール)/(Lambda関数名)"
]
},
"Action": "s3:*",
"Resource": "arn:aws:s3:::(S3バケット名)/*"
}
]
}
どうも、同一アカウント内でS3バケットに対する操作を行う場合でも、Lambda関数とレイヤーからの操作を受け入れるためのassumed roleを明示的に書かないと、S3バケットに対する操作ができないようです。
今回の構成では、Amazon S3向けのVPC Endpointは実装していないので、Lambda関数からS3へのアクセスの通信が、一旦インターネットに出ていっているが故に、明示的にこういったポリシーの設定をしてあげないとダメなのかもしれません。まだVPC Endpointを設定していないので、断定的なことは言えないのですが。
まぁとにかくやりたいことは実現できたのですが、Amazon S3とLambda関数の関係は、まだまだ勉強しなければいけないですね。。。日々精進です。