2
0

【AWS】TerraformでAmazon Systems Manager Fleet Managerの構成(+ VPC Flow Logs)

Last updated at Posted at 2024-08-03

世間はパリオリンピック・パラリンピックで盛り上がっていますね。
国籍、人種関係なく選手、並びに関係者の皆様、くれぐれもお体には気を付けて頑張っていただきたいですね!

本記事を書くに至った経緯

現職の年下の先輩からこんな問い合わせを受けました。
(吉本興業方式で考えると、年齢関係なく少しでも入社(吉本興業の場合は所属)が早い方が「兄さん」なんで、この方は私の「兄さん」、特にAWSに詳しいので「AWS兄さん」になりますね。)
「Fleet Manager経由でRDP接続できないって保守会社の方から言われてるんですが、古賀さん何か心当たりありますか?」
別のチームの方からの問い合わせなんですが、新卒2年目なのにめっちゃ優秀なAWS兄さんです。(確か昨年の部門内の資格取得数No.1です)
2024/8/5 追記
先ほどこの記事をAWS兄さんに見てもらったときに教えてもらったのが、なんとAWS兄さん、AWS資格全冠したそうです!
凄い!
AWS兄さん
もとい、AWS全冠兄さんですね!
2024/8/5 追記終わり

この日私は別件で出社してたんですが、AWS全冠兄さんが偶然私を見かけたのでわざわざ声をかけてくれました。
私はSSMでLinux相手にSSH接続するのは頻繁にやっている(というかQiitaの記事でLinux作るときは毎回必須で入れている)ので仕組みは理解していますが、Fleet Managerはやったとこが無かったのでやってみました。
当然この問い合わせ元のAWS兄さんの環境からはFleet Manager経由でアクセスできているので、ついでにVPC Flow Logsも取ってみて、どこでコケているのか確認してみようという話になりました。(正常系、異常系のログ比較の勉強も兼ねようと思ってます。)
元々私はAzureのNSGのノリで、Security Groupのログ見てみそうかなと思ってたんですが、Security GroupのログをCloud Watchに飛ばせないんですかね?調べても出てこなかったのと、VPC Flow Logsの方が網羅的にログが見れそうなのでこちらにしました。

構成図

はい。
では経緯もご説明しましたので構成図いきましょう。
構成図.png
今回も忘れずにちゃんとSpot Instanceにしました!
偉い!
お金は大事ですからね!

NAT Gatewayは要件的に不要なんですが、Internet Gateway直接Default Gatewayにして、Spot InstanceのWindows Server 2022のEC2にElastic IP Addressを割り振るとインターネット経由で直接RDPできてしまうので、RDPの接続元をSecurity Groupや他の機能を使うのではなく、アーキテクトレベルで絞る意味でもNAT Gateway経由にしました。
この構成にすることで間違いなくSystem Manager Fleet Manager経由でなければRDP接続できなくなると考えたのでこの構成にしました、ということですね。

AzureでいうとAzure Bastionと同じような機能ですね。
あれはあれで面白いので別でこのAmazon System Manager Fleet Manager(名称が長いんで以降本記事内ではFleet Managerと記載します)と比較で記事を書こうと思います。

あとはVPC Flow Logsの格納先としてS3を準備したことくらいでしょうか?
まぁ全体通して大したことはしていません。

Terraform

では早速Terraformいってみましょう。

変数設定

variables.tf
# ---------------------------
# Variables - 変数設定
# ---------------------------

# region
# ap-northeast-1 東京リージョン
# ap-south-1 ムンバイリージョン
variable "region" {
  type = string
  default = "ap-south-1"
}

# 環境種別(本番:prd,ステージング:stg,開発:dev)
variable "env_type" {
  type = string
  default = "dev"
}

# システム名
variable "sys_name" {
  type = string
  default = "fleet-test01"
}

