【AWS x S3 x IAM】アプリから S3 にデータをアップロードするときの IAM 権限設定

# やりたいこと
- アプリケーションからS3にデータをアップロードする
- 特定の bucket への参照権限だけ，アプリケーションに付与したい

気づいたら S3 の画面UIがガラッと変わっていたので，その辺もまとめなおした．
今回は，Rails X carrierwave で動作確認したが，SDKが使えるアプリケーションなら何にでも適用可能．

# S3 の設定
## bucket作成

誰も取得していない固有バケット名を指定する．
2. Set properties と 3. Set permissions は後で設定するので，一旦 `Create` をクリックして bucket を作成する．

![image.png](https://qiita-image-store.s3.amazonaws.com/0/31862/70d37d3e-a3d4-dead-b9b2-40b33540e287.png)


## CORS configuration

一覧から作成した bucket を選択し，右側メニューの `Permissions` を選択

![image.png](https://qiita-image-store.s3.amazonaws.com/0/31862/52674b2b-71a6-58aa-3440-11ebf8232476.png)


サンプルの policy が設定されているので，下記の通り書き換え，保存する．
保存後 `<CORSConfiguration>` タグには `xmlns="http://s3.amazonaws.com/doc/2006-03-01/"` が自動で追加される．

```
<!-- Sample policy -->
<CORSConfiguration>
	<CORSRule>
		<AllowedOrigin>*</AllowedOrigin>
		<AllowedMethod>GET</AllowedMethod>
		<MaxAgeSeconds>3000</MaxAgeSeconds>
		<AllowedHeader>Authorization</AllowedHeader>
	</CORSRule>
</CORSConfiguration>

<!-- 変更後 -->
<CORSConfiguration>
	<CORSRule>
	<AllowedOrigin>*</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <AllowedMethod>PUT</AllowedMethod>
        <AllowedMethod>POST</AllowedMethod>
        <AllowedMethod>DELETE</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>*</AllowedHeader>
	</CORSRule>
</CORSConfiguration>
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/31862/38a97462-2068-e56f-254b-e2cea3d0aac2.png)

`CORS configuration` が `No` から `Yes` になっていることを確認する．

![image.png](https://qiita-image-store.s3.amazonaws.com/0/31862/dad58cd8-bfe1-6da2-5d1c-2d2300a6be86.png)


## Static web hosting の設定

アップロードしたデータを `https://<バケット名>.s3-ap-northeast-1.amazonaws.com` の形式でアクセスできるようにする．

### Use this bucket to host a website を有効化

`bucket を選択 > Properties` を表示．
`Use this bucket to host a website` を選択し， `Index document` と `Error document` 名を指定する．

![image.png](https://qiita-image-store.s3.amazonaws.com/0/31862/80c15633-2076-1139-7274-3d0b1dc7e893.png)

### index.html, error.html を配置
bucket ルートに， index.html, error.html を追加する．

```html:index.html
<!DOCTYPE html>
<html>
  <body>
    <p>Hello Sample bucket!</p>
  </body>
</html>
```

```html:error.html
<!DOCTYPE html>
<html>
  <body>
    <p>Error...</p>
  </body>
</html>
```

`Static website hosting` が `Disabled` から `Enable website hosting` になっていることを確認する．
![image.png](https://qiita-image-store.s3.amazonaws.com/0/31862/2be3ada0-ac2c-34c2-2c6c-55d2771db566.png)


## Bucket Policy の設定 (※不要だった)
bucket にも policy が設定できるが，今回やりたいことは後述の IAM 設定だけで実現できたため未設定．

![image.png](https://qiita-image-store.s3.amazonaws.com/0/31862/ccadb73e-60ef-afcd-fa08-16ec6e3400b1.png)


# IAM の設定

IAM でアプリケーション用のユーザーを作成する．

## ユーザー追加

`Access Type` は `Programmatic access` を選択する．

![image.png](https://qiita-image-store.s3.amazonaws.com/0/31862/58272b97-74f4-ff74-0e26-764f2d62b61c.png)


## Policy の設定

`Attach existing policy > Create policy > Policy generator`

![image.png](https://qiita-image-store.s3.amazonaws.com/0/31862/4246eed2-e608-73e4-9220-623b6c99505b.png)



![image.png](https://qiita-image-store.s3.amazonaws.com/0/31862/1adfb5ac-bbf1-648e-c8d0-480a5a01d490.png)

次の項目を設定する．

- Effect: `Allow`
- AWS Service: `S3`
- Actions: `s3:GetObject`, `s3:PutObject`, `s3:PutObjectAcl`, `s3:DeleteObject` の4つを指定
- Resource: `arn:aws:s3:::sample-bucket-2017/*`
    - 最後の `*` も必要
    - 特定のディレクトリだけに制限したい場合は `*` の代わりにパス名を指定する

`Add Statement` をクリックして追加し， `Next Step` をクリック．

![image.png](https://qiita-image-store.s3.amazonaws.com/0/31862/fbf1595a-58c7-b495-f154-7ae738b9b958.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/31862/92dbc360-c6e1-8802-1acf-81ada94e93f3.png)

`Policy Name` や `Description` を設定して保存．
なお，Policy Generator を使わなくても直接生成や編集もできる．
編集する場合は，`IAM のユーザーを選択 > Policyを選択 > Edit policy` から編集可能．

![image.png](https://qiita-image-store.s3.amazonaws.com/0/31862/9619cd72-6463-5fa1-66e5-390735c9d4cc.png)


![image.png](https://qiita-image-store.s3.amazonaws.com/0/31862/1c0e2df0-012c-fb25-4540-740d8ae2c18f.png)

作成が完了すると， `Access key ID` と `Secret access key` が発行される．
後者はマネジメントコンソール上から確認できなくなるので，必ずCSVデータとしてダウンロードしておく．

# 動作確認

- Rails 5.1.3
- [carrierwave 0.10.0](https://github.com/carrierwaveuploader/carrierwave/releases)

Railsアプリケーションから，[carrierwave > Using Amazon S3](https://github.com/carrierwaveuploader/carrierwave#using-amazon-s3)で動作確認した．

- IAM の credential は，`aws_access_key_id`, `aws_secret_access_key` に設定
- `region` は S3 を作成したリージョンを指定
- `fog_directory` は bucket名を指定

IAM の permission に設定したバケットにのみアクセスでき，それ以外は下記のように `403` エラーになる．

```
Excon::Error::Forbidden - Expected(200) <=> Actual(403 Forbidden)
excon.error.response
  :body          => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>F955E426D1B1BD15</RequestId><HostId>/2yU0WpD3U1IQLOt3HncofPqyGGcXZjJt+FWxqQje0IN6XFUAcLTs0YRzTW1H25JfnxBgCgb2+k=</HostId></Error>"
  :cookies       => [
  ]
  :headers       => {
    "Connection"       => "close"
    "Content-Type"     => "application/xml"
    "Date"             => "Sun, 27 Aug 2017 06:04:32 GMT"
    "Server"           => "AmazonS3"
    "x-amz-id-2"       => "/2yU0WpD3U1IQLOt3HncofPqyGGcXZjJt+FWxqQje0IN6XFUAcLTs0YRzTW1H25JfnxBgCgb2+k="
    "x-amz-request-id" => "F955E426D1B1BD15"
  }
  :host          => "s3-ap-northeast-1.amazonaws.com"
  :local_address => "10.0.1.2"
  :local_port    => 59226
  :path          => "/bucket-name/uploads/model_name/column_name/model_id/xxxxxxxxxxxxxxxxxxxxxx.png"
  :port          => 443
  :reason_phrase => "Forbidden"
  :remote_ip     => "52.219.4.88"
  :status        => 403
  :status_line   => "HTTP/1.1 403 Forbidden\r\n"
```

アップロードしたデータは `https://bucket-name.s3-ap-northeast-1.amazonaws.com/uploads/model_name/column_name/model_id/xxxxxxxxxxxxxxxxxxxxxx.png` のような形式でアクセスできる．

TODO: サンプルアプリを掲載

# 参考
- [How to Restrict Amazon S3 Bucket Access to a Specific IAM Role](https://aws.amazon.com/blogs/security/how-to-restrict-amazon-s3-bucket-access-to-a-specific-iam-role/)
- [S3のアクセスコントロールまとめ](http://qiita.com/ryo0301/items/791c0a666feeea0a704c) ← コントロール関連よくわかってなかったので，参考になった．
- [S3特定バケットにアップロードできるだけの権限のユーザを作成する](https://github.com/mechamogera/MyTips/wiki/S3%E7%89%B9%E5%AE%9A%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%81%AB%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89%E3%81%A7%E3%81%8D%E3%82%8B%E3%81%A0%E3%81%91%E3%81%AE%E6%A8%A9%E9%99%90%E3%81%AE%E3%83%A6%E3%83%BC%E3%82%B6%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B)