セッションマネージャーを使用してプライベートサブネットのLinux用EC2にアクセス(VPCエンドポイント編)

セッションマネージャに必要な知識や手順をまとめつつ、Terraformで環境作るよ!!
2021.04.20

こんにちは!コンサル部のinomaso(@inomasosan)です。

プライベートサブネットのEC2にアクセスしたい場合は、これまでパブリックサブネットに踏み台サーバを作成してアクセスしていましたが、AWS Systems Manager Session Manager(以下、セッションマネージャー)を利用すれば、踏み台サーバの構築・運用が不要になるので検証してみました。

検証にあたり、以下のAWS公式ナレッジが分かりやすかったので、こちらをベースにした検証記事となります。

この記事で学べること

  • セッションマネージャーとVPCエンドポイントの知識
  • セッションマネージャーを使用したプライベートサブネットのLinux用EC2への接続方法
  • Terraformでのコードの書き方

前提知識

1. AWS Systems Manager Session Managerとは

セッションマネージャはAWS Systems Managerの機能の一つです。
AWSマネージメントコンソールやAWS CLI経由で、EC2に接続することができます。

2. VPCエンドポイントとは

VPC内のリソースと、サポートされているAWSサービスやVPCエンドポイントサービス間で、プライベートに接続可能になります。
VPCエンドポイントを利用する場合は、該当のAWSサービスへインターネット経由でのアクセスが不要になるため、インターネットゲートウェイ(以下、IGWという)やNatGateway等を作成する必要はありません。

システム構成図

今回、検証で構築するシステム構成図です。
必要なAWSリソースや、ネットワーク要件を記載しています。

セッションマネージャーやS3へアクセスするためにVPCエンドポイントを作成しますが、IGWやNatGateway等は作成しておりません。

必要な手順

AWSマネージメントコンソールからEC2へアクセスする場合は、1. セッションマネージャー関連のリソース作成のみの手順となります。
AWS CLIやSSHでアクセスしたい場合は2. AWS CLIやSSHでアクセスする準備も必要となります。

1. セッションマネージャー関連のリソース作成

  • EC2にSSM Agentのバージョン2.3.68.0以降をインストール
  • EC2にAmazonSSMManagedInstanceCoreポリシーを含むIAMロールをアタッチ
  • プライベートサブネットにセッションマネージャー接続用に、以下のVPCエンドポイントを作成
    • com.amazonaws.ap-northeast-1.ssm
    • com.amazonaws.ap-northeast-1.ssmmessages
    • com.amazonaws.ap-northeast-1.ec2messages
      ※1. セキュリティグループは、VPC内からHTTPSのインバウンド通信を許可
      ※2. EC2とVPCエンドポイントのサブネットを分ける分けないに関わらずプライベートDNS名を有効にする
  • SSM Agentのバージョン更新用に、以下のVPCエンドポイントを作成
    一時的にセッションマネージャが必要なケースの場合、作成しなくても問題ありません

    • com.amazonaws.ap-northeast-1.s3

2. AWS CLIやSSHでアクセスする準備

  • クライアントPCからAWS CLIで接続するために、以下のポリシーをもつIAMユーザかロールを作成
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "ssm:StartSession",
            "Resource": [
                "arn:aws:ec2:*:*:instance/(インスタンスID)",
                "arn:aws:ssm:*:*:document/AWS-StartSSHSession"
            ]
        }
    ]
}
  • クライアントPCにAWS CLI 用の Session Manager plugin をインストール
  • SSH 設定ファイル(~/.ssh/config)を更新して、Session Manager セッションを開始し、接続を介してすべてのデータを転送するプロキシコマンドを実行できるようにします。
    今回はAWS CLIでセッションマネージャー接続用にssmsessionプロファイルを作成しています。プロファイルを分けていないdafaultの場合は--profile ssmsessionは不要となります。
# SSH over Session Manager
host i-* mi-*
    ProxyCommand sh -c "aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p' --profile ssmsession"

やってみた

1. 環境

今回実行した環境は以下の通りです。

  • macOS Catalina 10.15.7
  • Terraform 0.14.8
  • AWSプロバイダー 3.35.0

2. コード

