【やってみた】Amazon CloudFrontのVPCオリジンを活用して、パブリックIPを持たないAmazon EC2を外部公開してみる

AWS

2025.1.20

Topics

はじめに

2024年11月20日にVPC Originという機能が追加されました。
ざっくり説明すると、CloudFrontのオリジンにプライベートサブネットに配置されたEC2やALB,NLBをオリジンにすることができるという機能です。
https://aws.amazon.com/jp/blogs/news/introducing-amazon-cloudfront-vpc-origins-enhanced-security-and-streamlined-operations-for-your-applications/
今回はこのVPC Originという機能を使って、パブリックIPを持たないEC2を外部公開してみようと思います。

VPC Originのメリット

CloudFrontのVPCオリジンを活用してプライベートサブネット内のリソースを公開するメリットは3点ほどあります。
・EC2用のElasticIPが必要なくなるため、コスト削減できる
・VPCオリジン配下を外部(インターネット)から隔離できアクセスされる心配が無いため、セキュリティ向上が期待できる
・今までパブリックサブネットに配置されていたEC2やALB等のオリジンに、IP制限やセキュリティヘッダを使ってCloudFrontのみアクセスできるようにする必要があったが不要になる

VPCオリジンを使ったパブリックIPの無いEC2を公開するまでの流れ

  1. VPC周りとEC2を作成する
  2. VPCオリジンを作成する
  3. セキュリティグループにVPCオリジンを追加する
  4. CloudFrontを作成する
  5. CloudFrontのドメインからWEBページを確認

完成イメージ図

1. VPC周りとEC2を作成する

手動で作成してもいいが、面倒なのでTerraformで作成する。(似たような構成が作れるなら手動でも可)

設定内容
・VPC作成(プライベートサブネット,VPCエンドポイント,セキュリティグループ)
・EC2作成(SSMFullAccessロール作成&EC2にアタッチ,Nginx・SSMAgentインストール&起動,htmlコンテンツ追加)

main.tf
terraform {
  required_version = ">=1.9.5"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.44.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

# ===============================================================================
# リソースの命名に yasuda-kensyo を付ける
# ===============================================================================
locals {
  env = "yasuda-kensyo"
}
aws_ec2.tf

# ===============================================================================
# WEBサーバー作成
# ===============================================================================
resource "aws_instance" "web-ec2" {
  ami                    = "ami-023ff3d4ab11b2525"
  instance_type          = "t3.micro"
  subnet_id = aws_subnet.private_1a.id
  vpc_security_group_ids = [aws_security_group.ec2.id]
  iam_instance_profile = aws_iam_instance_profile.ec2_instance_profile.name
  user_data = <<-EOF
  #!/bin/bash
    sudo dnf update -y
    sudo setenforce 0
    sudo dnf install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm
    sudo systemctl enable amazon-ssm-agent
    sudo systemctl start amazon-ssm-agent
    sudo dnf install -y nginx 
    sudo systemctl start nginx
    sudo systemctl enable nginx
    sudo echo "<html>hello</html>" > /usr/share/nginx/html/index.html
    EOF

  tags = {
    "Name" = "${local.env}-web-ec2"
  }
}

# ===============================================================================
# IAMロール作成
# ===============================================================================
resource "aws_iam_role" "ec2_ssm_role" {
  name               = "ec2-ssm-role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "ec2.amazonaws.com"
        }
      }
    ]
  })
}

# ===============================================================================
# IAMポリシーアタッチ
# ===============================================================================
resource "aws_iam_role_policy_attachment" "ssm_full_access" {
  role       = aws_iam_role.ec2_ssm_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMFullAccess"
}

# ===============================================================================
# EC2インスタンスプロファイル作成
# ===============================================================================
resource "aws_iam_instance_profile" "ec2_instance_profile" {
  name = "ec2-ssm-instance-profile"
  role = aws_iam_role.ec2_ssm_role.name
}


aws_vpc.tf
# ===============================================================================
# main vpc
# ===============================================================================
resource "aws_vpc" "main" {
    assign_generated_ipv6_cidr_block     = false
    cidr_block                           = "10.0.0.0/16"
    enable_dns_hostnames                 = true
    enable_dns_support                   = true
    tags                                 = {
        "Name" = "${local.env}-vpc"
    }
}

# ===============================================================================
# インターネットゲートウェイ作成
# ===============================================================================
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "${local.env}-igw"
  }
}

# ===============================================================================
# プライべートサブネットを作成
# ===============================================================================
resource "aws_subnet" "private_1a" {
    vpc_id = aws_vpc.main.id

    availability_zone = "ap-northeast-1a"
    cidr_block        = "10.0.1.0/24"

    tags = {
        Name = "${local.env}-private-a"
    }
}

# ===============================================================================
# プライベートサブネット用のルートテーブル作成
# ===============================================================================
resource "aws_route_table" "private" {
  vpc_id = aws_vpc.main.id
  tags = {
    Name = "${local.env}-private-route"
  }
}

# ===============================================================================
# プライベートルートにプライベートサブネット関連付け
# ===============================================================================
resource "aws_route_table_association" "private_subnet_A" {
   subnet_id = aws_subnet.private_1a.id
   route_table_id = aws_route_table.private.id
}

