0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

S3のPresigned URLアップロード奮闘記(CORSからIAMまでまとめ)

0
Posted at

S3のPresigned URLによるファイルアップロード実装でハマったことまとめ

S3にPresigned URLでファイルアップロードを実装する中で遭遇した問題を、段階ごとに整理しました。
同じような状況で悩んでいる人の参考になればと思います。


1. はじめに: Node.js Deprecation Warning

最初に以下のような警告が表示されました。

[DEP0060] util._extend is deprecated

これはNode.jsのDeprecation Warningです。

  • 単なる警告
  • 機能には影響なし
  • Object.assign() への置き換えが推奨されている

つまり、この警告は今回のアップロード失敗とは直接関係ありませんでした。

実際の問題とは無関係


2. CORSエラー発生

次に、ブラウザからS3へ直接リクエストした際にCORSエラーが発生しました。

No 'Access-Control-Allow-Origin'

原因

ブラウザからS3へ直接リクエストする場合、S3バケット側にCORS設定が必要です。

今回の原因は、S3バケットにCORS設定が無かったため、ブラウザによってリクエストがブロックされていたことでした。

解決方法

S3バケットの以下の画面からCORSを設定します。

S3 → Permissions → CORS

設定例:

[
  {
    "AllowedOrigins": ["http://localhost:3000"],
    "AllowedMethods": ["GET", "PUT", "POST", "HEAD"],
    "AllowedHeaders": ["*"],
    "ExposeHeaders": ["ETag"]
  }
]

これでブラウザからS3へのリクエストがCORSでブロックされなくなります。


3. CORS解決後もPUTリクエストが失敗

CORS設定を追加したことで、CORSエラー自体は解消されました。

しかし、その後もファイルアップロードのPUTリクエストは失敗しました。

ここで問題はCORSではなく、別の原因に移ります。


4. AccessDeniedエラー

PUTリクエストのレスポンスを見ると、以下のようなエラーが返っていました。

<Error>
  <Code>AccessDenied</Code>
  <Message>
    User: arn:aws:iam::...:user/my-user is not authorized to perform: s3:PutObject
  </Message>
</Error>

原因

エラーメッセージの通り、IAMユーザー my-users3:PutObject 権限がありませんでした。

現在のバケットポリシーでは、以下のように s3:GetObject のみが許可されていました。

"Action": "s3:GetObject"

つまり、読み取りは可能ですが、アップロードはできない状態でした。

解決方法

IAM Userに s3:PutObject 権限を追加します。

例:

{
  "Effect": "Allow",
  "Action": "s3:PutObject",
  "Resource": "arn:aws:s3:::バケット名/uploads/*"
}

これにより、対象パス配下へのアップロードが可能になります。


5. Presigned URLの動作方式

ここで重要なのが、Presigned URLの権限の考え方です。

Presigned URLは、URLを生成した主体の権限で動作します。

つまり、以下のようになります。

Presigned URLを生成したAWS主体 = 権限の基準

フロントエンドからPUTリクエストを送っているユーザーではなく、Presigned URLを生成したIAM UserやIAM Roleの権限が使われます。

そのため、フロントエンド側のユーザーが誰かは本質的には関係ありません。


6. 「なぜ my-user なのか?」問題

実装としてはRoleベースで動かしているつもりでした。

しかし、エラーメッセージには以下のように my-user が表示されていました。

arn:aws:iam::ACCOUNT_ID:user/my-user

原因

ローカル環境で実装したあと、masterにマージすればECS上ではRoleベースで動作する構成でした。

しかし今回の検証時は、ローカル環境で動かしているにもかかわらず、ECSのTask Roleが適用されているものだと思い込んでいました。

そのため、Presigned URLを生成しているAWS主体がECS Roleではなく、ローカルに設定されているIAM Userであることに気づくまで少しハマりました。

この過程で新しく分かったことは、AWS SDKがcredentialを自動的に解決する順番です。

