はじめに
個人開発アプリをAWSへ公開するためのTerraform Packを作り始めた当初、EC2への接続は普通にSSHでよいと思っていました。
ただ、Security Groupや運用手順を考えている途中で、少し引っかかりました。
「この商品を使う人全員に、SSH鍵を管理してもらうのは結構大変では?」
SSHを前提にすると、接続方法だけでも考えることが増えます。
- 秘密鍵をどこへ保管するか
- 22番ポートをどこまで許可するか
- 接続元IPが変わった場合にどうするか
- Private Subnetへ接続するために踏み台を作るか
- 複数人で運用するとき、鍵をどう発行・失効するか
- 秘密鍵を紛失したユーザーをどうサポートするか
もちろん、SSH自体が悪いわけではありません。
ただ、今回作っているのは個人開発者や小規模事業者向けの構成です。できるだけ管理するものを減らし、インフラに詳しくない人でも運用しやすくしたいと考えました。
そこで、EC2の管理接続にはSSHを使わず、AWS Systems Manager Session Managerを利用する構成にしました。
この記事では、SSM Session Managerを選んだ理由と、TerraformでEC2から利用できるようにする方法を紹介します。
SSM Session Managerとは
Session Managerは、AWS Systems Managerに含まれるEC2などの管理機能です。
AWS Management ConsoleやAWS CLIから、EC2へシェル接続できます。
SSHとの大きな違いは、EC2に管理接続用のインバウンドポートを開けなくても接続できることです。
AWS公式でも、Session Managerの特徴として次の点が挙げられています。
- インバウンドポートを開けずに接続できる
- SSH鍵を管理しなくてよい
- 踏み台サーバーを用意しなくてよい
- IAM Policyで接続権限を制御できる
詳細はAWS公式ドキュメントで確認できます。
今回の構成では、Webアクセスと管理接続を次のように分離しました。
ユーザーからの通信はALBを入口にし、EC2への管理接続にはSession Managerを利用します。
EC2のSecurity Groupに22番ポートは設定しません。
SSHではなくSSMにした理由
SSHポートを開けずに済む
ネットワーク経由でEC2へ直接SSHする場合、通常はSecurity Groupで22番ポートを許可します。
接続元IPを自宅などに限定すれば、公開範囲を狭めることはできます。
ただ、個人開発では外出先やテザリングから作業することもあり、接続元IPが変わるたびにSecurity Groupを変更するのは少し面倒です。
一時的に0.0.0.0/0で開けて、そのまま戻し忘れる…という経験、皆さんもあるのではないでしょうか?
私だけだったら普通に恥ずかしいなぁ。
Session Managerでは、EC2側のSSM AgentからSystems Managerへ通信するため、接続開始用のインバウンドポートを必要としません。
不要な入口を作らずに済むのが、採用した一番大きな理由です。
秘密鍵をユーザーへ管理してもらわなくてよい
SSH鍵を利用する場合は、秘密鍵の保管が必要です。
複数人で運用する場合、本来は同じ秘密鍵を共有するのではなく、利用者ごとに鍵を発行し、不要になった鍵を失効させる必要があります。
ただ、個人開発や小規模チームでそこまで運用するのは、意外と負担になります。
そして鍵の棚卸しを未来の自分に押し付けがちです。
そして商品として考えると、秘密鍵紛失による問い合わせが激増しそうでした……笑
Session Managerでは、接続する人のIAM権限を使ってアクセスを制御できます。
鍵を配布するのではなく、AWS側で「誰がどのEC2へ接続できるか」を管理できるため、今回のTerraform Packと相性がよいと判断しました。
踏み台EC2を増やしたくなかった
Private SubnetにあるEC2へSSHする方法として、Public Subnetに踏み台サーバーを配置する構成があります。
ただ、EC2へ接続するためだけに別のEC2を増やすと、管理対象も増えます。
- 踏み台EC2の料金
- OSアップデート
- Security Group
- SSH鍵
- ログ
- 障害監視
小規模な構成なのに、管理用サーバーの管理で忙しくなるのは本末転倒です。
Session Managerを使えば、踏み台サーバーを用意せずにPrivate Subnet上のEC2へ接続できます。
Public構成とPrivate構成で接続方法を統一できる
今回のEC2 Planには、次の2構成を含めています。
- 低コスト構成:Public Subnet + Security Group
- セキュア構成:Private Subnet + NAT Gateway
EC2の配置場所は異なりますが、管理接続をSession Managerへ統一すれば、利用者はどちらの構成でも同じ方法で接続できます。
aws ssm start-session `
--target i-xxxxxxxxxxxxxxxxx `
--region ap-northeast-1
構成によってSSH手順や鍵管理を変えなくてよいのも、運用上のメリットでした。
SSMならネットワーク設定が不要、ではない
ここは少し注意が必要です。
Session ManagerはEC2のインバウンドポートを必要としませんが、EC2からSystems Managerへ通信できる経路は必要です。
SSM Agentは、Systems ManagerのエンドポイントへHTTPSでアウトバウンド通信します。
主に次のエンドポイントが関係します。
ssm.<region>.amazonaws.com
ssmmessages.<region>.amazonaws.com
ec2messages.<region>.amazonaws.com
必要な接続先はリージョンや利用条件によって異なるため、Session Managerの前提条件も確認してください。
今回のPublic構成とPrivate構成では、通信経路が異なります。
Private Subnetからインターネットを経由させたくない場合は、Systems Manager用のVPCエンドポイントを使う方法もあります。
VPCエンドポイントを利用すれば、Internet GatewayやNAT Gatewayを通らず、AWSネットワーク内でSystems Managerへ接続できます。
詳細はAWS PrivateLinkを利用したSession Manager構成を参照してください。
必要な構成要素
EC2をSession Managerの管理対象にするには、主に次の要素が必要です。
- SSM Agent
- EC2用IAM Role
-
AmazonSSMManagedInstanceCorePolicy - IAM Instance Profile
- Systems Managerへの通信経路
- 操作者側のIAM権限
- AWS CLI接続時はSession Manager Plugin
Terraformでは、IAM RoleをEC2へ直接指定するのではなく、IAM Instance Profileを作成してEC2へ設定します。
EC2用IAM Roleを作成する
最初に、EC2が引き受けるIAM Roleを作成します。
resource "aws_iam_role" "ec2_ssm_role" {
name = "sample-ec2-ssm-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
Action = "sts:AssumeRole"
}
]
})
tags = {
Name = "sample-ec2-ssm-role"
}
}
assume_role_policyでは、EC2サービスがこのRoleを引き受けられるようにしています。
AmazonSSMManagedInstanceCoreを付与する
作成したIAM Roleへ、AWS管理PolicyのAmazonSSMManagedInstanceCoreを付与します。
resource "aws_iam_role_policy_attachment" "ssm" {
role = aws_iam_role.ec2_ssm_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
このPolicyには、EC2がSystems Managerの管理対象として動作するために必要な権限が含まれています。
最初から広い管理者権限を与える必要はありません。
IAM Instance Profileを作成する
次に、IAM RoleをEC2へ設定するためのInstance Profileを作成します。
resource "aws_iam_instance_profile" "ec2_ssm" {
name = "sample-ec2-ssm-instance-profile"
role = aws_iam_role.ec2_ssm_role.name
tags = {
Name = "sample-ec2-ssm-instance-profile"
}
}
EC2リソースでは、このInstance Profileの名前を指定します。
EC2へInstance Profileを設定する
Amazon Linux 2023を利用する例です。
data "aws_ami" "amazon_linux_2023" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-2023.*-x86_64"]
}
filter {
name = "architecture"
values = ["x86_64"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
resource "aws_instance" "app" {
ami = data.aws_ami.amazon_linux_2023.id
instance_type = "t3.micro"
subnet_id = aws_subnet.app.id
vpc_security_group_ids = [aws_security_group.app.id]
iam_instance_profile = aws_iam_instance_profile.ec2_ssm.name
metadata_options {
http_endpoint = "enabled"
http_tokens = "required"
}
tags = {
Name = "sample-app"
}
depends_on = [
aws_iam_role_policy_attachment.ssm
]
}
重要なのは次の部分です。
iam_instance_profile = aws_iam_instance_profile.ec2_ssm.name
これで、EC2がInstance Profile経由でIAM Roleを利用できるようになります。
metadata_optionsでは、Instance Metadata Service Version 2を必須にしています。
Security GroupにSSHを追加しない
アプリケーション用Security Groupには、22番ポートを設定しません。
ALBを利用する場合は、アプリケーションへのHTTP通信をALBのSecurity Groupからだけ許可します。
resource "aws_security_group" "app" {
name = "sample-app-sg"
vpc_id = aws_vpc.main.id
tags = {
Name = "sample-app-sg"
}
}
resource "aws_vpc_security_group_ingress_rule" "app_from_alb" {
security_group_id = aws_security_group.app.id
referenced_security_group_id = aws_security_group.alb.id
from_port = 80
to_port = 80
ip_protocol = "tcp"
description = "Allow HTTP only from ALB"
}
resource "aws_vpc_security_group_egress_rule" "app_outbound" {
security_group_id = aws_security_group.app.id
cidr_ipv4 = "0.0.0.0/0"
ip_protocol = "-1"
description = "Allow outbound traffic"
}
このSecurity Groupには、次のようなSSH用ルールは作成しません。
# 今回は作成しない
#
# resource "aws_vpc_security_group_ingress_rule" "ssh" {
# security_group_id = aws_security_group.app.id
# cidr_ipv4 = "0.0.0.0/0"
# from_port = 22
# to_port = 22
# ip_protocol = "tcp"
# }
WebアクセスはALBからだけ、管理接続はSession Managerから行う、という役割分担です。
SSM Agentを確認する
AWSが提供するAmazon Linux 2やAmazon Linux 2023のAMIでは、多くの場合SSM Agentが最初からインストールされています。
ただし、AMIによって異なる可能性があるため、「Amazon Linuxだから必ず動く」と決めつけず、状態を確認します。
EC2上では次のコマンドで確認できます。
sudo systemctl status amazon-ssm-agent
停止している場合は起動します。
sudo systemctl enable --now amazon-ssm-agent
AWS公式にも、Amazon Linux 2とAmazon Linux 2023では通常SSM Agentがプリインストールされていると記載されています。
Amazon Linux 2/Amazon Linux 2023へのSSM Agent導入
AWS CLIから接続する
ローカルPCから接続する場合は、AWS CLIとSession Manager Pluginをインストールします。
対象EC2のInstance IDを確認したら、次のコマンドを実行します。
aws ssm start-session `
--target i-xxxxxxxxxxxxxxxxx `
--region ap-northeast-1
接続に成功すると、EC2上のシェルを操作できます。
Starting session with SessionId: user-xxxxxxxxxxxxxxxxx
sh-5.2$
TerraformのOutputとしてコマンドを出しておくと、利用者がInstance IDを探す手間を減らせます。
output "ssm_start_session_command" {
description = "Command to connect to the EC2 instance using Session Manager"
value = join(" ", [
"aws ssm start-session",
"--target ${aws_instance.app.id}",
"--region ${var.aws_region}",
])
}
構築後は次のように確認できます。
terraform output -raw ssm_start_session_command
操作者側にもIAM権限が必要
EC2にIAM Roleを付けるだけでは、誰でも接続できるわけではありません。
Session Managerを開始するIAMユーザーやIAM Roleにも、対象EC2へのセッション開始を許可する権限が必要です。
設計上は、次の2つを分けて考えます。
- EC2側の権限:SSM AgentがSystems Managerと通信するための権限
- 操作者側の権限:対象EC2に対してSession Managerを開始する権限
開発者全員へAdministratorAccessを付けるのではなく、接続対象や操作範囲をIAM Policyで制限するのが理想です。
複数人で運用する場合は、IAM Identity CenterやIAM Roleと組み合わせる方法もあります。
接続できないときに確認すること
Session Managerで接続できない場合、よくあるのが次の4点です。
1. IAM Instance Profileが付いていない
EC2のIAM Role欄とTerraform stateを確認します。
terraform state show aws_instance.app
2. AmazonSSMManagedInstanceCoreが付いていない
IAM RoleにPolicyが付与されているか確認します。
aws iam list-attached-role-policies `
--role-name sample-ec2-ssm-role
3. SSM Agentが停止している
EC2上でAgentの状態を確認します。
sudo systemctl status amazon-ssm-agent
4. Systems Managerへ通信できない
Private Subnetの場合は特に注意が必要です。
- NAT Gatewayへのルートがあるか
- VPCエンドポイントを利用しているか
- 443番のアウトバウンド通信が許可されているか
- VPCエンドポイントのSecurity Groupが正しいか
AWS CLIから管理対象として認識されているかも確認できます。
aws ssm describe-instance-information `
--region ap-northeast-1
対象EC2が表示されなければ、まずEC2側のIAM Role、SSM Agent、通信経路を確認します。
SSM Session Managerも万能ではない
Session Managerを使えば、SSH鍵や踏み台サーバーを減らせます。
ただし、設定なしで安全になるわけではありません。
- 操作者のIAM権限を適切に制限する
- 不要になった権限を削除する
- SSM Agentを更新する
- Systems Managerへの通信経路を確認する
- 必要に応じてセッションログを記録する
- CloudTrailでAPI操作を追跡できるようにする
特に複数人で利用する場合は、「接続できるか」だけでなく、「誰が接続できるか」「操作をどこまで記録するか」まで考える必要があります。
まとめ
今回のTerraform Packでは、EC2への管理接続をSSM Session Managerへ統一しました。
理由はシンプルです。
- 22番ポートを開けたくない
- SSH秘密鍵を購入者へ管理してもらいたくない。
というかサポート対応したくない - 踏み台EC2を増やしたくない
- Public構成とPrivate構成で接続方法を統一したい
- IAMで接続権限を管理したい
実装上のポイントは次のとおりです。
1. EC2用IAM Roleを作成する
2. AmazonSSMManagedInstanceCoreを付与する
3. IAM Instance Profileを作成する
4. EC2へInstance Profileを設定する
5. SSM Agentを起動する
6. Systems Managerへのアウトバウンド通信を確保する
7. 操作者側へSession Managerの権限を付与する
8. Security Groupへ22番ポートを追加しない
SSMを採用したことで、SSH鍵を配らず、踏み台も増やさず、不要なインバウンドポートを開けない構成にできました。
個人開発でも、公開する以上は最低限守りたいところがあります。
一方で、セキュリティを理由に管理対象を増やしすぎると、今度は運用できなくなります。
その間を取る方法として、SSM Session Managerはかなり使いやすい選択肢でした。