[JAWS-UG CLI] S3:#9 S3バケットの作成 (CORS)

AWS CLIを利用して、S3上にPOST可能なWebサイトを作成してみます。


前提条件
========


S3への権限
----------

* S3に対してフル権限があること。


AWS CLIのバージョン
-------------------

* 以下のバージョンで動作確認済

  * AWS CLI 1.8.3

```bash:コマンド:
aws --version
```

```text:結果(例):
      aws-cli/1.8.3 Python/2.7.5 Darwin/13.4.0
```


0. 準備
=======


0.1. リージョンの決定
---------------------

Webサイトを構築するリージョンを決めます。

(カレントユーザが利用するカレントリージョンも切り変わります。)

```bash:コマンド(東京リージョンの場合):
export AWS_DEFAULT_REGION='ap-northeast-1'
```

0.2. 変数の確認
---------------

プロファイルとリージョンが想定のものになっていることを確認します。

```bash:変数の確認:
aws configure list
```

```text:結果(例):

            Name                    Value             Type    Location
            ----                    -----             ----    --------
         profile        s3Full-prjZ-mbp13              env    AWS_DEFAULT_PROFILE
      access_key     ****************LOAQ shared-credentials-file
      secret_key     ****************I1O1 shared-credentials-file
          region           ap-northeast-1              env    AWS_DEFAULT_REGION
```


1. 事前作業
===========


1.1. サイト名の決定
-------------------

組織名-handson-今日の日付とします。

```bash:変数の設定:
ORG=<組織名>
HOSTNAME_WEBSITE="${ORG}-handson-`date +%Y%m%d`"

echo ${HOSTNAME_WEBSITE}
```


1.2. コンテンツ用バケット名の決定
---------------------------------

```bash:変数の設定:
S3_BUCKET_SITE="${HOSTNAME_WEBSITE}" \
        && echo ${S3_BUCKET_SITE}
```

同名のバケットが存在しないことを確認します。

```bash:コマンド:
aws s3 ls s3://${S3_BUCKET_SITE}
```

```text:結果(例):
      A client error (NoSuchBucket) occurred when calling the ListObjects operation: The specified bucket does not exist
```


1.3. ログ用バケット名の決定
---------------------------

```bash:変数の設定:
S3_BUCKET_LOG="${S3_BUCKET_SITE}-log" \
        && echo ${S3_BUCKET_LOG}
```

同名のバケットが存在しないことを確認します。

```bash:コマンド:
aws s3 ls s3://${S3_BUCKET_LOG}
```

```text:結果(例)
      A client error (NoSuchBucket) occurred when calling the ListObjects operation: The specified bucket does not exist
```


1.4. ローカルディレクトリ作成
-----------------------------

```bash:変数の設定:
DIR_LOCAL_CONTENTS="$(pwd)/file-bucket" \
        && echo ${DIR_LOCAL_CONTENTS}
```

```bash:コマンド:
mkdir -p ${DIR_LOCAL_CONTENTS} \
        && cd ${DIR_LOCAL_CONTENTS}
```


1.5. index.html, error.html作成
-------------------------------

```bash:コマンド:
cd ${DIR_LOCAL_CONTENTS}
```

```bash:コマンド:
cat << EOF > index.html
      <!DOCTYPE html>
      <html lang="ja">
      <head>
        <meta charset="utf-8">
        <title>Title</title>
      </head>

      <body>

      <h1>Hello!</h1>
      <!-- comment -->

      </body>
      </html>
EOF

cat index.html
```

```bash:コマンド:
cat << EOF > error.html
      <!DOCTYPE html>
      <html lang="ja">

      <head>
        <meta charset="UTF-8">
      </head>

      <body>
        <h1>404 File Not Found</h1>
        <p>お探しのページは見つかりません。</p>
      </body>
      </html>
EOF

cat error.html
```


2. コンテンツ用バケットの作成
=============================

```bash:変数の設定:
S3_BUCKET_NAME="${S3_BUCKET_SITE}" \
        && echo ${S3_BUCKET_NAME}
```

```bash:変数の確認:
cat << ETX

          S3_BUCKET_NAME: ${S3_BUCKET_NAME}

ETX
```

```bash:コマンド:
aws s3 mb s3://${S3_BUCKET_NAME}
```


```text:結果(例):
      make_bucket: s3://www.example.jp/
```

```bash:コマンド:
aws s3api get-bucket-location --bucket ${S3_BUCKET_NAME}
```

```json:結果(例):
      {
        "LocationConstraint": "ap-northeast-1"
      }
```


3. ログ用バケットの作成/設定
============================


3.1.  ログ用バケットの作成
--------------------------

