Skip to content

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

はじめに

AWS Systems Manager Session Managerを使用すると、SSHキーを管理することなくEC2インスタンスにログインできます。
個人的な勉強でEC2インスタンスを使用するとき、毎回SSHキーの設定をしていました。
しかし今回はEC2インスタンスをpublicサブネットに配置し、Session Managerでアクセスできる状態をTerraformで作成してみます。

blue-print

課題

  • EC2インスタンスにSSHするために以下の手順が発生
    • SSHキーを作成
    • キーのダウンロード
    • ssh agentに鍵を登録
  • 踏み台サーバとして利用するときは、グローバルIPに合わせてセキュリティグループのインバウンドルールを修正しなければいけない

目標

この記事ではゴールを以下に設定します。

  • 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

ネットワークを整える

まずはVPC周辺のリソースを先に作成します。

main.tf
resource "aws_vpc" "ssm_vpc" {
cidr_block = "172.16.0.0/16"
tags = {
Name = "aws-ssm-example"
}
}
resource "aws_subnet" "ssm_public_subnet" {
vpc_id = aws_vpc.ssm_vpc.id
cidr_block = "172.16.10.0/24"
availability_zone = "ap-northeast-1a"
tags = {
Name = "ssm-public-subnet"
}
}
resource "aws_internet_gateway" "example_igw" {
vpc_id = aws_vpc.ssm_vpc.id
tags = {
Name = "ssm-example-igw"
}
}
resource "aws_route_table" "example_rt" {
vpc_id = aws_vpc.ssm_vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.example_igw.id
}
tags = {
Name = "ssm-example-rt"
}
}
resource "aws_route_table_association" "ssm_association" {
subnet_id = aws_subnet.ssm_public_subnet.id
route_table_id = aws_route_table.example_rt.id
}
resource "aws_security_group" "ssm_public_sg" {
name = "ssm-public-sg"
description = "ssm public 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-public-sg"
}
}

EC2インスタンスのAMIを探す

仕事ではAmazon Linux2を使用していますが、勉強目的なので今回はAmazon Linux 2023を使用します。

Amazon Linux 2023 (AL2023) のAMIにはすでに AWS System Managerエージェント(SSM Agent)がインストールされています。

参考:

ネット上に存在するAmazon Linux 2023の記事やamazonlinux/amazon-linux-2023 - GitHubをみると、AMI名はal2023-ami-xxxxxになっていることが確認できます。

Terraformだとdata sourcesを使うことでAMI IDを確認できます。

data "aws_ami" "amzn-linux-2023-ami" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-2023.*-x86_64"]
}
}
output "al2023" {
value = data.aws_ami.amzn-linux-2023-ami
}
terraform plan
Terminal window
$ terraform plan
data.aws_ami.amzn-linux-2023-ami: Reading...
data.aws_ami.amzn-linux-2023-ami: Read complete after 0s [id=ami-03dceaabddff8067e]
Changes to Outputs:
+ al2023 = {
+ architecture = "x86_64"
+ arn = "arn:aws:ec2:ap-northeast-1::image/ami-03dceaabddff8067e"
+ block_device_mappings = [
+ {
+ device_name = "/dev/xvda"
+ ebs = {
+ delete_on_termination = "true"
+ encrypted = "false"
+ iops = "3000"
+ snapshot_id = "snap-0c81aad031eccf0b3"
+ throughput = "125"
+ volume_size = "8"
+ volume_type = "gp3"
}
+ no_device = ""
+ virtual_name = ""
},
]
+ boot_mode = "uefi-preferred"
+ creation_date = "2023-05-15T20:19:40.000Z"
+ deprecation_time = "2023-08-13T20:20:00.000Z"
+ description = "Amazon Linux 2023 AMI 2023.0.20230517.1 x86_64 HVM kernel-6.1"
+ ena_support = true
+ executable_users = null
+ filter = [
+ {
+ name = "name"
+ values = [
+ "al2023-ami-2023.*-x86_64",
]
},
]
+ hypervisor = "xen"
+ id = "ami-03dceaabddff8067e"
+ image_id = "ami-03dceaabddff8067e"
+ image_location = "amazon/al2023-ami-2023.0.20230517.1-kernel-6.1-x86_64"
+ image_owner_alias = "amazon"
+ image_type = "machine"
+ imds_support = "v2.0"
+ include_deprecated = false
+ kernel_id = ""
+ most_recent = true
+ name = "al2023-ami-2023.0.20230517.1-kernel-6.1-x86_64"
+ name_regex = null
+ owner_id = "137112412989"
+ owners = [
+ "amazon",
]
+ platform = ""
+ platform_details = "Linux/UNIX"
+ product_codes = []
+ public = true
+ ramdisk_id = ""
+ root_device_name = "/dev/xvda"
+ root_device_type = "ebs"
+ root_snapshot_id = "snap-0c81aad031eccf0b3"
+ sriov_net_support = "simple"
+ state = "available"
+ state_reason = {
+ code = "UNSET"
+ message = "UNSET"
}
+ tags = {}
+ timeouts = null
+ tpm_support = ""
+ usage_operation = "RunInstances"
+ virtualization_type = "hvm"
}
You can apply this plan to save these new output values to the Terraform state, without changing any real infrastructure.