# ===============================================================================
# プライベートルートにS3エンドポイントゲートウェイ関連付け
# ===============================================================================
resource "aws_vpc_endpoint_route_table_association" "s3" {
  vpc_endpoint_id = aws_vpc_endpoint.s3.id
  route_table_id  = aws_route_table.private.id
}

# ===============================================================================
# EC2用のセキュリティグループ作成
# ===============================================================================
resource "aws_security_group" "ec2" {
  vpc_id= "${aws_vpc.main.id}"
  name = "${local.env}-ec2"
  description = "${local.env}-ec2"
  egress{
    from_port  = 0
    to_port    = 0
    protocol   = "-1"
    cidr_blocks=["0.0.0.0/0"]
  }
  tags         = {
    "Name" = "${local.env}-ec2"
  }
}

# ===============================================================================
# VPCエンドポイント用のセキュリティグループ作成
# ===============================================================================
resource "aws_security_group" "ssm" {
  vpc_id= "${aws_vpc.main.id}"
  name  = "${local.env}-ssm"
  description = "${local.env}-ssm"
  ingress{
    from_port = 443
    to_port   = 443
    protocol  = "tcp"
    cidr_blocks=["10.0.0.0/16"]
  }
  tags = {
    "Name" = "${local.env}-ssm"
  }
}

# ===============================================================================
# VPC Endpoint 作成
# ===============================================================================
data "aws_iam_policy_document" "vpc_endpoint" {
  statement {
    effect    = "Allow"
    actions   = [ "*" ]
    resources = [ "*" ]
    principals {
      type = "AWS"
      identifiers = [ "*" ]
    }
  }
}

resource "aws_vpc_endpoint" "ssm" {
  vpc_endpoint_type = "Interface"
  vpc_id            = aws_vpc.main.id
  service_name      = "com.amazonaws.ap-northeast-1.ssm"
  policy            = data.aws_iam_policy_document.vpc_endpoint.json
  subnet_ids = [
    aws_subnet.private_1a.id
  ]
  private_dns_enabled = true
  security_group_ids = [
    aws_security_group.ssm.id
  ]
}

resource "aws_vpc_endpoint" "ssmmessages" {
  vpc_endpoint_type = "Interface"
  vpc_id            = aws_vpc.main.id
  service_name      = "com.amazonaws.ap-northeast-1.ssmmessages"
  policy            = data.aws_iam_policy_document.vpc_endpoint.json
  subnet_ids = [
    aws_subnet.private_1a.id
  ]
  private_dns_enabled = true
  security_group_ids = [
    aws_security_group.ssm.id
  ]
}

resource "aws_vpc_endpoint" "ec2messages" {
  vpc_endpoint_type = "Interface"
  vpc_id            = aws_vpc.main.id
  service_name      = "com.amazonaws.ap-northeast-1.ec2messages"
  policy            = data.aws_iam_policy_document.vpc_endpoint.json
  subnet_ids = [
    aws_subnet.private_1a.id
  ]
  private_dns_enabled = true
  security_group_ids = [
    aws_security_group.ssm.id
  ]
}

resource "aws_vpc_endpoint" "s3" {
  vpc_endpoint_type = "Gateway"
  vpc_id            = aws_vpc.main.id
  service_name      = "com.amazonaws.ap-northeast-1.s3"
  policy            = data.aws_iam_policy_document.vpc_endpoint.json
}


2. VPCオリジンを作成する

CloudFrontの画面から「VPCオリジン」を選択し、下記の画面に入る

「Name」を適当に入力、「オリジンARN」にEC2のARNを入力し、「VPCオリジンを作成」を選択する

「Status」が「Deployed」になったら作成完了

3. セキュリティグループにVPCオリジンを追加する

VPCオリジンがEC2に対してHTTP通信可能にするために、EC2のセキュリティグループにVPCオリジンのセキュリティグループをHTTP通信許可を設定する
(Terraform通り作成した場合は、EC2のセキュリティグループが「yasuda-kensyo-ec2」となる)

4. CloudFrontを作成する

「Origin domain」に2. で作成したVPCOriginを選択する

「VPC origin domain」にEC2のプライベートDNS名を入力する

「ウェブアプリケーションファイアウォール(WAF)」は使わないので有効にしない

5. CloudFrontのドメインからWEBページを確認

確認できればOK
※今回は証明書を適用していないので、httpでアクセスすること

最後に

今回はCloudFrontのVPCオリジンを活用してパブリックIPを持たないEC2を外部公開しました。
VPCオリジンは、EC2のほかにもプライベートサブネットに配置されているALBやNLBに対しても使用できます。
本記事には掲載していませんが、InternalALB + ECSの構成でもデプロイできましたのでInternalALB越しであればなんでもOKみたいです。

テックブログ新着情報のほか、AWSやGoogle Cloudに関するお役立ち情報を配信中!

yasujun

インフラの勉強と趣味の武道をバランス良く両立していきたいエンジニアです。 よろしくお願いいたします!

Recommends

こちらもおすすめ

Special Topics

注目記事はこちら