```bash:変数の設定:
S3_BUCKET_NAME="${S3_BUCKET_LOG}" \
        && echo ${S3_BUCKET_NAME}
```

```bash:変数の確認:
cat << ETX

          S3_BUCKET_NAME: ${S3_BUCKET_NAME}

ETX
```

```bash:コマンド:
aws s3 mb s3://${S3_BUCKET_NAME}
```


```text:結果(例):
      make_bucket: s3://www.example.jp-log/
```

```bash:コマンド:
aws s3api get-bucket-location --bucket ${S3_BUCKET_NAME}
```

```json:結果(例):
      {
        "LocationConstraint": "ap-northeast-1"
      }
```


3.2. バケットACLの確認
----------------------

```bash:コマンド:
aws s3api get-bucket-acl \
        --bucket ${S3_BUCKET_NAME}
```

```json:結果(例):
      {
        "Owner": {
          "DisplayName": "prjz",
          "ID": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
        },
        "Grants": [
          {
              "Grantee": {
                  "DisplayName": "prjz",
                  "ID": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
              },
              "Permission": "FULL_CONTROL"
          }
        ]
      }
```


3.3. バケットACLの変更
----------------------

```bash:変数の確認:
cat << ETX

          S3_BUCKET_NAME: ${S3_BUCKET_NAME}

ETX
```


```bash:コマンド:
aws s3api put-bucket-acl \
        --bucket ${S3_BUCKET_NAME} \
        --grant-write 'URI="http://acs.amazonaws.com/groups/s3/LogDelivery"' \
        --grant-read-acp 'URI="http://acs.amazonaws.com/groups/s3/LogDelivery"'
```

```text:結果(例):
      (戻り値なし)
```

注釈: フルコントロールの設定が消えるため、オーナはログの削除ができなくなります。 (必要がある場合は、grant-full-controlで指定する。)


3.4. バケットACLの確認
----------------------

```bash:コマンド:
aws s3api get-bucket-acl \
        --bucket ${S3_BUCKET_NAME}
```

```json:結果(例):
      {
        "Owner": {
          "DisplayName": "prjz",
          "ID": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
        },
        "Grants": [
          {
              "Grantee": {
                  "URI": "http://acs.amazonaws.com/groups/s3/LogDelivery"
              },
              "Permission": "WRITE"
          },
          {
              "Grantee": {
                  "URI": "http://acs.amazonaws.com/groups/s3/LogDelivery"
              },
              "Permission": "READ_ACP"
          }
        ]
      }
```


4. ログ用バケットの設定 (ログローテーションの設定)
==================================================


4.1. 設定の確認
---------------

```bash:変数の確認:
cat << ETX

        S3_BUCKET_NAME:    ${S3_BUCKET_NAME}

ETX
```

```bash:コマンド:
aws s3api get-bucket-lifecycle \
        --bucket ${S3_BUCKET_NAME}
```

```text:結果(例):
      A client error (NoSuchLifecycleConfiguration) occurred when calling the GetBucketLifecycle operation: The lifecycle configuration does not exist
```


4.2. 設定ファイルの作成
-----------------------

```bash:変数の設定:
FILE_S3_LIFECYCLE='s3-lifecycle.json'
S3_LIFECYCLE_EXPIRE_DAYS='60'
S3_LIFECYCLE_TRANS_DAYS='30'
S3_LIFECYCLE_ID="${S3_BUCKET_NAME}-Archive"
```

S3_LIFECYCLE_IDは、Lifecycle RulesのRule Nameになります。(マネジメントコンソール上ではオプション扱い)

```bash:コマンド:
cat << EOF > ${FILE_S3_LIFECYCLE}
{
        "Rules": [
          {
            "Expiration": {
              "Days": ${S3_LIFECYCLE_EXPIRE_DAYS}
            },
            "ID": "${S3_LIFECYCLE_ID}",
            "Prefix": "Logs/",
            "Status": "Enabled",
            "Transition": {
              "Days": ${S3_LIFECYCLE_TRANS_DAYS},
              "StorageClass": "GLACIER"
            }
          }
        ]
}
EOF
```

JSONファイルを作成したら、フォーマットが壊れてないか必ず確認します。

```bash:コマンド:
jsonlint -q ${FILE_S3_LIFECYCLE}
```

```text:結果:
      (戻り値なし)
```


4.3. 設定
---------

```bash:変数の確認:
cat << ETX

        S3_BUCKET_NAME:    ${S3_BUCKET_NAME}
        FILE_S3_LIFECYCLE: ${FILE_S3_LIFECYCLE}

ETX
```