# availability_zone
variable "availability_zone" {
  type = object({
    a = string
    b = string #ムンバイリージョンで利用
    c = string
  })
  default = {
    a = "ap-south-1a" # ムンバイ(インド)のアベイラビリティゾーン
    b = "ap-south-1b" # ムンバイ(インド)のアベイラビリティゾーン
    c = "ap-south-1c" # ムンバイ(インド)のアベイラビリティゾーン
  }
}

# ------------------------------
# VPC 関連
# ------------------------------

# vpc address
variable "vpc_address_pre" {
  type = string
  default = "10.0."
}

variable "vpc_address_suf" {
  type = string
  default = "0.0/23"
}

# private subnet suffix address01
variable "private_sn_address_suf01" {
  type = string
  default = "1.0/24"
}

# public subnet suffix address01
variable "public_sn_address_suf01" {
  type = string
  default = "0.0/24"
}

# VPC Endpoint
variable "vpc_endpoints" {
  type    = list(any)
  default = ["ssm", "ssmmessages", "ec2messages"]
}

# ------------------------------
# EC2 作成
# ------------------------------

# Windows EC2のインスタンスタイプ
variable "ec2_instance_type_win" {
    type = string
    default = "m4.large"
}


# ------------------------------
# S3 作成
# ------------------------------

variable "s3_name" {
  type = string
  default = "log"
}

variable "bucket_name" {
  type = string
  default = "vpc_flow_log"
}

今回はS3作るので、S3の名前が追加されています。

main

main.tf
# ---------------------------
# main
# ---------------------------

terraform {
  required_version = ">= 1.4" # Terraformのバージョンを1.4以上に指定
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "= 5.22.0" # AWSプロバイダのバージョンを5.22.0に固定
    }
  }
}

# プロバイダー設定
provider "aws" {
  region = var.region
}


はい。
何の変哲もないmain.tfですね。

vpc

vpc.tf
# ---------------------------
# VPC 構築
# ---------------------------

# VPC
resource "aws_vpc" "vpc" {
  cidr_block           = "${var.vpc_address_pre}${var.vpc_address_suf}"
  enable_dns_hostnames = true
  enable_dns_support   = true
  tags = {
    Name = "${var.env_type}-${var.sys_name}-vpc"
  }
}

# ---------------------------
# Public Subnet 構築
# ---------------------------

resource "aws_subnet" "sn_public_1a" {
  vpc_id            = aws_vpc.vpc.id
  cidr_block        = "${var.vpc_address_pre}${var.public_sn_address_suf01}"
  availability_zone = var.availability_zone.a
  tags = {
    Name = "${var.env_type}-${var.sys_name}-sn-public-1a"
  }
}

# ---------------------------
# Private Subnet 構築
# ---------------------------

resource "aws_subnet" "sn_private_1b" {
  vpc_id            = aws_vpc.vpc.id
  cidr_block        = "${var.vpc_address_pre}${var.private_sn_address_suf01}"
  availability_zone = var.availability_zone.b
  tags = {
    Name = "${var.env_type}-${var.sys_name}-sn-private-1b"
  }
}

# ---------------------------
# Internet Gateway 作成
# ---------------------------
resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.vpc.id
  tags = {
    Name = "${var.env_type}-${var.sys_name}-igw"
  }
}

いつもNAT Gatewayやらルーティングテーブルやらも一緒にコード化していましたが、今回はこれらのリソースは別のファイルで管理するようにしました。
なのでここから少しネットワーク関連のコードが続きます。

NAT Gateway

nat_gateway.tf
# -------------------------------
# NAT Gateway作成
# -------------------------------

# EIP
resource "aws_eip" "eip_nat_1a" {
  tags = {
    Name = "${var.env_type}-${var.sys_name}-eip-nat1a"
  }
}

# Nat Gateway
resource "aws_nat_gateway" "nat_gateway_1a" {
  allocation_id = aws_eip.eip_nat_1a.id
  subnet_id     = aws_subnet.sn_public_1a.id
  tags = {
    Name = "${var.env_type}-${var.sys_name}-nat-1a"
  }
}

NAT GatewayとNAT Gatewayに関連付けるElastic IPアドレスの定義ですね。

