CloudFront + S3を使ってクローズしたサイトへのアクセスを他サイトにリダイレクトする

## 概要
「クローズ済みのサイトAへのアクセスを、稼働中のサイトBにリダイレクトする」
という機能を、AWSを使って以下を実装したのでその手順を記載します。


## 目的
閉鎖済みのサイト**closed.jp**への全てのアクセスを**running.jp**にリダイレクトする。
※ ドメイン名は適当です。

## 条件
- closed.jpはドメインだけ取得している状態
- closed.jpのサーバは破棄済みで、アクセスするとブラウザデフォルトのエラー画面が表示される
- HTTPSの通信もリダイレクトする


## 構成
利用するAWSサービスとしては以下
- Route53　　
  -  closed.jpへのアクセスを受け付けて、CloudFrontに流す
- ACM
  - HTTPS通信をするための証明書を発行する
- CloudFront
  - ClooudFront Functionを使って、ユーザアクセスを全てrunning.jpにリダイレクトする
- S3
  - CloudFrontのオリジン、ユーザアクセスはS3到達前にリダイレクトされるため中身は空

構成図のイメージは以下
![スクリーンショット 2024-04-21 22.41.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/813353/131846b2-6ebb-2dc2-ce99-f024ab75dfe5.png)


## リダイレクトのイメージ
以下の感じでリダイレクトされる
![スクリーンショット 2024-04-21 23.00.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/813353/e6ef49cb-927d-cc81-ab3d-b9ea12d0174d.png)


## 構築
Terraformで各構成を作っていく

構築順は、ざっくりと「Route53　->　ACM　-> S3　->　CloudFront」だと思う
ただしリソース同士で依存しているので、一部リソースの構ƒ築順は前後する

### Route53

``` terraform

# ホストゾーン作成
resource "aws_route53_zone" "main" {
  name = var.domain-name  # domain-nameはclosed.jp
}

# closed.jpのアクセスをcloudfrontに向けるAレコード
resource "aws_route53_record" "main" {
  name    = var.domain-name
  type    = "A"
  zone_id = aws_route53_zone.main.zone_id

  alias {
    name                   = aws_cloudfront_distribution.main.domain_name
    zone_id                = aws_cloudfront_distribution.main.hosted_zone_id
  }
}


```

 ## ACM
  ``` terraform

# us-east-1のプロバイダを作る（CloudFrontに当てる証明書はus-east-1に当てなければいけないので）
provider "aws" {
  alias   = "virginia"
  region  = "us-east-1"
}


# closed.jpの証明書発行
 resource "aws_acm_certificate" "main" {
  domain_name = "*.${var.domain-name}"

  subject_alternative_names = [var.domain-name]

  validation_method = "DNS"

  provider = aws.virginia　
}


# 発行した証明書のドメイン所有権を確認するためのCNAMEレコードをRoute53に登録する
resource "aws_route53_record" "acm-txt" {
  for_each = {
    for dvo in aws_acm_certificate.main.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  allow_overwrite = true
  name            = each.value.name
  records         = [each.value.record]
  ttl             = 60
  type            = each.value.type
  zone_id         = aws_route53_zone.main.zone_id
}

```

## S3
``` terraform
# バケットを作る
resource "aws_s3_bucket" "main" {
  bucket = "my_bucket"
}

#　CloudFrontからS3にアクセスするためのポリシーを作る
#　※ 今回の構成だとCloudFrontからS3にアクセスが行かないので不要かと思うが念のため
data "aws_iam_policy_document" "main-policy" {
  statement {
    actions   = ["s3:GetObject"]
    resources = ["${aws_s3_bucket.main.arn}/*"]

    principals {
      identifiers = ["cloudfront.amazonaws.com"]
      type        = "Service"
    }

    condition {
      test     = "StringEquals"
      values   = [
        aws_cloudfront_distribution.main.arn
      ]
      variable = "AWS:SourceArn"
    }
  }
}

#　作ったポリシーをバケットにアタッチする
resource "aws_s3_bucket_policy" "main-bucket-policy" {
  bucket = aws_s3_bucket.main.id
  policy = data.aws_iam_policy_document.main-policy.json
}

```