```bash:コマンド:
aws s3api put-bucket-lifecycle \
        --bucket ${S3_BUCKET_NAME} \
        --lifecycle-configuration file://${FILE_S3_LIFECYCLE}
```

```text:結果:
      (戻り値なし)
```


4.4. 設定の確認
---------------

```bash:コマンド:
aws s3api get-bucket-lifecycle \
        --bucket ${S3_BUCKET_NAME}
```

```json:結果:
      {
        "Rules": [
          {
              "Status": "Enabled",
              "Prefix": "/Logs",
              "Transition": {
                  "Days": 1,
                  "StorageClass": "GLACIER"
              },
              "Expiration": {
                  "Days": 2
              },
              "ID": "www.example.jp-log-Archive"
          }
        ]
      }
```


5. コンテンツ用バケットの設定 (ログ保存)
========================================

```bash:変数の設定:
S3_BUCKET_NAME="${S3_BUCKET_SITE}" \
        && echo ${S3_BUCKET_NAME}
```


5.1. ログ保存設定の確認
-----------------------

```bash:コマンド:
aws s3api get-bucket-logging \
        --bucket ${S3_BUCKET_NAME}
```

```text:結果:
      (戻り値なし)
```


5.2. ログ保存設定ファイルの作成
-------------------------------

```bash:変数の設定:
FILE_S3_LOGGING='s3-logging.json'
```

```bash:コマンド:
cat << EOF > ${FILE_S3_LOGGING}
{
        "LoggingEnabled": {
          "TargetBucket": "${S3_BUCKET_LOG}",
          "TargetPrefix": "/Logs"
        }
}
EOF
```

JSONファイルを作成したら、フォーマットが壊れてないか必ず確認します。

```bash:コマンド:
jsonlint -q ${FILE_S3_LOGGING}
```

```text:結果:
      (戻り値なし)
```


5.3. ログ保存の設定
-------------------

```bash:変数の確認:
cat << ETX

        S3_BUCKET_NAME:  ${S3_BUCKET_NAME}
        FILE_S3_LOGGING: ${FILE_S3_LOGGING}

ETX
```

```bash:コマンド:
aws s3api put-bucket-logging \
        --bucket ${S3_BUCKET_NAME} \
        --bucket-logging-status file://${FILE_S3_LOGGING}
```

```text:結果:
      (戻り値なし)
```


5.4. ログ保存設定の確認
-----------------------

```bash:コマンド:
aws s3api get-bucket-logging \
        --bucket ${S3_BUCKET_NAME}
```

```json:結果(例):
      {
        "LoggingEnabled": {
          "TargetPrefix": "/Logs",
          "TargetBucket": "www.example.jp-log"
        }
      }
```


6. コンテンツ用バケットの設定 (バケットポリシ)
==============================================


6.1. 設定の確認
---------------

```bash:変数の確認:
cat << ETX

        S3_BUCKET_NAME:  ${S3_BUCKET_NAME}

ETX
```

```bash:コマンド:
aws s3api get-bucket-policy \
        --bucket ${S3_BUCKET_NAME} |\
        sed 's/\\//g' |\
        sed 's/"{/{/' |\
        sed 's/}"/}/' |\
        jq .
```

```text:結果(例):
      A client error (NoSuchBucketPolicy) occurred when calling the GetBucketPolicy operation: The bucket policy does not exist
```


6.2. 設定ファイルの作成
-----------------------

```bash:変数の設定:
FILE_POLICY='s3-policy-readonly.json'
```

```bash:変数の確認:
cat << EOF > ${FILE_POLICY}
{
        "Version":"2012-10-17",
        "Statement":[{
            "Sid":"AddPerm",
            "Effect":"Allow",
              "Principal": "*",
            "Action":["s3:GetObject"],
            "Resource":["arn:aws:s3:::${S3_BUCKET_NAME}/*"]
          }
        ]
}
EOF
```

JSONファイルを作成したら、フォーマットが壊れてないか必ず確認します。

```bash:コマンド:
jsonlint -q ${FILE_POLICY}
```

```text:結果:
      (戻り値なし)
```


6.3. 設定
---------

```bash:変数の確認:
cat << ETX

        S3_BUCKET_NAME ${S3_BUCKET_NAME}
        FILE_POLICY:   ${FILE_POLICY}

ETX
```

```bash:コマンド:
aws s3api put-bucket-policy \
        --bucket ${S3_BUCKET_NAME} \
        --policy file://${FILE_POLICY}
```

```text:結果:
      (戻り値なし)
```


6.4. 設定の確認
---------------