Security Group

security_group.tf
# ---------------------------
# Security Group
# ---------------------------

# ---------------------------
# EC2用 Security Group作成
# ---------------------------

# Windows Server 用SG
resource "aws_security_group" "sg_ec2_windows01" {
  name   = "${var.env_type}-${var.sys_name}-sg-windows01"
  vpc_id = aws_vpc.vpc.id
  tags   = {
    Name = "${var.env_type}-${var.sys_name}-sg-windows01"
  }

  # インバウンドルール
  # from SSM
  ingress {
    description     = "AllowHttpsInBoundFromVPC for SSM"
    from_port       = 443
    to_port         = 443
    protocol        = "tcp"
    cidr_blocks = ["${var.vpc_address_pre}${var.vpc_address_suf}"]
  }

  # from VPC ICMPv4
  ingress {
    description     = "AllowICMPv4InBoundFromVPC"
    from_port       = -1
    to_port         = -1
    protocol        = "icmp"
    cidr_blocks = ["${var.vpc_address_pre}${var.vpc_address_suf}"]
  }

  # アウトバウンドルール
  egress {
    description = "AllowAnyOutBound"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# ---------------------------
# SSM用 Vpc endpoint Security Group作成
# ---------------------------

resource "aws_security_group" "sg_ssm" {
  name   = "${var.env_type}-${var.sys_name}-sg-ssm"
  vpc_id = aws_vpc.vpc.id
  tags   = {
    Name = "${var.env_type}-${var.sys_name}-sg-ssm"
  }

  # インバウンドルール
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["${var.vpc_address_pre}${var.vpc_address_suf}"]
  }

  # アウトバウンドルール
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_security_group" "sg_ssmmessages" {
  name   = "${var.env_type}-${var.sys_name}-sg-ssmmessages"
  vpc_id = aws_vpc.vpc.id
  tags   = {
    Name = "${var.env_type}-${var.sys_name}-sg-ssmmessages"
  }

  # インバウンドルール
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["${var.vpc_address_pre}${var.vpc_address_suf}"]
  }

  # アウトバウンドルール
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_security_group" "sg_ec2messages" {
  name   = "${var.env_type}-${var.sys_name}-sg-ec2messages"
  vpc_id = aws_vpc.vpc.id
  tags   = {
    Name = "${var.env_type}-${var.sys_name}-sg-ec2messages"
  }

  # インバウンドルール
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["${var.vpc_address_pre}${var.vpc_address_suf}"]
  }

  # アウトバウンドルール
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

これも大したことないですね。
Windows EC2用のSGとSSM用のSG、SSM用のSGはAWSが推奨している構成の最小構成です。

VPC Endpoint

vpc_endpoint.tf
# ---------------------------
# VPC Endpoint
# ---------------------------

# SSM用 VPC endpointの作成
resource "aws_vpc_endpoint" "interface" {
  for_each            = toset(var.vpc_endpoints)
  vpc_id              = aws_vpc.vpc.id
  service_name        = "com.amazonaws.${var.region}.${each.value}"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = [aws_subnet.sn_private_1b.id]
  private_dns_enabled = true
  security_group_ids  = [aws_security_group.sg_ssm.id]
  tags                = { "Name" = "${var.env_type}-${var.sys_name}-vpc-endpoint-${each.value}" }
}

はい。
これもAWS推奨の最小限の設定です。
というかこれ以上でもこれ以下でも設定は無いんじゃないのかな?

S3

s3.tf
# ---------------------------
# S3 作成
# ---------------------------

# VPC Flow Logs 格納用S3
resource "aws_s3_bucket" "s3" {
  bucket = "${var.env_type}-${var.sys_name}-${var.s3_name}-「お使いのAWSアカウントID」"
}

VPC Flow Logs格納用のS3です。
S3の名前を一意にするためS3の名称にAWSのアカウントIDを含めています。
Terraformにはランダム関数があるので、これを使って命名しても良いですね。

VPC Flow Logs

