はじめに
AWS試験対策の問題を解くと、しばしばEC2へのセキュアな接続方式として、 Systems Manager Session Managerを利用する方法を目にします。
今回は知識整理のため、Session Managerを利用してプライベートサブネットのEC2へ接続できる環境を、 Terraformで構築してみました。
今回作成する環境
Session ManagerでプライベートサブネットのEC2へ接続するために必要なこと
大まかに以下の作業が必要となります。
- EC2にSSM Agentをインストールする。
- Session Manager(及びSSM)へのアクセスを許可したIAMロールをEC2に付与する。
- VPCエンドポイントを作成する。
- EC2とVPCエンドポイントが相互に接続できるセキュリティグループを設定する。
それぞれの作業がTerraformコードのどの部分に対応するかは後述します。
なお、EC2がインターネット接続できる場合は、VPCエンドポイントの作成は不要です。
参考
- Session Managerユーザガイド『ステップ 1: Session Manager の前提条件を満たす』 https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/session-manager-prerequisites.html
利用したTerraformコード
環境
- OS:Windows 11 Pro
- バージョン:23H2
- OSビルド:22631.4037
- WSL2
- Ubuntu 22.04.3
Terraformバージョン
$ terraform providers -version
Terraform v1.11.0
on linux_amd64
+ provider registry.terraform.io/hashicorp/aws v5.94.1
ディレクトリ構成
$ tree
.
├── ec2.tf
├── iam.tf
├── provider.tf
├── security_group.tf
├── variable.tf
└── vpc.tf
コード内容
以下に利用したTerraformコードを記載します。
Session Managerで接続するために必要なこと等、特筆すべきことがあれば適宜記載します。
- provider.tf
terraform {
required_version = ">= 1.0.0, < 2.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
- variable.tf
locals {
region = "ap-northeast-1"
https_port = 443
any_protocol = -1
tcp_protocol = "tcp"
}
variable "vpc_cidr" {
description = "CIDR block for VPC"
type = string
default = "10.0.0.0/16"
}
variable "subnet" {
description = "CIDR block and region for subnet"
type = object({
az = string
cidr = string
})
default = {
az = "ap-northeast-1a"
cidr = "10.0.1.0/24"
}
}
variable "vpc_endpoint" {
description = "List of VPC endpoint name"
type = list(string)
default = ["ssm", "ssmmessages"]
}
- vpc.tf
# VPCの作成
resource "aws_vpc" "test_vpc" {
cidr_block = var.vpc_cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "test_vpc"
}
}
# プライベートサブネットの作成
resource "aws_subnet" "test_private_subnet" {
vpc_id = aws_vpc.test_vpc.id
cidr_block = var.subnet.cidr
availability_zone = var.subnet.az
tags = {
Name = "test_private_subnet"
}
}
# 空のルートテーブルを作成
resource "aws_route_table" "test_private_route_table" {
vpc_id = aws_vpc.test_vpc.id
route = []
tags = {
Name = "test_private_route_table"
}
}
# ルートテーブルをプライベートサブネットへアタッチ
resource "aws_route_table_association" "test_private_route_table_association" {
subnet_id = aws_subnet.test_private_subnet.id
route_table_id = aws_route_table.test_private_route_table.id
}
# VPCエンドポイントの作成
resource "aws_vpc_endpoint" "test_vpc_endpoint" {
for_each = toset(var.vpc_endpoint)
vpc_id = aws_vpc.test_vpc.id
subnet_ids = [aws_subnet.test_private_subnet.id]
service_name = "com.amazonaws.${local.region}.${each.value}"
vpc_endpoint_type = "Interface"
security_group_ids = [aws_security_group.vpc_endpoint_sg.id]
private_dns_enabled = true
}
こちらのコードでVPC、サブネット、VPCエンドポイントを作成しています。
Session Managerの接続には、以下のVPCエンドポイントの作成が必要になります。
com.amazonaws.region.ssm
com.amazonaws.region.ssmmessages
variable.tfでVPCエンドポイントの名前を要素に持つリストを作成しておき、 for_eachで一括で作成する方法にしてみました。
なお、以前まではec2messages.region.amazonaws.com
というVPCエンドポイントも作成が必要でしたが、
直近のSSM Agentではec2messagesは不要になったとのことです。
必要なVPCエンドポイントの情報は、以下の記事を大変参考にさせていただきました。
『SSM セッションマネージャーに必要なVPCエンドポイントが2つになっていた』(https://dev.classmethod.jp/articles/vpcendpoint-ec2messages-not-required/)
- iam.tf
# 信頼されたエンティティを定義
data "aws_iam_policy_document" "assume_role" {
statement {
actions = [
"sts:AssumeRole"
]
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}
# IAMロールの作成
resource "aws_iam_role" "test_iam_role" {
name = "test_role"
assume_role_policy = data.aws_iam_policy_document.assume_role.json
}
# IAMインスタンスプロファイルの作成
resource "aws_iam_instance_profile" "test_profile" {
name = "test_profile"
role = aws_iam_role.test_iam_role.name
}
# AmazonSSMManagedInstanceCoreのARNをフェッチ
data "aws_iam_policy" "ssm_access" {
arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
# IAMロールにAmazonSSMManagedInstanceCoreをアタッチ
resource "aws_iam_role_policy_attachment" "test_attachment" {
role = aws_iam_role.test_iam_role.name
policy_arn = data.aws_iam_policy.ssm_access.arn
}
こちらのコードでEC2にアタッチするロールを定義しています。
EC2にSession Managerへのアクセスを許可するため、AWS管理ポリシーのAmazonSSMManagedInstanceCore
をIAMロールにアタッチしています。
- ec2.tf
# 最新のAmazon Linux 2023 x86_64版のAMI IDを取得
data "aws_ami" "amzn_linux_2023_ami" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-2023.*-x86_64"]
}
}
resource "aws_instance" "test_ec2" {
ami = data.aws_ami.amzn_linux_2023_ami.id
instance_type = "t2.micro"
subnet_id = aws_subnet.test_private_subnet.id
iam_instance_profile = aws_iam_instance_profile.test_profile.name
vpc_security_group_ids = [aws_security_group.ec2_sg.id]
tags = {
Name = "test_ec2"
}
}
こちらのコードでEC2の作成を定義しています。
今回はデフォルトでSSM AgentがインストールされているAmazon Linux 2023を利用しました。
また、aws_ami
Data Sourcesを利用して、自動で最新のAmazon Linux 2023のAMIを指定するようにしています。
- security_group.tf
# EC2用セキュリティグループの作成
resource "aws_security_group" "ec2_sg" {
name = "ec2_sg"
vpc_id = aws_vpc.test_vpc.id
tags = {
Name = "ec2_sg"
}
}
# VPCエンドポイント用セキュリティグループの作成
resource "aws_security_group" "vpc_endpoint_sg" {
name = "vpc_endpoint_sg"
vpc_id = aws_vpc.test_vpc.id
tags = {
Name = "vpc_endpoint_sg"
}
}
# HTTPSのインバウンド許可設定を作成し、VPCエンドポイント用セキュリティグループへアタッチ
resource "aws_vpc_security_group_ingress_rule" "allow_https_ingress" {
security_group_id = aws_security_group.vpc_endpoint_sg.id
cidr_ipv4 = var.subnet.cidr
from_port = local.https_port
ip_protocol = local.tcp_protocol
to_port = local.https_port
}
# HTTPSのアウトバウンド許可設定を作成し、EC2用セキュリティグループへアタッチ
resource "aws_vpc_security_group_egress_rule" "allow_https_egress" {
security_group_id = aws_security_group.ec2_sg.id
cidr_ipv4 = var.subnet.cidr
from_port = local.https_port
ip_protocol = local.tcp_protocol
to_port = local.https_port
}
最後にこちらのコードでセキュリティグループを定義しています。
Session Managerを利用するために、EC2とVPCエンドポイントでそれぞれの以下の通りセキュリティグループを設定は必要になります。
種別 | 許可設定 |
---|---|
EC2 | VPCエンドポイントへのHTTPS(ポート443)アウトバウンド |
VPCエンドポイント | EC2からのHTTPS(ポート443)インバウンド |
Terraformの実行
上記コードを作成後、以下のコマンドでterraformによる構築を開始します。
$ terraform init
$ terraform apply
Session Managerでの接続
Terraformで環境構築が終わったら、Session Manager経由でのEC2接続を試すことができます。
Session Managerへの接続方法として、マネジメントコンソールからの接続方法とAWS CLIを用いた方法があるのでそれぞれ記載します。
マネジメントコンソールでの接続
EC2 > インスタンス > 接続したインスタンスを選択 > 接続タブをクリックし、Session Managerの接続ページへアクセスします。
Session Managerの画面が開き、シェル操作ができるようになります。
Terraformで環境構築をした直後だと、以下のようなエラーが出る場合がありました。 その場合は数分程度待てば接続できるようになりました。
AWS CLIでの接続
AWS CLIでSession Managerに接続する場合は、Session Managerプラグインをインストールする必要があります。
今回は、WSL2のUbuntuに導入するため、以下の手順を実行しました。
『Debian Server と Ubuntu Server での Session Manager プラグインのインストール』 https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/install-plugin-debian-and-ubuntu.html
Session Managerプラグインをインストール後、以下のコマンドを実行するとSession Manager経由でEC2の操作ができます。
$ aws ssm start-session --target インスタンスID --region リージョン名 --profile プロファイル名(プロファイルを使用していれば)
Starting session with SessionId: botocore-session-xxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxx
sh-5.2$ uname -n
ip-10-0-1-172.ap-northeast-1.compute.internal
sh-5.2$
終わりに
Session Managerを利用すると、EC2のセキュリティグループでSSHを許可しなくてもよかったり、SSH鍵を用意しなくても接続できて便利そうです。
個人のAWSアカウントで検証する際は、積極的に利用したいと思います。