aws cliからAMI IDを探す

Systems Manager を使用して最新の Amazon Linux AMI を検索するにはaws cliにあるaws ssm get-parameters-by-pathコマンドを実行すると取得できます。

aws ssm get-parameters-by-path
Terminal window
$ aws ssm get-parameters-by-path --path /aws/service/ami-amazon-linux-latest --query "Parameters[?contains(Name,\`al2023\`)].{Name: Name,Value:Value}"
[
{
"Name": "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-arm64",
"Value": "ami-0ebbfa73436e5e751"
},
{
"Name": "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64",
"Value": "ami-03dceaabddff8067e"
},
{
"Name": "/aws/service/ami-amazon-linux-latest/al2023-ami-minimal-kernel-6.1-arm64",
"Value": "ami-0283f118a0c8175da"
},
{
"Name": "/aws/service/ami-amazon-linux-latest/al2023-ami-minimal-kernel-6.1-x86_64",
"Value": "ami-092e2a393f8606a75"
},
{
"Name": "/aws/service/ami-amazon-linux-latest/al2023-ami-minimal-kernel-default-arm64",
"Value": "ami-0283f118a0c8175da"
},
{
"Name": "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64",
"Value": "ami-03dceaabddff8067e"
},
{
"Name": "/aws/service/ami-amazon-linux-latest/al2023-ami-minimal-kernel-default-x86_64",
"Value": "ami-092e2a393f8606a75"
},
{
"Name": "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64",
"Value": "ami-0ebbfa73436e5e751"
}
]

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

ドキュメントによるとAmazonSSMManagedInstanceCoreポリシーを持ったロールをアタッチする必要があるそうです。

Session Manager に必要な基本許可を持つインスタンスプロファイルがすでにAmazon EC2インスタンスに添付されている可能性があります。AWS 管理ポリシー AmazonSSMManagedInstanceCore を含むインスタンスプロファイルが既にインスタンスにアタッチされている場合、Session Manager に必要なアクセス許可は既に付与されています。

引用:ステップ 2: Session Manager 許可を持つ IAM ロールを確認または作成する

TerraformでIAMロール、インスタンスプロファイル、EC2インスタンスを作成します。
コードは先ほどの続きで、main.tf に追記します。

main.tf
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_public_subnet.id
iam_instance_profile = aws_iam_instance_profile.ssm_instance_core.name
tags = {
Name = "aws-ssm-punlic-example"
}
associate_public_ip_address = true
}
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 plan、terraform applyコマンドを実行するとEC2インスタンスの作成できます。

接続

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

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

publicサブネットに配置した EC2インスタンスは接続ボタンが押せるようになっています! 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-00b32e54399466f12
Starting session with SessionId: xxxxxx-0b606847d50d67039
sh-5.2$

さいごに

今回はpublicサブネットに EC2インスタンスを配置しました。
しかし、実際の運用ではprivateサブネットに置くことの方が多いと思います。
次回はprivateサブネットに配置した EC2インスタンスにアクセスできるようにしてみようと思います。

使ったコードは blog-code/2023/05/aws-ssm-public に置いています。