Skip to content

privateサブネットに配置したAWS EC2インスタンスにssmでログインする

はじめに

前回はpublicサブネットにEC2インスタンスを配置してssmでログインできるようにしました。

しかし、実際の仕事ではEC2インスタンスをpublicサブネットに置くことは稀だと思っています。
そのため今回は、privateサブネットにEC2インスタンスを配置してSSM経由でセッションを開始できるようにします。

blue-print

目標

  • AWSマネジメントコンソールからEC2インスタンスへセッションを開始できる
  • ローカルPCからaws ssm session-startコマンドを実行できるようにする

前提

  • ローカルでterraformを実行できる
  • ローカルでaws cliを実行できる
  • ~/.aws/credentialsにAWSアクセスキーを設定している

バージョン

ツールバージョン
terraformv1.4.6
aws cliaws-cli/2.9.19

準備

環境変数

環境変数をエクスポートします。
筆者はdirenvを使っているため.envファイルを定義します

AWS_PROFILEの値は、~/.aws/credentialsに合わせて設定してください

Terminal window
$ cat ~/.aws/credentials
[profile_name]
aws_access_key_id = xxxxxxxxxx
aws_secret_access_key = xxxxxxxxxx
.env
AWS_PROFILE=profile_name
AWS_REGION=ap-northeast-1

terraform

使用するTerraformのバージョンは2023年5月現在最新の1.4.6を使用します。

Terminal window
$ asdf list all terraform | tail
1.4.1
1.4.2
1.4.3
1.4.4
1.4.5
1.4.6
1.5.0-alpha20230405
1.5.0-alpha20230504
1.5.0-beta1
1.5.0-beta2
Terminal window
$ asdf local terraform 1.4.6 # .tool-versions作成
$ terraform version
Terraform v1.4.6
on darwin_arm64

terraform init

aws providerのバージョンも2023年5月現在、最新のバージョンにします。

providers.tf
terraform {
required_version = "1.4.6"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0.1"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}

tfstateはあくまで勉強用なのでs3ではなくローカルに置くことにします。

backend.tf
terraform {
backend "local" {
path = "./terraform.tfstate"
}
}

terraform initを実行してみます。

Terminal window
$ terraform init
Initializing the backend...
Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Using previously-installed hashicorp/aws v5.0.1
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

とりあえず準備は完了しました。この段階でmain.tfには何も書いていませんが、ここまで以下のようなディレクトリ構成になっています。

Terminal window
$ tree -a -L 1
.
├── .env
├── .gitignore
├── .terraform
├── .terraform.lock.hcl
├── .tool-versions
├── backend.tf
├── main.tf
└── providers.tf

privateサブネットに配置したEC2インスタンスがアクセスできるよう設定する

EC2インスタンスを作成する

VPCを含め、EC2インスタンスまで一気に作成します。

main.tf
resource "aws_vpc" "ssm_vpc" {
cidr_block = "172.16.0.0/16"
tags = {
Name = "aws-ssm-example"
}
}
resource "aws_subnet" "ssm_private_subnet" {
vpc_id = aws_vpc.ssm_vpc.id
cidr_block = "172.16.11.0/24"
availability_zone = "ap-northeast-1c"
tags = {
Name = "ssm-private-subnet"
}
}
resource "aws_security_group" "ssm_private_sg" {
name = "ssm-private-sg"
description = "ssm private security group"
vpc_id = aws_vpc.ssm_vpc.id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
tags = {
Name = "ssm-private-sg"
}
}
data "aws_ami" "amzn-linux-2023-ami" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-2023.*-x86_64"]
}
}
resource "aws_instance" "ssm" {
ami = data.aws_ami.amzn-linux-2023-ami.id
instance_type = "t2.micro"
subnet_id = aws_subnet.ssm_private_subnet.id
iam_instance_profile = aws_iam_instance_profile.ssm_instance_core.name
tags = {
Name = "aws-ssm-private-example"
}
}
data "aws_iam_policy_document" "instance_assume_role_policy" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}
data "aws_iam_policy" "ssm_managed_instance_core" {
arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
resource "aws_iam_role" "ssm_instance_core" {
name = "SSMInstanceCoreRole"
assume_role_policy = data.aws_iam_policy_document.instance_assume_role_policy.json
managed_policy_arns = [data.aws_iam_policy.ssm_managed_instance_core.arn]
}
resource "aws_iam_instance_profile" "ssm_instance_core" {
name = "ssm"
role = aws_iam_role.ssm_instance_core.name
}

terraform applyを実行します。

Terminal window
$ terraform plan
$ terraform apply --auto-approve

EC2インスタンス作成後、接続ボタンを押します。 select-connect

しかし・・・
まだインスタンスに接続することはできません。
connot-connect

エラーには、以下のことが書いてあります。

  1. インスタンスに SSM エージェントがインストールされていません。エージェントは Windows インスタンスと Linux インスタンスの両方にインストールできます。
  2. 必須の IAM インスタンスプロファイルがインスタンスにアタッチされていません。プロファイルは AWS Systems Manager 高速セットアップを使ってアタッチできます。

EC2インスタンスに使っているAMIはAmazon Linux 2023です。このAMIはデフォルトでSSMエージェントが入っているので、原因ではありません。 SSM Agent がプリインストールされた Amazon Machine Images (AMIs) - AWS Docs

IAM インスタンスプロファイルについては、先ほどapplyしたTerraformのコードをにしっかり書いています。インスタンスプロファイルも原因ではありません。

main.tf
data "aws_iam_policy_document" "instance_assume_role_policy" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}
data "aws_iam_policy" "ssm_managed_instance_core" {
arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
resource "aws_iam_role" "ssm_instance_core" {
name = "SSMInstanceCoreRole"
assume_role_policy = data.aws_iam_policy_document.instance_assume_role_policy.json
managed_policy_arns = [data.aws_iam_policy.ssm_managed_instance_core.arn]
}
resource "aws_iam_instance_profile" "ssm_instance_core" {
name = "ssm"
role = aws_iam_role.ssm_instance_core.name
}

原因を調べる

Systems Manager を使用してインターネットアクセスなしでプライベート EC2 インスタンスを管理できるように、VPC エンドポイントを作成するにはどうすればよいですか?という記事によると、やっていないことがわかりました。

  1. VPCエンドポイントを追加する 最低以下3つのサービスにエンドポイントを追加する必要があります。

    • com.amazonaws.ap-northeast-1.ec2messages
    • com.amazonaws.ap-northeast-1.ssm
    • com.amazonaws.ap-northeast-1.ssmmessages
  2. VPCエンドポイントにセキュリティグループに設定する VPCエンドポイントにセキュリティグループを設定し、インバウンドルールとしてHTTP(ポート443)のトラフィックを許可する必要があります。

  3. VPCエンドポイントのDNSを有効にする VPCエンドポイントにはDNSを有効にする設定があるので、これを有効にします。 プライベート DNS 名を有効にする - AWS Docs

  4. VPCにDNSホスト名とDNS解決を有効にする VPCエンドポイントのDNSを有効にするには、VPCのDNSホスト名とDNS解決を有効にする必要があります。 インターフェイス VPC エンドポイントを使用して AWS のサービス にアクセスする - AWS Docs

Terraformのコードを修正 & 追記します。

main.tf
resource "aws_vpc" "ssm_vpc" {
cidr_block = "172.16.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "aws-ssm-example"
}
}
# 省略...
# == 以下を追加 ====
resource "aws_security_group" "ssm" {
name = "ssm-endpoint-sg"
description = "ssm-endpoint-sg"
vpc_id = aws_vpc.ssm_vpc.id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [
"172.16.0.0/16"
]
}
tags = {
Name = "ssm-endpoint-sg"
}
}
resource "aws_vpc_endpoint" "ec2_messages" {
vpc_id = aws_vpc.ssm_vpc.id
service_name = "com.amazonaws.ap-northeast-1.ec2messages"
vpc_endpoint_type = "Interface"
private_dns_enabled = true
security_group_ids = [ aws_security_group.ssm.id ]
subnet_ids = [ aws_subnet.ssm_private_subnet.id ]
}
resource "aws_vpc_endpoint" "ssm" {
vpc_id = aws_vpc.ssm_vpc.id
service_name = "com.amazonaws.ap-northeast-1.ssm"
vpc_endpoint_type = "Interface"
private_dns_enabled = true
security_group_ids = [ aws_security_group.ssm.id ]
subnet_ids = [ aws_subnet.ssm_private_subnet.id ]
}
resource "aws_vpc_endpoint" "ssm_messages" {
vpc_id = aws_vpc.ssm_vpc.id
service_name = "com.amazonaws.ap-northeast-1.ssmmessages"
vpc_endpoint_type = "Interface"
private_dns_enabled = true
security_group_ids = [ aws_security_group.ssm.id ]
subnet_ids = [ aws_subnet.ssm_private_subnet.id ]
}

