[terraform]AWSのプライベートサブネットにあるEC2にSession Managerを使ってセキュアにアクセスする
Session Managerについて
Session ManagerはAWS Systems Managerの一部であり、安全にEC2インスタンスなどに接続するためのツールです。Session Managerを使用すれば、SSHキーやパブリックIPを割り当てることなく、AWSのプライベートサブネットに配置されたEC2インスタンスに接続することができます。
VPCエンドポイントについて
VPCエンドポイントは、VPC内のリソース(例:EC2 インスタンス)がインターネットを経由せずに、他のAWSサービスに接続できるようにするための仮想デバイスです。
VPCエンドポイントを使ったSession Managerでのアクセスに必要なもの
・IAMポリシーのAmazonSSMManagedInstanceCore
を持ったIAMロールを持つEC2
・SSM エージェントがインストールされているEC2
・以下の4つのインターフェース型のVPCエンドポイント
・com.amazonaws.region.ec2
・com.amazonaws.region.ec2messages
・com.amazonaws.region.ssm
・com.amazonaws.region.ssmmessages
・VPCエンドポイントへの443通信の許可
今回作るもの
今回はプライベートサブネットにあるEC2にVPCエンドポイント経由でSession Managerを使ってセキュアにアクセスできるようにします。
作るのは、以下の通りです。
・VPCとサブネット
・EC2インスタンス
・IAMロール
・VPCエンドポイント
図にするとこんな感じです。
実際に作っていく
ステップ1 VPCとサブネットを作る
VPCとプライベートサブネットをEC2用とVPCエンドポイント用で2つ作っていきます。
VPCとサブネットのcidr_blockと設計は以下の通りです。
10.0.0.0/16 は全体のアドレス範囲、
10.0.0.0/18 はリージョン、AZ用(4個)、
10.0.0.0/20 はサブネット用(4個)、
アドレス部は12ビット(=4096-5個)
になるような設計をしました。
name | cidr_block |
---|---|
VPC | 10.0.0.0/16 |
EC2のサブネット | 10.0.16.0/20 |
VPCエンドポイントのサブネット | 10.0.48.0/20 |
locals {
aws_region = "ap-northeast-1"
name = "sample"
cidr_blocks = {
vpc = "10.0.0.0/16"
private = "10.0.16.0/20"
endpoint = "10.0.48.0/20"
}
}
# VPC
# 10.0.0.0/16 は全体のアドレス範囲
# 10.0.0.0/18 はリージョン、AZ用(4個)
# 10.0.0.0/20 はサブネット用(4個)
# アドレス部は12ビット(=4096-5個)
resource "aws_vpc" "main" {
cidr_block = local.cidr_blocks.vpc
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = local.name
}
}
# EC2用のプライベートサブネット
resource "aws_subnet" "private" {
availability_zone = "ap-northeast-1a"
cidr_block = local.cidr_blocks.private
vpc_id = aws_vpc.main.id
}
resource "aws_network_acl" "private" {
vpc_id = aws_vpc.main.id
subnet_ids = [aws_subnet.main.id]
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
}
resource "aws_route_table_association" "private" {
subnet_id = aws_subnet.private.id
route_table_id = aws_route_table.private.id
}
# VPCエンドポイント用のプライベートサブネット
resource "aws_subnet" "endpoint" {
availability_zone = "ap-northeast-1a"
cidr_block = local.cidr_blocks.endpoint
vpc_id = aws_vpc.main.id
}
resource "aws_network_acl" "endpoint" {
vpc_id = aws_vpc.main.id
subnet_ids = [aws_subnet.main.id]
}
resource "aws_route_table" "endpoint" {
vpc_id = aws_vpc.main.id
}
resource "aws_route_table_association" "endpoint" {
subnet_id = aws_subnet.endpoint.id
route_table_id = aws_route_table.endpoint.id
}
ステップ2 IAMロールの作成
IAMポリシーのAmazonSSMManagedInstanceCore
を持ったIAMロールを作ります。
data "aws_iam_policy_document" "assume_role" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}
resource "aws_iam_role" "main" {
name = "ssm-iam-role"
assume_role_policy = data.aws_iam_policy_document.assume_role.json
}
data "aws_iam_policy" "systems_manager" {
arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
resource "aws_iam_role_policy_attachment" "main" {
role = aws_iam_role.main.name
policy_arn = data.aws_iam_policy.systems_manager.arn
}
resource "aws_iam_instance_profile" "systems_manager" {
name = "ssm-instance-profile"
role = aws_iam_role.main.name
}
ステップ3 EC2インスタンスの作成
EC2用のプライベートサブネットに、先ほど作ったIAMロールを付与したEC2インスタンスを作っていきます。
AMIは、SSM エージェントがすでにインストールされているAmazon Linux2023を使います。
resource "aws_instance" "main" {
ami = "ami-05779067e4eff0b9d"
instance_type = "t4g.nano"
subnet_id = local.cidr_blocks.private
associate_public_ip_address = false
vpc_security_group_ids = [aws_security_group.ec2.id]
availability_zone = "ap-northeast-1a"
iam_instance_profile = aws_iam_instance_profile.systems_manager.name
}
resource "aws_security_group" "ec2" {
name = "ec2-sg"
vpc_id = aws_vpc.main.id
description = "ec2 security group"
}
ステップ4 VPCエンドポイントの作成
以下の4つのインターフェースエンドポイントを作成します。
・com.amazonaws.region.ec2
・com.amazonaws.region.ec2messages
・com.amazonaws.region.ssm
・com.amazonaws.region.ssmmessages
resource "aws_vpc_endpoint" "main" {
for_each = {
ec2 = "com.amazonaws.ap-northeast-1.ec2"
ec2messages = "com.amazonaws.ap-northeast-1.ec2messages"
ssm = "com.amazonaws.ap-northeast-1.ssm"
ssmmessages = "com.amazonaws.ap-northeast-1.ssmmessages"
}
service_name = each.value
vpc_endpoint_type = "Interface"
vpc_id = var.vpc_id
security_group_ids = [aws_security_group.vpc_endpoint.id]
subnet_ids = [aws_subnet.private.id]
}
resource "aws_security_group" "vpc_endpoint" {
description = "vpc-endpoint"
name = "vpc-endpoint"
vpc_id = aws_vpc.main.id
}
ステップ5 セキュリティグループとNACLの調整
先ほど作成したエンドポイントに対して、EC2からポート443のアクセスができるようにセキュリティグループとNACLを調整します。
注意ポイント:
・行きの通信だけではなく、帰りの通信も通れるようにしよう
・NACLは帰りの通信も記載する必要がありますが、セキュリティグループはステートレスなので、行きの通信のみで良いです
NACLの調整
# ステップ1で作成した、EC2用のプライベートサブネットのファイルの続き
resource "aws_network_acl_rule" "subnet_private1_ingress" {
for_each = {
ephemeral = { action = "allow", cidr_block = local.cidr_blocks.endpoint, from_port = 1024, to_port = 65535, protocol = "tcp", rule_no = 100 }, #ssmの443通信の帰り
}
network_acl_id = aws_network_acl.private.id
rule_action = each.value.action
cidr_block = each.value.cidr_block
from_port = each.value.from_port
to_port = each.value.to_port
protocol = each.value.protocol
rule_number = each.value.rule_no
egress = false
}
resource "aws_network_acl_rule" "subnet_private1_egress" {
for_each = {
https = { action = "allow", cidr_block = local.cidr_blocks.endpoint, from_port = 443, to_port = 443, protocol = "tcp", rule_no = 100 }, #ec2からインターネット通信するため
}
network_acl_id = aws_network_acl.private.id
rule_action = each.value.action
cidr_block = each.value.cidr_block
from_port = each.value.from_port
to_port = each.value.to_port
protocol = each.value.protocol
rule_number = each.value.rule_no
egress = true
}
# ステップ1で作成した、VPCエンドポイント用のプライベートサブネットのファイルの続き
resource "aws_network_acl_rule" "subnet_endpoint1_ingress" {
for_each = {
https_from_private = { action = "allow", cidr_block = local.cidr_blocks.private, from_port = 443, to_port = 443, protocol = "tcp", rule_no = 100 },
}
network_acl_id = aws_network_acl.endpoint.id
rule_action = each.value.action
cidr_block = each.value.cidr_block
from_port = each.value.from_port
to_port = each.value.to_port
protocol = each.value.protocol
rule_number = each.value.rule_no
egress = false
}
resource "aws_network_acl_rule" "subnet_endpoint1_egress" {
for_each = {
ephemeral_to_private1 = { action = "allow", cidr_block = local.cidr_blocks.private, from_port = 1024, to_port = 65535, protocol = "tcp", rule_no = 100 },
}
network_acl_id = aws_network_acl.endpoint.id
rule_action = each.value.action
cidr_block = each.value.cidr_block
from_port = each.value.from_port
to_port = each.value.to_port
protocol = each.value.protocol
rule_number = each.value.rule_no
egress = true
}
セキュリティグループの調整
# ステップ3で作ったEC2のファイルの続き
resource "aws_vpc_security_group_egress_rule" "ec2_egress" {
from_port = 443
to_port = 443
ip_protocol = "tcp"
security_group_id = aws_security_group.ec2.id
cidr_ipv4 = local.cidr_blocks.private
}
#ステップ4で作ったVPCエンドポイントのファイルの続き
resource "aws_vpc_security_group_ingress_rule" "vpc_endpoint_ingress" {
from_port = 443
to_port = 443
ip_protocol = "tcp"
security_group_id = aws_security_group.vpc_endpoint.id
cidr_ipv4 = local.cidr_blocks.private
}
ステップ6 Session Managerでアクセスする
-
AWSにアクセスして、EC2のインスタンス選択して、接続ボタンを押す
-
画像のようになっていれば成功です!
まとめ
今回SSMでの接続をしましたが、個人的にネットワーク周りにかなり苦戦したので、うまくいかない時は入念に見直しましょう。
あと、公式ドキュメント読みにくくて、個人で書いたブログに行きがちですが、公式は間違いがないので最優先で読みましょう。
不明点や問題点などありましたらコメントお願いします。
参考記事
Discussion