この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
なつのうたを聴きながら、暑い暑いと連日思っています。コウペンちゃんが可愛いです。
日本の夏はとっても暑いので、プライベートサブネット配下のインスタンスをPrivateLinkを通じてSSMを使用して操作したくなりました。
ついでにCloudFormationのテンプレートも作成したくなったのでつくりました。
VPC Endpointとは
本題に入る前に、VPC Endpointについて少しだけ記載します。
2015年の4月まで、EC2インスタンスから各種AWSリソース(S3、DynamoDB、CodeCommit...)に対してアクセスするためにはパブリックIPアドレスが必要でした。
パブリックサブネット配下のインスタンスは、インスタンス -> Internet GW -> S3
のような経路で、プライベートサブネット配下のインスタンスは、インスタンス -> NAT GW -> S3
のような経路でアクセスする必要がありました。
NAT GWが必要になったり、インターネットを通じてアクセスするため経路をAWS内に閉じ込めるということができませんね。
同年の5月にVPC Endpointが新機能として提供され、S3に対してインターネットを通さずにアクセスできるようになりました。
インスタンスからS3への通信をエンドポイントを通じて行います。
ルートテーブル単位でエンドポイントへのルーティングを行います。
なので、通信経路はインスタンス -> エンドポイント -> S3
となります。
このルートテーブルを使用して制御するエンドポイントのことを、ゲートウェイVPCエンドポイントと呼び、S3とDynamoDBのみ対応しています。
2017年11月にインターフェイスVPCエンドポイントという方式のエンドポイントが発表されました。
この方式では、VPC配下にENIを作成して、ENIと各種サービスのエンドポイントをPrivateLinkで繋ぎます。
ENIを使用するのでセキュリティグループを紐づける必要があり、またサブネット単位で制御することになります。
なので、インスタンス -> ENI -> SSM Endpoint
のような経路で通信を行います。
実装について
冒頭でも記載した通り、CloudFormationのテンプレートを作成していきます。
実際に使用する場合は色々拡張してください。
Resources
配下で、下記のパラメータを使用しているので頭の部分だけ載せておきます。
thin-private-subnet-ssm.yaml
AWSTemplateFormatVersion: 2010-09-09
Description: SSM for Private Subnet
Parameters:
Prefix:
Description: Prefix
Type: String
Default: thin-ssm
AZ1:
Description: AZ1
Type: String
Default: a
AMZN2:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
今後使う予定のAZとAMI IDのみ記載しています。
SSMのエンドポイントはap-northeast-1dを現在サポートしていないのでご注意ください。
VPC
まずは、大枠のVPCから書いていきましょう。CIDRやタグなどはご自由にどうぞ。
ハイライトがある5、6行目のEnableDnsSupport
とEnableDnsHostnames
は必ず有効化してください。
特に、EnableDnsHostnames
のデフォルトがfalseなので、書き忘れていて動かない...とかよくありそうです。
thin-private-subnet-ssm.yaml VPC
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 192.168.0.0/24
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub "${Prefix}-vpc"
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${Prefix}-private-rtb"
PrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Sub "${AWS::Region}${AZ1}"
VpcId: !Ref VPC
CidrBlock: 192.168.0.0/28
Tags:
- Key: Name
Value: !Sub "${Prefix}-private-subnet"
PrivateSubnetAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet
RouteTableId: !Ref PrivateRouteTable
Security Group
1つ目のSecurity Group(SG)はEC2インスタンスに紐づけるためのものです。
特に外部と通信するとかの要件がなかったので、何もルールをつけていません。
2つ目のVPCEndpoint用のSecurity Group(SSMEndpointSG)では、VPC内のインスタンスからHTTPSでの通信を許可しています。
このルールは必ず必要なので作成しましょう。
thin-private-subnet-ssm.yaml: Security Group
SG:
Type: "AWS::EC2::SecurityGroup"
Properties:
VpcId: !Ref VPC
GroupName: !Sub "${Prefix}-sg"
GroupDescription: !Sub "${Prefix}-sg"
Tags:
- Key: Name
Value: !Sub "${Prefix}-sg"
SSMEndpointSG:
Type: "AWS::EC2::SecurityGroup"
Properties:
VpcId: !Ref VPC
GroupName: !Sub "${Prefix}-ssm-endpoint-sg"
GroupDescription: !Sub "${Prefix}-ssm-endpoint-sg"
Tags:
- Key: Name
Value: !Sub "${Prefix}-ssm-endpoint-sg"
SSMEndpointSGIngress1:
Type: "AWS::EC2::SecurityGroupIngress"
Properties:
GroupId: !Ref SSMEndpointSG
IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 192.168.0.0/24
VPC Endpoint
VPC Endpointを作っていきましょう。
SSMのドキュメントに書いてある通りに下記のエンドポイントを作成していきます。また今回は東京リージョンに作成するので、region
の値はap-northeast-1
になります。
ssmmessagesのみ作成するかは任意選択となっています。
セッションマネージャーを使用したい場合にssmmessagesのエンドポイントを作成します。
- com.amazonaws.
region
.ssm - com.amazonaws.
region
.ec2messages - com.amazonaws.
region
.ec2: - com.amazonaws.
region
.ssmmessages - com.amazonaws.
region
.s3
インタフェース型のVPC Endpointは、PrivateDnsEnabled
をtrueにしてください。
注意点はこのくらいです。後は今までの流れを組んでよしなに書いていきます。
thin-private-subnet-ssm.yaml: VPC Endpoint
SSMEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub "com.amazonaws.${AWS::Region}.ssm"
VpcEndpointType: Interface
PrivateDnsEnabled: true
VpcId: !Ref VPC
SubnetIds:
- !Ref PrivateSubnet
SecurityGroupIds:
- !Ref SSMEndpointSG
EC2MessageEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub "com.amazonaws.${AWS::Region}.ec2messages"
VpcEndpointType: Interface
PrivateDnsEnabled: true
VpcId: !Ref VPC
SubnetIds:
- !Ref PrivateSubnet
SecurityGroupIds:
- !Ref SSMEndpointSG
EC2Endpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub "com.amazonaws.${AWS::Region}.ec2"
VpcEndpointType: Interface
PrivateDnsEnabled: true
VpcId: !Ref VPC
SubnetIds:
- !Ref PrivateSubnet
SecurityGroupIds:
- !Ref SSMEndpointSG
SSMAgentEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub "com.amazonaws.${AWS::Region}.ssmmessages"
VpcEndpointType: Interface
PrivateDnsEnabled: true
VpcId: !Ref VPC
SubnetIds:
- !Ref PrivateSubnet
SecurityGroupIds:
- !Ref SSMEndpointSG
S3Endpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub "com.amazonaws.${AWS::Region}.s3"
VpcId: !Ref VPC
RouteTableIds:
- !Ref PrivateRouteTable
EC2 Instance
インスタンスプロファイルを指定します(IAMの項で作成します)。
それ以外についてはよしなに書いていきます。
OSが要件を満たしているかどうかは、こちらを確認してください。
thin-private-subnet-ssm.yaml: EC2 Instance
Instance:
Type: "AWS::EC2::Instance"
Properties:
IamInstanceProfile: !Ref ServerProfile
ImageId: !Ref AMZN2
InstanceType: t3.small
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
VolumeType: gp2
VolumeSize: 8
DeleteOnTermination: true
NetworkInterfaces:
- AssociatePublicIpAddress: false
DeviceIndex: "0"
DeleteOnTermination: true
GroupSet:
- !Ref SG
SubnetId: !Ref PrivateSubnet
Tags:
- Key: Name
Value: !Sub "${Prefix}-Instance"
IAM
SSM用のポリシー作成の方針として、AmazonSSMManagedInstanceCore
をベースにして必要なポリシーを追加していく必要があります。
今回は最小限の設定で問題ないので、こちらを参考にして最小のS3アクセスのみを追加しています。
作成したインスタンスプロファイルをEC2側で参照するようにしています。
thin-private-subnet-ssm.yaml: IAM
ServerRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Principal:
Service:
- "ec2.amazonaws.com"
Action:
- "sts:AssumeRole"
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
S3BucketPolicyForSSM:
Type: AWS::IAM::Policy
Properties:
PolicyName: S3BucketPolicyForSSM
Roles:
- !Ref ServerRole
PolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Action: "s3:GetObject"
Resource:
- !Sub "arn:aws:s3:::aws-ssm-${AWS::Region}/*"
- !Sub "arn:aws:s3:::aws-windows-downloads-${AWS::Region}/*"
- !Sub "arn:aws:s3:::amazon-ssm-${AWS::Region}/*"
- !Sub "arn:aws:s3:::amazon-ssm-packages-${AWS::Region}/*"
- !Sub "arn:aws:s3:::${AWS::Region}-birdwatcher-prod/*"
- !Sub "arn:aws:s3:::patch-baseline-snapshot-${AWS::Region}/*"
ServerProfile:
Type: "AWS::IAM::InstanceProfile"
Properties:
Path: "/"
Roles:
- Ref: ServerRole
InstanceProfileName: !Sub "${Prefix}-Server"
全体像
今までの内容をまとめるとこのようなテンプレートが出来上がりました。
これで、プライベートサブネット配下のEC2インスタンスに対してSSMで作業ができますね。
thin-private-subnet-ssm.yaml
AWSTemplateFormatVersion: 2010-09-09
Description: SSM for Private Subnet
Parameters:
Prefix:
Description: Prefix
Type: String
Default: thin-ssm
AZ1:
Description: AZ1
Type: String
Default: a
AMZN2:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
Resources:
#-----------------------------------------------------------------------------
# VPC
#-----------------------------------------------------------------------------
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 192.168.0.0/24
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub "${Prefix}-vpc"
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${Prefix}-private-rtb"
PrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Sub "${AWS::Region}${AZ1}"
VpcId: !Ref VPC
CidrBlock: 192.168.0.0/28
Tags:
- Key: Name
Value: !Sub "${Prefix}-private-subnet"
PrivateSubnetAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet
RouteTableId: !Ref PrivateRouteTable
#-----------------------------------------------------------------------------
# Securty Group
#-----------------------------------------------------------------------------
SG:
Type: "AWS::EC2::SecurityGroup"
Properties:
VpcId: !Ref VPC
GroupName: !Sub "${Prefix}-sg"
GroupDescription: !Sub "${Prefix}-sg"
Tags:
- Key: Name
Value: !Sub "${Prefix}-sg"
SSMEndpointSG:
Type: "AWS::EC2::SecurityGroup"
Properties:
VpcId: !Ref VPC
GroupName: !Sub "${Prefix}-ssm-endpoint-sg"
GroupDescription: !Sub "${Prefix}-ssm-endpoint-sg"
Tags:
- Key: Name
Value: !Sub "${Prefix}-ssm-endpoint-sg"
#-----------------------------------------------------------------------------
# SG(Security Group) Rules
#-----------------------------------------------------------------------------
SSMEndpointSGIngress1:
Type: "AWS::EC2::SecurityGroupIngress"
Properties:
GroupId: !Ref SSMEndpointSG
IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 192.168.0.0/24
#-----------------------------------------------------------------------------
# Endpoint
#-----------------------------------------------------------------------------
SSMEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub "com.amazonaws.${AWS::Region}.ssm"
VpcEndpointType: Interface
PrivateDnsEnabled: true
VpcId: !Ref VPC
SubnetIds:
- !Ref PrivateSubnet
SecurityGroupIds:
- !Ref SSMEndpointSG
EC2MessageEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub "com.amazonaws.${AWS::Region}.ec2messages"
VpcEndpointType: Interface
PrivateDnsEnabled: true
VpcId: !Ref VPC
SubnetIds:
- !Ref PrivateSubnet
SecurityGroupIds:
- !Ref SSMEndpointSG
EC2Endpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub "com.amazonaws.${AWS::Region}.ec2"
VpcEndpointType: Interface
PrivateDnsEnabled: true
VpcId: !Ref VPC
SubnetIds:
- !Ref PrivateSubnet
SecurityGroupIds:
- !Ref SSMEndpointSG
SSMAgentEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub "com.amazonaws.${AWS::Region}.ssmmessages"
VpcEndpointType: Interface
PrivateDnsEnabled: true
VpcId: !Ref VPC
SubnetIds:
- !Ref PrivateSubnet
SecurityGroupIds:
- !Ref SSMEndpointSG
S3Endpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub "com.amazonaws.${AWS::Region}.s3"
VpcId: !Ref VPC
RouteTableIds:
- !Ref PrivateRouteTable
#-----------------------------------------------------------------------------
# EC2 Instance
#-----------------------------------------------------------------------------
Instance:
Type: "AWS::EC2::Instance"
Properties:
IamInstanceProfile: !Ref ServerProfile
ImageId: !Ref AMZN2
InstanceType: t3.small
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
VolumeType: gp2
VolumeSize: 8
DeleteOnTermination: true
NetworkInterfaces:
- AssociatePublicIpAddress: false
DeviceIndex: "0"
DeleteOnTermination: true
GroupSet:
- !Ref SG
SubnetId: !Ref PrivateSubnet
Tags:
- Key: Name
Value: !Sub "${Prefix}-Instance"
#-----------------------------------------------------------------------------
# IAM
#-----------------------------------------------------------------------------
ServerRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Principal:
Service:
- "ec2.amazonaws.com"
Action:
- "sts:AssumeRole"
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
S3BucketPolicyForSSM:
Type: AWS::IAM::Policy
Properties:
PolicyName: S3BucketPolicyForSSM
Roles:
- !Ref ServerRole
PolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Action: "s3:GetObject"
Resource:
- !Sub "arn:aws:s3:::aws-ssm-${AWS::Region}/*"
- !Sub "arn:aws:s3:::aws-windows-downloads-${AWS::Region}/*"
- !Sub "arn:aws:s3:::amazon-ssm-${AWS::Region}/*"
- !Sub "arn:aws:s3:::amazon-ssm-packages-${AWS::Region}/*"
- !Sub "arn:aws:s3:::${AWS::Region}-birdwatcher-prod/*"
- !Sub "arn:aws:s3:::patch-baseline-snapshot-${AWS::Region}/*"
ServerProfile:
Type: "AWS::IAM::InstanceProfile"
Properties:
Path: "/"
Roles:
- Ref: ServerRole
InstanceProfileName: !Sub "${Prefix}-Server"
このテンプレートを元にスタックを作成するには下記のコマンドを実行します。
IAMを操作するのでcapabilitiesを追加しています。
aws cloudformation create-stack \
--stack-name ssm-thin-sampple \
--template-url https://gist.githubusercontent.com/37108/ad4519a138addf291000e44491ec46ed/raw/e9bcf573db6c18b3d756f8c73dba781d799acd6b/thin-private-subnet-ssm.yaml \
--capabilities CAPABILITY_NAMED_IAM \
--region ap-northeast-1
さいごに
VPC Endpointは便利ですね。自分のやろうとしたことに似たテンプレートが見つからなかったので自分で作って公開してみました。
ぜひ、なつのうたを聴いて癒されてください。