【CloudFormation】プライベートサブネットにCloud9(Session Manager経由)をデプロイしてみる
1. はじめに
こんにちわ、Mitsuoです。
皆さん選挙には行かれましたか。
私は家族と行ったのですが、息子がおばあさま方に大人気でして、その息子の父親であることが嬉しかったです。
正直、中々帰れなくて辛かったです 。
2. 今回のお題
さて、前回のお題と同様になりますが、「CloudFormationを用いてCloud9をデプロイ」してみました。
前回のトピックは、パブリック上にCloud9を配置するテンプレートのご紹介でしたが、今回はプライベートサブネットに配置するバージョンとなります。
接続方式はSystems Manager(Session Manager)です。
SSH用を希望される方がいない気はしていますが、プライベートサブネット上のCloud9はSSH接続が出来ない仕様のようです。
(どこかに書いていた気がするのですが、Cloud9にデフォルトでアタッチされるセキュリティグループのインバウンドは、Cloud9接続用のグローバルIPアドレスのCIDRレンジ、およびSSHポートを許可する設定になっています。
そのような仕様であるため、直接SSHアクセスする事が出来ず、Cloud9のIDE画面を表示出来ないと言うわけです。
公式ドキュメントにも記載がありました。
If your environment is accessing its EC2 instance directly though SSH, the instance can be launched into a public subnet only.
一方で、Session Managerの場合はどちらのサブネットでも利用する事が出来ます。
If you're accessing a no-ingress Amazon EC2 instance using Systems Manager, the instance can be launched into either a public or a private subnet.
Cloud9に限った話では無いですが、インターネット通信が必要な時は、Cloud9インスタンスにNAT Gatewayと接続出来る設定を行う必要があります。
If you're using a private subnet, allow the instance for the subnet to communicate with the internet by hosting a NAT gateway in a public subnet.
詳細は、公式ドキュメントを参照ください。
Amazon VPC requirements for AWS Cloud9
3. 作成したリソース
テンプレートで作成するリソースは以下の通りです。
- VPC
- Public Subnet
- Private Subnet
- InternetGateway
- Elastic IP
- NAT Gateway
- NetworkACL
- Route Table
- Cloud9
- IAMロール(Cloud9)
- Security Group(Systems Manager、CodeCommmitおよびECRのVPCEndPoint用)
- VPCEndpoint(Interface型:Systems Manager、CodeCommitおよびECR用)
- VPCEndpoint(Gateway型:S3用)
4 テンプレート情報
作成したテンプレートになります。
AWSTemplateFormatVersion: 2010-09-09
Description: Create Cloud9 on Private subnet using SSM. Additionally this template includes related resources such as Network, route table and NACL.
# ------------------------------------------------------------#
# Caution:
# This template is for deploying resources in Tokyo region(ap-northeast-1).
# If you deploy them in another region, make sure that you understand below content and make modification for parameters such as AvailabilityZone.
# This server is for Amazon Linux 2. If you want to change other AMI or SSM Paths, you have to modify Cloud9 resources, which is ImageId of AWS::Cloud9::EnvironmentEC2.
# For more information, see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloud9-environmentec2.html#cfn-cloud9-environmentec2-imageid
# The Cloud9 on private subnet has to disable AMTC because Cloud9-enabled AMTC cannot execute API command except public IP address to which Cloud9 is assigned,
# which is specification Cloud9 has. (As of July 4 2022)
# ------------------------------------------------------------#
# Metadata:
# This can privide details about the template.
# For more information, see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/metadata-section-structure.html.
# As Metadata Keys, AWS::CloudFormation::Interface can Defines the grouping and ordering of input parameters
# when they are displayed in the AWS CloudFormation console.
# For more information, see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-interface.html.
# ------------------------------------------------------------#
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
-
Parameters:
- SystemName
- EnvType
- Tagprefix
- Owner
- InstanceType
- VPCCIDR
- PublicSubnet1CIDR
- PrivateSubnet1CIDR
- AvailabilityZone
- RepositoryName
ParameterLabels:
SystemName:
default: "Type the Systemname"
EnvType:
default: "Select the environment in which you deploy resources"
Tagprefix:
default: "Type the Tagprefix which is logical unit name about this stack(e.g. Webserver,Monitoring,Logging)"
Owner:
default: "Type who owns these resources (e.g. project name or worker, whether this purpose is just verification or not)"
InstanceType:
default: "Type InstanceType you want to create for Cloud9"
VPCCIDR:
default: "Type VPC's CIDR such as NN.NN.NN.NN/NN based on RFC 1918"
PublicSubnet1CIDR:
default: "Type your public subnet's CIDR in availability zone such as NN.NN.NN.NN/NN based on RFC 1918"
PrivateSubnet1CIDR:
default: "Type your private subnet's CIDR in availability zone such as NN.NN.NN.NN/NN based on RFC 1918"
AvailabilityZone:
default: "Type your availability zone you want to use"
RepositoryName:
default: "Type Repository name in ECR"
# ------------------------------------------------------------#
# Parameters
# This Can enable templates to input custom values each time you create or update a stack.
# For more information, see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html.
# ------------------------------------------------------------#
Parameters:
SystemName:
Type: String
EnvType:
Type: String
Default: "dev"
AllowedValues:
- dev
- stg
- prd
Tagprefix:
Type: String
Owner:
Type: String
InstanceType:
Type: String
Default: "t3.small"
ConstraintDescription: "Type InstanceType you want to create for Cloud9"
VPCCIDR:
Type: String
Default: "10.10.0.0/16"
ConstraintDescription: "Type VPC's CIDR such as NN.NN.NN.NN/NN based on RFC 1918"
PublicSubnet1CIDR:
Type: String
Default: "10.10.1.0/24"
ConstraintDescription: "Type your public subnet's CIDR in availability zone such as NN.NN.NN.NN/NN based on RFC 1918"
PrivateSubnet1CIDR:
Type: String
Default: "10.10.100.0/24"
ConstraintDescription: "Type your private subnet's CIDR in availability zone such as NN.NN.NN.NN/NN based on RFC 1918"
AvailabilityZone:
Type: String
Default: "ap-northeast-1a"
AllowedValues:
- ap-northeast-1a
- ap-northeast-1c
- ap-northeast-1d
RepositoryName:
Type: String
ConstraintDescription: "Type Repository name in ECR"
# ------------------------------------------------------------#
# Resources
# This can declare the AWS resources that you want to include in the stack.
# For more information, see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html.
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
# Create VPC
# ------------------------------------------------------------#
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VPCCIDR
EnableDnsHostnames: "true"
EnableDnsSupport: "true"
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-VPC
- Key: Owner
Value: !Ref Owner
# ------------------------------------------------------------#
# Create public subnet
# ------------------------------------------------------------#
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Ref PublicSubnet1CIDR
AvailabilityZone: !Ref AvailabilityZone
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-PublicSubnet
- Key: Owner
Value: !Ref Owner
# ------------------------------------------------------------#
# Create PrivateSubnet
# ------------------------------------------------------------#
PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Ref PrivateSubnet1CIDR
AvailabilityZone: !Ref AvailabilityZone
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-PrivateSubnet
- Key: Owner
Value: !Ref Owner
# ------------------------------------------------------------#
# Create InternetGateway and associate it with VPC
# ------------------------------------------------------------#
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-Internet-Gateway
- Key: Owner
Value: !Ref Owner
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
# ------------------------------------------------------------#
# Create Nat Gateway
# ------------------------------------------------------------#
NatGatewayEIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-EIP
- Key: Owner
Value: !Ref Owner
NatGateway:
Type: AWS::EC2::NatGateway
Properties:
AllocationId:
Fn::GetAtt:
- NatGatewayEIP
- AllocationId
SubnetId: !Ref PublicSubnet1
ConnectivityType: public
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-NatGateway
- Key: Owner
Value: !Ref Owner
# ------------------------------------------------------------#
# Create NetworkACL and associate it with public subnet
# ------------------------------------------------------------#
NetworkACLForPublicSubnet1:
Type: AWS::EC2::NetworkAcl
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-PublicSubnet-Networkacl
- Key: Owner
Value: !Ref Owner
NetworkACLAssocForPublicSubnet1:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
NetworkAclId: !Ref NetworkACLForPublicSubnet1
SubnetId: !Ref PublicSubnet1
# ------------------------------------------------------------#
# Add In/Outbound Rule to NetworkACL with public subnet
# ------------------------------------------------------------#
NaclInboundRuleForPublicSubnet1:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref NetworkACLForPublicSubnet1
RuleNumber: 100
Protocol: -1
Egress: false
RuleAction: allow
CidrBlock: 0.0.0.0/0
NaclOutboundRuleForPublicSubnet1:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref NetworkACLForPublicSubnet1
RuleNumber: 100
Protocol: -1
Egress: true
RuleAction: allow
CidrBlock: 0.0.0.0/0
# ------------------------------------------------------------#
# Create NetworkACL and associate it with private subnet
# ------------------------------------------------------------#
NetworkACLForPrivateSubnet1:
Type: AWS::EC2::NetworkAcl
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-PrivateSubnet-Networkacl
- Key: Owner
Value: !Ref Owner
NetworkACLAssocForPrivateSubnet1:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
NetworkAclId: !Ref NetworkACLForPrivateSubnet1
SubnetId: !Ref PrivateSubnet1
# ------------------------------------------------------------#
# Add In/Outbound Rule to NetworkACL with private subnet
# ------------------------------------------------------------#
NaclInboundRuleForPrivateSubnet1:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref NetworkACLForPrivateSubnet1
RuleNumber: 100
Protocol: -1
Egress: false
RuleAction: allow
CidrBlock: 0.0.0.0/0
NaclOutboundRuleForPrivateSubnet1:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref NetworkACLForPrivateSubnet1
RuleNumber: 100
Protocol: -1
Egress: true
RuleAction: allow
CidrBlock: 0.0.0.0/0
# ------------------------------------------------------------#
# Create Route Table and associate with public subnet
# ------------------------------------------------------------#
PublicRouteTable1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-RouteTable-PublicSubnet
- Key: Owner
Value: !Ref Owner
PublicSubnetRouteTableAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet1
RouteTableId: !Ref PublicRouteTable1
# ------------------------------------------------------------#
# Add route in a route table with public subnet
# ------------------------------------------------------------#
PublicRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PublicRouteTable1
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
# ------------------------------------------------------------#
# Create Route Table and associate with Private Subnet
# ------------------------------------------------------------#
PrivateRouteTable1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-RouteTable-PrivateSubnet
- Key: Owner
Value: !Ref Owner
PrivateSubnetRouteTableAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet1
RouteTableId: !Ref PrivateRouteTable1
# ------------------------------------------------------------#
# Add route in a route table with private subnet
# ------------------------------------------------------------#
PrivateRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable1
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway
# ------------------------------------------------------------#
# Create Cloud9
# ------------------------------------------------------------#
Cloud9:
Type: AWS::Cloud9::EnvironmentEC2
Properties:
Description: "The Cloud9 on Private subnet connecting Via SSM. This server is for building Docker images so on."
AutomaticStopTimeMinutes: 30
ConnectionType: CONNECT_SSM
ImageId: resolve:ssm:/aws/service/cloud9/amis/amazonlinux-2-x86_64
InstanceType: !Ref InstanceType
Name: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9
SubnetId: !Ref PrivateSubnet1
Tags:
- Key: Owner
Value: !Ref Owner
# ------------------------------------------------------------#
# Create IAM resources for Cloud9
# ------------------------------------------------------------#
IAMRoleforCloud9:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-IAM-Role1
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
- cloud9.amazonaws.com
Action:
- sts:AssumeRole
MaxSessionDuration: 3600
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-IAM-Role1
- Key: Owner
Value: !Ref Owner
InstanceProfileforCloud9:
Type: 'AWS::IAM::InstanceProfile'
Properties:
InstanceProfileName: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-IAM-Role1
Path: /
Roles:
- !Ref IAMRoleforCloud9
IAMPolicyforCloud9:
Type: 'AWS::IAM::ManagedPolicy'
Properties:
Description: Policy for Cloud9
Path: /
ManagedPolicyName: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-IAM-Policy1
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- ecr:BatchCheckLayerAvailability
- ecr:GetDownloadUrlForLayer
- ecr:GetRepositoryPolicy
- ecr:DescribeRepositories
- ecr:ListImages
- ecr:DescribeImages
- ecr:BatchGetImage
- ecr:GetLifecyclePolicy
- ecr:GetLifecyclePolicyPreview
- ecr:ListTagsForResource
- ecr:DescribeImageScanFindings
- ecr:InitiateLayerUpload
- ecr:UploadLayerPart
- ecr:CompleteLayerUpload
- ecr:PutImage
Resource: !Sub 'arn:aws:ecr:${AWS::Region}:${AWS::AccountId}:repository/${RepositoryName}'
- Effect: Allow
Action: ecr:GetAuthorizationToken
Resource: '*'
- Effect: Allow
Action: s3:GetObject
Resource: !Sub 'arn:aws:s3:::prod-${AWS::Region}-starport-layer-bucket/*'
- Effect: Allow
Action: codecommit:GitPull
Resource: '*'
Roles:
- !Ref IAMRoleforCloud9
# ------------------------------------------------------------#
# Create Security Group for VPC EndPoint(Systems Manager)
# ------------------------------------------------------------#
SecurityGroupEndpointsEc2messages:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-SG-ForEndpoint-Ec2messages
GroupDescription: Security Group for SSM VPC Endpoint.
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: !Ref VPCCIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-SG-ForEndpoint-Ec2messages
- Key: Owner
Value: !Ref Owner
SecurityGroupEndpointsSsm:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-SG-ForEndpoint-Ssm
GroupDescription: Security Group for SSM VPC Endpoint.
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: !Ref VPCCIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-SG-ForEndpoint-Ssm
- Key: Owner
Value: !Ref Owner
SecurityGroupEndpointsSsmmessages:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-SG-ForEndpoint-Ssmmessages
GroupDescription: Security Group for SSM VPC Endpoint.
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: !Ref VPCCIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-SG-ForEndpoint-Ssmmessages
- Key: Owner
Value: !Ref Owner
# ------------------------------------------------------------#
# Create Security Group for VPC End Point(CodeCommit)
# ------------------------------------------------------------#
SecurityGroupEndpointsCodeCommit:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-SG-ForEndpoint-CodeCommit
GroupDescription: Security Group for CodeCommit VPC Endpoint.
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: !Ref VPCCIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-SG-ForEndpoint-CodeCommit
- Key: Owner
Value: !Ref Owner
SecurityGroupEndpointsGitCodeCommit:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-SG-ForEndpoint-GitCodeCommit
GroupDescription: Security Group for GitCodeCommit VPC Endpoint.
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: !Ref VPCCIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-SG-ForEndpoint-GitCodeCommit
- Key: Owner
Value: !Ref Owner
# ------------------------------------------------------------#
# Create Security Group for VPC End Point(ECR)
# ------------------------------------------------------------#
SecurityGroupEndpointsEcrdkr:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-SG-ForEndpoint-Ecrdkr
GroupDescription: Security Group for ECR VPC Endpoint.
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: !Ref VPCCIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-SG-ForEndpoint-Ecrdkr
- Key: Owner
Value: !Ref Owner
SecurityGroupEndpointsEcrapi:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-SG-ForEndpoint-Ecrapi
GroupDescription: Security Group for ECR VPC Endpoint.
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: !Ref VPCCIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-SG-ForEndpoint-Ecrapi
- Key: Owner
Value: !Ref Owner
# ------------------------------------------------------------#
# Create Systems Manager End Point
# ------------------------------------------------------------#
Ec2messagesEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2messages
SubnetIds:
- !Ref PrivateSubnet1
VpcId: !Ref VPC
VpcEndpointType: Interface
SecurityGroupIds:
- !Ref SecurityGroupEndpointsEc2messages
PrivateDnsEnabled: true
DependsOn: SecurityGroupEndpointsEc2messages
SsmEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
SubnetIds:
- !Ref PrivateSubnet1
VpcId: !Ref VPC
VpcEndpointType: Interface
SecurityGroupIds:
- !Ref SecurityGroupEndpointsSsm
PrivateDnsEnabled: true
DependsOn: SecurityGroupEndpointsSsm
SsmmessagesEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
SubnetIds:
- !Ref PrivateSubnet1
VpcId: !Ref VPC
VpcEndpointType: Interface
SecurityGroupIds:
- !Ref SecurityGroupEndpointsSsmmessages
PrivateDnsEnabled: true
DependsOn: SecurityGroupEndpointsSsmmessages
# ------------------------------------------------------------#
# Create CodeCommit End Point
# ------------------------------------------------------------#
CodeCommitEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub com.amazonaws.${AWS::Region}.codecommit
SubnetIds:
- !Ref PrivateSubnet1
VpcId: !Ref VPC
VpcEndpointType: Interface
SecurityGroupIds:
- !Ref SecurityGroupEndpointsCodeCommit
PrivateDnsEnabled: true
DependsOn: SecurityGroupEndpointsCodeCommit
GitCodeCommitEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub com.amazonaws.${AWS::Region}.git-codecommit
SubnetIds:
- !Ref PrivateSubnet1
VpcId: !Ref VPC
VpcEndpointType: Interface
SecurityGroupIds:
- !Ref SecurityGroupEndpointsGitCodeCommit
PrivateDnsEnabled: true
DependsOn: SecurityGroupEndpointsGitCodeCommit
# ------------------------------------------------------------#
# Create ECR End Point
# Amazon ECS tasks hosted on Fargate using platform version 1.4.0 or later require both the com.amazonaws.region.ecr.dkr
# and com.amazonaws.region.ecr.api Amazon ECR VPC endpoints as well as the Amazon S3 gateway endpoint to take advantage of this feature.
# For more information, see https://docs.aws.amazon.com/AmazonECR/latest/userguide/vpc-endpoints.html
# ------------------------------------------------------------#
EcrdkrEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub com.amazonaws.${AWS::Region}.ecr.dkr
SubnetIds:
- !Ref PrivateSubnet1
VpcId: !Ref VPC
VpcEndpointType: Interface
SecurityGroupIds:
- !Ref SecurityGroupEndpointsEcrdkr
PrivateDnsEnabled: true
DependsOn: SecurityGroupEndpointsEcrdkr
EcrapiEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub com.amazonaws.${AWS::Region}.ecr.api
SubnetIds:
- !Ref PrivateSubnet1
VpcId: !Ref VPC
VpcEndpointType: Interface
SecurityGroupIds:
- !Ref SecurityGroupEndpointsEcrapi
PrivateDnsEnabled: true
DependsOn: SecurityGroupEndpointsEcrapi
# ------------------------------------------------------------#
# Create S3 End Point(Gateway)
# ------------------------------------------------------------#
S3GatewayEndpoint:
Type: 'AWS::EC2::VPCEndpoint'
Properties:
RouteTableIds:
- !Ref PrivateRouteTable1
ServiceName: !Sub com.amazonaws.${AWS::Region}.s3
VpcId: !Ref VPC
VpcEndpointType: Gateway
# ------------------------------------------------------------#
# Outputs
# This can output each value specified as output section.
# For more information, see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html.
# ------------------------------------------------------------#
Outputs:
VPC:
Description: VPC
Value: !Ref VPC
PublicSubnet1:
Description: PublicSubnet1
Value: !Ref PublicSubnet1
PrivateSubnet1:
Description: PrivateSubnet1
Value: !Ref PrivateSubnet1
InternetGateway:
Description: InternetGateway
Value: !Ref InternetGateway
NatGatewayEIP:
Description: NatGatewayEIP
Value: !Ref NatGatewayEIP
NatGateway:
Description: NatGateway
Value: !Ref NatGateway
NetworkACLForPublicSubnet1:
Description: NetworkACLForPublicSubnet1
Value: !Ref NetworkACLForPublicSubnet1
NetworkACLForPrivateSubnet1:
Description: NetworkACLForPrivateSubnet1
Value: !Ref NetworkACLForPrivateSubnet1
PublicRouteTable1:
Description: PublicRouteTable1
Value: !Ref PublicRouteTable1
PrivateRouteTable1:
Description: PrivateRouteTable1
Value: !Ref PrivateRouteTable1
Cloud9:
Description: Cloud9
Value: !Ref Cloud9
AZ1:
Description: Availability Zone 1
Value: !GetAtt
- PrivateSubnet1
- AvailabilityZone
# ------------------------------------------------------------#
# Output Security Groups
# ------------------------------------------------------------#
SecurityGroupEndpointsEc2messages:
Description: SecurityGroupEndpointsEc2messages
Value: !Ref SecurityGroupEndpointsEc2messages
SecurityGroupEndpointsSsm:
Description: SecurityGroupEndpointsSsm
Value: !Ref SecurityGroupEndpointsSsm
SecurityGroupEndpointsSsmmessages:
Description: SecurityGroupEndpointsSsmmessages
Value: !Ref SecurityGroupEndpointsSsmmessages
SecurityGroupEndpointsEcrdkr:
Description: SecurityGroupEndpointsEcrdkr
Value: !Ref SecurityGroupEndpointsEcrdkr
SecurityGroupEndpointsEcrapi:
Description: SecurityGroupEndpointsEcrapi
Value: !Ref SecurityGroupEndpointsEcrapi
SecurityGroupEndpointsCodeCommit:
Description: SecurityGroupEndpointsCodeCommit
Value: !Ref SecurityGroupEndpointsCodeCommit
SecurityGroupEndpointsGitCodeCommit:
Description: SecurityGroupEndpointsGitCodeCommit
Value: !Ref SecurityGroupEndpointsGitCodeCommit
# ------------------------------------------------------------#
# Output VPC Endpoints using Interface Type
# ------------------------------------------------------------#
Ec2messagesEndpoint:
Description: Ec2messagesEndpoint
Value: !Ref Ec2messagesEndpoint
SsmEndpoint:
Description: SsmEndpoint
Value: !Ref SsmEndpoint
SsmmessagesEndpoint:
Description: SsmmessagesEndpoint
Value: !Ref SsmmessagesEndpoint
S3GatewayEndpoint:
Description: S3GatewayEndpoint
Value: !Ref S3GatewayEndpoint
EcrdkrEndpoint:
Description: EcrdkrEndpoint
Value: !Ref EcrdkrEndpoint
EcrapiEndpoint:
Description: EcrapiEndpoint
Value: !Ref EcrapiEndpoint
CodeCommitEndpoint:
Description: CodeCommitEndpoint
Value: !Ref CodeCommitEndpoint
GitCodeCommitEndpoint:
Description: GitCodeCommitEndpoint
Value: !Ref GitCodeCommitEndpoint
# ------------------------------------------------------------#
# Output VPC Endpoints using Gateway Type
# ------------------------------------------------------------#
S3GatewayEndpoint:
Description: S3GatewayEndpoint
Value: !Ref S3GatewayEndpoint
# ------------------------------------------------------------#
# Output IAM resources
# ------------------------------------------------------------#
IAMRoleforCloud9:
Description: IAMRoleforCloud9
Value: !Ref IAMRoleforCloud9
IAMPolicyforCloud9:
Description: IAMPolicyforCloud9
Value: !Ref IAMPolicyforCloud9
5. テンプレートの補足
テンプレートに関して補足します。
なお、以前のブログ【CloudFormation】パブリックサブネットにCloud9(SSH)をデプロイしてみるで補足した内容については、冗長になるので割愛します。
5.1 想定ユースケース
今回のテンプレートは、次の様なユースケースを想定して作成しました。
- DockerFileをローカルからアップロードして、Build/Ship/Runを実行できる
- したがって、NAT Gatewayを配置している
- コンテナイメージは、ECRのプライベートリポジトリに格納する
- セッションマネージャー経由でCloud9インスタンスにアクセスする
- セッションマネージャーの諸設定(Preference)は行わない(ロギング設定等はしない)
5.2 セキュリティグループとIAMロール
Cloud9を生成する時、セキュリティグループとIAMロールが自動で生成、アタッチされます。CloudFormationのプロパティでも指定する事が出来ません。
セキュリティグループ名は、指定したCloud9のインスタンス名の末尾に-InstanceSecurityGroup-ランダム文字列
を付与した形になります。
上記のような仕様のため、例えばSSM用のVPCエンドポイントにおけるセキュリティグループ設定で接続元をVPC CIDRではなく、セキュリティグループにすると、Cloud9のセキュリティグループ名と一致させることが出来ず、SSM経由のアクセスに失敗しテンプレートがロールバックしますので、ご注意ください。
別のブログにまとめようと思っていますが、Cloud9のインスタンス作成後かつデフォルトで生成されるセキュリティグループがアタッチされた後に、SSM用VPCエンドポイントのセキュリティグループの接続元に指定されたセキュリティグループをCloud9にアタッチすることでテンプレートを作成することが出来ます。
続いて、IAMロールですが、接続方式がSSMの場合、自動的にインスタンスプロファイルAWSCloud9SSMInstanceProfile
が作成されCloud9にアタッチされます。
このインスタンスプロファイルによってCloud9にIAMロール(AWSCloud9SSMAccessRole)
がアタッチされます。
このIAMロール(AWSCloud9SSMAccessRole)
には許可ポリシー(AWSCloud9SSMInstanceProfile)
が付与されています。
次の通り、AWSCloud9SSMInstanceProfile
には、SSMに関するアクションの許可権限しかないです。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ssmmessages:CreateControlChannel",
"ssmmessages:CreateDataChannel",
"ssmmessages:OpenControlChannel",
"ssmmessages:OpenDataChannel",
"ssm:UpdateInstanceInformation"
],
"Resource": "*"
}
]
}
したがって、CloudFormationで以下のIAMロールを作っています。
繰り返しになりますが、テンプレートのプロファイルではCloud9にIAMロールを直接付与することが出来ないので、作成後にIAMロールを付け替えるようにしてください。
IAMRoleforCloud9:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-IAM-Role1
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
- cloud9.amazonaws.com
Action:
- sts:AssumeRole
MaxSessionDuration: 3600
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
Tags:
- Key: Name
Value: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-IAM-Role1
- Key: Owner
Value: !Ref Owner
InstanceProfileforCloud9:
Type: 'AWS::IAM::InstanceProfile'
Properties:
InstanceProfileName: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-IAM-Role1
Path: /
Roles:
- !Ref IAMRoleforCloud9
IAMPolicyforCloud9:
Type: 'AWS::IAM::ManagedPolicy'
Properties:
Description: Policy for Cloud9
Path: /
ManagedPolicyName: !Sub ${SystemName}-${EnvType}-${Tagprefix}-Cloud9-IAM-Policy1
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- ecr:BatchCheckLayerAvailability
- ecr:GetDownloadUrlForLayer
- ecr:GetRepositoryPolicy
- ecr:DescribeRepositories
- ecr:ListImages
- ecr:DescribeImages
- ecr:BatchGetImage
- ecr:GetLifecyclePolicy
- ecr:GetLifecyclePolicyPreview
- ecr:ListTagsForResource
- ecr:DescribeImageScanFindings
- ecr:InitiateLayerUpload
- ecr:UploadLayerPart
- ecr:CompleteLayerUpload
- ecr:PutImage
Resource: !Sub 'arn:aws:ecr:${AWS::Region}:${AWS::AccountId}:repository/${RepositoryName}'
- Effect: Allow
Action: ecr:GetAuthorizationToken
Resource: '*'
- Effect: Allow
Action: s3:GetObject
Resource: !Sub 'arn:aws:s3:::prod-${AWS::Region}-starport-layer-bucket/*'
- Effect: Allow
Action: codecommit:GitPull
Resource: '*'
Roles:
- !Ref IAMRoleforCloud9
必要に応じてポリシーを変更してください。
公式ドキュメント抜粋:
This policy grants administrative permissions that allow IAM users to read and write to repositories, but doesn't allow them to delete repositories or change the policy documents that are applied to them.
This policy includes the following permissions:
ecr – Allows principals to read and write to respositores, as well as read lifecycle policies. Principals aren't granted permission to delete repositories or change the lifecycle policies that are applied to them.
詳細は、公式ドキュメントを参照ください。
AWS managed policies for Amazon Elastic Container Registry
5.3 ECR・S3について
ECRへのアクセスはVPCエンドポイントを経由しますが。
主にイメージのPullおよびPushになると思います。
テンプレートにも記載がある通り、ECR用のエンドポイントを作成しています。
EcrdkrEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub com.amazonaws.${AWS::Region}.ecr.dkr
SubnetIds:
- !Ref PrivateSubnet1
VpcId: !Ref VPC
VpcEndpointType: Interface
SecurityGroupIds:
- !Ref SecurityGroupEndpointsEcrdkr
PrivateDnsEnabled: true
DependsOn: SecurityGroupEndpointsEcrdkr
EcrapiEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub com.amazonaws.${AWS::Region}.ecr.api
SubnetIds:
- !Ref PrivateSubnet1
VpcId: !Ref VPC
VpcEndpointType: Interface
SecurityGroupIds:
- !Ref SecurityGroupEndpointsEcrapi
PrivateDnsEnabled: true
DependsOn: SecurityGroupEndpointsEcrapi
ECR用のエンドポイントは2つあり、今回Cloud9用として両方作成しています。
これらのエンドポイントは、ECSのバージョンまたはデータノードがFargateかによって必要な数が異なるのですが、
Cloud9の場合でもECRにShipする場合もECR用のエンドポイントが必要になります。
Amazon ECS tasks hosted on Fargate using platform version 1.3.0 or earlier only require the com.amazonaws.region.ecr.dkr Amazon ECR VPC endpoint and the Amazon S3 gateway endpoint to take advantage of this feature.
Amazon ECS tasks hosted on Fargate using platform version 1.4.0 or later require both the com.amazonaws.region.ecr.dkr and com.amazonaws.region.ecr.api Amazon ECR VPC endpoints as well as the Amazon S3 gateway endpoint to take advantage of this feature.
ECRプライベートリポジトリのイメージをPullする時の注意点として、S3のエンドポイントも必要になります。
これは、ECRで保管するイメージの実態がS3になるからだと思いますが、次の様なARNをポリシーのResource
で指定する必要があります。
arn:aws:s3:::prod-region-starport-layer-bucket/*
したがって、S3エンドポイントのポリシーに次のような設定を行っています。
Statement:
- Effect: Allow
Principal:
AWS: !GetAtt IAMRoleforCloud9.Arn
Action: s3:GetObject
Resource: !Sub 'arn:aws:s3:::prod-${AWS::Region}-starport-layer-bucket/*'
詳細は公式ドキュメントを参照ください。
Amazon ECR interface VPC endpoints (AWS PrivateLink)
5.4 CodeCommitについて
CodeCommitのエンドポイントとGitアクションを行うために、codecommitおよびgit-codecommitのエンドポイントを作成する必要があります。
CodeCommitEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub com.amazonaws.${AWS::Region}.codecommit
SubnetIds:
- !Ref PrivateSubnet1
VpcId: !Ref VPC
VpcEndpointType: Interface
SecurityGroupIds:
- !Ref SecurityGroupEndpointsCodeCommit
PrivateDnsEnabled: true
DependsOn: SecurityGroupEndpointsCodeCommit
GitCodeCommitEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub com.amazonaws.${AWS::Region}.git-codecommit
SubnetIds:
- !Ref PrivateSubnet1
VpcId: !Ref VPC
VpcEndpointType: Interface
SecurityGroupIds:
- !Ref SecurityGroupEndpointsGitCodeCommit
PrivateDnsEnabled: true
DependsOn: SecurityGroupEndpointsGitCodeCommit
5.5 AWS managed temporary credentials(AMTC)について
Cloud9 IDEでAWS CLIを利用する際にAWS側で一時的なクレデンシャルが発行され、それをAWS managed temporary credentials
といいます。
結論から言うと、このクレデンシャルをプライベートサブネットで利用する事は(例外を除き)出来ません。
理由は、利用者を概ね同じ権限が付与されますが、接続元IPがCloud9のグローバルIPアドレスに制限されるためです。
プライベートサブネット上のIDEからのAPIコールが失敗します。
ですので、AWS managed temporary credentials
を無効化してからAWS CLIを実行する様にしてください。
クラスメソッド様の記事が非常に分かりやすかったのでこちらを参照いただくのが、良いかと思います。
Cloud9 IDE で AWS CLIを実行する際に注意したいネットワーク的制約
公式ドキュメントはこのリンクのActions supported by AWS managed temporary credentials
を参照ください。
6. まとめ
プライベートサブネット上でCloud9を利用する際に、DockerのBuild/Shipが実行出来てかつ他リソースと隔離されたテンプレートがあれば良いなと思い作成したリソースです。
この記事が誰かの役に立てれば嬉しいです。Mitsuoでした!
Discussion