はじめに
EC2 へのリモートアクセス方法の一つである AWS Systems Manager (SSM) Session Manager を試してみた記録です。CloudFormationを使って、プライベートサブネットのEC2にSSM接続できる環境をゼロから構築しました。
SSM Session Manager はAWSが提供するマネージドなリモートアクセスサービスです。SSH鍵やインバウンドポートの開放が不要で、IAMによるアクセス制御と CloudTrail による監査ログが利用できます。
構成
デプロイすると、以下の構成でリソースが作成されます。
ローカルPC(AWS CLI)
↓ HTTPS(SSM API)
VPCエンドポイント × 3(ssm / ssmmessages / ec2messages)
↓
EC2(プライベートサブネット)
※ インターネットゲートウェイ・NATゲートウェイなし
VPC・サブネットからEC2まで、すべてCloudFormation一枚で作成します。
CloudFormationテンプレートの確認
今回使用するテンプレートは cfn-ssm.yaml (後述)です。パラメータは InstanceType(デフォルト: t3.micro)のみで、VPCを含むすべてのリソースを自動作成します。
| リソース | 役割 |
|---|---|
VPC / PrivateSubnet
|
10.0.0.0/16 のVPCとプライベートサブネット(10.0.1.0/24) |
PrivateRouteTable |
インターネットへのルートなし |
SSMRole / SSMInstanceProfile
|
EC2にSSM操作権限を付与(AmazonSSMManagedInstanceCore) |
EC2SecurityGroup |
インバウンドなし・アウトバウンドは全許可 |
EndpointSecurityGroup |
VPCエンドポイント向け Inbound 443 許可 |
SSMEndpoint / SSMMessagesEndpoint / EC2MessagesEndpoint
|
SSM接続に必要なVPCエンドポイント3種 |
EC2Instance |
Amazon Linux 2023、KeyNameなし(SSH不要) |
cfn-ssm.yaml(全文)
AWSTemplateFormatVersion: "2010-09-09"
Description: >
EC2 SSM Session Manager セットアップ(VPC込み・プライベートサブネット)
VPCエンドポイントを使用してインターネット接続なしでSSM接続を確立する。
Parameters:
InstanceType:
Type: String
Default: t3.micro
Description: EC2 インスタンスタイプ
LatestAmiId:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64
Description: Amazon Linux 2023 の最新 AMI ID(SSM Parameter Store から自動取得)
# ============================================================
# Resources
# ============================================================
Resources:
# ----------------------------------------------------------
# VPC・ネットワーク
# ----------------------------------------------------------
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-vpc"
PrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: !Select [0, !GetAZs ""]
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-private-subnet"
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-private-rtb"
SubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet
RouteTableId: !Ref PrivateRouteTable
# ----------------------------------------------------------
# IAM: SSM接続用ロールとインスタンスプロファイル
# ----------------------------------------------------------
SSMRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-ssm-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-ssm-role"
SSMInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
InstanceProfileName: !Sub "${AWS::StackName}-ssm-instance-profile"
Roles:
- !Ref SSMRole
# ----------------------------------------------------------
# Security Groups
# ----------------------------------------------------------
EC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${AWS::StackName}-ec2-sg"
GroupDescription: EC2 for SSM - No inbound rules required
VpcId: !Ref VPC
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Description: Allow all outbound traffic
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-ec2-sg"
EndpointSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${AWS::StackName}-endpoint-sg"
GroupDescription: VPC Endpoints for SSM Session Manager
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
SourceSecurityGroupId: !Ref EC2SecurityGroup
Description: Allow HTTPS from EC2 instances
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-endpoint-sg"
# ----------------------------------------------------------
# VPC Endpoints(プライベートサブネットからSSMへの通信に必須)
# ----------------------------------------------------------
SSMEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPC
ServiceName: !Sub "com.amazonaws.${AWS::Region}.ssm"
VpcEndpointType: Interface
SubnetIds:
- !Ref PrivateSubnet
SecurityGroupIds:
- !Ref EndpointSecurityGroup
PrivateDnsEnabled: true
SSMMessagesEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPC
ServiceName: !Sub "com.amazonaws.${AWS::Region}.ssmmessages"
VpcEndpointType: Interface
SubnetIds:
- !Ref PrivateSubnet
SecurityGroupIds:
- !Ref EndpointSecurityGroup
PrivateDnsEnabled: true
EC2MessagesEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPC
ServiceName: !Sub "com.amazonaws.${AWS::Region}.ec2messages"
VpcEndpointType: Interface
SubnetIds:
- !Ref PrivateSubnet
SecurityGroupIds:
- !Ref EndpointSecurityGroup
PrivateDnsEnabled: true
# ----------------------------------------------------------
# EC2 Instance
# ----------------------------------------------------------
EC2Instance:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
ImageId: !Ref LatestAmiId
SubnetId: !Ref PrivateSubnet
IamInstanceProfile: !Ref SSMInstanceProfile
SecurityGroupIds:
- !Ref EC2SecurityGroup
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-ec2"
# ============================================================
# Outputs
# ============================================================
Outputs:
InstanceId:
Description: SSM接続時に使用するEC2インスタンスID
Value: !Ref EC2Instance
ConnectCommand:
Description: SSM Session Manager で接続するコマンド
Value: !Sub "aws ssm start-session --target ${EC2Instance} --region ${AWS::Region}"
VpcId:
Description: 作成されたVPC ID
Value: !Ref VPC
デプロイ
以下のコマンドでスタックを作成します。パラメータ指定は不要です。
aws cloudformation deploy \
--template-file cfn-ssm.yaml \
--stack-name ec2-ssm-setup \
--capabilities CAPABILITY_NAMED_IAM
3分ほどで完了しました。完了後、インスタンスIDを取得します。
aws cloudformation describe-stacks \
--stack-name ec2-ssm-setup \
--query "Stacks[0].Outputs[?OutputKey=='InstanceId'].OutputValue" \
--output text
接続確認
インスタンス起動後、SSM Agentの登録に1〜2分かかります。以下のコマンドで Online になっていることを確認します。
aws ssm describe-instance-information \
--filters "Key=InstanceIds,Values=<INSTANCE_ID>" \
--query "InstanceInformationList[0].PingStatus" \
--output text
Online になったらセッションを開始します。
aws ssm start-session --target <INSTANCE_ID>
Session Manager セッションを開始するには、ローカルマシンにAWS CLI 用の Session Manager プラグインをインストールする必要があります。
AWS CLI 用の Session Manager プラグインをインストールする
接続が成功するとプロンプトが表示されます。
Starting session with SessionId: xxxxxxxxxxxxxxxxxxxxxxxx
sh-5.2$
試しにいくつか実行してみます。
# 実行ユーザーの確認
sh-5.2$ whoami
ssm-user
# インターネット接続がないことを確認(タイムアウトすればOK)
sh-5.2$ curl --connect-timeout 3 https://example.com
curl: (28) Connection timed out after 3001 milliseconds
インターネットに出られないことも確認でき、閉じたプライベート環境でSSM接続だけが通っている状態になっています。
おわりに
CloudFormationで3つのVPCエンドポイントとIAMロールを定義するだけで、プライベートサブネットのEC2にSSH不要・ポート開放なしで接続できる環境が手に入りました。
今後は Kiro CLI を使って、EC2に接続する際のよくある接続エラーのトラブルシューティングをしてみたいです。
また、EC2への接続方法はSSM Session Manager以外にも EC2 Instance Connect や EC2 Instance Connect Endpoint があります。それぞれ特性が異なるので、今後試してみたいと思います。