・VPC関連

VPCやサブネット、ルートテーブルは作成しますが、IGWやNatGateway等は不要なため作成しません。

また、今回はEC2とVPCエンドポイントのサブネットを分けていますが、同じサブネットでも問題ありません。

####################
# VPC
####################
resource "aws_vpc" "vpc" {
  cidr_block           = "10.0.0.0/16"
  instance_tenancy     = "default"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "inomaso-dev-vpc"
  }
}

####################
# Subnet
####################
resource "aws_subnet" "sub_privatelink_1a" {
  vpc_id     = aws_vpc.vpc.id
  cidr_block = "10.0.1.0/24"
  availability_zone = "ap-northeast-1a"

  tags = {
    Name = "inomaso-dev-privatelink-subnet-a"
  }
}

resource "aws_subnet" "sub_app_1a" {
  vpc_id     = aws_vpc.vpc.id
  cidr_block = "10.0.11.0/24"
  availability_zone = "ap-northeast-1a"

  tags = {
    Name = "inomaso-dev-app-subnet-a"
  }
}

resource "aws_route_table" "privatelink_rt" {
  vpc_id = aws_vpc.vpc.id

  tags = {
    Name = "inomaso-dev-privatelink-rtb"
  }
}

resource "aws_route_table" "app_rt" {
  vpc_id = aws_vpc.vpc.id

  tags = {
    Name = "inomaso-dev-app-rtb"
  }
}

resource "aws_route_table_association" "sub_privatelink_1a_rt_assocication" {
  subnet_id      = aws_subnet.sub_privatelink_1a.id
  route_table_id = aws_route_table.privatelink_rt.id
}

resource "aws_route_table_association" "sub_app_1a_rt_assocication" {
  subnet_id      = aws_subnet.sub_app_1a.id
  route_table_id = aws_route_table.app_rt.id
}

・EC2関連

今回はSSM Agentがインストール済みである、最新のAmazon Linux 2のAMIを使用してEC2を起動します。

セキュリティグループは、今回のネットワーク要件だとEC2へのインバウンドルールは不要なため、アウトバウンドルールのみの設定となります。

EC2のIAMロールには、AWS Systems Managerのサービスコア機能を使用できるようにAmazonSSMManagedInstanceCoreポリシーを指定します。

####################
# EC2
####################
resource "aws_instance" "ec2" {
  # Amazon Linux 2
  ami                         = "ami-06098fd00463352b6"
  instance_type               = "t3.micro"
  vpc_security_group_ids      = [aws_security_group.ec2.id]
  key_name  = "aws-ssh-key"
  subnet_id = aws_subnet.sub_app_1a.id

  # EBS最適化を有効
  ebs_optimized = "true"

  # IAM Role
  iam_instance_profile = "EC2RoleforSSM"

  # EBSのルートボリューム設定
  root_block_device {
    # ボリュームサイズ(GiB)
    volume_size = 8
    # ボリュームタイプ
    volume_type = "gp3"
    # GP3のIOPS
    iops = 3000
    # GP3のスループット
    throughput = 125
    # EC2終了時に削除
    delete_on_termination = true

    # EBSのNameタグ
    tags = {
      Name = "inomaso-dev-ec2"
    }
  }

  # EC2のNameタグ
  tags = {
    Name = "inomaso-dev-ec2"
  }
}

####################
# EC2 Security Group
####################
resource "aws_security_group" "ec2" {
  name        = "inomaso-dev-ec2-sg"
  description = "inomaso-dev-ec2-sg"
  vpc_id      = aws_vpc.vpc.id
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "inomaso-dev-ec2-sg"
  }
}


####################
# EC2 IAM Role
####################
data "aws_iam_policy_document" "ssm_role" {
  statement {
    actions = ["sts:AssumeRole"]
    principals {
      type        = "Service"
      identifiers = ["ec2.amazonaws.com"]
    }
  }
}

resource "aws_iam_instance_profile" "ssm_role" {
  name = "EC2RoleforSSM"
  role = aws_iam_role.ssm_role.name
}

resource "aws_iam_role" "ssm_role" {
  name               = "EC2RoleforSSM"
  assume_role_policy = data.aws_iam_policy_document.ssm_role.json
}

