AWS Cloud9 環境を作成したら裏で CloudFormation が動いていたので内容を確認してみた
コンバンハ、千葉(幸)です。
AWS Cloud9 はクラウドベースの統合開発環境(IDE)です。Cloud9 環境を作成することで、ユーザーはブラウザ上の AWS Cloud9 IDE を使用して対話的に環境を使用できます。
Cloud9 環境のタイプとして以下3種類があり、それぞれのコンピューティングリソースも環境に接続します。
- EC2 環境(直接接続)
- EC2 環境(インバウンドなし、Systems Manager 経由)
- SSH 環境(任意の既存のサーバ)
EC2 環境を選択した場合、Cloud9 環境の作成にあわせて EC2 インスタンスも作成されます。
その作成が CloudFormation で行われていたため、内容を確認してみました。
きっかけは AWS Cloud9 のサービスロール
CloudFormation が動いていることに気づいたきっかけは、 AWS Cloud9 で使用されるサービスロールAWSServiceRoleForAWSCloud9
が持つ権限を確認したことでした。
アタッチされているポリシーAWSCloud9ServiceRolePolicy
内訳はこのようになっています。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ec2:RunInstances", "ec2:CreateSecurityGroup", "ec2:DescribeVpcs", "ec2:DescribeSubnets", "ec2:DescribeSecurityGroups", "ec2:DescribeInstances", "ec2:DescribeInstanceStatus", "cloudformation:CreateStack", "cloudformation:DescribeStacks", "cloudformation:DescribeStackEvents", "cloudformation:DescribeStackResources" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "ec2:TerminateInstances", "ec2:DeleteSecurityGroup", "ec2:AuthorizeSecurityGroupIngress" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "cloudformation:DeleteStack" ], "Resource": "arn:aws:cloudformation:*:*:stack/aws-cloud9-*" }, { "Effect": "Allow", "Action": [ "ec2:CreateTags" ], "Resource": [ "arn:aws:ec2:*:*:instance/*", "arn:aws:ec2:*:*:security-group/*" ], "Condition": { "StringLike": { "aws:RequestTag/Name": "aws-cloud9-*" } } }, { "Effect": "Allow", "Action": [ "ec2:StartInstances", "ec2:StopInstances" ], "Resource": "*", "Condition": { "StringLike": { "ec2:ResourceTag/aws:cloudformation:stack-name": "aws-cloud9-*" } } }, { "Effect": "Allow", "Action": [ "iam:ListInstanceProfiles", "iam:GetInstanceProfile" ], "Resource": [ "arn:aws:iam::*:instance-profile/cloud9/*" ] }, { "Effect": "Allow", "Action": [ "iam:PassRole" ], "Resource": [ "arn:aws:iam::*:role/service-role/AWSCloud9SSMAccessRole" ], "Condition": { "StringLike": { "iam:PassedToService": "ec2.amazonaws.com" } } } ] }
EC2 インスタンスの操作の他に、CloudFormation に関する権限が定義されていますね。
Cloud9 用 Cfn テンプレートの内容を確認
実際に CloudFormation のコンソールを確認すると、aws-cloud9-
というプレフィックスを持つスタックが作成されています。
Cloud 9 環境を削除すると CloudFormation スタックも削除されます。検証のために作成や削除を繰り返したので、わたしの環境では多くのスタックが表示されます。
スタックからテンプレートが確認できるため、その内容をチェックしていきます。
ちなみに、Cloud9 環境作成時には大まかに以下のパラメータを指定できます。
- 環境名
- 環境タイプ
- インスタンスタイプ
- プラットフォーム
- 配置 VPC、サブネット
EC2 環境の場合
テンプレートは以下の通りです。
{ "Resources": { "Instance": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId": "ami-0b397970e53f7d4f0", "InstanceType": "t2.micro", "UserData": ...略..., "Tags": [ { "Key": "Name", "Value": "aws-cloud9-環境名-環境ID" } ], "NetworkInterfaces": [ { "AssociatePublicIpAddress": true, "DeviceIndex": 0, "SubnetId": "subnet-0caa45223899b4b73", "GroupSet": [{"Ref": "InstanceSecurityGroup"}] } ] } }, "InstanceSecurityGroup": { "Type": "AWS::EC2::SecurityGroup", "Properties": { "GroupDescription": "Security group for AWS Cloud9 environment aws-cloud9-環境名-環境ID", "VpcId": "vpc-0e4acafc38414468c", "SecurityGroupIngress": [ { "FromPort": 22, "ToPort": 22, "IpProtocol": "tcp", "CidrIp": "18.179.48.128/27" }, { "FromPort": 22, "ToPort": 22, "IpProtocol": "tcp", "CidrIp": "18.179.48.96/27" } ] ,"Tags": [ { "Key": "Name", "Value": "aws-cloud9-環境名-環境ID" } ] } } } }
インスタンスタイプや配置 VPC、サブネットは環境作成時に指定したものが直接埋め込まれています。ユーザーデータは後で取り上げるとして、その他いくつかピックアップします。
パブリック IP アドレスの割り当て
AssociatePublicIpAddress
が true となっています。よって、配置するサブネットの設定に依らず、ここで作成される EC2 インスタンスにはパブリック IP アドレスが付与されます。
Cloud9 用 AMI
今回の例ではプラットフォームとして Amazon Linux2 を指定しています。使用された AMI を確認してみます。
% aws ec2 describe-images --image-ids ami-0b397970e53f7d4f0 --output table ------------------------------------------------------------------------ | DescribeImages | +----------------------------------------------------------------------+ || Images || |+---------------------+----------------------------------------------+| || Architecture | x86_64 || || CreationDate | 2021-08-03T05:14:16.000Z || || Description | Cloud9 Cloud9AmazonLinux2 AMI || || EnaSupport | True || || Hypervisor | xen || || ImageId | ami-0b397970e53f7d4f0 || || ImageLocation | amazon/Cloud9AmazonLinux2-2021-08-03T04-25 || || ImageOwnerAlias | amazon || || ImageType | machine || || Name | Cloud9AmazonLinux2-2021-08-03T04-25 || || OwnerId | 465558535106 || || PlatformDetails | Linux/UNIX || || Public | True || || RootDeviceName | /dev/xvda || || RootDeviceType | ebs || || SriovNetSupport | simple || || State | available || || UsageOperation | RunInstances || || VirtualizationType | hvm || |+---------------------+----------------------------------------------+| ||| BlockDeviceMappings ||| ||+---------------------------------+--------------------------------+|| ||| DeviceName | /dev/xvda ||| ||+---------------------------------+--------------------------------+|| |||| Ebs |||| |||+-----------------------------+----------------------------------+||| |||| DeleteOnTermination | True |||| |||| Encrypted | False |||| |||| SnapshotId | snap-073c7086836ec34ce |||| |||| VolumeSize | 10 |||| |||| VolumeType | gp2 |||| |||+-----------------------------+----------------------------------+|||
構築当時で最新と思われる AMI が使用されています。AMI 名や Description から、Cloud9 用の AMI が用意されていることが読み取れます。
SecuriryGroup
特定の CIDR からの SSH でのインバウンドが許可された SecuriryGroup が作成されています。
以下からダウンロードできるip-ranges.json
から確認すると、サービスコードCLOUD9
に属する IP プレフィックスであることがわかります。
% jq -r '.prefixes[] | select(.region=="ap-northeast-1" and .service=="CLOUD9") | .ip_prefix' < ip-ranges.json 18.179.48.96/27 18.179.48.128/27
Ingress なしの EC2 環境の場合
テンプレートは以下の通りです。
{ "Resources": { "Instance": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId": "ami-0b397970e53f7d4f0", "InstanceType": "t3.small", "IamInstanceProfile": "AWSCloud9SSMInstanceProfile", "UserData": ...略..., "Tags": [ { "Key": "Name", "Value": "aws-cloud9-環境名-環境ID" } ], "NetworkInterfaces": [ { "DeviceIndex": 0, "SubnetId": "subnet-09b107b0d68026bb8", "GroupSet": [{"Ref": "InstanceSecurityGroup"}] } ] } }, "InstanceSecurityGroup": { "Type": "AWS::EC2::SecurityGroup", "Properties": { "GroupDescription": "Security group for AWS Cloud9 environment aws-cloud9-環境名-環境ID", "VpcId": "vpc-0e4acafc38414468c" ,"Tags": [ { "Key": "Name", "Value": "aws-cloud9-環境名-環境ID" } ] } } } }
EC2 環境との差異を見ていきます。
インスタンスプロファイル
AWSCloud9SSMInstanceProfile
というインスタンスプロファイルが指定されています。
該当のインスタンスプロファイルと関連づけられている IAM ロールはAWSCloud9SSMAccessRole
であり、以下の IAM ポリシーがアタッチされています。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ssmmessages:CreateControlChannel", "ssmmessages:CreateDataChannel", "ssmmessages:OpenControlChannel", "ssmmessages:OpenDataChannel", "ssm:UpdateInstanceInformation" ], "Resource": "*" } ] }
Systems Manager による接続に必要な権限のみが与えられています。
パブリック IP アドレスの割り当て
"AssociatePublicIpAddress": true
の定義がないため、インスタンスとしてパブリック IP アドレスの割り当ては有効になりません。
Ingress なしの EC2 環境はパブリックサブネットにもプライベートサブネットにも配置ができますが、パブリックサブネットに配置する場合、サブネット側でパブリック IP アドレスの割り当てが有効になっている必要があります。(もしくは後から EIP をアタッチする。)
SecuriryGroup
インバウンドが不要のため、以下のルールを持つ SecuriryGroup が作成されます。
- インバウンドルール:なし
- アウトバウンドルール:0.0.0.0/0 向けにすべてのトラフィックを許可
Cloud9 用ユーザーデータの内容を確認
先ほどテンプレートの記載を省略したユーザーデータは、以下のような値を持ちます。
一部を除き、どのテンプレートでも内容は同一です。
IyEvYmluL2Jhc2gKClVOSVhfVVNFUj0iZWMyLXVzZXIiClVOSVhfVVNFUl9IT01FPSIvaG9tZS9lYzItdXNlciIKRU5WSVJPTk1FTlRfUEFUSD0iL2hvbWUvZWMyLXVzZXIvZW52aXJvbm1lbnQiClVOSVhfR1JPVVA9JChpZCAtZyAtbiAiJFVOSVhfVVNFUiIpCgojIEFwcGx5IHNlY3VyaXR5IHBhdGNoZXMKT1BFUkFUSU5HX1NZU1RFTT0kKGF3ayAtRj0gJyQxPT0iSUQiIHsgcHJpbnQgJDIgO30nIC9ldGMvb3MtcmVsZWFzZSB8IHNlZCAtZSAncy9eIi8vJyAtZSAncy8iJC8vJykKaWYgWyAiJE9QRVJBVElOR19TWVNURU0iID09ICJhbXpuIiBdOyB0aGVuCiAgICB5dW0gLXEgLXkgdXBkYXRlIC0tc2VjdXJpdHkgPiAvdG1wL2luaXQteXVtLXVwZGF0ZS1zZWN1cml0eSAyPiYxICYKZWxpZiBbICIkT1BFUkFUSU5HX1NZU1RFTSIgPT0gInVidW50dSIgXTsgdGhlbgogICAgdW5hdHRlbmRlZC11cGdyYWRlICYKZmkKCiMgYWRkIFNTSCBrZXkKaW5zdGFsbCAtZyAiJFVOSVhfR1JPVVAiIC1vICIkVU5JWF9VU0VSIiAtbSA3NTUgLWQgIiRVTklYX1VTRVJfSE9NRSIvLnNzaApjYXQgPDwnRU9GJyA+PiAiJFVOSVhfVVNFUl9IT01FIi8uc3NoL2F1dGhvcml6ZWRfa2V5cwojIEltcG9ydGFudAojIC0tLS0tLS0tLQojIFRoZSBmb2xsb3dpbmcgcHVibGljIGtleSBpcyByZXF1aXJlZCBieSBDbG91ZDkgSURFCiMgUmVtb3ZpbmcgdGhpcyBrZXkgd2lsbCBtYWtlIHRoaXMgRUMyIGluc3RhbmNlIGluYWNjZXNzaWJsZSBieSB0aGUgSURFCiMKY2VydC1hdXRob3JpdHkgc3NoLXJzYSBBQUFBQjNOemFDMXljMkVBQUFBREFRQUJBQUFDQVFERXFlcWhMR2xNK2xSOHcwRnBrYlR3ZnJWKzA4VXV4STFiTzZjSzJQMFRMUFp3L3ZRTlFmRU1yejJRMVJueFczMHdpQzFLL254allLRmQwNVpKZzZ0T1Jhc1RQUTdUSml4ZitQM2JKdVhaRmd3dmNsajRxU3VhckpVOFVCaW1UMnVzMjZobHA0RmtydTVjd0ZRUUtsM3JJRk9LVkdhT0RBS05FMUp0bHlLN3F2QkppWjRLQnlKd2FXeG5uU0JoblIxK0RTL1M3U25DYUY3ZGhtSE5SQjM4SFJlVDY4TEFQaElOMXh2bzcvZnNCaHVBa05DZ2VhdXl0RTRHSGRlVDcwT1ZKVjFQSWI3bjM0aG5nL3BwZS9PR2FxOHZOZE9wY3Nac2lJeDhTNTRiSGlNQUQ3Z0pHSld6NWRWMEpyc2VzZG14WjZmT1gxTVZza0YrN3Y0ckw0cGt6c2svYzlSbStyQ2pXUW9tUHBzQVR2ZnBzdkZxVFQ4OUppZEIweDlaaXF1ck9lcHN6Nk9TVXFmUmxrNytFSXBRNGZiVFoxZ2JqTlRlcDlCUXQ3Z3hLdmVVL3BxNmR6ZGprMEVOS1FpeVhtWDgzTFRHdFg0OG04a2tITm9mTnkyUWtoRFFiQmRpS2JkdVcvVCt5NG53M3VDYzU0d01qTkxqd1dSY3c0U0VwVTAxNnBnZ3lWTnliWUNmc2VCRFJuNFRnT2pRWWZEZ3ZjQWVwd01Ma3prR1NXSEExWTIzSGV5bFVIWGNZb1MrY1dPaDZEZjhMbFF3ZWNZemlPa0liYnFTZXdLMERVRlRhZHY3RlpLU2hTYVlrbWtpTE1Zb1orSTQrQ0F6Y3NvQUxRbXV5cS8xVTJqYlMvUTRHWGRWN2lBdTE3TE9hTWVUT052SkRtNWtVUnNVWFE9PSBjNWIyMWEyZjM1MWQ0YjdkOGExMDRjZWViNGJiNTI0MkBjbG91ZDkuYW1hem9uLmNvbQoKCiMKIyBBZGQgYW55IGFkZGl0aW9uYWwga2V5cyBiZWxvdyB0aGlzIGxpbmUKIwoKRU9GCgojIGFsbG93IGF1dG9tYXRpYyBzaHV0ZG93bgplY2hvICIkVU5JWF9VU0VSICAgIEFMTD0oQUxMKSBOT1BBU1NXRDogL3NiaW4vcG93ZXJvZmYsIC9zYmluL3JlYm9vdCwgL3NiaW4vc2h1dGRvd24iID4+IC9ldGMvc3Vkb2VycwoKbG4gLXMgL29wdC9jOSAiJFVOSVhfVVNFUl9IT01FIi8uYzkKY2hvd24gLVIgIiRVTklYX1VTRVIiOiIkVU5JWF9HUk9VUCIgIiRVTklYX1VTRVJfSE9NRSIvLmM5IC9vcHQvYzkKaW5zdGFsbCAtZyAiJFVOSVhfR1JPVVAiIC1vICIkVU5JWF9VU0VSIiAtbSA3NTUgLWQgIiRFTlZJUk9OTUVOVF9QQVRIIgoKaWYgWyAiJEVOVklST05NRU5UX1BBVEgiID09ICIvaG9tZS9lYzItdXNlci9lbnZpcm9ubWVudCIgXSAmJiBncmVwICJhbGlhcyBweXRob249cHl0aG9uMjciICIkVU5JWF9VU0VSX0hPTUUiLy5iYXNocmM7IHRoZW4KCiAgICBjYXQgPDwnRU9GJyA+ICIkVU5JWF9VU0VSX0hPTUUiLy5iYXNocmMKIyAuYmFzaHJjCgpleHBvcnQgUEFUSD0kUEFUSDokSE9NRS8ubG9jYWwvYmluOiRIT01FL2JpbgoKIyBsb2FkIG52bQpleHBvcnQgTlZNX0RJUj0iJEhPTUUvLm52bSIKWyAiJEJBU0hfVkVSU0lPTiIgXSAmJiBucG0oKSB7CiAgICAjIGhhY2s6IGF2b2lkIHNsb3cgbnBtIHNhbml0eSBjaGVjayBpbiBudm0KICAgIGlmIFsgIiQqIiA9PSAiY29uZmlnIGdldCBwcmVmaXgiIF07IHRoZW4gd2hpY2ggbm9kZSB8IHNlZCAicy9iaW5cL25vZGUvLyI7CiAgICBlbHNlICQod2hpY2ggbnBtKSAiJEAiOyBmaQp9CiMgWyAtcyAiJE5WTV9ESVIvbnZtLnNoIiBdICYmIC4gIiROVk1fRElSL252bS5zaCIgICMgVGhpcyBsb2FkcyBudm0KcnZtX3NpbGVuY2VfcGF0aF9taXNtYXRjaF9jaGVja19mbGFnPTEgIyBwcmV2ZW50IHJ2bSBjb21wbGFpbnRzIHRoYXQgbnZtIGlzIGZpcnN0IGluIFBBVEgKdW5zZXQgbnBtICMgZW5kIGhhY2sKCgojIFVzZXIgc3BlY2lmaWMgYWxpYXNlcyBhbmQgZnVuY3Rpb25zCmFsaWFzIHB5dGhvbj1weXRob24yNwoKIyBtb2RpZmljYXRpb25zIG5lZWRlZCBvbmx5IGluIGludGVyYWN0aXZlIG1vZGUKaWYgWyAiJFBTMSIgIT0gIiIgXTsgdGhlbgogICAgIyBTZXQgZGVmYXVsdCBlZGl0b3IgZm9yIGdpdAogICAgZ2l0IGNvbmZpZyAtLWdsb2JhbCBjb3JlLmVkaXRvciBuYW5vCgogICAgIyBUdXJuIG9uIGNoZWNrd2luc2l6ZQogICAgc2hvcHQgLXMgY2hlY2t3aW5zaXplCgogICAgIyBrZWVwIG1vcmUgaGlzdG9yeQogICAgc2hvcHQgLXMgaGlzdGFwcGVuZAogICAgZXhwb3J0IEhJU1RTSVpFPTEwMDAwMAogICAgZXhwb3J0IEhJU1RGSUxFU0laRT0xMDAwMDAKICAgIGV4cG9ydCBQUk9NUFRfQ09NTUFORD0iaGlzdG9yeSAtYTsiCgogICAgIyBTb3VyY2UgZm9yIEdpdCBQUzEgZnVuY3Rpb24KICAgIGlmICEgdHlwZSAtdCBfX2dpdF9wczEgJiYgWyAtZSAiL3Vzci9zaGFyZS9naXQtY29yZS9jb250cmliL2NvbXBsZXRpb24vZ2l0LXByb21wdC5zaCIgXTsgdGhlbgogICAgICAgIC4gL3Vzci9zaGFyZS9naXQtY29yZS9jb250cmliL2NvbXBsZXRpb24vZ2l0LXByb21wdC5zaAogICAgZmkKCiAgICAjIENsb3VkOSBkZWZhdWx0IHByb21wdAogICAgX2Nsb3VkOV9wcm9tcHRfdXNlcigpIHsKICAgICAgICBpZiBbICIkQzlfVVNFUiIgPSByb290IF07IHRoZW4KICAgICAgICAgICAgZWNobyAiJFVTRVIiCiAgICAgICAgZWxzZQogICAgICAgICAgICBlY2hvICIkQzlfVVNFUiIKICAgICAgICBmaQogICAgfQoKICAgIFBTMT0nXFtcMDMzWzAxOzMybVxdJChfY2xvdWQ5X3Byb21wdF91c2VyKVxbXDAzM1swMG1cXTpcW1wwMzNbMDE7MzRtXF1cd1xbXDAzM1swMG1cXSQoX19naXRfcHMxICIgKCVzKSIgMj4vZGV2L251bGwpICQgJwpmaQoKRU9GCgogICAgY2hvd24gIiRVTklYX1VTRVIiOiIkVU5JWF9HUk9VUCIgIiRVTklYX1VTRVJfSE9NRSIvLmJhc2hyYwpmaQoKaWYgWyAiJEVOVklST05NRU5UX1BBVEgiID09ICIvaG9tZS9lYzItdXNlci9lbnZpcm9ubWVudCIgXSAmJiBbICEgLWYgIiRFTlZJUk9OTUVOVF9QQVRIIi9SRUFETUUubWQgXTsgdGhlbgogICAgY2F0IDw8J0VPRicgPj4gIiRFTlZJUk9OTUVOVF9QQVRIIi9SRUFETUUubWQKICAgICAgICAgX19fICAgICAgICBfX19fX18gICAgIF9fX18gXyAgICAgICAgICAgICAgICAgXyAgX19fCiAgICAgICAgLyBcIFwgICAgICAvIC8gX19ffCAgIC8gX19ffCB8IF9fXyAgXyAgIF8gIF9ffCB8LyBfIFwKICAgICAgIC8gXyBcIFwgL1wgLyAvXF9fXyBcICB8IHwgICB8IHwvIF8gXHwgfCB8IHwvIF9gIHwgKF8pIHwKICAgICAgLyBfX18gXCBWICBWIC8gIF9fXykgfCB8IHxfX198IHwgKF8pIHwgfF98IHwgKF98IHxcX18sIHwKICAgICAvXy8gICBcX1xfL1xfLyAgfF9fX18vICAgXF9fX198X3xcX19fLyBcX18sX3xcX18sX3wgIC9fLwogLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCgpIaSB0aGVyZSEgV2VsY29tZSB0byBBV1MgQ2xvdWQ5IQoKVG8gZ2V0IHN0YXJ0ZWQsIGNyZWF0ZSBzb21lIGZpbGVzLCBwbGF5IHdpdGggdGhlIHRlcm1pbmFsLApvciB2aXNpdCBodHRwczovL2RvY3MuYXdzLmFtYXpvbi5jb20vY29uc29sZS9jbG91ZDkvIGZvciBvdXIgZG9jdW1lbnRhdGlvbi4KCkhhcHB5IGNvZGluZyEKCkVPRgoKICAgIGNob3duICIkVU5JWF9VU0VSIjoiJFVOSVhfR1JPVVAiICIkVU5JWF9VU0VSX0hPTUUiL2Vudmlyb25tZW50L1JFQURNRS5tZApmaQoKIyBGaXggZm9yIHBlcm1pc3Npb24gZXJyb3Igd2hlbiB0cnlpbmcgdG8gY2FsbCBgZ2VtIGluc3RhbGxgCmNob3duICIkVU5JWF9VU0VSIiAtUiAvdXNyL2xvY2FsL3J2bS9nZW1zCgojVGhpcyBzY3JpcHQgaXMgYXBwZW5kZWQgdG8gYW5vdGhlciBiYXNoIHNjcmlwdCwgc28gaXQgZG9lcyBub3QgbmVlZCBhIGJhc2ggc2NyaXB0IGZpbGUgaGVhZGVyLgoKVU5JWF9VU0VSX0hPTUU9Ii9ob21lL2VjMi11c2VyIgoKQzlfRElSPSRVTklYX1VTRVJfSE9NRS8uYzkKQ09ORklHX0ZJTEVfUEFUSD0iJEM5X0RJUiIvYXV0b3NodXRkb3duLWNvbmZpZ3VyYXRpb24KVkZTX0NIRUNLX0ZJTEVfUEFUSD0iJEM5X0RJUiIvc3RvcC1pZi1pbmFjdGl2ZS5zaAoKZWNobyAiU0hVVERPV05fVElNRU9VVD0zMCIgPiAiJENPTkZJR19GSUxFX1BBVEgiCmNobW9kIGErdyAiJENPTkZJR19GSUxFX1BBVEgiCgplY2hvIC1lICcjIS9iaW4vYmFzaApzZXQgLWV1byBwaXBlZmFpbApDT05GSUc9JChjYXQgJyRDT05GSUdfRklMRV9QQVRIJykKU0hVVERPV05fVElNRU9VVD0ke0NPTkZJRyMqPX0KaWYgISBbWyAkU0hVVERPV05fVElNRU9VVCA9fiBeWzAtOV0qJCBdXTsgdGhlbgogICAgZWNobyAic2h1dGRvd24gdGltZW91dCBpcyBpbnZhbGlkIgogICAgZXhpdCAxCmZpCmlzX3NodXR0aW5nX2Rvd24oKSB7CiAgICBpc19zaHV0dGluZ19kb3duX3VidW50dSAmPiAvZGV2L251bGwgfHwgaXNfc2h1dHRpbmdfZG93bl9hbDEgJj4gL2Rldi9udWxsIHx8IGlzX3NodXR0aW5nX2Rvd25fYWwyICY+IC9kZXYvbnVsbAp9CmlzX3NodXR0aW5nX2Rvd25fdWJ1bnR1KCkgewogICAgbG9jYWwgVElNRU9VVAogICAgVElNRU9VVD0kKGJ1c2N0bCBnZXQtcHJvcGVydHkgb3JnLmZyZWVkZXNrdG9wLmxvZ2luMSAvb3JnL2ZyZWVkZXNrdG9wL2xvZ2luMSBvcmcuZnJlZWRlc2t0b3AubG9naW4xLk1hbmFnZXIgU2NoZWR1bGVkU2h1dGRvd24pCiAgICBpZiBbICIkPyIgLW5lICIwIiBdOyB0aGVuCiAgICAgICAgcmV0dXJuIDEKICAgIGZpCiAgICBpZiBbICIkKGVjaG8gJFRJTUVPVVQgfCBhd2sgIntwcmludCBcJDN9IikiID09ICIwIiBdOyB0aGVuCiAgICAgICAgcmV0dXJuIDEKICAgIGVsc2UKICAgICAgICByZXR1cm4gMAogICAgZmkKfQppc19zaHV0dGluZ19kb3duX2FsMSgpIHsKICAgIHBncmVwIHNodXRkb3duCn0KaXNfc2h1dHRpbmdfZG93bl9hbDIoKSB7CiAgICBsb2NhbCBGSUxFCiAgICBGSUxFPS9ydW4vc3lzdGVtZC9zaHV0ZG93bi9zY2hlZHVsZWQKICAgIGlmIFtbIC1mICIkRklMRSIgXV07IHRoZW4KICAgICAgICByZXR1cm4gMAogICAgZWxzZQogICAgICAgIHJldHVybiAxCiAgICBmaQp9CmlzX3Zmc19jb25uZWN0ZWQoKSB7CiAgICBwZ3JlcCAtZiB2ZnMtd29ya2VyID4vZGV2L251bGwKfQoKaWYgaXNfc2h1dHRpbmdfZG93bjsgdGhlbgogICAgaWYgW1sgISAkU0hVVERPV05fVElNRU9VVCA9fiBeWzAtOV0rJCBdXSB8fCBpc192ZnNfY29ubmVjdGVkOyB0aGVuCiAgICAgICAgc3VkbyBzaHV0ZG93biAtYwogICAgZmkKZWxzZQogICAgaWYgW1sgJFNIVVRET1dOX1RJTUVPVVQgPX4gXlswLTldKyQgXV0gJiYgISBpc192ZnNfY29ubmVjdGVkOyB0aGVuCiAgICAgICAgc3VkbyBzaHV0ZG93biAtaCAkU0hVVERPV05fVElNRU9VVAogICAgZmkKZmknID4gIiRWRlNfQ0hFQ0tfRklMRV9QQVRIIgoKY2htb2QgK3ggIiRWRlNfQ0hFQ0tfRklMRV9QQVRIIgoKZWNobyAiKiAqICogKiAqIHJvb3QgJFZGU19DSEVDS19GSUxFX1BBVEgiID4gL2V0Yy9jcm9uLmQvYzktYXV0b21hdGljLXNodXRkb3duCg==
ユーザーデータは base64 エンコードされた状態のため、デコードしてみます。
ハイライトしている箇所は Cloud9 IDE からの接続に使用される公開鍵で、この値は環境ごとに異なるものが払い出されます。
# 環境変数 USERDATA に値を格納済みとして % echo $USERDATA | base64 -d #!/bin/bash UNIX_USER="ec2-user" UNIX_USER_HOME="/home/ec2-user" ENVIRONMENT_PATH="/home/ec2-user/environment" UNIX_GROUP=$(id -g -n "$UNIX_USER") # Apply security patches OPERATING_SYSTEM=$(awk -F= '$1=="ID" { print $2 ;}' /etc/os-release | sed -e 's/^"//' -e 's/"$//') if [ "$OPERATING_SYSTEM" == "amzn" ]; then yum -q -y update --security > /tmp/init-yum-update-security 2>&1 & elif [ "$OPERATING_SYSTEM" == "ubuntu" ]; then unattended-upgrade & fi # add SSH key install -g "$UNIX_GROUP" -o "$UNIX_USER" -m 755 -d "$UNIX_USER_HOME"/.ssh cat <<'EOF' >> "$UNIX_USER_HOME"/.ssh/authorized_keys # Important # --------- # The following public key is required by Cloud9 IDE # Removing this key will make this EC2 instance inaccessible by the IDE # cert-authority ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDEqeqhLGlM+lR8w0FpkbTwfrV+08UuxI1bO6cK2P0TLPZw/vQNQfEMrz2Q1RnxW30wiC1K/nxjYKFd05ZJg6tORasTPQ7TJixf+P3bJuXZFgwvclj4qSuarJU8UBimT2us26hlp4Fkru5cwFQQKl3rIFOKVGaODAKNE1JtlyK7qvBJiZ4KByJwaWxnnSBhnR1+DS/S7SnCaF7dhmHNRB38HReT68LAPhIN1xvo7/fsBhuAkNCgeauytE4GHdeT70OVJV1PIb7n34hng/ppe/OGaq8vNdOpcsZsiIx8S54bHiMAD7gJGJWz5dV0JrsesdmxZ6fOX1MVskF+7v4rL4pkzsk/c9Rm+rCjWQomPpsATvfpsvFqTT89JidB0x9ZiqurOepsz6OSUqfRlk7+EIpQ4fbTZ1gbjNTep9BQt7gxKveU/pq6dzdjk0ENKQiyXmX83LTGtX48m8kkHNofNy2QkhDQbBdiKbduW/T+y4nw3uCc54wMjNLjwWRcw4SEpU016pggyVNybYCfseBDRn4TgOjQYfDgvcAepwMLkzkGSWHA1Y23HeylUHXcYoS+cWOh6Df8LlQwecYziOkIbbqSewK0DUFTadv7FZKShSaYkmkiLMYoZ+I4+CAzcsoALQmuyq/1U2jbS/Q4GXdV7iAu17LOaMeTONvJDm5kURsUXQ== c5b21a2f351d4b7d8a104ceeb4bb5242@cloud9.amazon.com # # Add any additional keys below this line # EOF # allow automatic shutdown echo "$UNIX_USER ALL=(ALL) NOPASSWD: /sbin/poweroff, /sbin/reboot, /sbin/shutdown" >> /etc/sudoers ln -s /opt/c9 "$UNIX_USER_HOME"/.c9 chown -R "$UNIX_USER":"$UNIX_GROUP" "$UNIX_USER_HOME"/.c9 /opt/c9 install -g "$UNIX_GROUP" -o "$UNIX_USER" -m 755 -d "$ENVIRONMENT_PATH" if [ "$ENVIRONMENT_PATH" == "/home/ec2-user/environment" ] && grep "alias python=python27" "$UNIX_USER_HOME"/.bashrc; then cat <<'EOF' > "$UNIX_USER_HOME"/.bashrc # .bashrc export PATH=$PATH:$HOME/.local/bin:$HOME/bin # load nvm export NVM_DIR="$HOME/.nvm" [ "$BASH_VERSION" ] && npm() { # hack: avoid slow npm sanity check in nvm if [ "$*" == "config get prefix" ]; then which node | sed "s/bin\/node//"; else $(which npm) "$@"; fi } # [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" # This loads nvm rvm_silence_path_mismatch_check_flag=1 # prevent rvm complaints that nvm is first in PATH unset npm # end hack # User specific aliases and functions alias python=python27 # modifications needed only in interactive mode if [ "$PS1" != "" ]; then # Set default editor for git git config --global core.editor nano # Turn on checkwinsize shopt -s checkwinsize # keep more history shopt -s histappend export HISTSIZE=100000 export HISTFILESIZE=100000 export PROMPT_COMMAND="history -a;" # Source for Git PS1 function if ! type -t __git_ps1 && [ -e "/usr/share/git-core/contrib/completion/git-prompt.sh" ]; then . /usr/share/git-core/contrib/completion/git-prompt.sh fi # Cloud9 default prompt _cloud9_prompt_user() { if [ "$C9_USER" = root ]; then echo "$USER" else echo "$C9_USER" fi } PS1='\[\033[01;32m\]$(_cloud9_prompt_user)\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]$(__git_ps1 " (%s)" 2>/dev/null) $ ' fi EOF chown "$UNIX_USER":"$UNIX_GROUP" "$UNIX_USER_HOME"/.bashrc fi if [ "$ENVIRONMENT_PATH" == "/home/ec2-user/environment" ] && [ ! -f "$ENVIRONMENT_PATH"/README.md ]; then cat <<'EOF' >> "$ENVIRONMENT_PATH"/README.md ___ ______ ____ _ _ ___ / \ \ / / ___| / ___| | ___ _ _ __| |/ _ \ / _ \ \ /\ / /\___ \ | | | |/ _ \| | | |/ _` | (_) | / ___ \ V V / ___) | | |___| | (_) | |_| | (_| |\__, | /_/ \_\_/\_/ |____/ \____|_|\___/ \__,_|\__,_| /_/ ----------------------------------------------------------------- Hi there! Welcome to AWS Cloud9! To get started, create some files, play with the terminal, or visit https://docs.aws.amazon.com/console/cloud9/ for our documentation. Happy coding! EOF chown "$UNIX_USER":"$UNIX_GROUP" "$UNIX_USER_HOME"/environment/README.md fi # Fix for permission error when trying to call `gem install` chown "$UNIX_USER" -R /usr/local/rvm/gems #This script is appended to another bash script, so it does not need a bash script file header. UNIX_USER_HOME="/home/ec2-user" C9_DIR=$UNIX_USER_HOME/.c9 CONFIG_FILE_PATH="$C9_DIR"/autoshutdown-configuration VFS_CHECK_FILE_PATH="$C9_DIR"/stop-if-inactive.sh echo "SHUTDOWN_TIMEOUT=30" > "$CONFIG_FILE_PATH" chmod a+w "$CONFIG_FILE_PATH" echo -e '#!/bin/bash set -euo pipefail CONFIG=$(cat '$CONFIG_FILE_PATH') SHUTDOWN_TIMEOUT=${CONFIG#*=} if ! [[ $SHUTDOWN_TIMEOUT =~ ^[0-9]*$ ]]; then echo "shutdown timeout is invalid" exit 1 fi is_shutting_down() { is_shutting_down_ubuntu &> /dev/null || is_shutting_down_al1 &> /dev/null || is_shutting_down_al2 &> /dev/null } is_shutting_down_ubuntu() { local TIMEOUT TIMEOUT=$(busctl get-property org.freedesktop.login1 /org/freedesktop/login1 org.freedesktop.login1.Manager ScheduledShutdown) if [ "$?" -ne "0" ]; then return 1 fi if [ "$(echo $TIMEOUT | awk "{print \$3}")" == "0" ]; then return 1 else return 0 fi } is_shutting_down_al1() { pgrep shutdown } is_shutting_down_al2() { local FILE FILE=/run/systemd/shutdown/scheduled if [[ -f "$FILE" ]]; then return 0 else return 1 fi } is_vfs_connected() { pgrep -f vfs-worker >/dev/null } if is_shutting_down; then if [[ ! $SHUTDOWN_TIMEOUT =~ ^[0-9]+$ ]] || is_vfs_connected; then sudo shutdown -c fi else if [[ $SHUTDOWN_TIMEOUT =~ ^[0-9]+$ ]] && ! is_vfs_connected; then sudo shutdown -h $SHUTDOWN_TIMEOUT fi fi' > "$VFS_CHECK_FILE_PATH" chmod +x "$VFS_CHECK_FILE_PATH" echo "* * * * * root $VFS_CHECK_FILE_PATH" > /etc/cron.d/c9-automatic-shutdown
大まかに以下が行われていることが読み取れます。
- セキュリティパッチの適用
- SSH 鍵の追加
- 自動シャットダウンの許可
- /home/ec2-user/enviromentへの README.md の作成
- /usr/local/rvm/gems の所有者の変更
- 自動シャットダウンスクリプトの設定
シャットダウンスクリプトの確認
Cloud9 環境から、上記のユーザーデータで作成されたスクリプトを確認してみます。
SHUTDOWN_TIME=30
↑この設定を確認した Cloud9 環境は、自動停止期間をデフォルトの 30 分で設定しています。Cloud9 環境作成時に指定したパラメータにより、上記の値は変動するかと思います。
#!/bin/bash set -euo pipefail CONFIG=$(cat /home/ec2-user/.c9/autoshutdown-configuration) SHUTDOWN_TIMEOUT=${CONFIG#*=} if ! [[ $SHUTDOWN_TIMEOUT =~ ^[0-9]*$ ]]; then echo "shutdown timeout is invalid" exit 1 fi is_shutting_down() { is_shutting_down_ubuntu &> /dev/null || is_shutting_down_al1 &> /dev/null || is_shutting_down_al2 &> /dev/null } is_shutting_down_ubuntu() { local TIMEOUT TIMEOUT=$(busctl get-property org.freedesktop.login1 /org/freedesktop/login1 org.freedesktop.login1.Manager ScheduledShutdown) if [ "$?" -ne "0" ]; then return 1 fi if [ "$(echo $TIMEOUT | awk "{print \$3}")" == "0" ]; then return 1 else return 0 fi } is_shutting_down_al1() { pgrep shutdown } is_shutting_down_al2() { local FILE FILE=/run/systemd/shutdown/scheduled if [[ -f "$FILE" ]]; then return 0 else return 1 fi } is_vfs_connected() { pgrep -f vfs-worker >/dev/null } if is_shutting_down; then if [[ ! $SHUTDOWN_TIMEOUT =~ ^[0-9]+$ ]] || is_vfs_connected; then sudo shutdown -c fi else if [[ $SHUTDOWN_TIMEOUT =~ ^[0-9]+$ ]] && ! is_vfs_connected; then sudo shutdown -h $SHUTDOWN_TIMEOUT fi fi
Cloud9 環境は一定時間操作しない場合に自動で停止する設定が可能ですが、このような形で実現されているのか、というのが確認できて面白いですね。
終わりに
Cloud9 環境作成時に付随して作成される CloudFormation テンプレートの中身を確認してみました。
裏側でよしなにやってくれるのはとても便利ですが、どういったリソースが作成されているかを押さえておくのも大事かと思います。
いざという時に困らないよういろんなことを気にしておきましょう。
以上、 チバユキ (@batchicchi) がお送りしました。