S3 + CloudFront + Aws Certificate Manager + Route53 + Middleman + terraformを使ってHTTPS対応した静的Webを100円/月で構築する

# 概要

タイトルが長い。


個人プロジェクトでSSL対応したWebサイトを作る機会があって、折角なので無料のACM(Aws Certificate Manager)を使ってみました。
そこそこ安く、SSL対応したWebサイトを構築します。

インフラはほぼAWSを使っています。


# なぜこれらの技術を採用したか

## S3

WebサイトはWordPress等で構築でも良かったのですが、サーバ費用をかけたくなかったため。
middlemanでビルドした静的WebをS3にホストしています。

## CloudFront

S3 + ACMの組み合わせだと必須だったため、仕方なく使っています。
わざわざCloudFrontを使うほどのトラフィックではない。
S3で直接ACM使えたらいいのになぁ。


## Aws Certificate Manager

Let’s Encryptでも良かったけど、サーバの運用とかしたくなかったため。
というか、そもそもACMを使ってみたいというモチベーションだった。


## Route53

何かと相性が良いので。
ドメインはAWS外のサービスで取りました。


## Middleman

静的サイトジェネレーターです。
https://middlemanapp.com/jp/

要件的には静的WebでOKかつ自分しか記事更新しないので、middleman-blogを採用しました。
もし非エンジニアの人が記事書くようなプロジェクトだと、middlemanは採用しづらいですね...

## terraform

INFRASTRUCTURE AS CODEです。
https://www.terraform.io/

AWSの管理画面でぽちぽちするのがあまり好きでは無いので、なるだけterraformでAWSリソースの構築をしました。

記事時点では v0.6.15 を使っています。

# 構成

クライアント → CloudFront → S3 ← middleman ← おれ

## terraformのtf

[こちら](http://dev.classmethod.jp/cloud/aws/terraform-v0-6-15-supports-cloudfront/)を大変参考にさせて頂きました。
クラメソさんの記事いつもお世話になっております:bangbang: 

そしてこの記事の一番価値あるのはここだよ:bangbang: 

```terraform.tf
variable "name" { default = "bucket-name.here" }

resource "aws_cloudfront_origin_access_identity" "sample" {
  comment = "${var.name}"
}
 
resource "template_file" "sample_s3_policy" {
  template = "${file(concat(path.module, "/sample_s3_policy.json.tpl"))}"
 
  vars {
    bucket_name            = "${var.name}"
    origin_access_identity = "${aws_cloudfront_origin_access_identity.sample.id}"
  }
}

resource "aws_s3_bucket" "sample" {
  bucket = "${var.name}"
  acl    = "private"
  policy = "${template_file.sample_s3_policy.rendered}"

  website {
    index_document = "index.html"
    error_document = "404.html"
  }
}

resource "aws_cloudfront_distribution" "sample" {
  enabled             = true
  comment             = "${var.name}"
  default_root_object = "index.html"
  price_class         = "PriceClass_200"
  retain_on_delete    = true
  aliases = ["${var.name}", "www.${var.name}"]
 
  origin {
    domain_name = "${aws_s3_bucket.sample.website_endpoint}"
    origin_id   = "${var.name}"
 
    custom_origin_config {
      http_port = "80"
      https_port = "443"
      origin_protocol_policy = "http-only"
      origin_ssl_protocols = ["TLSv1", "TLSv1.1", "TLSv1.2"]
    }
  }
 
  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "${aws_s3_bucket.sample.id}"
    compress         = true
 
    forwarded_values {
      query_string = false
 
      cookies {
        forward = "none"
      }
    }
 
    viewer_protocol_policy = "redirect-to-https"
    min_ttl                = 0
    default_ttl            = 3600
    max_ttl                = 86400
  }
 
  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }
 
  viewer_certificate {
    acm_certificate_arn = "arn:aws:acm:us-east-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    ssl_support_method = "sni-only"
    minimum_protocol_version = "TLSv1"
  }
}

resource "aws_route53_zone" "sample" {
  name = "${var.name}"
}

resource "aws_route53_record" "sample" {
  zone_id = "${aws_route53_zone.sample.zone_id}"
  name    = "${var.name}"
  type    = "A"

  alias {
    name    = "${aws_cloudfront_distribution.sample.domain_name}"
    zone_id = "Z2FDTNDATAQYW2"
    evaluate_target_health = false
  }
}

resource "aws_route53_record" "www-sample" {
  zone_id = "${aws_route53_zone.sample.zone_id}"
  name    = "www.${var.name}"
  type    = "A"

  alias {
    name    = "${aws_cloudfront_distribution.sample.domain_name}"
    zone_id = "Z2FDTNDATAQYW2"
    evaluate_target_health = false
  }
}
```

```sample_s3_policy.json.tpl
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadForGetBucketObjects",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${origin_access_identity}"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::${bucket_name}/*"
    }
  ]
}
```

クラメソさんの記事をベースに作っていますが、いくつか変更点があります。

* CloudFrontのindexDocumentの設定
	* `bucket-name.here/foo/index.html`に対して、`bucket-name.here/foo` だけでアクセスできるようにしたかった
	* そのため`custom_origin_config`を設定している
* ACMを利用するためにCloudFrontの`viewer_certificate`の設定をしている


## terraform管理外のリソース

できるだけterraformで完結したかったのですが、ACMに関してはまだterraformから扱えないようでしたので、管理画面からぽちぽちつくりました。

今回はwwwあり/なしそれぞれのドメインをあてているので、certificateもこの2つを含むものを作成しました。

また、ACM設定の際にメールの受信設定等が必要です。
メール受信するためにSESも使うのですが、同じくterraformから管理できないので管理画面からぽちぽちしました。
[こちら](http://qiita.com/toshihirock/items/cf4e6d8afa08beaa728c)を参考にさせて頂きました。

無事にACMの設定が完了するとarnがわかるので、その値をterraformの`acm_certificate_arn`にセットします。


# 費用

月に100円〜200円程度でしょうか。

||月額(ざっくり)|
|---:|:---|
|ドメイン|10 〜 100円|
|Route53|60円|
|S3|10円|
|CloudFront|20円|

# まとめ

middlemanの話は無くても良かったですね...
(werckerの話も最初書いたけど関係ないから消した)

S3 + CloudFront + ACM を terraform で実現したい場合の参考になれば幸いです。