vpc_flow_logs.tf
# ---------------------------
# VPC Flow Logs 作成
# ---------------------------

resource "aws_flow_log" "flow-log" {
  log_destination      = aws_s3_bucket.s3.arn
  log_destination_type = "s3"
  traffic_type         = "ALL"
  vpc_id               = aws_vpc.vpc.id
  destination_options {
    per_hour_partition  = false
  }
}

はい。
どのVPCでFlow Logsを取得し、どのS3バケットに出力するか、どのようなログをどのような期間に区切って出力するか?という設定です。
出力したいログをバイネームで指定して出力させることも可能です。
この点は皆様のお好みで環境に合わせてください。

Route Table

route_table.tf
# ---------------------------
# Route table 作成
# ---------------------------

# Public Subnet 用Route Table 作成
resource "aws_route_table" "rt_sn_public_1a" {
  vpc_id = aws_vpc.vpc.id
  # Default GatewayをInternet Gatewayに向ける
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.igw.id
  }
  tags = {
    Name = "${var.env_type}-${var.sys_name}-rt-sn-public-1a"
  }
}

# Private Subnet 用Route Table 作成
resource "aws_route_table" "rt_sn_private_1b" {
  vpc_id = aws_vpc.vpc.id
  # Default GatewayをNAT Gatewayに向ける
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_nat_gateway.nat_gateway_1a.id
  }
  tags = {
    Name = "${var.env_type}-${var.sys_name}-rt-sn-private-1b"
  }
}

# Public Subnet とDefault Route table の関連付け
resource "aws_route_table_association" "associate_rt_sn_public_1a_sn_public_1a" {
  subnet_id      = aws_subnet.sn_public_1a.id
  route_table_id = aws_route_table.rt_sn_public_1a.id
}

# Private Subnet とDefault Route table の関連付け
resource "aws_route_table_association" "associate_rt_sn_private_1b_sn_private_1b" {
  subnet_id      = aws_subnet.sn_private_1b.id
  route_table_id = aws_route_table.rt_sn_private_1b.id
}

ルートテーブル作成です。
Public Subnet用のルートテーブルとPrivate Subnet用のルートテーブルを分けて定義しています。
まぁ普通ですよね。

EC2

ec2.tf
# -------------------------------
# EC2作成
# -------------------------------

# -------------------------------
# Windows Server EC2を作成
# -------------------------------

# -------------------------------
# Windows Server 用のKey pair作成
# -------------------------------
variable "key_name" {
  default = "kp-ec2-01"
}
variable "key_path" {
  # - Windowsの場合はフォルダを"\\"で区切る(エスケープする必要がある)実行中のtfファイルと同じ場所にキーファイルを出力する
  default = ".\\"
}
resource "tls_private_key" "kp-windows-ec2-01" {
  algorithm = "RSA"
  rsa_bits  = 2048
}

# クライアントPCにKey pair(秘密鍵と公開鍵)を作成
# - Windowsの場合はフォルダを"\\"で区切る(エスケープする必要がある)
# - [terraform apply] 実行後はクライアントPCの公開鍵は自動削除される
locals {
  public_key_file  = "${var.key_path}\\${var.key_name}.id_rsa.pub"
  private_key_file = "${var.key_path}\\${var.key_name}.id_rsa"
}

resource "local_file" "kp_windows_ec2_01_pem" {
  filename = "${local.private_key_file}"
  content  = "${tls_private_key.kp-windows-ec2-01.private_key_pem}"
}

# 上記で作成した公開鍵をAWSのKey pairにインポート
resource "aws_key_pair" "kp_windows_ec2_01" {
  key_name   = "${var.key_name}"
  public_key = "${tls_private_key.kp-windows-ec2-01.public_key_openssh}"
}

