💻

【Terraform】EC2にSession ManagerでSSHできるようにする

2025/01/12に公開

Session Manager とは

  • 鍵不要、22 ポートの穴あけ不要で EC2 へアクセスできる AWS Systems Manager の機能の 1 つ。
  • アクセスするには AWS コンソールや AWS CLI を使用します
  • 操作ログは CloudWatch Logs や S3 で保管することができます。
    • アクセスした IAM ユーザの履歴を CloudTrail に 残すこともできます。
  • プライベートサブネットの EC2 にもアクセスできるので踏み台サーバがいりません。
  • IAM ロールで IAM ユーザのアクセス制御を行えます

Session Manager に関する公式ドキュメントは ↓ になります
https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/session-manager.html

イメージ

Session Manager はどのようにして EC2 へアクセスするか気になりませんか?
BlackBeltの 60P に答えがありました。

  1. AWS コンソールや CLI で Session Manager へアクセス
  2. SSM Agent 経由で EC2 にアクセス ⇒SSM Agent が Session Manager を見に行くため、EC2 側に IAM ロールが必要

操作ログも EC2 に S3 や CloudWatch Logs へのアクセス権限(IAM ロール)がないとダメです

ゴール

  1. パブリックサブネットにある EC2 に Session Manager を使用してアクセスする
  2. 操作ログが S3 に出力されたことを確認する

実装方針

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/session-manager-getting-started.html

↑ を参考に今回は以下のステップで実装していきます

  1. EC2 インスタンスに SSM Agent をインストールする
  2. 操作ログ用の S3 バケットを作成する
  3. Session Manager および S3 にアクセスする IAM ポリシーを作成する
  4. IAM ロールを作成する
  5. インスタンスプロファイルを作成し、EC2 にアタッチする
  6. 操作ログを S3 に保存するように Session Manager の設定をする

Terraform

EC2 インスタンスに SSM Agent をインストールする

EC2 に SSM Agent がインストールされていないと Session Manager が使えません。

ただ、特定の AMI を使用していればすでにインストールされており、作業はありません。
今回使用しているAmazon Linux 2023 はその対象でありすでにインストールされています

操作ログ用の S3 バケットを作成する

操作ログを保存しておく S3 バケットを作成します。

パブリックアクセスもブロックしておきます

バケット名は世界で一意のため、各々変えてください

########################
# S3
########################

# ログ用S3バケット
resource "aws_s3_bucket" "log_bucket" {
  # バケット名は世界で一意のため、各々変えてください
  bucket = "example-log-bucket"
}

