概要
S3 にファイルが置かれたら Lambda 関数で読み込んで変換し、別のバケットへ書き出す。よくある構成ですが、作ってみると関数のログに AccessDenied が出て、S3 の読み書きができないことがあります。コードもバケット名も合っているのに動かない場合、まず疑うのは関数がどの権限で動いているかです。
Lambda 関数が AWS のサービスを呼ぶときの権限は、関数に割り当てた 実行ロール(IAM ロール)で決まります。S3 や CloudWatch Logs への権限を書いた IAM ポリシーを用意しても、それを実行ロールにアタッチしなければ関数には効きません。IAM ユーザーを作ってアクセスキーを環境変数に入れる、という方法は使いません。
この記事では、実行ロールの仕組みと、権限を渡す方法の正しい・誤りを整理します。実践では、S3 トリガーで動く関数がまず AccessDenied で失敗することを確かめ、用意したポリシーを実行ロールにアタッチして成功させます。

クラウドおさる荷運びの若猿(Lambda)に「倉庫から荷を出せ」と言っても、倉庫番に見せる役目札(実行ロール)を持たせていなければ追い返されるのでござる。今日はその役目札を正しく持たせるでござるよ。
この記事のメリット
- Lambda 関数の権限が実行ロールで決まる仕組みを説明できるようになる
- IAM ユーザーやアクセスキーではなく、実行ロールで権限を渡すべき理由が分かる
- S3 トリガーで動く関数の
AccessDeniedを、実行ロールのポリシーから切り分けられるようになる - 用意済みの IAM ポリシーを実行ロールにアタッチできるようになる
執筆者とキャラクター紹介
執筆者:土肥(とひ)
株式会社ジェニュインの代表であり元エンジニア。これまでオンプレミスと AWS 環境のインフラ業務を担当。AWS 認定資格の学習で得た知識を、ハンズオンとともに解説するのが得意。直近では新人の教育も行っており、これから技術を学ぶ方が納得できる解説を心がけています。基礎編・Cloud Practitioner の記事では、解説役としても登場します。


クラウドおさる



はじめましてでござる。弊猿はクラウドおさる。IT インフラが異常に発達した猿山から、雲に乗って地上の AWS を学びに降りてきたのでござる。



基礎編や Cloud Practitioner の記事では、とひさんに素朴な疑問をぶつける役でござるな。Security - Specialty などの記事では、要所でワンポイントや注意点をひとこと添えるでござる。



猿山にもデータセンターや監視当番があって、地上で起きることはだいたい猿山にも同じ仕組みがあるのでござる。例え話がつい猿山に寄るのは、弊猿の癖でござる。
技術解説
実行ロールとは
実行ロールは、Lambda 関数に AWS のサービスやリソースへのアクセスを許可する IAM ロールです。関数を作るときに指定し、関数が呼び出されると Lambda がこのロールを引き受け、一時的な認証情報で SDK の呼び出しを行います。コードの中で認証情報を扱う必要はありません。
実行ロールには 2 種類のポリシーが関わります。
| ポリシー | 書いてあること |
|---|---|
| 信頼ポリシー | このロールを引き受けてよいのは lambda.amazonaws.com(Lambda のサービス) |
| 許可ポリシー | 関数が実行してよい操作(例: s3:GetObject、logs:PutLogEvents) |
コンソールで関数を作るときに「基本的な Lambda アクセス許可で新しいロールを作成」を選ぶと、CloudWatch Logs への出力だけを許可したロールが自動で作られます。S3 などの権限は、自分で足す必要があります。
権限を渡す方法の比較
| 方法 | 結果 |
|---|---|
| IAM ポリシーを実行ロールにアタッチし、そのロールを関数に割り当てる | 正しい。Lambda がロールを引き受けて権限が効く |
| IAM ユーザーを作り、ポリシーをユーザーに付けて「関数に割り当てる」 | できない。関数に割り当てられるのはロールだけ |
| IAM ユーザーのアクセスキーを環境変数に入れる | 動くことはあるが、長期の認証情報が設定に残り、漏えいとローテーションの問題を抱える |
| ロールの名前や ARN を環境変数に入れる | 権限は付かない。関数が使うのは「実行ロール」の設定だけ |