```bash:コマンド:
aws s3api get-bucket-policy \
        --bucket ${S3_BUCKET_NAME} |\
        sed 's/\\//g' |\
        sed 's/"{/{/' |\
        sed 's/}"/}/' |\
        jq .
```

```json:結果:
      {
        "Policy": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Sid": "AddPerm",
              "Effect": "Allow",
              "Principal": "*",
              "Action": "s3:GetObject",
              "Resource": "arn:aws:s3:::www.example.jp/*"
            }
          ]
        }
      }
```


7. コンテンツ用バケットの設定 (Website設定)
===========================================


7.1. 設定の確認
---------------

```bash:コマンド:
aws s3api get-bucket-website \
        --bucket ${S3_BUCKET_NAME}
```

```text:結果(例):
      A client error (NoSuchWebsiteConfiguration) occurred when calling the GetBucketWebsite operation: The specified bucket does not have a website configuration
```


7.2. 設定
---------

```bash:変数の確認:
cat << ETX

          S3_BUCKET_NAME: ${S3_BUCKET_NAME}

ETX
```

```bash:コマンド:
aws s3 website "s3://${S3_BUCKET_NAME}" \
        --index-document index.html \
        --error-document error.html
```

```text:結果:
      (戻り値なし)
```


7.4. 設定の確認
---------------

```bash:コマンド:
aws s3api get-bucket-website \
        --bucket ${S3_BUCKET_NAME}
```

```json:結果:
      {
          "IndexDocument": {
              "Suffix": "index.html"
          },
          "ErrorDocument": {
              "Key": "error.html"
          }
      }
```


8. コンテンツ転送
=================

```bash:コマンド:
cd ${DIR_LOCAL_CONTENTS}
```

```bash:コマンド:
aws s3 sync . "s3://${S3_BUCKET_NAME}/" \
        --exclude ".git*" \
        --exclude ".hg*"
```


9. アクセス確認
===============

バケットのURLを確認します。

```bash:コマンド:
S3_BUCKET_ENDPOINT="${S3_BUCKET_NAME}.s3-website-`aws s3api get-bucket-location --bucket ${S3_BUCKET_NAME} --output text`.amazonaws.com" \
        && echo ${S3_BUCKET_ENDPOINT}
```

```text:結果(例):
      www.example.jp.s3-website-ap-northeast-1.amazonaws.com
```

ブラウザでアクセスできることを確認しましょう。


10. CORSの設定
==============


10.1. 設定の確認
----------------

```bash:コマンド:
aws s3api get-bucket-cors \
        --bucket ${S3_BUCKET_NAME}
```

```text:結果(例)
      A client error (NoSuchCORSConfiguration) occurred when calling the GetBucketCors operation: The CORS configuration does not exist
```


10.2. 設定ファイルの作成
------------------------

```bash:変数の設定:
FILE_CORS_RULE="${S3_BUCKET_NAME}-cors.json"
```

```bash:コマンド:
cat << EOF > ${FILE_CORS_RULE}
{
        "CORSRules": [
          {
            "AllowedHeaders": [
              "*"
            ],
            "MaxAgeSeconds": 3000,
            "AllowedMethods": [
              "PUT"
            ],
            "AllowedOrigins": [
              "http://${S3_BUCKET_ENDPOINT}"
            ]
          }
        ]
}
EOF

cat ${FILE_CORS_RULE}
```

JSONファイルを作成したら、フォーマットが壊れてないか必ず確認します。

```bash:コマンド:
jsonlint -q ${FILE_CORS_RULE}
```

```text:結果:
      (戻り値なし)
```


10.3. 設定
----------

```bash:コマンド:
aws s3api put-bucket-cors \
        --bucket ${S3_BUCKET_NAME} \
        --cors-configuration file://${FILE_CORS_RULE}
```

```text:結果:
      (戻り値なし)
```


10.4. 設定の確認
----------------

```bash:コマンド:
aws s3api get-bucket-cors \
        --bucket ${S3_BUCKET_NAME}
```

```json:結果(例)
      {
        "CORSRules": [
          {
              "AllowedHeaders": [
                  "*"
              ],
              "MaxAgeSeconds": 3000,
              "AllowedMethods": [
                  "PUT"
              ],
              "AllowedOrigins": [
                  "http://example-handson-20150928.s3-website-ap-northeast-1.amazonaws.com"
              ]
          }
        ]
      }
```


完了
====

Cognitoで識別子プールを作成し、コンテンツを置きます。

- 識別子プールの作成: http://qiita.com/tcsh/items/be3c65d7c66f19169b2b
- コンテンツの作成: http://qiita.com/tcsh/items/61727b244e95368d8c16
- IAMロールの作成: http://qiita.com/tcsh/items/ce884d7b285999f858b7