# ブロックパブリックアクセス
resource "aws_s3_bucket_public_access_block" "log_bucket" {
  bucket                  = aws_s3_bucket.log_bucket.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

Session Manager および S3 にアクセスする IAM ポリシーを作成する

公式ドキュメントによると、

Session Manager を使うにはAmazonSSMManagedInstanceCoreという IAM ポリシーが必要。

このポリシーは AWS が管理しており新規に作成する必要はありません。

操作ログを S3 に保存するために必要な権限はこちらを参考に、

  • s3:PutObject
  • s3:GetEncryptionConfiguration

今回、S3 の暗号化にはデフォルト設定であるSSE-S3を使用しています。 なので、KMS 系の権限は不要です

なので、IAM ポリシーを作成する流れとしては

  1. AmazonSSMManagedInstanceCore を参照する
  2. 1.に S3 系の権限を追加する
  3. 2.をもとに新しい IAM ポリシーを作成する
# AmazonSSMManagedInstanceCoreを参照
data "aws_iam_policy" "ssm_core" {
  name = "AmazonSSMManagedInstanceCore"
}

# 操作ログを保存するためにS3への操作権限を追加
data "aws_iam_policy_document" "ec2_ssm_policy_doc" {
  source_policy_documents = [data.aws_iam_policy.ssm_core.policy]

  statement {
    effect = "Allow"
    actions = [
      "s3:PutObject",
    ]
    resources = [
      "${aws_s3_bucket.log_bucket.arn}",
      "${aws_s3_bucket.log_bucket.arn}/*"
    ]
  }

  statement {
    effect = "Allow"
    actions = [
      "s3:GetEncryptionConfiguration"
    ]
    resources = [
      "*"
    ]
  }
}

# SSMを使用するためのIAMポリシーの作成
resource "aws_iam_policy" "ec2_ssm_policy" {
  name   = "ec2_ssm_policy"
  policy = data.aws_iam_policy_document.ec2_ssm_policy_doc.json
}

IAM ロールを作成する

ここでやっていることは

  1. aws_iam_role で IAM ロールを作成。
  2. EC2 が IAM ポリシーを使えるように信頼ポリシーを定義し、IAM ロールに紐づけ。
  3. IAM ポリシーを IAM ロールへ紐づけ。
# IAMロール
resource "aws_iam_role" "ec2_role" {
  name               = "ec2_role"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json
}

# 信頼ポリシー
data "aws_iam_policy_document" "assume_role" {
  statement {
    actions = ["sts:AssumeRole"]

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

# IAMポリシーをIAMロールに紐づける
resource "aws_iam_role_policy_attachment" "ssm_core" {
  role       = aws_iam_role.ec2_role.name
  policy_arn = aws_iam_policy.ec2_ssm_policy.arn
}

インスタンスプロファイルを作成し、EC2 にアタッチする

先ほど作成した IAM ロールをもとにインスタンスプロファイルを作成します

# インスタンスプロファイル
resource "aws_iam_instance_profile" "main" {
  name = "instance_profile"
  role = aws_iam_role.ec2_role.name
}

これを EC2 にアタッチします。すでにある aws_instance ブロックに 1 行追加します

resource "aws_instance" "main" {

  ---略-------------------------

  iam_instance_profile = aws_iam_instance_profile.main.name
}

操作ログを S3 に保存するように Session Manager の設定をする

Session Manager の設定は SSM のドキュメントを使用します。

具体的には、↓ のリポジトリを参考にコードを書きました

https://github.com/gazoakley/terraform-aws-session-manager-settings

バケット名は各々変えてください

プロパティ 説明
name ドキュメント名。
document_type ドキュメントのタイプ。 Session Managerで使用するため、「Session」を指定。
document_format ドキュメントのフォーマット。JSONかYAMLか選べる
content ドキュメントの中身。 フォーマットをJSONにしたので、jsonencodeでJsonへ変換。
schemaVersion スキーマのバージョン。 2023/11時点で1.0のみサポートされている。
description 説明。わかりやすいように入れておけばOK
sessionType セッションのタイプ。 参考にしたコードから「Standard_Stream」のまま。
inputs セッションに対する設定。
s3BucketName ログを保存するS3バケット名。
s3KeyPrefix S3のどの階層に保存するか。
s3EncryptionEnabled 操作ログを暗号化するか。 trueにした場合、S3は暗号化されている必要がある。 ただ、デフォルトでS3は暗号化されているため設定は必要ない。
# ドキュメント
resource "aws_ssm_document" "session_manager_prefs" {
  name            = "Test-SSM-SessionManagerRunShell"
  document_type   = "Session"
  document_format = "JSON"

  content = jsonencode({
    schemaVersion = "1.0"
    description   = "Document to hold regional settings for Session Manager"
    sessionType   = "Standard_Stream"
    inputs = {
      # ログを保存するS3のバケット名に書き換えてください
      s3BucketName        = "example-log-bucket"
      s3KeyPrefix         = "session_manager/"
      s3EncryptionEnabled = true
    }
  })
}

完成したコード

コード
########################
# S3
########################

# ログ用S3バケット
resource "aws_s3_bucket" "log_bucket" {
  # バケット名は世界で一意のため、各々変えてください
  bucket = "example-log-bucket"
}

# ブロックパブリックアクセス
resource "aws_s3_bucket_public_access_block" "log_bucket" {
  bucket                  = aws_s3_bucket.log_bucket.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}


########################
# IAM
########################
# AmazonSSMManagedInstanceCoreを参照
data "aws_iam_policy" "ssm_core" {
  name = "AmazonSSMManagedInstanceCore"
}

# 操作ログを保存するためにS3への操作権限を追加
data "aws_iam_policy_document" "ec2_ssm_policy_doc" {
  source_policy_documents = [data.aws_iam_policy.ssm_core.policy]

  statement {
    effect = "Allow"
    actions = [
      "s3:PutObject",
    ]
    resources = [
      "${aws_s3_bucket.log_bucket.arn}",
      "${aws_s3_bucket.log_bucket.arn}/*"
    ]
  }

  statement {
    effect = "Allow"
    actions = [
      "s3:GetEncryptionConfiguration"
    ]
    resources = [
      "*"
    ]
  }
}

# SSMを使用するためのIAMポリシーの作成
resource "aws_iam_policy" "ec2_ssm_policy" {
  name   = "ec2_ssm_policy"
  policy = data.aws_iam_policy_document.ec2_ssm_policy_doc.json
}

# IAMロール
resource "aws_iam_role" "ec2_role" {
  name               = "ec2_role"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json
}

# 信頼ポリシー
data "aws_iam_policy_document" "assume_role" {
  statement {
    actions = ["sts:AssumeRole"]

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

# IAMポリシーをIAMロールに紐づける
resource "aws_iam_role_policy_attachment" "ssm_core" {
  role       = aws_iam_role.ec2_role.name
  policy_arn = aws_iam_policy.ec2_ssm_policy.arn
}

# インスタンスプロファイル
resource "aws_iam_instance_profile" "main" {
  name = "instance_profile"
  role = aws_iam_role.ec2_role.name
}

########################
# EC2
########################
resource "aws_instance" "main" {

  ---略-------------------------

  iam_instance_profile = aws_iam_instance_profile.main.name
}


########################
# SSM
########################
# ドキュメント
resource "aws_ssm_document" "session_manager_prefs" {
  name            = "Test-SSM-SessionManagerRunShell"
  document_type   = "Session"
  document_format = "JSON"

  content = jsonencode({
    schemaVersion = "1.0"
    description   = "Document to hold regional settings for Session Manager"
    sessionType   = "Standard_Stream"
    inputs = {
      # ログを保存するS3のバケット名に書き換えてください
      s3BucketName        = "example-log-bucket"
      s3KeyPrefix         = "session_manager/"
      s3EncryptionEnabled = true
    }
  })
}

動作確認

コードが完成したら、terraform apply して動作確認をしていきます

Session Manager を使って EC2 に SSH 接続する

AWS コンソールを使用して SSH 接続を試してみます。
IAM ロールの反映に時間がかかる場合があるので、うまくいかないときはすこし時間をおいて試してみましょう!

Systems Manager  > Session Managerにいき、「セッションの開始」をクリック

接続したいインスタンスを選択して、「Next」をクリック

Terraform で作成した SSM ドキュメントを選択し、「Next」をクリック

Start session」をクリックして、EC2 にアクセス

アクセスできました!

右上の「終了」をクリックすると、ダイアログが表示されます。

もう一度「終了」をクリックすると、セッションを閉じることができます

操作ログの確認

操作ログの保存先の S3 バケットを見てみると、session_manager というフォルダがあります

※フォルダ名は SSM ドキュメントの s3KeyPrefix で設定した文字列

この中に log ファイルが保存されています!

SSM ドキュメントも確認してみる

一応、SSM ドキュメントも確認しておきます

Systems Manager > ドキュメント > 自己所有で確認できます

最後に....ハマりポイント

S3 にログが保存されず、かなりハマりました。

原因はIAM ロールに適切な権限を設定していなかったから。

エラーメッセージが出るわけではないので原因の特定に時間がかかりました。

S3 に操作ログが保存されないよーという方は
インスタンスプロファイルの IAM ロールを確認してみてください

参考

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/session-manager.html

https://pages.awscloud.com/rs/112-TZM-766//images/20200212_AWSBlackBelt_SystemsManager_0214.pdf

https://dev.classmethod.jp/articles/ec2-access-with-session-manager/

https://qiita.com/kakita-yzrh/items/980f3209d587e7e349a6

https://zenn.dev/yutaro1985/articles/f55d41fd88f83e

Discussion