CloudFrontを経由してS3の静的ウェブサイトホスティングの機能を使う


Amazon S3をWebサーバとして利用できる、静的ウェブサイトホスティングの機能を利用してみました。
また、今回はCloudFrontを経由させることで、ELBとS3へそれぞれリクエストを振り分ける、ということも合わせて試しました。
(CloudFrontのキャッシュ機能の話については今回の対象外となります。今回はリクエストの振り分け用途として利用しています)

イメージとしては以下のようになります。

- http://example.com/
CloudFront => ELB => EC2

- http://example.com/test/
CloudFront => S3

また、S3へのファイルアップロードは、EC2から行う想定です。

# ELB

事前に作成して設定しておきます。
作成方法は公式ドキュメント (http://docs.aws.amazon.com/ja_jp/ElasticLoadBalancing/latest/DeveloperGuide/elb-getting-started.html) を参照してください。

また、CloudFront経由でのアクセスになるため、セキュリティグループでInboundへの任意の場所からのHTTP 80番ポートを許可しておきます。

# EC2
事前にEC2を作成しておき、Apache httpdをインストールした後、コンテンツを配置します。
作成方法については割愛します。
今回はデフォルトのテストページをそのまま利用します。

また、このコンテンツをインターネットに公開したくない場合、ELB=>EC2の場合はセキュリティグループでIPアドレス制限を設定することが多いと思いますが、CloudFrontは今日時点ではアクセス制限を掛けることが出来ません。
そのため、EC2のWebサーバ側でアクセス制限を設定します。(S3へのアクセスはこの方法だと制限出来ませんが)

以下はApache httpd2.4での最低限の設定例になります。CloudFront, ELBを経由するため、X-Forwarded-Forにリクエスト元のIPアドレスが入ります。
(詳細は要件に合わせて設定します。)

```bash
RemoteIPHeader X-Forwarded-For

<Location />
  Require all denied
  Require ip IPアドレス/32
</Location>
```

# S3

## バケットの作成
公式ドキュメント (http://docs.aws.amazon.com/ja_jp/AmazonS3/latest/UG/CreatingaBucket.html) などを参照し、バケットを作成しておきます。

## バケットポリシーの追加

ポリシージェネレーターを利用して、バケットポリシーを作成します。
(http://awspolicygen.s3.amazonaws.com/policygen.html)

- 例

```bash
{
  "Id": "Policyxxxxxxxxxxxxx",
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Stmtxxxxxxxxxxxxx",
      "Action": [
        "s3:GetObject"
      ],
      "Effect": "Allow",
      "Resource": "arn:aws:s3:::バケット名/*",
      "Principal": "*"
    }
  ]
}
```

## 静的ウェブサイトホスティングの設定

S3の静的ウェブサイトホスティングの設定を行います。

- インデックスドキュメント：必須設定となります。設定したいファイルがある場合は、設定したいファイル名を指定します。今回は特定のパスかつ特定のファイル名の場合のみS3から返すことを想定し、実在しないファイル名を指定しました。
- エラードキュメント：必要に応じて設定します。
- リダイレクト：必要に応じて設定します。今回は、インデックスドキュメントのindex.htmlは存在しないため、404になってしまった場合はELBにリダイレクトする、という形を試しました。

![Kobito.sd4n2Y.png](https://qiita-image-store.s3.amazonaws.com/0/93640/b7842fe9-c35d-3781-d6bb-f903dca8da2c.png "Kobito.sd4n2Y.png")

- リダイレクトルール例

```bash
<RoutingRules>
    <RoutingRule>
        <Condition>
            <HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
        </Condition>
        <Redirect>
            <HostName>ELB名.ap-northeast-1.elb.amazonaws.com</HostName>
            <ReplaceKeyWith></ReplaceKeyWith>
        </Redirect>
    </RoutingRule>
</RoutingRules>
```

# CloudFront

## Distributionの設定

公式ドキュメント等を参照し、Distibutionの設定を行います。
設定後は以下のようになります。

![Kobito.5riopm.png](https://qiita-image-store.s3.amazonaws.com/0/93640/f60cff4a-1907-a5ef-c000-4b4f95f1d6b9.png "Kobito.5riopm.png")

## Originの設定

Originの設定を行います。ELBとS3の2つ分を設定します。
設定後は以下のようになります。

S3向けの設定の注意点として、S3に設定したリダイレクト設定を効かせたい場合、Origin Domain Nameにはドロップダウンリストに表示されるS3バケット名ではなく、S3のEndpoint名を直接指定します。
(そうしないとリダイレクトが効かなかった)

![Kobito.GFb5EH.png](https://qiita-image-store.s3.amazonaws.com/0/93640/beaf9556-1185-a01e-7244-7873560483e4.png "Kobito.GFb5EH.png")

## Behaviorの設定

Behaviorの設定にて、振り分けるパスの設定を行います。
今回は、/test/* の場合はS3、それ以外はELBへ振り分けを行うようにしています。

![Kobito.VEakPe.png](https://qiita-image-store.s3.amazonaws.com/0/93640/6dce5116-e031-d19a-0c97-5dc876727684.png "Kobito.VEakPe.png")


# IAM設定

EC2からS3にファイルをアップロード出来るようにするため、EC2に割り当てているIAMロールにS3へのポリシーをアタッチします。

AWS Management Consoleから行う場合、IAM - ポリシーの作成 - Policy Generator と進み、必要なアクションを指定してポリシーを作成します。

![Kobito.7JLRet.png](https://qiita-image-store.s3.amazonaws.com/0/93640/d581204e-085b-1b80-e72a-3049cd35cb89.png "Kobito.7JLRet.png")

ここで必要分だけアクションを指定するのですが、とりあえずs3:DeleteObject、s3:GetObject、s3:PutObject、s3:RestoreObjectあたりを設定してEC2からAWS CLIにてlsを試したところ、エラーとなりました。

```bash
$ aws s3 ls s3://バケット名/
A client error (AccessDenied) occurred when calling the ListObjects operation: Access Denied
```

DEBUGしてみたところ、確かに403になっています。ListObjectという文言もありますが、S3のアクションではListObjectというのはありません。

```bash
$ aws s3 ls s3://バケット名 --debug
(snip)
MainThread - botocore.vendored.requests.packages.urllib3.connectionpool - INFO - Starting new HTTPS connection (1): バケット名.s3-ap-northeast-1.amazonaws.com
MainThread - botocore.vendored.requests.packages.urllib3.connectionpool - DEBUG - "GET /?prefix=&delimiter=%2F HTTP/1.1" 403 None
MainThread - botocore.hooks - DEBUG - Event after-call.s3.ListObjects: calling handler <function enhance_error_msg at 0x7fe82cb3e488>
(スタックトレース等が続く)
```

色々試行錯誤の結果、以下のようになりました。
その後、AWS CLIにてlsやrmが実行可能なことを確認しました。
細かい要件が決まっていない場合は、S3FullAccessとかでも良いかもしれません。

```bash
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmtxxxxxxxxxxxxx",
            "Effect": "Allow",
            "Action": [
                "s3:DeleteObject",
                "s3:DeleteObjectVersion",
                "s3:GetLifecycleConfiguration",
                "s3:GetObject",
                "s3:GetObjectAcl",
                "s3:GetObjectTorrent",
                "s3:GetObjectVersion",
                "s3:GetObjectVersionAcl",
                "s3:GetObjectVersionTorrent",
                "s3:ListAllMyBuckets",
                "s3:ListBucket",
                "s3:ListBucketMultipartUploads",
                "s3:ListBucketVersions",
                "s3:ListMultipartUploadParts",
                "s3:PutLifecycleConfiguration",
                "s3:PutObject",
                "s3:PutObjectAcl",
                "s3:PutObjectVersionAcl",
                "s3:RestoreObject"
            ],
            "Resource": [
                "arn:aws:s3:::バケット名",
                "arn:aws:s3:::バケット名/*"
            ]
        }
    ]
}
```

最終的には以下にファイルを配置しました。

```bash
$ aws s3 ls s3://バケット名/test/
2015-12-19 14:34:06          0
2015-12-19 14:34:34          8 test.html
```

# Distributionを有効化

全て設定が終わったら、CloudFrontのDistributionを有効化します。
StateがEnabledに変化しています。

![Kobito.oF9uKj.png](https://qiita-image-store.s3.amazonaws.com/0/93640/d4cd5fb6-aab2-70cf-d42c-9cb38f6a26a4.png "Kobito.oF9uKj.png")

# ブラウザからアクセス

それでは、この状態で、CloudFront経由でアクセスしてみます。
パスごとに振り分け出来ていることが確認出来ました。

- http://CloudFrontのDomainName/

→EC2上のコンテンツが表示される

![Kobito.eFPQwD.png](https://qiita-image-store.s3.amazonaws.com/0/93640/365051a2-5171-2456-3daa-54bb466e65c2.png "Kobito.eFPQwD.png")

- http://CloudFrontのDomainName/test/test.html

→自分でS3に配置したコンテンツが表示される