Terraformで、Systems Manager（Session Manager）でアクセスできるようにしたEC2インスタンスを構築する

# TL;DR

* AWS Systems Managerを使って、EC2インスタンスにSSH鍵なしでアクセスできるようにしたい
* そのための環境を、Terraformで構築する
* AWS Systems Managerを利用するためには、EC2に`AmazonSSMManagedInstanceCore`ポリシーを含むIAMインスタンスプロファイルを付与する必要がある


# AWS Systems Manager

AWS Systems Managerとは、AWSインフラリソースの運用の役立つサービスです。

[AWS Systems Manager](https://aws.amazon.com/jp/systems-manager/)

AWS Systems Managerの機能は、こちら。

[Systems Manager の機能](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/features.html)

また、関連するドキュメントやポリシーなどの名前で「SSM」というのが出てくるのですが、こういう経緯みたいです。

[AWS Systems Manager とは何ですか?](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/what-is-systems-manager.html)

> AWS Systems Manager (Systems Manager) は、以前は「 Amazon Simple Systems Manager (SSM) 」や「 Amazon EC2 Systems Manager (SSM) 」と呼ばれていました。サービスの元の省略名「 SSM 」は、他のいくつかのサービスコンソールを含むさまざまな AWS リソースにまだ反映されています。

AWS Systems Managerが利用できるOSは、こちら。

[サポートされるオペレーティングシステム](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/prereqs-operating-systems.html)

あと、利用のための前提条件も読んでおくとよいでしょう。

[Systems Manager の前提条件](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/systems-manager-prereqs.html)

# Session Manager

Session Managerは、AWS Systems Managerの機能のうちのひとつで、AWS CLIを使ってEC2等の仮想マシンを管理することができるツールです。

[AWS Systems Manager Session Manager](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/session-manager.html)

Session Managerを使うと、SSHキーを管理することなく、EC2インスタンスにアクセスしたり、管理することができます。

今回は、このSession Managerを使える状態にしたEC2インスタンスを、Terraformで作ることを目標にします。EC2インスタンスは、PublicサブネットおよびPrivateサブネットに配置することにします。

OSに関しては、Amazon Linux 2を使用します。


# 環境

今回の環境は、こちらになります。

```shell
$ terraform version
Terraform v0.12.24


$ aws --version
aws-cli/2.0.12 Python/3.7.3 Linux/4.15.0-99-generic botocore/2.0.0dev16


$ session-manager-plugin --version
1.1.61.0
```


# コード

作成したコードは、こちら。

`main.tf`

```go
terraform {
  required_version = ">=  0.12.24"
}

provider "aws" {
  version = "2.61.0"
}

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "2.33.0"

  name = "my-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["ap-northeast-1a"]
  public_subnets  = ["10.0.101.0/24"]
  private_subnets = ["10.0.1.0/24"]

  map_public_ip_on_launch = true

  enable_nat_gateway = true
  single_nat_gateway = true
}

data "aws_iam_policy_document" "assume_role" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["ec2.amazonaws.com"]
    }
  }
}

resource "aws_iam_role" "role" {
  name               = "MyRole"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json
}

data "aws_iam_policy" "systems_manager" {
  arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

resource "aws_iam_role_policy_attachment" "default" {
  role       = aws_iam_role.role.name
  policy_arn = data.aws_iam_policy.systems_manager.arn
}

resource "aws_iam_instance_profile" "systems_manager" {
  name = "MyInstanceProfile"
  role = aws_iam_role.role.name
}

resource "aws_instance" "public" {
  ami                  = "ami-0f310fced6141e627"
  instance_type        = "t2.micro"
  iam_instance_profile = aws_iam_instance_profile.systems_manager.name
  subnet_id            = module.vpc.public_subnets[0]
}

resource "aws_instance" "private" {
  ami                  = "ami-0f310fced6141e627"
  instance_type        = "t2.micro"
  iam_instance_profile = aws_iam_instance_profile.systems_manager.name
  subnet_id            = module.vpc.private_subnets[0]
}
```

クレデンシャルは、環境変数で設定することにします。

```shell
$ export AWS_ACCESS_KEY_ID=...
$ export AWS_SECRET_ACCESS_KEY=...
$ export AWS_DEFAULT_REGION=ap-northeast-1
```


## VPC、サブネットを定義する

基礎となるネットワーク部分に関しては、[AWS VPC Terraform module](https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws)を使ってまるっと定義しました。

```go
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "2.33.0"

  name = "my-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["ap-northeast-1a"]
  public_subnets  = ["10.0.101.0/24"]
  private_subnets = ["10.0.1.0/24"]

  map_public_ip_on_launch = true

  enable_nat_gateway = true
  single_nat_gateway = true
}
```

これで、以下が組み上がります。

* VPC
* インターネットゲートウェイ
* EIP
* NAT ゲートウェイ
* ルーティングテーブル
* Publicサブネット
    * `map_public_ip_on_launch`を`true`にして、Public IPを付与する
* Privateサブネット


## IAMインスタンスプロファイルを定義する

続いて、EC2をAWS Systems Managerで利用できるように、EC2にアタッチするIAMインスタンスプロファイルを作成します。

まずは、IAMロールを定義。

```go
data "aws_iam_policy_document" "assume_role" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["ec2.amazonaws.com"]
    }
  }
}

resource "aws_iam_role" "role" {
  name               = "MyRole"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json
}
```

IAMロールにアタッチしているのは、EC2がロールを使えるようにするためのポリシーです。

[サービス用のロールを作成する (AWS CLI)](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_create_for-service.html#roles-creatingrole-service-cli)

定義したIAMロールに、`AmazonSSMManagedInstanceCore`ポリシーをアタッチします。

```go
data "aws_iam_policy" "systems_manager" {
  arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

resource "aws_iam_role_policy_attachment" "default" {
  role       = aws_iam_role.role.name
  policy_arn = data.aws_iam_policy.systems_manager.arn
}
```

[Systems Manager の IAM インスタンスプロファイルを作成する](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/setup-instance-profile.html)

IAMインスタンスポリシーを定義し、IAMロールを紐付けます。

```go
resource "aws_iam_instance_profile" "systems_manager" {
  name = "MyInstanceProfile"
  role = aws_iam_role.role.name
}
```

これで、EC2に付与するIAMインスタンスプロファイルが定義できました。

IAMインスタンスプロファイルって、AWSマネジメントコンソールからIAMロールを作った時には、自動で作られるんですね…。

[Amazon EC2 の IAM ロール](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html)

> [IAM role] リストには、IAM ロールの作成時に作成したインスタンスプロファイルの名前が表示されます。コンソールを使用して IAM ロールを作成した場合、インスタンスプロファイルが自動的に作成され、ロールと同じ名前が付けられます。IAM、API、または AWS CLI SDK を使用して AWS を作成した場合、インスタンスプロファイルに異なる名前を付けた可能性があります。

[Amazon EC2 インスタンスで実行されるアプリケーションに IAM ロールを使用してアクセス許可を付与する](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_use_switch-role-ec2.html)

## EC2インスタンスを定義する

あとは、IAMインスタンスプロファイルを紐付けたEC2インスタンスを定義します。

```go
resource "aws_instance" "public" {
  ami                  = "ami-0f310fced6141e627"
  instance_type        = "t2.micro"
  iam_instance_profile = aws_iam_instance_profile.systems_manager.name
  subnet_id            = module.vpc.public_subnets[0]
}

resource "aws_instance" "private" {
  ami                  = "ami-0f310fced6141e627"
  instance_type        = "t2.micro"
  iam_instance_profile = aws_iam_instance_profile.systems_manager.name
  subnet_id            = module.vpc.private_subnets[0]
}
```


## 参考

* [aws_iam_policy](https://www.terraform.io/docs/providers/aws/d/iam_policy.html)
* [Data Source: aws_iam_policy_document](https://www.terraform.io/docs/providers/aws/d/iam_policy_document.html)
* [Resource: aws_iam_role](https://www.terraform.io/docs/providers/aws/r/iam_role.html)
* [Resource: aws_iam_role_policy_attachment](https://www.terraform.io/docs/providers/aws/r/iam_role_policy_attachment.html)
* [Resource: aws_iam_instance_profile](https://www.terraform.io/docs/providers/aws/r/iam_instance_profile.html)


# 構築

```shell
$ terraform init
$ terraform apply
```

# Session Managerでアクセス

以下のコマンドで、EC2にアクセスできます。

```shell
$ aws ssm start-session --target [EC2のインスタンスID]
```

成功すると、こんなメッセージが表示されて

```
Starting session with SessionId: xxxxx
```

シェルが使えるようになります。

```shell
sh-4.2$
```


# その他

今回、Publicサブネットで起動するEC2にはPublic IPを付与し、PrivateサブネットではNAT ゲートウェイを使える構成にしてますが、これらをやめるとSession Managerで接続できなくなります。

本来は、VPCエンドポイントを作成してアクセスするのが筋なんでしょうね。

[Virtual Private Cloud エンドポイントの作成](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/setup-create-vpc.html)