AWS CloudFront で S3 にホスティングした静的ウェブサイトを CDN 配信(&独自ドメインのHTTPS化)

この記事は個人ブログと同じ内容です

www.ritolab.com


AWS の S3 にホスティングした静的 WEB サイトを CloudFront を用いて CDN での配信を行うと共に、SSL 証明書等の整備も行って独自ドメインHTTPS でアクセスできるようにしていきます。

なお、S3 にホスティングした WEB サイトを独自ドメインHTTPS でアクセスできるようにするにも同じで、今回のように CloudFront を用いて構築する必要があり、手段手順としては同じになります。

S3 にホスティングした静的 WEB サイト

今回は CloudFront や SSL/TLS 証明書回りがメインです。

S3 に静的 WEB サイトをホスティングする部分については前回の記事にまとめています。

今回は前回の記事の続きになるので、環境変数周りや Route53 や S3 の設定等もこちらを確認してください。

www.ritolab.com

実行環境

今回の実行環境は以下になります。

  • Terraform v0.15.1

コードはモジュール化を行わずに一つの tf ファイルに書いていきます。

AWS プロバイダの追加

まずは aws プロバイダを追加します。

既に前回で東京リージョンの aws プロバイダを作成済みですが、ACM で作成した SSL/TLS 証明書を CloudFront にアタッチすためには証明書をバージニア北部リージョンで作成する必要があるため(2021/05/04現在)、別途こちらの aws プロバイダを作成しておきます。

main.tf

provider "aws" {
  access_key = var.aws_access_key
  secret_key = var.aws_secret_key
  region     = "us-east-1"
  alias      = "virginia"
}

リージョンをバージニア北部にし、エイリアスを付けてデフォルトとは別にこちらを指定して使用できるようにしています。

ログ用の S3 バケット作成

CloudFront のアクセスログを格納するバケットを S3 に作成します。

main.tf

## S3 for cloudfront logs
resource "aws_s3_bucket" "cloudfront_logs" {
  bucket = "${local.fqdn.name}-cloudfront-logs"
  acl    = "private"

  tags = {
    Name        = var.project_tag_name
    Environment = var.project_environment_name
  }
}

CloudFront ディストリビューション作成

CloudFront のディストリビューションを作成します。

main.tf

## キャッシュポリシー
data "aws_cloudfront_cache_policy" "managed_caching_optimized" {
  name = "Managed-CachingOptimized"
}

## ディストリビューション
resource "aws_cloudfront_distribution" "main" {
  origin {
    domain_name = "${local.bucket.name}.s3-${var.aws_region}.amazonaws.com"
    origin_id   = "S3-${local.fqdn.name}"
  }

  enabled             = true
  is_ipv6_enabled     = true
  default_root_object = "index.html"

  # Alternate Domain Names (CNAMEs)
  aliases = [local.fqdn.name]

  # 証明書の設定
  viewer_certificate {
    cloudfront_default_certificate = false
    acm_certificate_arn            = aws_acm_certificate.main.arn
    minimum_protocol_version       = "TLSv1.2_2019"
    ssl_support_method             = "sni-only"
  }

  retain_on_delete = false

  logging_config {
    include_cookies = true
    bucket          = "${aws_s3_bucket.cloudfront_logs.id}.s3.amazonaws.com"
    prefix          = "log/"
  }

  default_cache_behavior {
    allowed_methods        = ["GET", "HEAD"]
    cached_methods         = ["GET", "HEAD"]
    target_origin_id       = "S3-${local.fqdn.name}"
    viewer_protocol_policy = "allow-all"
    compress               = true
    cache_policy_id        = data.aws_cloudfront_cache_policy.managed_caching_optimized.id
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }
}
  • キャッシュポリシー
    • 今回は特に細かく設定する必要がないためマネージドのものを使用しています。
    • 管理キャッシュポリシーの使用
  • ディストリビューション
    • 証明書の設定
      • acm_certificate_arn は、この次で設定する ACM の値を設定します。
      • 前項でバージニア北部リージョンのプロバイダを作成しましたが、記事執筆時点では us-east-1 region のものしか指定できません。

SSL/TLS 証明書 作成

ACM(AWS Certificate Manager) にて SSL/TLS 証明書を作成し、独自ドメインに紐づけていきます。

aws.amazon.com

証明書のリクエス

SSL/TLS 証明書の発行を ACM にリクエストします。

main.tf

resource "aws_acm_certificate" "main" {
  provider                  = aws.virginia
  domain_name               = local.fqdn.name
  validation_method         = "DNS"
}
  • プロバイダはバージニア北部リージョンの方を指定しています。
  • 今回使用する独自ドメインFQDN を指定しています。
  • 検証は DNS で行なうように指定しています。

CNAME レコード 作成

証明書リクエストに対して DNS で検証を行なうため、CNAME レコードを追加します。

main.tf

## CNAME レコード
resource "aws_route53_record" "main_acm_c" {
  for_each = {
    for d in aws_acm_certificate.main.domain_validation_options : d.domain_name => {
      name   = d.resource_record_name
      record = d.resource_record_value
      type   = d.resource_record_type
    }
  }
  zone_id = data.aws_route53_zone.naked.id
  name    = each.value.name
  type    = each.value.type
  ttl     = 172800
  records = [each.value.record]
  allow_overwrite = true
}

証明書の検証

証明書のリクエストに CNAME レコードを連携させ、DNS 検証の完了を確認します。

main.tf

## ACM 証明書 / CNAME レコード 連携
resource "aws_acm_certificate_validation" "main" {
  provider        = aws.virginia
  certificate_arn = aws_acm_certificate.main.arn
  validation_record_fqdns = [for record in aws_route53_record.main_acm_c : record.fqdn]
}

