aws s3でオブジェクトkeyの先頭にスラッシュ(/)をつけるとどうなるか?

これはなに

  • s3はbucketとkeyの組み合わせでオブジェクトを保存している
  • このbucketとkeyを s3://$bucket/$key のように組み合わせると S3URIとなる
  • keyの先頭のslashがついているとき、aws cliを含む一部のツールでは 先頭のslashを削除する場合がある。
  • この仕様についての記載はaws cli側のドキュメントでは見つからなかったが、本記事では go-sdk での命名に習ってURI cleaningと呼ぶことにする。
  • この場合、ファイルは存在するように見えるが取得しようとすると NoSuchKey が返却されるといった事象が起きる可能性があるので、それについて解説する

HeadObjectの仕様

まずファイルの存在確認ではHeadObjectを利用することが多いので、そこの仕様を確認する。

HeadObject - Amazon Simple Storage Service を読むと

Permissions

You need the relevant read object (or version) permission for this operation. For more information, see Specifying Permissions in a Policy. If the object you request does not exist, the error Amazon S3 returns depends on whether you also have the s3:ListBucket permission.

If you have the s3:ListBucket permission on the bucket, Amazon S3 returns an HTTP status code 404 ("no such key") error.

If you don’t have the s3:ListBucket permission, Amazon S3 returns an HTTP status code 403 ("access denied") error. 

とある。つまり

  • ListBucket権限がありファイルがないなら404
  • ListBucket権限がないなら403 (access denied)

が返ってくる。

aws java-sdk v2 などでは 404の場合 NoSuchKeyExceptionthrowされ、それ以外の場合は SdkClientException などがthrowされる。

そのため NoSuchKeyException がthrowされている場合は、権限不足ではなくファイルが実際に存在していないことを疑ってもよいだろう。

HeadObjectをcliで叩く

HeadObjectをaws-cliで叩くためには通常の aws s3 <command> ではなく aws s3api <command> という低レベルAPIを直接たたくコマンドを利用する必要がある。(詳細は AWS CLI での API レベル (s3api) コマンドの使用 - AWS Command Line Interface)

head-objectコマンドを利用する場合はkeyとbucketを以下のように分けて指定することが可能なので、先頭にスラッシュを含まないファイルと含むファイルの存在を以下のようにチェックすることができる。

# keyの先頭にスラッシュなし
aws s3api --profile ... head-object --bucket some-bucket --key some/key/foo.json

# keyの先頭にスラッシュあり
aws s3api --profile ... head-object --bucket some-bucket --key /some/key/foo.json

もし後者の先頭スラッシュつきでファイルが存在するレスポンスが返ってきたらURI cleaningの影響でファイルが見つからない状態になっている可能性が高い。

余談

  • 基本的にHeadObjectはGetのbodyを取得しない版と同等なので性能を気にしなくていい場合はaws s3 cp $file . などでファイルを取得してもよい。
  • 今回はURI cleaningを避けるためにs3URI形式ではなくbucket, keyを分割して渡すようにする目的で低レベルAPIを利用している。

各ツールの仕様

java-sdk

java-sdkでは、bucket, keyを分けて put, get する仕様になっている。

このとき、key側の先頭slashが消されると言った挙動は(少なくともHeadObjectでは)確認できなかったのでURI cleaningの罠を踏むことはなさそう。

val request = HeadObjectRequest.builder().bucket("some-bucket").key("some-key").build()

go-sdk

go-sdkでもbucket, keyを分けてput, getする仕様になっている。

しかし Automatic URI cleaning という仕様があり、key先頭のslashは削除される。

この挙動は DisableRestProtocolURICleaning をtrueに設定すると回避できる。

svc := s3.New(sess, &aws.Config{
      DisableRestProtocolURICleaning: aws.Bool(true),
})
out, err := svc.GetObject(&s3.GetObjectInput {
      Bucket: aws.String("bucketname"),
      Key: aws.String("//foo//bar//moo"),
})

aws-cli

aws-cliでは s3://$bucket/$key という形式でget, putすることになる。

このとき key先頭のslashが削除されるために先頭にslashが入っているオブジェクトは取得ができないことがある。

なお、先頭slashは1個だけ削除される仕様ではなく、複数個削除されるようになっているため、以下のように $bucket//$key のような文字列を指定しても、ファイルは存在しない扱いになる。

$ aws s3 --profile ... ls s3://some-bucket//some/key/foo.json

web-console

試してないので誰か教えて ( ◜◡‾)

結論

keyの先頭にスラッシュをつけるとファイルを手動操作したい場合に余計な混乱を呼ぶのでやめたほうが無難そう。

※ s3から静的ファイルを配信する際のURLも難しくなってしまうので、そういう意味でも避けたほうが良さそう