SSMセッションマネージャーでEC2タグベースのアクセス制御を試してみた
はじめに
クラウド事業本部、あきやまです。
特定のEC2インスタンスにだけSSM接続を許可したいっていうことってありませんか?私はあります。
そこで、EC2のタグとIAMポリシーの条件キーを組み合わせたアクセス制御を検証してみました。
環境
| 項目 | 値 |
|---|---|
| OS | macOS Sequoia |
| Terraform | >= 1.0 |
| AWS Provider | ~> 5.0 |
| リージョン | ap-northeast-1 |
| EC2 AMI | Amazon Linux 2023(最新) |
| インスタンスタイプ | t3.micro |
やってみた
全体構成
今回の検証環境はTerraformで構築しました。構成は以下のとおりです。
- プライベートサブネットにEC2を配置(パブリックIP なし)
- VPCエンドポイント経由でSSM接続(NAT Gateway 不要)
- IAMグループにアタッチしたポリシーでタグベースのアクセス制御
Step 1: VPCとVPCエンドポイントの作成
プライベートサブネットからSSMに接続するため、3つのVPCエンドポイントを作成します。
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "${var.project}-vpc"
Project = var.project
}
}
resource "aws_subnet" "private" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = data.aws_availability_zones.available.names[0]
tags = {
Name = "${var.project}-private"
Project = var.project
}
}
VPCエンドポイントはSSMセッションマネージャーに必要な3つを作成します。
resource "aws_vpc_endpoint" "ssm" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.ssm"
vpc_endpoint_type = "Interface"
subnet_ids = [aws_subnet.private.id]
security_group_ids = [aws_security_group.vpce.id]
private_dns_enabled = true
tags = {
Name = "${var.project}-ssm"
}
}
resource "aws_vpc_endpoint" "ssmmessages" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.ssmmessages"
vpc_endpoint_type = "Interface"
subnet_ids = [aws_subnet.private.id]
security_group_ids = [aws_security_group.vpce.id]
private_dns_enabled = true
tags = {
Name = "${var.project}-ssmmessages"
}
}
resource "aws_vpc_endpoint" "ec2messages" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.ec2messages"
vpc_endpoint_type = "Interface"
subnet_ids = [aws_subnet.private.id]
security_group_ids = [aws_security_group.vpce.id]
private_dns_enabled = true
tags = {
Name = "${var.project}-ec2messages"
}
}
VPCエンドポイント用のセキュリティグループは、VPC内からの443ポートを許可します。
resource "aws_security_group" "vpce" {
name = "${var.project}-vpce"
vpc_id = aws_vpc.main.id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [aws_vpc.main.cidr_block]
}
tags = {
Name = "${var.project}-vpce"
}
}
Step 2: EC2インスタンスの作成
EC2にはSSMエージェント用のIAMロールと、アクセス制御に使う SSMAccess=allowed タグを付与します。
data "aws_ssm_parameter" "al2023_ami" {
name = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64"
}
resource "aws_instance" "target" {
ami = data.aws_ssm_parameter.al2023_ami.value
instance_type = "t3.micro"
subnet_id = aws_subnet.private.id
iam_instance_profile = aws_iam_instance_profile.ec2.name
vpc_security_group_ids = [aws_security_group.ec2.id]
tags = {
Name = "${var.project}-target"
Project = var.project
SSMAccess = "allowed"
}
}
EC2用のセキュリティグループは、VPCエンドポイントへの通信(443)のみ許可します。
resource "aws_security_group" "ec2" {
name = "${var.project}-ec2"
vpc_id = aws_vpc.main.id
egress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [aws_vpc.main.cidr_block]
}
tags = {
Name = "${var.project}-ec2"
}
}
EC2にアタッチするIAMロールには、SSMエージェントの動作に必要な AmazonSSMManagedInstanceCore マネージドポリシーを付与します。
resource "aws_iam_role" "ec2" {
name = "${var.project}-ec2-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
}]
})
}
resource "aws_iam_role_policy_attachment" "ec2_ssm" {
role = aws_iam_role.ec2.name
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
resource "aws_iam_instance_profile" "ec2" {
name = "${var.project}-ec2-profile"
role = aws_iam_role.ec2.name
}
Step 3: タグベースアクセス制御のIAMポリシー作成
ここが今回の本題です。IAMグループを作成し、タグ条件付きのポリシーをアタッチします。
resource "aws_iam_group" "ssm_access" {
name = "${var.project}-ssm-access"
}
resource "aws_iam_group_policy" "ssm_access" {
name = "${var.project}-ssm-start-session"
group = aws_iam_group.ssm_access.name
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowStartSession"
Effect = "Allow"
Action = "ssm:StartSession"
Resource = "arn:aws:ec2:${var.region}:*:instance/*"
Condition = {
StringLike = {
"ssm:resourceTag/SSMAccess" = "allowed"
}
}
},
{
Sid = "AllowSessionDocument"
Effect = "Allow"
Action = "ssm:StartSession"
Resource = [
"arn:aws:ssm:${var.region}::document/SSM-SessionManagerRunShell",
"arn:aws:ssm:${var.region}:*:document/SSM-SessionManagerRunShell"
]
},
{
Sid = "AllowDescribe"
Effect = "Allow"
Action = [
"ssm:DescribeSessions",
"ssm:DescribeInstanceProperties",
"ssm:GetConnectionStatus",
"ec2:DescribeInstances"
]
Resource = "*"
},
{
Sid = "AllowSessionManagement"
Effect = "Allow"
Action = [
"ssm:TerminateSession",
"ssm:ResumeSession"
]
Resource = "arn:aws:ssm:*:*:session/$${aws:username}-*"
}
]
})
}
ポリシーは4つのStatementで構成されています。
AllowStartSession — タグベースアクセス制御の核です。ssm:resourceTag/SSMAccess が allowed であるインスタンスに対してのみ ssm:StartSession を許可します。
AllowSessionDocument — ssm:StartSession はインスタンスだけでなく、セッションで使用するSSMドキュメントに対しても許可が必要です。SSM-SessionManagerRunShell はデフォルトのセッションドキュメントで、このStatementがないとセッション開始時にアクセス拒否になります。
AllowDescribe — マネジメントコンソールやCLIでインスタンス一覧・セッション状態を取得するために必要です。これらのDescribe系アクションはリソースレベルの絞り込みができないため、Resource: "*" を指定します。
AllowSessionManagement — 自分が開始したセッションの終了・再開を許可します。${aws:username} により、他のユーザーのセッションは操作できません。
Step 4: デプロイと動作確認
Terraformでデプロイします。
terraform init
terraform apply
デプロイ完了後、動作確認を行います。
GUIから確認したところ問題なく接続できることを確認しました。

EC2に付与されたタグを変更します。

再度、GUIから接続を試みたところエラーが表示され接続できないことを確認しました。

注意点・制約
Describe系アクションも必要
ssm:StartSession だけでは不十分です。マネジメントコンソールやCLIでの操作には以下のアクションも必要になります。
ssm:DescribeSessions— セッション一覧の取得ssm:DescribeInstanceProperties— インスタンスのSSM情報取得ssm:GetConnectionStatus— 接続状態の確認ec2:DescribeInstances— EC2インスタンス情報の取得
これらはリソースレベルの絞り込みができないため、Resource: "*" の指定が必要です。
タグの変更には注意
EC2のタグを変更するだけでアクセス権が変わります。タグの変更権限(ec2:CreateTags、ec2:DeleteTags)の管理も合わせて検討してください。
まとめ
EC2タグとIAMポリシーの条件キーを組み合わせることで、SSMセッションマネージャーのアクセス制御を実現できました。
ssm:resourceTagの条件キーでインスタンス単位のアクセス制御が可能- IAMグループとの組み合わせで、ユーザーの追加・削除だけで権限管理できる
- VPCエンドポイント経由ならNAT Gateway不要でコスト削減にもなる
参考になれば幸いです。