独自ドメインの向き先を変更する

最後に、前回までは S3 に向いていた独自ドメインの向き先を CloudFront に変更します。

main.tf

## A レコード(to CloudFront)
resource "aws_route53_record" "main_cdn_a" {
  zone_id = data.aws_route53_zone.naked.zone_id
  name    = local.fqdn.name
  type    = "A"
  alias {
    evaluate_target_health = true
    name                   = aws_cloudfront_distribution.main.domain_name
    zone_id                = aws_cloudfront_distribution.main.hosted_zone_id
  }
}

CloudFront へ A レコードを設定しました。

前回の記事で S3 に向けた A レコードは削除します。

## 削除
## A レコード(to S3 Bucket)
//resource "aws_route53_record" "main_a" {
//  zone_id = data.aws_route53_zone.naked.zone_id
//  name    = local.fqdn.name
//  type    = "A"
//  alias {
//    evaluate_target_health = true
//    name = "s3-website-${var.aws_region}.amazonaws.com"
//    zone_id = aws_s3_bucket.app.hosted_zone_id
//  }
//}

動作確認

ここまでで terraform から環境を構築すると、CloudFront 及び HTTPS でのアクセスが可能となっている事が確認できます。

f:id:ro9rito:20210514061244p:plain

HTTPS でのみのアクセスにする

現時点では http と https どちらでもアクセス可能な状態なので、http でアクセスがきたものは https にリダイレクトするようにします。

main.tf

# allow-all を redirect-to-https に変更する
# viewer_protocol_policy = "allow-all"
viewer_protocol_policy = "redirect-to-https"

aws_cloudfront_distribution リソースの default_cache_behavior にある viewer_protocol_policy の値を allow-all から redirect-to-https へ変更します。

これで http でアクセスがきたものは https にリダイレクトされるようになります。

S3 へのアクセスを制限する

訪問者が S3 のバケットへ直接アクセスしないように制限を掛け、CloudFront ディストリビューションを介してのみアクセスできるようにします。

docs.aws.amazon.com

Origin Access Identity(OAI)作成

Origin Access Identity(以下、OAI)を作成します。

main.tf

## CloudFront OAI 作成
resource "aws_cloudfront_origin_access_identity" "main" {
  comment = "Origin Access Identity for s3 ${local.bucket.name} bucket"
}

OAI をディストーションに関連付ける

OAI で作成した CloudFront ユーザーをディストリビューションに関連付けます。

main.tf

resource "aws_cloudfront_distribution" "main" {
  origin {
    # 追加
    s3_origin_config {
      origin_access_identity = aws_cloudfront_origin_access_identity.main.cloudfront_access_identity_path
    }
  }
  .
  .
  .

aws_cloudfront_distribution リソースの origin に s3_origin_config として origin_access_identity を設定します。

IAMポリシードキュメント作成

アクセスを制御するポリシードキュメントを作成します。(ここで作成したポリシーを json としてアタッチする)

main.tf

# IAMポリシードキュメント作成
data "aws_iam_policy_document" "s3_policy" {
  statement {
    actions   = ["s3:GetObject"]
    resources = ["${aws_s3_bucket.app.arn}/*"]

    principals {
      identifiers = [aws_cloudfront_origin_access_identity.main.iam_arn]
      type        = "AWS"
    }
  }
}

ポリシーをバケットにアタッチ

作成したポリシーをアプリケーションのバケットに紐付けます。

main.tf

# ポリシーをバケットに紐付け
resource "aws_s3_bucket_policy" "main" {
  bucket = aws_s3_bucket.app.id
  policy = data.aws_iam_policy_document.s3_policy.json
}

アプリケーションのバケットへのアクセス制御変更

前回の記事で作成したアプリケーションを配置するバケットでは、PublicRead が ON になっている状態です。これをプライベートに変更します。

main.tf

## S3 for Static Website Hosting
resource "aws_s3_bucket" "app" {
  bucket = local.bucket.name
  acl    = "private"

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

  tags = {
    Name        = var.project_tag_name
    Environment = var.project_environment_name
  }
}

これで、作成した OAI での CloudFront からのアクセスのみ S3 バケットへのアクセスが可能になりました。

S3 アプリケーションログ用のバケット削除

この時点で S3 へのアクセスは全て CloudFront 経由になり、S3 への訪問者のアクセスログバケットは不要になるので、前回の記事で作成したバケットは削除します。

main.tf

# 削除
//data "aws_canonical_user_id" "current_user" {}

# 削除
## S3 for app logs
## https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket
//resource "aws_s3_bucket" "app_logs" {
//  bucket = "${local.bucket.name}-logs"
//
//  grant {
//    id          = data.aws_canonical_user_id.current_user.id
//    permissions = ["FULL_CONTROL"]
//    type        = "CanonicalUser"
//  }
//  grant {
//    permissions = ["READ_ACP", "WRITE"]
//    type        = "Group"
//    uri         = "http://acs.amazonaws.com/groups/s3/LogDelivery"
//  }
//
//  tags = {
//    Name        = var.project_tag_name
//    Environment = var.project_environment_name
//  }
//}

動作確認

ここまでの設定を適用させると、ブラウザから S3 の URL でコンテンツへアクセスできなくなった事が確認できました。

f:id:ro9rito:20210514061805p:plain

f:id:ro9rito:20210514061813p:plain

まとめ

静的な WEB を公開するのであればコスト面で優れている S3 へのホスティングはおすすめです。

また、CloudFront も使うと高速にコンテンツを配信する、独自ドメインHTTPS 化するなど、できる幅も広がるのでおすすめです。