AWS SDKは一般的に、以下のような順番でcredentialを選択します。

  1. 環境変数
  2. ローカルのcredentialsファイル(~/.aws/credentials
  3. AWS_PROFILE
  4. IAM Role

つまり、ローカル環境にIAM Userのcredentialが設定されている場合、ECSのRoleではなく、そのローカルcredentialが使われることがあります。

AWS SDKはcredentialを自動的に解決します。

その際、一般的には以下のような順番でcredentialが選択されます。

  1. 環境変数
  2. ローカルcredentialsファイル
  3. AWS_PROFILE
  4. IAM Role

ローカル環境に ~/.aws/credentials が存在し、そこに my-user のcredentialが設定されている場合、SDKはそのIAM Userを使用します。

そのため、Roleベースで実装しているつもりでも、ローカルではIAM Userのcredentialが優先されていました。


7. ECS Roleとの関係

ECS上で正常にTask Roleが使われている場合、呼び出し元のARNは以下のようになります。

arn:aws:sts::ACCOUNT_ID:assumed-role/ROLE_NAME/SESSION

一方、ローカル環境では以下のようになっていました。

arn:aws:iam::ACCOUNT_ID:user/my-user

理由

ECSとローカルではcredentialの取得元が異なります。

ECS    → Task Roleが自動適用される
ローカル → ローカルのIAM User credentialが使用される

つまり、ECSではRoleが使われますが、ローカルでは ~/.aws/credentials や環境変数に設定されたIAM Userが使われます。


8. 最終的な原因整理

今回の最終的な原因は以下の流れでした。

ローカル環境
↓
AWS SDKが my-user credential を使用
↓
my-user に s3:PutObject 権限がない
↓
Presigned URLもPutObject権限を持たない
↓
S3へのPUTリクエストでAccessDeniedが発生

つまり、問題はPresigned URLそのものではなく、Presigned URLを生成しているAWS主体の権限でした。


9. 解決方法

方法1: ローカルIAM Userに権限を追加する

一番簡単な方法は、ローカルで使われているIAM Userに s3:PutObject 権限を追加することです。

{
  "Effect": "Allow",
  "Action": "s3:PutObject",
  "Resource": "arn:aws:s3:::バケット名/uploads/*"
}

ローカル開発だけであれば、この方法が最も手早いです。


方法2: Roleベースでテストする

本番環境と同じRoleで動作確認したい場合は、aws sts assume-role を使用します。

または、AWS CLIのprofileを設定して、特定のRoleを使うようにします。

例:

aws sts assume-role \
  --role-arn arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME \
  --role-session-name local-test-session

Roleを使ってcredentialを発行し、そのcredentialを環境変数に設定してからアプリケーションを起動します。


方法3: 環境ごとにcredentialを分離する

ローカル環境とECS環境で、credentialの使い方を明確に分ける方法です。

ローカル → IAM User または assume-role
ECS    → Task Role

このように環境ごとに利用するAWS主体を明確にしておくと、原因調査がしやすくなります。


10. デバッグのコツ

AWS関連の権限エラーが出た場合は、まず「今どのAWS主体で実行されているか」を確認するのが重要です。

AWS CLIでは以下のコマンドで確認できます。

aws sts get-caller-identity

コードから確認する場合は、以下のように GetCallerIdentityCommand を使います。

import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";

const client = new STSClient({});

const res = await client.send(new GetCallerIdentityCommand({}));

console.log(res);

出力例:

{
  "UserId": "XXXXXXXXXXXXXXXXXXXXX",
  "Account": "123456789012",
  "Arn": "arn:aws:iam::123456789012:user/my-user"
}

この Arn を見れば、現在どのIAM UserまたはIAM Roleで実行されているかが分かります。


結論

今回の問題の本質は、次の一つでした。

今どのAWS主体でリクエストしているのか

S3 Presigned URLまわりで問題が起きた場合は、以下の3つを切り分けると原因を追いやすくなります。

CORS         → ブラウザとS3の通信設定の問題
AccessDenied → IAM権限の問題
Role vs User → 実行環境とcredential解決の問題

Presigned URLは便利ですが、実際には「URLを生成したAWS主体の権限」に強く依存します。

そのため、アップロードが失敗した場合は、まずCORSだけでなく、Presigned URLを生成しているIAM UserやIAM Roleに s3:PutObject 権限があるかを確認することが重要です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?