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

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

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

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

# 構成図
はい。
では経緯もご説明しましたので構成図いきましょう。
![構成図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/4caff0fe-b25a-045f-68eb-705e6679a1c5.png)
今回も忘れずにちゃんと<font color="Red">**Spot Instance**にしました！</font>
**偉い！
<font color="Red">お金は大事ですからね！**</font>

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

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

あとは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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/e16c010e-fbea-a832-44f1-c7677f18650d.png)

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/6f71a26e-f061-948c-ce61-0a99b7fba176.png)

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

# 構成確認
構成確認しましょう。
## S3
まずはS3の名前から
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/f434bdb8-e2d3-b1e5-98c8-eb27e53e133d.png)
はい。
命名通りですね。

## VPC Flow Logs
AWS Consoleの流れでそのままVPC Flow Logsも見ていきましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/e59b3e09-6cce-2b8c-d3d8-3ea283c6f2da.png)
はい。
ちゃんとありますね。
## NAT GatewayのElastic IP Address
はい。
続いてNAT GatewayのElastic IP Addressです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/4ad14dd3-70e5-288c-0004-989896bf8cbc.png)
はい。
こちらも合致していますね。

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

# Fleet manager動作確認
Fleet Managerへのアクセスは構成図の通り、AWS Console経由です。
まずはAWS Console内のEC2ですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/c05ba5c6-0f8e-911f-6e81-a9634af8df57.png)
はい。
EC2無事できていますね。
該当のEC2を選択し、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/c186af4f-4493-d71b-32be-05c694803125.png)
```接続```をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/69388e6b-fd0e-3fe4-62a2-198f730cae6f.png)
```RDPクライアント```をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/73602be6-7e4b-bff1-0726-3cbed74b7995.png)
```Fleet Managerを使用して接続する```を選択し```Fleet Manager Remote Desktop```をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/5c8d0d10-727b-f62f-ea6c-4594556ab874.png)
```キーペア```を選択し、EC2に関連づいているキーペア名を確認します。
この図だと```kp-ec2-01```です。
キーペアファイルはec2.tfファイルはec2.tfファイルと同じ場所に出力する設定になっています。
```ローカルマシンを参照して、キーペアファイルを選択します。```を選択し、```参照```をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/6b80dc0a-7bf6-708c-e057-c83480a308b0.png)
キーファイルが指定されていることを確認し、```Connect```をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/057d65f1-e31b-f17c-42ca-ec1e02cf86ce.png)
RDPセッションが開始されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/5be74d72-2bfb-3262-4265-c5488db55b6d.png)
しばらく待つと
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/db3e62ff-0fe6-1ade-6e59-8d6daf80489e.png)
はい。
Windows Server 2022ですね。

## Nat Gatewayとルートテーブル確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/6f62e2fe-44c1-7f32-3e8b-22bf34e94c56.png)
はい。
NAT GatewayのグローバルIPアドレスと合致しますね。
これでWindows EC2がNAT Gatewayを経由してインターネットに出ていることが確実なので、ルートテーブルも設計通り動作していることが確認できました。

## VPC Flow Logs確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643392/188e57bd-fe52-5c59-2bae-79f3af2f661d.png)
該当の時間帯のログを出力しましたが、RDP、TCP3389のログが無いんですよね・・・
ここから先はAWSのサポートに聞かないとわからないですかね。

本日はここまで。