Amazon EC2 SSH 救命索

これは Amazon EC2 Run Command Advent Calendar 2016 の 75 日目の記事です。
皆さんは、アドベントカレンダーですか?

http://cdn-ak.f.st-hatena.com/images/fotolife/a/asannou/20170212/20170212195445.png

なんらかの理由(キー紛失、設定ミス、ユーザ不在、人類滅亡など)で Amazon EC2SSH 接続できなくなったとき インスタンス再作成 を強いられることがありますが、そうならないように AWS Systems Manager の特徴 – アマゾン ウェブ サービス (AWS) で保険をかけておくことができます。

Run Command は EC2 インスタンス上に SSM エージェント をインストールし設定する - AWS Systems Manager をしておくことで、AWS CLI などでリモートからシェルコマンドの実行ができるフレンズです。

http://cdn-ak.f.st-hatena.com/images/fotolife/a/asannou/20170212/20170212195445.png

最初に、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

http://cdn-ak.f.st-hatena.com/images/fotolife/a/asannou/20170212/20170212195445.png

さて、リモートからコマンドを送信するための、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

http://cdn-ak.f.st-hatena.com/images/fotolife/a/asannou/20170212/20170212195445.png

それでは、やっていきます

$ 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.

http://cdn-ak.f.st-hatena.com/images/fotolife/a/asannou/20170212/20170212195445.png

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

たっのしー!

http://cdn-ak.f.st-hatena.com/images/fotolife/a/asannou/20170212/20170212195445.png

なお、お分かりですが ssm:ListCommandInvocations の Resource が適切ではないので、ことごとく実行結果が見放題です。AWS Systems Manager にあるのもガバガバだし、そうか、アマゾンは、、