resource "aws_iam_role_policy_attachment" "ssm_role" {
  role       = aws_iam_role.ssm_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

尚、Amazon Linux 2へ明示的にSSM Agentの最新バージョンをインストールしたい場合は、以下のユーザデータを設定しEC2起動時に実行させてください。

#!/bin/bash
sudo yum install -y https://s3.ap-northeast-1.amazonaws.com/amazon-ssm-ap-northeast-1/latest/linux_amd64/amazon-ssm-agent.rpm

・VPCエンドポイント関連

VPCエンドポイントのIAMポリシーは、今回はフルアクセスで作成しています。

また、SSMAgent更新用にS3のGateway側VPCエンドポイント作成時にroute_table_idsを設定することで、ルートテーブルのS3へのルートが追加されます。

セッションマネージャ接続用のInterface型VPCエンドポイントに設定するセキュリティグループは、VPC内部からのHTTPSアクセスのインバウンドルールを追加します。
尚、アウトバウンドを許可していなくても通信可能なため、今回はインバウンドルールのみ定義します。

####################
# VPC Endpoint
####################
data "aws_iam_policy_document" "vpc_endpoint" {
  statement {
    effect    = "Allow"
    actions   = [ "*" ]
    resources = [ "*" ]
    principals {
      type = "AWS"
      identifiers = [ "*" ]
    }
  }
}

resource "aws_vpc_endpoint" "ssm" {
  vpc_endpoint_type = "Interface"
  vpc_id            = aws_vpc.vpc.id
  service_name      = "com.amazonaws.ap-northeast-1.ssm"
  policy            = data.aws_iam_policy_document.vpc_endpoint.json
  subnet_ids = [
    aws_subnet.sub_privatelink_1a.id
  ]
  private_dns_enabled = true
  security_group_ids = [
    aws_security_group.ssm.id
  ]
}

resource "aws_vpc_endpoint" "ssmmessages" {
  vpc_endpoint_type = "Interface"
  vpc_id            = aws_vpc.vpc.id
  service_name      = "com.amazonaws.ap-northeast-1.ssmmessages"
  policy            = data.aws_iam_policy_document.vpc_endpoint.json
  subnet_ids = [
    aws_subnet.sub_privatelink_1a.id
  ]
  private_dns_enabled = true
  security_group_ids = [
    aws_security_group.ssm.id
  ]
}

resource "aws_vpc_endpoint" "ec2messages" {
  vpc_endpoint_type = "Interface"
  vpc_id            = aws_vpc.vpc.id
  service_name      = "com.amazonaws.ap-northeast-1.ec2messages"
  policy            = data.aws_iam_policy_document.vpc_endpoint.json
  subnet_ids = [
    aws_subnet.sub_privatelink_1a.id
  ]
  private_dns_enabled = true
  security_group_ids = [
    aws_security_group.ssm.id
  ]
}

resource "aws_vpc_endpoint" "s3" {
  vpc_endpoint_type = "Gateway"
  vpc_id            = aws_vpc.vpc.id
  service_name      = "com.amazonaws.ap-northeast-1.s3"
  policy            = data.aws_iam_policy_document.vpc_endpoint.json
  route_table_ids = [
    aws_route_table.app_rt.id
  ]
}

####################
# VPC Endpoint Security Group
####################
resource "aws_security_group" "ssm" {
  name        = "inomaso-dev-ssm-sg"
  description = "inomaso-dev-ssm-sg"
  vpc_id      = aws_vpc.vpc.id
  ingress {
    from_port   = 443
    to_port     = 443
    protocol  = "tcp"
    cidr_blocks = [
      "10.0.0.0/16"
    ]
  }

  tags = {
    Name = "inomaso-dev-ssm-sg"
  }
}

・クライアントPC関連

クライアントPCからAWS CLI使用してセッションを開始するためのIAM作成です。
今回使用する環境は、AssumeRole+MFAが必要なためIAMロールを作成します。

また、IAMポリシーでは、任意のEC2へのセッション開始を許可しました。
リージョンやアカウントID、インスタンスIDを指定することで制限を厳しくすることもできます。

####################
# VPC Endpoint IAM Role
####################
data "aws_iam_policy_document" "assume_role" {
  statement {
    effect    = "Allow"
    actions   = [ "sts:AssumeRole" ]
    principals {
      type = "AWS"
      identifiers = [ "arn:aws:iam::1234567890:user/hogehoge" ]
    }
    condition {
      test     = "Bool"
      variable = "aws:MultiFactorAuthPresent"
      values   = ["true"]
    }
  }
}

resource "aws_iam_role" "session_role" {
  name               = "SessionRoleforSSM"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json
}

data "aws_iam_policy_document" "ssm_startsesstion" {
  statement {
    effect    = "Allow"
    actions   = [ "ssm:StartSession" ]
    resources = [ 
      "arn:aws:ec2:*:*:instance/*",
      "arn:aws:ssm:*:*:document/AWS-StartSSHSession"
    ]
  }
}

resource "aws_iam_role_policy" "session_role" {
  name = "SSMSession_policy"
  role = aws_iam_role.session_role.id
  policy = data.aws_iam_policy_document.ssm_startsesstion.json
}

3. 接続確認

下記いずれかの方法でEC2とのセッションを開始します。

・AWSマネジメントコンソール(EC2コンソール)

  1. AWSマネジメントコンソールにログインします。
  2. EC2コンソールを開き、接続したいEC2にチェックを入れ、接続をクリックします。
  3. セッションセッションマネージャータブを選択し、接続をクリックします。
    ※環境作成してすぐの場合は、接続できない場合があるので十数分ほどお待ちください。
  4. 接続が確立され、下記のような画面が表示されればbashコマンドを実行可能です。

・AWS CLI

--targetにEC2のinstance-id指定し、セッションを開始します。 特にプロファイルを分けていない場合は、--profileの指定は不要です。

% aws ssm start-session --target instance-id --profile ssmsession

Starting session with SessionId: hoge.hoge-instance-id
sh-4.2$

・SSH

セッションマネージャ経由でSSH接続します。
/path/my-key-pair.pemにキーペアを、usernameにはEC2のOSユーザ名(ec2-user等)を指定してください。

% ssh -i /path/my-key-pair.pem username@instance-id
The authenticity of host 'instance-id (<no hostip for proxy command>)' can't be established.
ECDSA key fingerprint is SHA256:123456789ABCDEFGHIJKLMN.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'instance-id' (ECDSA) to the list of known hosts.

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
No packages needed for security; 2 packages available
Run "sudo yum update" to apply all updates.
[ec2-user@ip-10-0-11-200 ~]$

・その他

上記以外にも接続方法はあります。
詳細は公式ドキュメントをご参照願います。

検証中に感じたQA

1. システム構成図のセッションマネージャとEC2間で通信の向き逆じゃね?

EC2のSSMAgentから、セッションマネージャのエンドポイントにポーリングしているので、システム構成図の通信の向きは正しいです。

2. EC2のSSMAgentは自動更新できないの?

AWS Systems Managerコンソールのマネージドインスタンスページで、Agent auto update (エージェントの自動更新)を選択で設定可能です。

3. クライアントPCからセッションマネージャへのアクセス制限はどうするの?

VPCエンドポイントのセキュリティグループでは、クライアントPCからのアクセス制御を設定することはできません。
アクセス制御するためには、クライアントPC用のIAMポリシーで実施する必要があります。

詳しくは、弊社の以下ブログに詳細な内容がまとめられておりますので、かなりオススメです。

4. VPCエンドポイントやプライベートDNSの仕組みがいまいちよく分からんタスケテ?

弊社の以下ブログに、技術同人誌が作れるくらいの濃い内容がまとまっています。

まとめ

セッションマネージャでプライベートサブネットのEC2へのアクセスするための手順について、一つにまとまっている記事を見つけられなかったので検証がてらに書いてみました。

設定や考慮点がいくつかあるものの、VPCの設定変更が軽微で、EC2のインバウンドルール変更が不要なので、既存環境への一時的なアクセス経路追加等でも有効な手段だと感じました。

この記事が、どなたかのお役に立てば幸いです。それでは!