【Terraform】EC2にSession ManagerでSSHできるようにする
Session Manager とは
- 鍵不要、22 ポートの穴あけ不要で EC2 へアクセスできる AWS Systems Manager の機能の 1 つ。
- アクセスするには AWS コンソールや AWS CLI を使用します
- 操作ログは CloudWatch Logs や S3 で保管することができます。
- アクセスした IAM ユーザの履歴を CloudTrail に 残すこともできます。
- プライベートサブネットの EC2 にもアクセスできるので踏み台サーバがいりません。
- IAM ロールで IAM ユーザのアクセス制御を行えます
Session Manager に関する公式ドキュメントは ↓ になります
イメージ
Session Manager はどのようにして EC2 へアクセスするか気になりませんか?
BlackBeltの 60P に答えがありました。
- AWS コンソールや CLI で Session Manager へアクセス
- SSM Agent 経由で EC2 にアクセス ⇒SSM Agent が Session Manager を見に行くため、EC2 側に IAM ロールが必要
操作ログも EC2 に S3 や CloudWatch Logs へのアクセス権限(IAM ロール)がないとダメです
ゴール
- パブリックサブネットにある EC2 に Session Manager を使用してアクセスする
- 操作ログが S3 に出力されたことを確認する
実装方針
↑ を参考に今回は以下のステップで実装していきます
- EC2 インスタンスに SSM Agent をインストールする
- 操作ログ用の S3 バケットを作成する
- Session Manager および S3 にアクセスする IAM ポリシーを作成する
- IAM ロールを作成する
- インスタンスプロファイルを作成し、EC2 にアタッチする
- 操作ログを 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 ポリシーを作成する流れとしては
- AmazonSSMManagedInstanceCore を参照する
- 1.に S3 系の権限を追加する
- 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 ロールを作成する
ここでやっていることは
- aws_iam_role で IAM ロールを作成。
- EC2 が IAM ポリシーを使えるように信頼ポリシーを定義し、IAM ロールに紐づけ。
- 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 のドキュメントを使用します。
具体的には、↓ のリポジトリを参考にコードを書きました
バケット名は各々変えてください
プロパティ | 説明 |
---|---|
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://pages.awscloud.com/rs/112-TZM-766//images/20200212_AWSBlackBelt_SystemsManager_0214.pdf
Discussion