猫でもわかるWebプログラミングと副業

本業エンジニアリングマネージャー。副業Webエンジニア。Web開発のヒントや、副業、日常生活のことを書きます。

AWScala で S3 にファイルを put しようとしてAccess Denied Status Code: 403;

f:id:yoshiki_utakata:20201225212533p:plain

AWScala

Scala から AWS S3 にファイルをアップロードするのに、 AWScala というライブラリを利用していました。

github.com

S3 のライブラリを利用するには、 build.sbt の libraryDependencies に以下を追加します。

"com.github.seratch" %% "awscala-s3" % "0.8.4",

しかし、若干ハマりどころがあったのでまとめておきます。

AWS IAM の設定

まず、AWS IAM で、特定のバケットのみのアクセスを許可したユーザーを作成します。権限設定は以下の通り。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::バケット名",
                "arn:aws:s3:::バケット名/*"
            ]
        }
    ]
}

これについては以下の記事にもまとめたとおりです。

www.utakata.work

Scala のコード

上記 IAM の権限をつけたユーザーのアクセスキーを発行し、以下のようにコードを書きました。

import java.io.File
import awscala.s3.{Bucket, S3}
import awscala.Region

val accessKeyId: String = "XXXXX"
val secretAccessKey: String = "XXXXX"
val bucketName: String = "バケット名"

implicit val s3: S3 = S3(accessKeyId, secretAccessKey)(Region.AP_NORTHEAST_1) 
val bucket: Bucket = s3.bucket(bucketName).get

val file = new File("ファイルパス")
bucket.put(convertedImage.id.toString, file)

しかし、このコードはエラーになります。

com.amazonaws.services.s3.model.AmazonS3Exception: 
Access Denied (
 Service: Amazon S3;
 Status Code: 403;
 Error Code: AccessDenied;
 ...

試しに AWS CLI をつかって S3 にアクセスするとエラーになりません。これでエラーになる場合は権限が正しく付与されていないのですが、アップロードは成功しているので、場決tの権限は正しく付与されています。

$ aws s3 cp ~/Downloads/IMG_0755.JPG s3://バケット名/ファイル名 --profile lgtmoon_dev
upload: Downloads/IMG_0755.JPG to s3://lgtmoon-dev/test

エラーにならない方法

implicit val s3: S3 = S3(accessKeyId, secretAccessKey)(Region.AP_NORTHEAST_1)
val bucket = new Bucket(bucketName)

val file = new File("ファイルパス")
s3.put(bucket, convertedImage.id.toString, file)

val bucket: Bucket = s3.bucket(bucketName).get を使うとエラーになるので、

val bucket = new Bucket(bucketName) とし、 s3.put メソッドに bucket を渡す形にしたらエラーがでなくなりました。

なぜエラーになるのか

s3.bucket(bucketName) は、内部では S3 の API でバケット一覧を取得し、その中から指定したバケット名のバケットを取得する、という処理になっています。

def bucket(name: String): Option[Bucket] = buckets.find(_.name == name)

しかし、アカウントにはバケットの中身にアクセスする権限しか与えられていません。バケット名一覧を取得する権限は与えてないのです。

そのため、エラーになっていました。

まとめ

AWScala、完成度がそんなに高くない気がしていて、結構罠がある気がするので、気をつけたほうがよさそうです。