「鍵(アクセスキー)を荷札に書いて若猿に持たせる」のは、落としたら倉庫が開けっぱなしになるのと同じでござる。権限は役目札、つまり実行ロールで渡すのが正解でござるぞ。
AccessDenied のときの確認ポイント
S3 トリガーで動く関数が AccessDenied になったら、次の順に確認します。
- 関数の実行ロール: 「設定」→「アクセス権限」で、どのロールで動いているかを確認する
- ロールの許可ポリシー: そのロールに、必要な操作(読むなら
s3:GetObject、書くならs3:PutObject)が、正しいバケットの ARN(arn:aws:s3:::バケット名/*)で許可されているか - バケットポリシー: バケット側で明示的に拒否していないか
- 暗号化: バケットが SSE-KMS のカスタマー管理キーで暗号化されているなら、キーの
kms:Decrypt(読み取り)・kms:GenerateDataKey(書き込み)も要る
S3 から Lambda を呼び出す許可(関数のリソースベースポリシー)は、関数が S3 を読み書きする権限とは別物です。コンソールで S3 トリガーを追加すると、呼び出しの許可は自動で入りますが、実行ロールの権限は増えません。


実践
S3 の転送元バケットに .txt ファイルが置かれたら、内容を大文字に変換して出力先バケットへ書き出す Lambda 関数 dva-convert-fn を作ります。関数はコンソールの既定の実行ロールで作るので、最初は S3 の権限がありません。AccessDenied を確かめてから、用意済みのポリシー dva-convert-s3-policy を実行ロールにアタッチします。
前提条件
- リージョン:
ap-northeast-1(東京) - 以下のリソースが作成済みであること
まだ用意していない場合は、「作り方の参考」の記事の手順で作れます。リソース名は、この記事の名前に読み替えてください。
| リソース種別 | リソース名 | 内容 | 作り方の参考 |
|---|---|---|---|
| S3 バケット(転送元) | dva-convert-src-<アカウントID> | 変換前のテキストファイルを置く | Amazon S3 でバケットを作成しオブジェクトを管理する |
| S3 バケット(出力先) | dva-convert-dst-<アカウントID> | 変換後のファイルを置く | 同上 |
| IAM ポリシー(カスタマー管理) | dva-convert-s3-policy | 転送元の s3:GetObject、出力先の s3:PutObject、CloudWatch Logs への出力を許可 | AWS IAM でユーザー・ロール・ポリシーを作成しアクセス制御する |
作成するリソース一覧
| リソース種別 | リソース名 | 用途 |
|---|---|---|
| Lambda 関数 | dva-convert-fn | テキストファイルを変換して出力先に書く |
| IAM ロール | dva-convert-fn-role-xxxxxxxx(自動作成) | 関数の実行ロール |
| S3 トリガー | (転送元バケット・サフィックス .txt) | ファイルの配置で関数を呼ぶ |
手順 1: 関数を作る
AWS マネジメントコンソールにログインし、リージョンが ap-northeast-1(東京)であることを確認します。上部の検索バーに Lambda と入力し、「Lambda」を選択します。
「関数を作成」をクリックし、以下を設定します。
- 「一から作成」を選択
- 関数名:
dva-convert-fn - ランタイム:
Python 3.13 - 実行ロール: 「その他の設定」の「カスタム実行ロール」はオフ(デフォルト)のまま。CloudWatch Logs への出力だけを許可する実行ロールが自動で作られます


画面下部の「関数を作成」をクリックします(作成後に案内のダイアログが出たら閉じます)。「コード」タブのエディタで lambda_function.py を次の内容に置き換え、「Deploy」をクリックします。DST_BUCKET の <アカウントID> は自分のアカウント ID に置き換えます。
import urllib.parse
import boto3
s3 = boto3.client("s3")
DST_BUCKET = "dva-convert-dst-<アカウントID>"
def lambda_handler(event, context):
for record in event["Records"]:
src_bucket = record["s3"]["bucket"]["name"]
key = urllib.parse.unquote_plus(record["s3"]["object"]["key"])
body = s3.get_object(Bucket=src_bucket, Key=key)["Body"].read().decode("utf-8")
converted = body.upper() # 変換処理の代わりに大文字にする
out_key = key.rsplit(".", 1)[0] + ".converted.txt"
s3.put_object(Bucket=DST_BUCKET, Key=out_key, Body=converted.encode("utf-8"))
print(f"converted s3://{src_bucket}/{key} -> s3://{DST_BUCKET}/{out_key}")手順 2: S3 トリガーを追加する
関数の概要の「トリガーを追加」をクリックし、以下を設定します。
- ソース:
S3 - バケット:
dva-convert-src-<アカウントID>(一覧から選んで「入力が無効です」と表示される場合は、arn:aws:s3:::dva-convert-src-<アカウントID>と ARN で入力します) - イベントタイプ: 「すべてのオブジェクト作成イベント」
- サフィックス – オプション:
.txt - 「再帰呼び出し」の確認のチェックボックスにチェック(出力先は別のバケットなので、再帰にはなりません)


「追加」をクリックします。
手順 3: ファイルを置いて AccessDenied を確かめる
S3 のコンソールで dva-convert-src-<アカウントID> を開き、次の内容の hello.txt をアップロードします。
hello from the banana warehouseLambda のコンソールに戻り、dva-convert-fn の「モニタリング」タブ →「CloudWatch Logs を表示」をクリックして、最新のログストリームを開きます。An error occurred (AccessDenied) when calling the GetObject operation のエラーが記録されています。トリガーで関数は呼ばれたものの、関数が転送元バケットを読めなかったことが分かります。





若猿は呼ばれて倉庫まで来たのに、役目札に「この倉庫の荷を出してよい」と書いていないので、倉庫番に断られたのでござるな。呼ぶ許可と、荷を出す許可は別物でござる。
手順 4: 実行ロールにポリシーをアタッチする
dva-convert-fn の「設定」タブ →「アクセス権限」を選択します。「実行ロール」に表示されているロール名(dva-convert-fn-role- で始まる名前)をクリックすると、IAM コンソールでロールが開きます。「許可ポリシー」には、CloudWatch Logs への出力だけを許可するポリシーが付いています。
「許可を追加」→「ポリシーをアタッチ」を選択します。検索欄に dva-convert-s3-policy と入力し、チェックを入れて「許可を追加」をクリックします。


手順 5: もう一度ファイルを置く
S3 のコンソールで、転送元バケットに hello.txt をもう一度アップロードします(同じ名前で上書きすると、新しい作成イベントとして関数が呼ばれます)。
出力先の dva-convert-dst-<アカウントID> を開くと、hello.converted.txt が作られています。オブジェクトを開いて「開く」をクリックすると、内容が HELLO FROM THE BANANA WAREHOUSE になっていることを確認できます。CloudWatch Logs にも converted s3://... -> s3://... のログが記録されます。





コードもトリガーも同じまま、役目札にポリシーを足しただけで荷が運べたでござる。Lambda の AccessDenied は、まず実行ロールを見に行く。これでござるな。
まとめ
| 困りごと | 確認するところ |
|---|---|
関数のログに S3 の AccessDenied が出る | 実行ロールの許可ポリシー(操作とバケットの ARN) |
| SSE-KMS のバケットで読めない・書けない | 実行ロールの kms:Decrypt / kms:GenerateDataKey と、キーポリシー |
| S3 から関数が呼ばれない | トリガーの設定と、関数のリソースベースポリシー(呼び出しの許可) |
- Lambda 関数の権限は実行ロール(IAM ロール)で決まる。関数の呼び出し時に Lambda がロールを引き受ける
- 権限は IAM ポリシーを実行ロールにアタッチして渡す。IAM ユーザーを関数に割り当てることはできない
- アクセスキーを環境変数に入れるのは避ける。ロール名を環境変数に入れても権限は付かない
- コンソールの既定のロールは CloudWatch Logs への出力だけ。S3 などの権限は自分で足す
- S3 → Lambda の呼び出し許可(リソースベースポリシー)と、Lambda → S3 の読み書きの権限(実行ロール)は別物
参照先
- 実行ロールを使用した Lambda 関数のアクセス許可の定義 – AWS Lambda
- AWS Lambda アクセス許可の管理 – AWS Lambda
- Lambda を使用した Amazon S3 イベント通知の処理 – AWS Lambda
- チュートリアル: Amazon S3 トリガーを使用して Lambda 関数を呼び出す – AWS Lambda
- Amazon S3 でのアクセス拒否 (403 Forbidden) エラーのトラブルシューティング – Amazon Simple Storage Service














