この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
コンバンハ、千葉(幸)です。
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 ポリシーがアタッチされています。
arn:aws:iam::aws:policy/AWSCloud9SSMInstanceProfile
{
"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 環境から、上記のユーザーデータで作成されたスクリプトを確認してみます。
/home/ec2-user/.c9/autoshutdown-configuration
SHUTDOWN_TIME=30
↑この設定を確認した Cloud9 環境は、自動停止期間をデフォルトの 30 分で設定しています。Cloud9 環境作成時に指定したパラメータにより、上記の値は変動するかと思います。
/home/ec2-user/.c9/stop-if-inactive.sh
#!/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) がお送りしました。