Amazon EC2 SSH 救命索
これは Amazon EC2 Run Command Advent Calendar 2016 の 75 日目の記事です。
皆さんは、アドベントカレンダーですか?
なんらかの理由(キー紛失、設定ミス、ユーザ不在、人類滅亡など)で Amazon EC2 に SSH 接続できなくなったとき インスタンス再作成 を強いられることがありますが、そうならないように AWS Systems Manager の特徴 – アマゾン ウェブ サービス (AWS) で保険をかけておくことができます。
Run Command は EC2 インスタンス上に SSM エージェント をインストールし設定する - AWS Systems Manager をしておくことで、AWS CLI などでリモートからシェルコマンドの実行ができるフレンズです。
最初に、EC2 インスタンスが SSM サービスと通信するための IAM ロール "EC2RoleforSSMRunShellScript" をアタッチした、インスタンスプロファイル "EC2RoleJaparipark" を Terraform で作成します。
provider "aws" { region = "ap-northeast-1" } resource "aws_iam_policy" "ec2-ssm" { name = "EC2RoleforSSMRunShellScript" path = "/" policy = <<EOD { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ssm:DescribeAssociation", "ssm:GetDeployablePatchSnapshotForInstance", "ssm:GetDocument", "ssm:GetParameters", "ssm:ListAssociations", "ssm:ListInstanceAssociations", "ssm:PutInventory", "ssm:UpdateAssociationStatus", "ssm:UpdateInstanceAssociationStatus", "ssm:UpdateInstanceInformation" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "ec2messages:AcknowledgeMessage", "ec2messages:DeleteMessage", "ec2messages:FailMessage", "ec2messages:GetEndpoint", "ec2messages:GetMessages", "ec2messages:SendReply" ], "Resource": "*" } ] } EOD } resource "aws_iam_role" "ec2-ssm" { name = "EC2RoleJaparipark" path = "/" assume_role_policy = <<EOD { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } EOD } resource "aws_iam_role_policy_attachment" "ec2-ssm" { role = "${aws_iam_role.ec2-ssm.name}" policy_arn = "${aws_iam_policy.ec2-ssm.arn}" } resource "aws_iam_instance_profile" "ec2-ssm" { name = "${aws_iam_role.ec2-ssm.name}" roles = ["${aws_iam_role.ec2-ssm.name}"] }
用意されている AmazonEC2RoleforSSM でも同じことができますが、権限が強すぎるため、必要がなさそうなものを削りました。それについては EC2's most dangerous feature を読むと、圧倒的なわかりを得ます。
対象の EC2 インスタンス i-xxxxxxxxxxxxxxxxx に "EC2RoleJaparipark" をアタッチします。以前はインスタンス作成時にのみ可能だったのですが New! Attach an AWS IAM Role to an Existing Amazon EC2 Instance by Using the AWS CLI | AWS Security Blog にて、作成済みインスタンスでもできるようになりました。すごーい!
$ aws ec2 associate-iam-instance-profile --instance-id i-xxxxxxxxxxxxxxxxx --iam-instance-profile Name=EC2RoleJaparipark
そして EC2 インスタンス i-xxxxxxxxxxxxxxxxx に SSM エージェントをインストールしましょう。
$ sudo yum install amazon-ssm-agent $ sudo start amazon-ssm-agent
さて、リモートからコマンドを送信するための、IAM ポリシー "SSMLuckyBeast" を作成します。AWS 管理ポリシーの AmazonSSMFullAccess なども使えますが、その場合 "EC2RoleforSSMRunShellScript" をアタッチしているすべてのインスタンスに送信可能になります。
provider "aws" { region = "ap-northeast-1" } data "aws_caller_identity" "aws" {} resource "aws_iam_policy" "ssm" { name = "SSMLuckyBeast" path = "/" policy = <<EOD { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "ssm:SendCommand", "Resource": [ "arn:aws:ssm:ap-northeast-1::document/AWS-RunShellScript", "arn:aws:ec2:ap-northeast-1:${data.aws_caller_identity.aws.account_id}:instance/i-xxxxxxxxxxxxxxxxx" ] }, { "Effect": "Allow", "Action": "ssm:ListCommandInvocations", "Resource": "arn:aws:ssm:ap-northeast-1:${data.aws_caller_identity.aws.account_id}:*" } ] } EOD }
最後に、適当な IAM ユーザに "SSMLuckyBeast" をアタッチして準備は終わりです。
$ aws iam attach-user-policy --user-name kaban --policy-arn arn:aws:iam::$(aws sts get-caller-identity --output text --query Account):policy/SSMLuckyBeast
それでは、やっていきます
$ aws ssm send-command --output text --document-name AWS-RunShellScript --instance-ids i-xxxxxxxxxxxxxxxxx --parameters commands="uname -a" COMMAND 8020f507-8adc-4536-80af-d18dd2dafa5f 0 AWS-RunShellScript 0 1486890312.52 50 0 1486886712.52 Pending Pending 1 INSTANCEIDS i-xxxxxxxxxxxxxxxxx NOTIFICATIONCONFIG COMMANDS uname -a
command-id で結果
$ aws ssm list-command-invocations --output text --details --command-id 8020f507-8adc-4536-80af-d18dd2dafa5f COMMANDINVOCATIONS 8020f507-8adc-4536-80af-d18dd2dafa5f AWS-RunShellScript i-xxxxxxxxxxxxxxxxx 1486886712.64 Success Success COMMANDPLUGINS aws:runShellScript Linux ip-172-31-28-103 4.4.30-32.54.amzn1.x86_64 #1 SMP Thu Nov 10 15:52:05 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux ap-northeast-1 0 1486886713.22 1486886713.22 Success Success NOTIFICATIONCONFIG
がおー
$ aws ssm send-command --output text --query "Command.CommandId" --document-name AWS-RunShellScript --instance-ids i-xxxxxxxxxxxxxxxxx --parameters commands="echo $(cat ~/.ssh/id_rsa.pub) >> /home/ec2-user/.ssh/authorized_keys" 7b410797-1d73-4cc9-a29c-e845e6f0621a $ aws ssm list-command-invocations --output text --details --command-id 7b410797-1d73-4cc9-a29c-e845e6f0621a COMMANDINVOCATIONS 7b410797-1d73-4cc9-a29c-e845e6f0621a AWS-RunShellScript i-xxxxxxxxxxxxxxxxx 1486888202.22 Success Success COMMANDPLUGINS aws:runShellScript ap-northeast-1 0 1486888202.66 1486888202.66 Success Success NOTIFICATIONCONFIG
よかったですね
$ ssh ec2-user@203.0.113.1 Last login: Thu Feb 9 19:08:31 2017 from 198.51.100.1 __| __|_ ) _| ( / Amazon Linux AMI ___|\___|___| https://aws.amazon.com/amazon-linux-ami/2016.09-release-notes/ No packages needed for security; 3 packages available Run "sudo yum update" to apply all updates.
Run Command をバックドアっぽく使いましたが、玄関にしてもよさがあります。そうすると、権限を IAM で管理でき AWS CloudTrail (AWS API の呼び出し記録とログファイル送信) | AWS でコマンド送信が記録されるため、監査にも便利です。
Run Command を対話的にする GitHub - koshigoe/aws-ssm-console があり asannou/aws-ssm-console - Docker Hub したので、こうなります。
$ docker run -it --rm -v ~/.aws:/root/.aws asannou/aws-ssm-console --instance-ids i-xxxxxxxxxxxxxxxxx >> uname -a Running uname -a [i-xxxxxxxxxxxxxxxxx] Success: uname -a Linux ip-192-168-1-5 4.4.41-36.55.amzn1.x86_64 #1 SMP Wed Jan 18 01:03:26 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
たっのしー!
なお、お分かりですが ssm:ListCommandInvocations の Resource が適切ではないので、ことごとく実行結果が見放題です。AWS Systems Manager にあるのもガバガバだし、そうか、アマゾンは、、