#20240523時点のAMI
#Microsoft Windows Server 2022 Base:ami-0f346136f3b372267
#Microsoft Windows Server 2019 Base:ami-01bd28d73d0053a15
#Microsoft Windows Server 2016 Base:ami-063d3d00f8a97a6d1

 resource "aws_spot_instance_request" "ec2_windows01" { #spot_priceを設定する。ムンバイリージョンのAvailability Zone CではSpot Instanceは使えない。
   spot_price                  = "0.132"
   ami                         = "ami-0f346136f3b372267" #Microsoft Windows Server 2022 Base
   instance_type               = var.ec2_instance_type_win
   availability_zone           = var.availability_zone.b
   vpc_security_group_ids      = [aws_security_group.sg_ec2_windows01.id]
   subnet_id                   = aws_subnet.sn_private_1b.id
   associate_public_ip_address = "false"
   key_name                    = "${var.key_name}"
   iam_instance_profile        = aws_iam_instance_profile.instance_prof.name
   root_block_device {
     volume_size = 50   # GB
     volume_type = "gp3" # 汎用SSD
     encrypted   = false
     tags        = {
       Snapshot = "false"      
     }
   }
   tags = {
     Name = "${var.env_type}-${var.sys_name}-ec2-windows01"
   }
 }

はい。
EC2です。
もう何にも言うことは無いですね。

Output

output.tf
# ---------------------------
# Output
# ---------------------------

# 作成したS3の名前
output "s3_name" {
   value = aws_s3_bucket.s3.bucket_domain_name
 }

# NAT GatewayのパブリックIPアドレス
output "nat_gateway_global_ips" {
   value = aws_nat_gateway.nat_gateway_1a.*.public_ip
 }

今回はS3の名前とNAT GatewayのElastic IP Addressを出力しています。

Terraformの実行

はい。
ではちゃちゃっと実行しましょう。
いつものようにVS Codeのターミナルでterraform appylyからのyesです。
image.png

今回の構成だと2~3分くらいですかね。
SSMのVPC Endpointの作成とNAT Gatewayの構築に時間がかかります。

image.png

はい。
綺麗に実行が完了していますね。
赤枠内の通りS3の名前とNAT GatewayのElastic IP Addressがちゃんと出力されていますね。

構成確認

構成確認しましょう。

S3

まずはS3の名前から
image.png
はい。
命名通りですね。

VPC Flow Logs

AWS Consoleの流れでそのままVPC Flow Logsも見ていきましょう。
image.png
はい。
ちゃんとありますね。

NAT GatewayのElastic IP Address

はい。
続いてNAT GatewayのElastic IP Addressです。
image.png
はい。
こちらも合致していますね。

動作確認

では設計通りWindows EC2にFleet Manager経由でRDPログインできるか、NAT Gateway経由でWindows EC2がインターネットに抜けているのか確認します。

Fleet manager動作確認

Fleet Managerへのアクセスは構成図の通り、AWS Console経由です。
まずはAWS Console内のEC2ですね。
image.png
はい。
EC2無事できていますね。
該当のEC2を選択し、
image.png
接続をクリックします。
image.png
RDPクライアントをクリックします。
image.png
Fleet Managerを使用して接続するを選択しFleet Manager Remote Desktopをクリックします。
image.png
キーペアを選択し、EC2に関連づいているキーペア名を確認します。
この図だとkp-ec2-01です。
キーペアファイルはec2.tfファイルはec2.tfファイルと同じ場所に出力する設定になっています。
ローカルマシンを参照して、キーペアファイルを選択します。を選択し、参照をクリックします。
image.png
キーファイルが指定されていることを確認し、Connectをクリックします。
image.png
RDPセッションが開始されます。
image.png
しばらく待つと
image.png
はい。
Windows Server 2022ですね。

Nat Gatewayとルートテーブル確認

image.png
はい。
NAT GatewayのグローバルIPアドレスと合致しますね。
これでWindows EC2がNAT Gatewayを経由してインターネットに出ていることが確実なので、ルートテーブルも設計通り動作していることが確認できました。

VPC Flow Logs確認

image.png
該当の時間帯のログを出力しましたが、RDP、TCP3389のログが無いんですよね・・・
ここから先はAWSのサポートに聞かないとわからないですかね。

本日はここまで。

2
0
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0