## CloudFront
``` terraform
# 外部リソース（キャッシュポリシー）を取得
data "aws_cloudfront_cache_policy" "enable-cache" {
  name = "Managed-CachingOptimized"
}

# 　CloudFront　Functionを指定のソースから読み込み
resource "aws_cloudfront_function" "all_redirect" {
  name    = "all-redirect"
  runtime = "cloudfront-js-1.0"
  code    = file("./src/redirect.js")
}

# CloudFront作成
resource "aws_cloudfront_distribution" "main" {
  enabled             = true
  aliases             = "closed.jp"
  default_root_object = "index.html"

  # オリジンとしてS3を宣言
  origin {
    domain_name              = aws_s3_bucket.main.bucket_regional_domain_name
    origin_id                = aws_s3_bucket.main.bucket
    origin_access_control_id = aws_cloudfront_origin_access_control.main.id
  }
  
  default_cache_behavior {
    allowed_methods        = ["GET", "HEAD"]
    cached_methods         = ["GET", "HEAD"]
    # デフォルトのビヘイビアのオリジンをS3にする
    target_origin_id       = aws_s3_bucket.main.bucket
    viewer_protocol_policy = "redirect-to-https"
    cache_policy_id        = data.aws_cloudfront_cache_policy.enable-cache.id

    # デフォルトビヘイビアにリダイレクト用のCloudFrontFunctionを設定
    function_association {
      # viewer requestなのでオリジンへのアクセス前に実行される
      event_type   = "viewer-request"
      function_arn = aws_cloudfront_function.all_redirect.arn
    }
  }

  # アクセス制限は特に設けない
  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }

  # ACMの証明書を当てる
  viewer_certificate {
    acm_certificate_arn            = aws_acm_certificate.main.arn
    minimum_protocol_version       = "TLSv1.2_2021"
    ssl_support_method             = "sni-only"

  }


}

# CloudFrontがs3にアクセスするためのOAC
resource "aws_cloudfront_origin_access_control" "main" {
  name                              = "s3"
  description                       = "s3"
  origin_access_control_origin_type = "s3"
  signing_behavior                  = "always"
  signing_protocol                  = "sigv4"
}

```

## CloudFront Function
``` javascript
// 全てのアクセスをrunning.jpにリダイレクトする。
function handler() { 
    return {
            statusCode: 301,
            statusDescription: 'Moved Permanently',
            headers:
                { "location": { "value": `https://running.jp` } }
        };

}
```

## DNS権限委任
AWS側の準備は前述の通りだが、これだけだとclosed.jpの名前解決を行う際にRoute53が経路に入らないためAWSにアクセスが来ない

そのためclosed.jpを管理しているDNSに、当該ドメインの権限をRoute53側に委任してもらうよう、設定を施す必要がある

権限委任の設定方法は使っているDNSのサービスによって異なるが、基本的にはRoute53のホストゾーン作成時に発行されるNSレコードをDNSに設定することになるはず。

例えばお名前comだと以下記事の手順になる。
https://dev.classmethod.jp/articles/route53-domain-onamae/

## 感想
AWSを使ってリダイレクトする方法は沢山ある。

例えば下記が思いつく
・S3の静的ホスティング機能
・ELBのリスナールール
・EC2 / ECSにWebサーバを立ててそこでやる
・Lambda
・WAF

ただ、CloudFront＋S3以外の機能だと、固定費がかかるケースが多いため
安さの観点だと今回の方法が良いと思う。

また、S3の静的ホスティング機能を使ってリダイレクトさせる方法も
今回の方法と手間・料金がほぼ変わらないのでそちらでも良いと思う
※ むしろ高アクセスの場合はそっちの方が安いかも