接続

terraform applyを実行した後、接続できるか確認しましょう。

AWSマネジメントコンソールからアクセスする

EC2のページからインスタンスを選択して接続ボタンをクリックしてみます。

privateサブネットに配置した EC2インスタンスは接続ボタンが押せるようになっています!
(一度EC2インスタンスを作り直したので、インスタンスIDが変わっています) connect-instance-view

ボタンをクリックするとAWSマネジメントコンソールからセッションを開始することができました! connect-instance

これで1つ目の目標 “AWSマネジメントコンソールからEC2インスタンスへセッションを開始できる” が完了しました。

ターミナルからセッションを開始する

aws ssm start-sessionコマンドでセッションを開始するには、Session Manegerプラグインをインストール必要があります。

Terminal window
$ aws ssm start-session --target i-00b32e54399466f12
SessionManagerPlugin is not found. Please refer to SessionManager Documentation here: http://docs.aws.amazon.com/console/systems-manager/session-manager-plugin-not-found
Terminal window
$ curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/mac_arm64/sessionmanager-bundle.zip" -o "sessionmanager-bundle.zip"
$ unzip sessionmanager-bundle.zip
$ sudo ./sessionmanager-bundle/install -i /usr/local/sessionmanagerplugin -b /usr/local/bin/session-manager-plugin

参考:AWS CLI 用の Session Manager プラグインをインストールする

無事にセッションをスタートさせることができました。
これで2つ目の目標 “ローカルPCからaws ssm session-startコマンドを実行できるようにする” が完了しました。

Terminal window
$ aws ssm start-session --target i-07d3006a86659ee30
Starting session with SessionId: xxxxx-088961133074ed89d
sh-5.2$

その他学んだこと

さいごに

無事にprivateサブネットに配置したEC2インスタンスもSSMを使ってアクセスできるようになりました。
しかしこの記事では、IAMでアクセス制御したり、ログについて考慮しませんでした。
なので、実務で使用する際はもう少しTerraformのコードを修正した方が良いと思いました。

参考

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