SummerWind

Web, Photography, Space Development

AWS の Session Manager でインスタンスにログインする

仕事で久しぶりに AWS を使うことになりそうなので、基本的なサービスの使い方などを確認している。そんな中で AWS System Manager の Session Manager (ややこしい名前だ) を使って、VPC のプライベートなネットワークに構築したインスタンスにログインしようとしたら少しハマったのでメモ。特に IAM まわりと VPC エンドポイントの設定まわりは、検索をしてみても「事前にいい感じにセットアップしてあること」を前提とする記事が多かったので分かりにくかった。

今回は AWS のリソースは全て Terraform で作成することを前提として話を進める。まずは次のように VPC のプライベートなネットワークのリソースを定義する。

# VPC
resource "aws_vpc" "example" {
  cidr_block           = "172.16.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true
}

# VPC サブネット
resource "aws_subnet" "example" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "172.16.10.0/24"
  availability_zone       = "ap-northeast-1a"
  map_public_ip_on_launch = false
}

ここで定義した VPC サブネットの内部で起動する仮想マシンインスタンスはインターネットに直接アクセスできないので、AWS の各種サービスにも接続できない。そこで VPC エンドポイントを使って VPC サブネット内に AWS の各種サービスにアクセスするエンドポイントを作成する。

作成するエンドポイントはドキュメントに記載されている。今回は Session Manager の利用に必要になるエンドポイントを以下のように定義する。なお、インターフェースタイプのエンドポイントにはセキュリティグループで HTTPS への通信を許可しておく必要があるので注意。

# エンドポイント用のセキュリティグループ
resource "aws_security_group" "endpoint" {
  name   = "endpoint"
  vpc_id = aws_vpc.example.id
}

# エンドポイントに対する HTTPS 通信を許可
resource "aws_security_group_rule" "endpoint_ingress_https" {
  security_group_id = aws_security_group.endpoint.id
  type              = "ingress"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = 0
  to_port           = 443
  protocol          = "tcp"
}

# ssm エンドポイント
resource "aws_vpc_endpoint" "ssm" {
  vpc_id            = aws_vpc.example.id
  service_name      = "com.amazonaws.ap-northeast-1.ssm"
  vpc_endpoint_type = "Interface"

  security_group_ids  = [aws_security_group.endpoint.id]
  subnet_ids          = [aws_subnet.example.id]
  private_dns_enabled = true
}

# ssmmessages エンドポイント
resource "aws_vpc_endpoint" "ssmmessages" {
  vpc_id            = aws_vpc.example.id
  service_name      = "com.amazonaws.ap-northeast-1.ssmmessages"
  vpc_endpoint_type = "Interface"

  security_group_ids  = [aws_security_group.endpoint.id]
  subnet_ids          = [aws_subnet.example.id]
  private_dns_enabled = true
}

# ec2messages エンドポイント
resource "aws_vpc_endpoint" "ec2messages" {
  vpc_id            = aws_vpc.example.id
  service_name      = "com.amazonaws.ap-northeast-1.ec2messages"
  vpc_endpoint_type = "Interface"

  security_group_ids  = [aws_security_group.endpoint.id]
  subnet_ids          = [aws_subnet.example.id]
  private_dns_enabled = true
}

# S3 エンドポイント
resource "aws_vpc_endpoint" "s3" {
  vpc_id       = aws_vpc.example.id
  service_name = "com.amazonaws.ap-northeast-1.s3"
}

これで VPC のリソースの定義が完了したので、続いて EC2 インスタンスに関連するリソースを定義する。Session Manager でインスタンスにログインするには SSM Agent の起動が必要になるため、SSM Agent の実行に必要な権限をインスタンスに付与する必要がある。権限付与のため、AmazonSSMManagedInstanceCore ポリシーが付与された IAM インスタンスプロファイルを作成する。

# AmazonSSMManagedInstanceCore の情報を取得
data "aws_iam_policy" "ssm_core" {
  arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

# IAM ロール
resource "aws_iam_role" "instance" {
  name               = "instance"
  path               = "/"
  assume_role_policy = file("policy.json")
}

# IAM ロールにポリシーを付与
resource "aws_iam_role_policy" "instance_ssm" {
  name   = "instance_ssm"
  role   = aws_iam_role.instance.id
  policy = data.aws_iam_policy.ssm_core.policy
}

# 作成した IAM ロールを割り当てた IAM インスタンスプロファイル
resource "aws_iam_instance_profile" "instance" {
  name = "instance"
  role = aws_iam_role.instance.name
}

上記のリソース定義で使用している policy.json ファイルの内容は次のようになる。EC2 が AssumeRole を実行するための権限を設定している。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}

最後に EC2 インスタンスに関連するリソースを定義する。ここでは Amazon Linux 2 の AMI を使用して作成した VPC サブネットにインスタンスを起動している。

# Amazon Linux 2 AMI
data aws_ssm_parameter "ami" {
  name = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
}

# インスタンス用セキュリティグループ
resource "aws_security_group" "instance" {
  name   = "instance"
  vpc_id = aws_vpc.example.id
}

# インスタンスからの通信は全て許可
resource "aws_security_group_rule" "instance_egress_all" {
  security_group_id = aws_security_group.instance.id
  type              = "egress"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = 0
  to_port           = 0
  protocol          = "all"
}

# EC2 インスタンス
resource "aws_instance" "instance" {
  ami                  = data.aws_ssm_parameter.ami.value
  instance_type        = "t2.micro"
  iam_instance_profile = aws_iam_instance_profile.instance.name
  subnet_id            = aws_subnet.example.id
  vpc_security_group_ids = [aws_security_group.instance.id]
}

これまでのリソース定義を含む Terraform ファイルを作成し、次のように Terraform を実行してリソースを作成する。

$ terraform init
$ terraform apply

事前に AWS CLISession Manager Plugin をインストールしておけば、作成した EC2 インスタンスのIDを使用して Session Manager 経由でインスタンスにログインできる。

$ aws ssm start-session --target i-0123456789abcdefg

AWS は細かいところにも手が届く便利なサービスが揃っているけど、概念や用語が多いので1つ1つ理解していくには時間がかかりそうだ。

Moto Ishizawa

Moto Ishizawa
ソフトウェアエンジニア。ロケットの打上げを見学するために、たびたびフロリダや種子島にでかけるなど、宇宙開発分野のファンでもある。