クロスアカウントのS3へ署名付きURLでアップロードする

別のAWSアカウントに存在するS3に対するPUT用の署名付きURL(Presigned URL)を発行し、ファイルのアップロードを行う。その際に、オブジェクト所有権を引き継ぐ設定を行う。
2021.11.19

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

データアナリティクス事業本部、池田です。
クロスアカウントS3署名付きURL でファイルをアップロードする仕組みを作る必要があったのですが、 オブジェクト所有権の辺りで少し苦労したのでブログにします。

オブジェクト所有権については以下の弊社ブログが分かりやすかったです。

[アップデート] オブジェクト所有権でもう悩まない!S3 バケット所有者がアップロード時に自動的にオブジェクト所有権を引き継げるようになりました。

PUTでファイルをアップロードする

今回はAWS Lambda上のPython(boto3)で署名付きURLを発行します。 アップロードは何でも良いのですが、 curlコマンド で簡単に確認してみます。

↓Lambdaのコード(Python 3.9)はこんな感じです。

import boto3
from botocore.client import Config

def lambda_handler(event, context):
    S3_BUCKET_NAME = "{バケット名}"
    FILE_NAME = "{パスとファイル名}"

    s3 = boto3.client("s3", config=Config(signature_version="s3v4"))
    url = s3.generate_presigned_url(ClientMethod="put_object",
                                    Params={"Bucket": S3_BUCKET_NAME,
                                            "Key": FILE_NAME,
                                            "ACL": "bucket-owner-full-control"},
                                    ExpiresIn=3600,
                                    HttpMethod="PUT")
    return url

↑結果としてURLが取得できるので、
↓それを使ってファイルをアップロードします。

put_url='{取得したURL}'
target_file='{パスとアップロードするファイル名}'

curl -X PUT \
-H 'x-amz-acl:bucket-owner-full-control' \
--upload-file $target_file \
$put_url

Pythonの方の12行目で、ACLを指定してURLを発行しています。 ( generate_presigned_url()ドキュメント を見ても「Params (dict) -- The parameters normally passed to ClientMethod.」 としか書いていなかったので手探りの実装でした…)

アップロードの際はヘッダーに x-amz-acl を加えてリクエストを実行します。
ちなみにこのヘッダーが無いと以下のようなエラーになりました。
<Error><Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>…</Error>

POSTでファイルをアップロードする

generate_presigned_url() を使ったPUTでのアップロードはよくある実装かと思うのですが、 ファイルのアップロードはPOSTのメソッドでもできるようです。 ( generate_presigned_post()

↓こんな感じの実装になるようです。

import boto3
from botocore.client import Config

def lambda_handler(event, context):
    S3_BUCKET_NAME = "{バケット名}"
    FILE_NAME = "{パスとファイル名}"

    s3 = boto3.client("s3", config=Config(signature_version="s3v4"))
    res = s3.generate_presigned_post(Bucket=S3_BUCKET_NAME,
                                     Key=FILE_NAME,
                                     Fields={"acl": "bucket-owner-full-control"},
                                     Conditions=[
                                         {"acl": "bucket-owner-full-control"}
                                     ],
                                     ExpiresIn=3600)
    return res

戻り値としては、リクエストで使用するパラメータ(url・fields)が辞書形式で手に入ります。

↓そのパラメータを使用してリクエストを作成します。

curl -v -X POST \
-F acl={取得したfieldsを設定} \
-F key={取得したfieldsを設定} \
-F x-amz-algorithm={取得したfieldsを設定} \
-F x-amz-credential={取得したfieldsを設定} \
-F x-amz-date={取得したfieldsを設定} \
-F x-amz-security-token={取得したfieldsを設定} \
-F policy={取得したfieldsを設定} \
-F x-amz-signature={取得したfieldsを設定} \
-F file=@{アップロードするファイル名} \
{取得したurlを設定}

(fileは一番最後に設定しないとエラーになりました。)


(2021/12/13追記)
PUT・POST共に、上記のアップロードする側の設定と併せて、 バケット側でもアクセス許可の「オブジェクト所有者」項目を「希望するバケット所有者」(ドキュメント上は「優先されるバケット所有者」)に設定しておく必要があります。
S3 のオブジェクトの所有権を使用したアップロードされたオブジェクトの所有権の管理

おわりに

いろいろ試行錯誤して、最終的にアップロードできました。
初めはPOSTでないとできないかと思っていたのですが、PUTでもできてしまったので両方掲載しました。 (けっきょくPOSTのURLの利用ケースがどういう場合なのか分からないまま終わってしまいました…)

(2021/12/13追記)
以下のようなアップデートがあり、ACLの考慮が不要になりそうです。
Amazon S3 Object Ownership で、S3 内のデータのアクセス管理をシンプル化するためのアクセスコントロールリストの無効化が可能に
以下の動画の解説が分かりやすかったです。
「何が嬉しい?S3 Object ACL無効化」についてre:Growth 2021で登壇しました #reInvent #cmregrowth

関連情報/参考にさせていただいたページ