
KiroのAI-DLCで、あえて脆弱なEC2環境のCloudFormationテンプレートを生成・検証してみた
はじめに
AI-DLCは、Kiro CLIで利用できる仕様駆動の開発フローです。要件定義→コード生成→レビューの各ステージを、人間の承認を挟みながら進めます。
今回はAI-DLCを使って、あえて脆弱な設定を持つEC2環境をCloudFormationで生成しました。脆弱版と修正版のリソース論理ID、cfn-guardルール、E2E確認スクリプトの検証対象をそろえる必要がある題材です。AI-DLCのsupervisedモードで要件定義書を起点に各成果物を作成し、デプロイ検証で見つかった差分をrequirements.mdへ書き戻すところまで確認しています。
検証範囲は、Security Hub Findingsそのものの検出・解消確認ではなく、cfn-guardによる静的検証とAWS CLIによるE2E確認です。
検証環境
| 項目 | 内容 |
|---|---|
| Kiro CLI | 2.7.0 / EC2上(t4g.small, AL2023 ARM64) |
| モデル | claude-sonnet-4.6 |
| AI-DLC | v2ブランチ(2026-06-14入手) |
| 題材にしたコントロール | EC2.8, EC2.3, EC2.18, EC2.19, IAM.1 |
| 対象リソース | EC2, EBS, SecurityGroup, IAM Role |
Kiro CLIはこのテンプレートでEC2上にデプロイしました。CloudFormationのデプロイに必要な権限をIAMロールで与えています。Kiro CLIと必要な認証情報が利用できる環境であれば、AI-DLCの仕様開発フロー自体はローカルからでも実行できます。
AI-DLCワークフロー実行
AI-DLCスキルをプロジェクトに配置し、orchestrationスキル(/skill aidlc-orchestration)を起動してsupervisedモードで各ステージを逐次実行しました。
# AI-DLC v2をクローン
git clone -b v2 https://github.com/awslabs/aidlc-workflows.git
# プロジェクトディレクトリにスキルをシンボリックリンク
mkdir -p weak-ec2-securityhub-lab/.kiro/skills
for dir in aidlc-workflows/kiro/src/skills/*/; do
ln -s "$(realpath "$dir")" "weak-ec2-securityhub-lab/.kiro/skills/$(basename "$dir")"
done
以降の記事構成では、Kiroが提案した6ステージを4つのStepに整理して説明します。
Step 1: Kickoff — ステージ構成の合意
スキルを呼び出し、以下のプロンプトを投入しました。

/skill aidlc-orchestration
SecurityHubで検出される脆弱性を意図的に持たせたEC2環境をCloudFormationで構築したい。
目的はSecurityHub指摘の検出→IaC修正→指摘解消の学習サイクル体験。
対象脆弱性: EC2.8, EC2.3, EC2.18, EC2.19, IAM.1(計5件)
対象リソース: EC2, EBS, SecurityGroup, IAMロール
supervisedモードで各ステージのclarificationと成果物を毎回提示して。
次のステージに進む前に承認を得て。
要件定義書はファイルとして生成して。
Kiroはclarification質問として4件を提示し、同時にステージ構成を提案しました。
| # | 質問内容 |
|---|---|
| Q1 | デプロイ先リージョン? SecurityHub有効化済みか? |
| Q2 | 各コントロールの違反内容の確認(表形式で提案) |
| Q3 | 学習後に削除想定か? インスタンスタイプの希望は? |
| Q4 | CFnテンプレートは単一ファイルか分割か? 要件定義書のフォーマットは? |
提案されたステージ構成:
- clarification(質問→回答)
- 要件定義書の作成(ファイル生成)
- CFnテンプレート作成(weak/fixed)
- cfn-guardルール作成
- E2Eテストスクリプト作成
- デプロイ
「要件定義書はファイルとして生成して」と指定したことで、要件定義書が独立ファイルとして出力される構成になりました。後段のコード生成に方針を引き継ぐ土台になります。
Q2で提示された違反内容の案:
| コントロール | 違反内容(案) |
|---|---|
| EC2.8 | IMDSv2 無効(HttpTokens=optional) |
| EC2.3 | EBSボリューム 暗号化なし |
| EC2.18 | セキュリティグループ 0.0.0.0/0 インバウンド許可(SSH:22) |
| EC2.19 | セキュリティグループ 0.0.0.0/0 インバウンド許可(RDP:3389) |
| IAM.1 | IAMロールにフルアクセスポリシー付与 |
Step 2: Requirements Analysis — 仕様を固める
clarification回答
Q1. ap-northeast-1。SecurityHub有効化済み。
Q2. 全てOK。ただしIAM.1はManagedPolicy付与ではなくインラインポリシーで実装して(iam:CreatePolicy権限がないため)。Action:"*", Resource:"*" のインラインポリシー。
Q3. 学習後に削除する。t4g.nano(ARM/Graviton)で。SSM Session Managerで接続、SSH鍵なし。
Q4. 単一ファイル。要件定義書はMarkdownでOK。
ポイントは、IAM.1の実装制約(インラインポリシーで実装)を明示したことです。実行環境にiam:CreatePolicyがないため、この制約を伝えておくことで後段のデプロイ失敗を防ぎやすくなります。
requirements.md 生成と追記
Kiroが生成したrequirements.mdには修正版テンプレート・cfn-guard・E2Eテスト方式の記述が不足していたため、以下を追記依頼しました。
要件定義書に以下を追記して:
1. 修正版テンプレートも成果物に含める。脆弱版と同一論理IDで脆弱設定に関わるプロパティのみ変更または削除。同一スタック名で上書きデプロイする構成
2. cfn-guardルールで静的検証できること(期待結果: weak→全ルールFAIL、fixed→PASS)
3. デプロイ後にAWS CLIで実設定値を直接確認するE2Eテストスクリプト(SecurityHubポーリングではない。即時実行可能)
また、AMI IDがハードコードされていたため、SSMパラメータ参照への変更も依頼しました。
追記反映後のrequirements.md最終版です。なお、requirements.md上は当初の学習目的としてSecurity Hubでの検出・解消サイクルを記載していますが、本記事で実施した確認はcfn-guardとAWS CLI E2Eまでです。
requirements.md(仕様書フィードバック反映後の最終版から抜粋)
# 要件定義書: SecurityHub 学習用 脆弱性EC2環境
## 1. 目的
AWS SecurityHub で検出される脆弱性を意図的に持つ EC2 環境を CloudFormation で構築し、
「SecurityHub 指摘の検出 → IaC 修正 → 指摘解消」の学習サイクルを体験する。
## 2. 前提条件
| 項目 | 値 |
|---|---|
| AWSリージョン | ap-northeast-1(東京) |
| SecurityHub | 有効化済み |
| デプロイ方式 | CloudFormation(単一テンプレート) |
| ライフサイクル | 学習後にスタック削除(一時利用) |
| AMI | SSM 公開パラメータから動的取得 |
## 3. 対象脆弱性と違反仕様
### 3.1 EC2.8 — IMDSv2 の無効化
- 実装方法: `MetadataOptions.HttpTokens = optional`
### 3.2 EC2.3 — EBS 暗号化の無効化
- 実装方法: BlockDeviceMappings の `Encrypted = false`
### 3.3 EC2.18 — セキュリティグループ インバウンド SSH 全開放
- 実装方法: Ingress: Port 22, CidrIp = 0.0.0.0/0
### 3.4 EC2.19 — セキュリティグループ インバウンド RDP 全開放
- 実装方法: Ingress: Port 3389, CidrIp = 0.0.0.0/0
### 3.5 IAM.1 — IAM インラインポリシー フルアクセス
- 実装方法: インラインポリシー Action="*", Resource="*"
- 制約: `iam:CreatePolicy` 権限がないためインラインポリシーで実装
## 4. リソース構成
CloudFormation Stack: weak-ec2-securityhub-lab
├── SecurityGroup (EC2.18, EC2.19 違反)
│ ├── VpcId: パラメータ参照
│ ├── Ingress: TCP 22, 0.0.0.0/0
│ └── Ingress: TCP 3389, 0.0.0.0/0
├── IAM Role (IAM.1 違反)
│ └── Inline Policy: Action=*, Resource=*
├── IAM Instance Profile
└── EC2 Instance (EC2.8, EC2.3 違反)
├── InstanceType: t4g.nano (ARM/Graviton)
├── MetadataOptions.HttpTokens: optional
├── BlockDeviceMapping: Encrypted=false
└── KeyName: なし(SSM Session Manager で接続)
(中略)
## 6. 成果物
| 成果物 | ファイル名 |
|---|---|
| 要件定義書 | requirements.md |
| 脆弱版 CFn テンプレート | template-weak.yaml |
| 修正版 CFn テンプレート | template-fixed.yaml |
| cfn-guard ルールファイル | rules.guard |
| E2E テストスクリプト | e2e-test.sh |
### テンプレート設計方針
- 脆弱版・修正版は同一の論理 ID を使用
- 脆弱設定に関わるプロパティのみを変更または削除
- 同一スタック名で上書きデプロイ可能
### E2E テストスクリプト仕様
| 検証項目 | 期待値(weak) | 期待値(fixed) |
|---|---|---|
| EC2.8 HttpTokens | optional | required |
| EC2.3 Encrypted | false | true |
| EC2.18 Ingress 22 | 0.0.0.0/0 あり | なし |
| EC2.19 Ingress 3389 | 0.0.0.0/0 あり | なし |
| IAM.1 インラインポリシー | Action:* あり | なし |
Step 3: Code Generation — 仕様をCFnと確認手段に落とし込む
Kiroは要件定義書に基づいて4ファイルを生成しました。
| ファイル | 役割 |
|---|---|
| template-weak.yaml | 5件の脆弱設定を含むCFnテンプレート |
| template-fixed.yaml | 脆弱版と同一論理IDを維持し、5つの検証項目を修正したテンプレート |
| rules.guard | cfn-guardルール(5件) |
| e2e-test.sh | デプロイ後の実設定確認スクリプト |
論理ID統一
requirements.mdに「脆弱版と同一論理IDで、脆弱設定に関わるプロパティのみを変更または削除」と明記したことで、両テンプレートの論理IDが統一されました。
| リソース | weak | fixed |
|---|---|---|
| SG | LabSecurityGroup | LabSecurityGroup ✅ |
| Role | LabRole | LabRole ✅ |
| Profile | LabInstanceProfile | LabInstanceProfile ✅ |
| Instance | LabInstance | LabInstance ✅ |
同一スタック名での上書きデプロイ時に、変更対象のリソースがCloudFormationの差分更新として扱われます。プロパティによってはUpdateではなくReplaceになる場合があります。
脆弱版テンプレート
template-weak.yaml(脆弱設定の抜粋)
# セキュリティグループ — EC2.18, EC2.19
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0 # EC2.18
- IpProtocol: tcp
FromPort: 3389
ToPort: 3389
CidrIp: 0.0.0.0/0 # EC2.19
# IAMロール — IAM.1
Policies:
- PolicyName: OverlyPermissive
PolicyDocument:
Statement:
- Effect: Allow
Action: "*"
Resource: "*"
# EC2インスタンス — EC2.8, EC2.3
MetadataOptions:
HttpTokens: optional # EC2.8
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
Encrypted: false # EC2.3
修正版テンプレートの変更点
| コントロール | 修正内容 |
|---|---|
| EC2.8 | HttpTokens: required |
| EC2.3 | Encrypted: true |
| EC2.18 / EC2.19 | SecurityGroupIngress: [] とし、22/tcp・3389/tcp のインバウンドルールを削除 |
| IAM.1 | インラインポリシー削除 → AmazonSSMManagedInstanceCoreのみ |
cfn-guard検証
cfn-guardのFAILは「ルール違反を検出した」ことを意味します。脆弱版で全件FAILになるのは、脆弱設定が意図どおり含まれていることの確認です。
rules.guard
# EC2.8相当: IMDSv2 強制(HttpTokens=required)
rule EC2_IMDSV2_REQUIRED {
AWS::EC2::Instance {
Properties.MetadataOptions.HttpTokens == "required"
}
}
# EC2.3相当: EBS ルートボリューム暗号化
rule EBS_ENCRYPTED {
AWS::EC2::Instance {
Properties.BlockDeviceMappings[*].Ebs.Encrypted == true
}
}
# EC2.18相当: セキュリティグループ インバウンド 0.0.0.0/0 禁止
rule NO_UNRESTRICTED_INGRESS {
AWS::EC2::SecurityGroup {
Properties.SecurityGroupIngress not exists
or Properties.SecurityGroupIngress == []
or Properties.SecurityGroupIngress[*].CidrIp != "0.0.0.0/0"
}
}
# EC2.19相当: 高リスクポート(3389)への無制限インバウンド禁止
rule NO_HIGH_RISK_PORT_INGRESS {
AWS::EC2::SecurityGroup {
Properties.SecurityGroupIngress not exists
or Properties.SecurityGroupIngress == []
or Properties.SecurityGroupIngress[*] {
FromPort != 3389
or CidrIp != "0.0.0.0/0"
}
}
}
# IAM.1相当: 今回のテンプレートでは過剰権限のインラインポリシーを削除する設計のため、
# インラインポリシーの有無で簡易的に判定
rule NO_IAM_INLINE_POLICY {
AWS::IAM::Role {
Properties.Policies not exists
or Properties.Policies == []
}
}
NO_UNRESTRICTED_INGRESS は0.0.0.0/0インバウンド全般を検出する簡易ルールです。今回のweakテンプレートでは、EC2.18相当・EC2.19相当のどちらの設定も0.0.0.0/0を含みますが、検証項目との対応を明示するためルールを分けています。
初回生成されたルールではProperties.SecurityGroupIngress not existsのみを見ていました。fixed版のSecurityGroupIngress: []を通過できずFAILになったため、or Properties.SecurityGroupIngress == []を追加して解決しました。
最終検証結果:
| ルール | weak | fixed |
|---|---|---|
| EC2_IMDSV2_REQUIRED | FAIL ✅ | PASS ✅ |
| EBS_ENCRYPTED | FAIL ✅ | PASS ✅ |
| NO_UNRESTRICTED_INGRESS | FAIL ✅ | PASS ✅ |
| NO_HIGH_RISK_PORT_INGRESS | FAIL ✅ | PASS ✅ |
| NO_IAM_INLINE_POLICY | FAIL ✅ | PASS ✅ |
E2Eテストスクリプト
./e2e-test.sh weak|fixed で、デプロイ済みリソースの設定値をAWS CLIで取得し、期待値と照合します。
e2e-test.sh(概要)
#!/bin/bash
set -euo pipefail
# Usage: ./e2e-test.sh [weak|fixed]
MODE="${1:-}"
STACK_NAME="weak-ec2-securityhub-lab"
REGION="ap-northeast-1"
# CFn Outputsからリソース情報取得
get_output() {
aws cloudformation describe-stacks \
--stack-name "$STACK_NAME" --region "$REGION" \
--query "Stacks[0].Outputs[?OutputKey=='$1'].OutputValue" \
--output text
}
INSTANCE_ID=$(get_output InstanceId)
SG_ID=$(get_output SecurityGroupId)
ROLE_NAME=$(get_output RoleName)
echo "=== E2E Test: $STACK_NAME ==="
echo " InstanceId: $INSTANCE_ID"
echo " SgId: $SG_ID"
echo " RoleName: $ROLE_NAME"
FAIL=0
# EC2.8: IMDSv2
HTTP_TOKENS=$(aws ec2 describe-instances --instance-ids "$INSTANCE_ID" \
--region "$REGION" \
--query 'Reservations[0].Instances[0].MetadataOptions.HttpTokens' \
--output text)
# EC2.3: EBS暗号化
ENCRYPTED=$(aws ec2 describe-volumes --region "$REGION" \
--filters "Name=attachment.instance-id,Values=$INSTANCE_ID" \
--query 'Volumes[0].Encrypted' --output text)
# EC2.18: Ingress 0.0.0.0/0:22
INGRESS_SSH=$(aws ec2 describe-security-groups --group-ids "$SG_ID" \
--region "$REGION" \
--query 'SecurityGroups[0].IpPermissions[?FromPort==`22`].IpRanges[].CidrIp' \
--output text)
# EC2.19: Ingress 0.0.0.0/0:3389
INGRESS_RDP=$(aws ec2 describe-security-groups --group-ids "$SG_ID" \
--region "$REGION" \
--query 'SecurityGroups[0].IpPermissions[?FromPort==`3389`].IpRanges[].CidrIp' \
--output text)
# IAM.1: インラインポリシー
INLINE_POLICIES=$(aws iam list-role-policies --role-name "$ROLE_NAME" \
--query 'PolicyNames' --output text)
# ... weak/fixed判定ロジック(省略)
Step 4: Review + デプロイ検証
AI-DLCに「承認」と伝えてデプロイを実行しました。
デプロイ失敗→修正: VpcId未指定
初回デプロイで失敗しました。セキュリティグループにVpcIdを指定していなかったため、CloudFormationがリソース作成に失敗しました。
Kiroが修正案を提示し、差分確認のうえ反映しました:
- 両テンプレートに
VpcIdパラメータ追加(Type: AWS::EC2::VPC::Id) - セキュリティグループに
VpcId: !Ref VpcId追加 - 失敗スタック削除 → パラメータにVPC IDを指定して再デプロイ
再デプロイは CREATE_COMPLETE で成功しました。
E2Eテスト失敗→修正: JMESPathクエリ
E2Eテスト初回実行でEC2.18/EC2.19の検証が失敗しました。
FAIL: EC2.18 expected 0.0.0.0/0, got ''
FAIL: EC2.19 expected 0.0.0.0/0, got ''
原因は2つありました:
- JMESPathのバッククォートとシェルの解釈が衝突し、期待したクエリとして渡せていなかった
- ネストしたフィルタ
[?条件].IpRanges[?条件].CidrIpが期待どおり展開されなかった
Kiroは実データを --output json で確認し、シングルクォート+フラット展開に修正しました。
# 修正後
--query 'SecurityGroups[0].IpPermissions[?FromPort==`22`].IpRanges[].CidrIp'
--query 'SecurityGroups[0].IpPermissions[?FromPort==`3389`].IpRanges[].CidrIp'
weak ALL PASS
E2Eスクリプトのweakモードで全5件PASSを確認しました。
=== E2E Test: weak-ec2-securityhub-lab ===
InstanceId: i-xxxxxxxxxxxxxxxxx
SgId: sg-xxxxxxxxxxxxxxxxx
RoleName: weak-ec2-securityhub-lab-role
[EC2.8] HttpTokens = optional → PASS
[EC2.3] Encrypted = False → PASS
[EC2.18] Ingress 0.0.0.0/0:22 あり → PASS
[EC2.19] Ingress 0.0.0.0/0:3389 あり → PASS
[IAM.1] Action:* インラインポリシー → PASS
=== Result: ALL PASS ===
修正版上書きデプロイ → fixed ALL PASS
同一スタック weak-ec2-securityhub-lab に修正版を上書きデプロイしました。EBS暗号化変更によりインスタンスがReplaceされました(想定内)。
[EC2.8] HttpTokens = required PASS
[EC2.3] Encrypted = True PASS
[EC2.18] Ingress 0.0.0.0/0:22 なし PASS
[EC2.19] Ingress 0.0.0.0/0:3389 なし PASS
[IAM.1] インラインポリシーなし PASS
=== Result: ALL PASS ===
E2Eスクリプトのweakモードをfixedスタックに対して実行する逆確認も全5件FAILとなり、判定が期待どおり動くことを確認しました。検証後にスタックは削除しています。
Step 4 所感
デプロイ失敗(VpcId)とE2Eテスト失敗(JMESPath)の2件とも、人間が追加で原因を説明しなくても、Kiroが実データ確認→原因特定→修正案生成まで進められました。
参考値として、今回の環境ではKickoff〜デプロイ検証完了までの所要時間は約16分、消費クレジットは8.40でした。
仕様書フィードバック — 実装→仕様への逆反映
デプロイとE2Eテストが完了した後、要件定義書と実ファイルの整合性を確認しました。
requirements.md と実ファイルに差分がでていない?探して。
Kiroは2点の差分を検出し、requirements.mdへの修正案を提示しました。確認のうえ反映しています。
| 差分 | 修正内容 |
|---|---|
| VpcId パラメータ | セクション4の構成図にVpcId追加、パラメータ表を新設、CFnの制約を注記 |
| EC2.19 Ingressルール | Ingressの仕様をweak(SSH+RDP開放)/fixed(全ルール削除)それぞれ明示 |
「差分を探して」と依頼するだけで、Kiroが仕様と実装を比較し、差分検出と修正案の提示まで進められました。
補足: 既存の脆弱EC2もIaC化して改善サイクルに乗せる
AI-DLCには逆解析(reverse-engineering)のスキルがあり、既存リソースをもとにIaC化のたたき台や仕様書を生成できます。先行記事で紹介しています。
今回は、脆弱版を再デプロイした検証用EC2に対して逆解析を実行しました。インスタンスID1つからdescribe-instances → SG → EBS → IAM → インラインポリシーと関連リソースを芋づる式に取得します。CFnテンプレートと仕様書アーティファクトを生成する流れで、今回の検証ではCloudFormation管理外のリソースでもインスタンスIDを起点にIaC化のたたき台を生成できました。
今回の逆解析では、プロンプトでSecurity Hubに関する指示をしていません。しかし生成されたcomponent-inventory.mdにはSecurity Hub検出マッピングが含まれていました。以下はその内容です(一部IDはマスクしています)。
# Component Inventory
## IAM Resources
### LabRole (AWS::IAM::Role)
- **論理ID**: LabRole
- **信頼ポリシー**: ec2.amazonaws.com
- **マネージドポリシー**: arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
- **インラインポリシー**:
- 名前: `OverlyPermissive`
- Effect: Allow / Action: `*` / Resource: `*`
- **⚠️ リスク**: 全 AWS サービスへの完全アクセス(Security Hub IAM.1 対象)
### LabInstanceProfile (AWS::IAM::InstanceProfile)
- **論理ID**: LabInstanceProfile
- **ロール**: !Ref LabRole
## Network Resources
### LabSecurityGroup (AWS::EC2::SecurityGroup)
- **論理ID**: LabSecurityGroup
- **説明**: "Lab SG - intentionally weak"
- **VPC**: !Ref VpcId
- **インバウンド**: TCP:22 ← 0.0.0.0/0 (⚠️ Security Hub EC2.13/EC2.19 対象)
- **アウトバウンド**: All ← 0.0.0.0/0
## Compute Resources
### LabInstance (AWS::EC2::Instance)
- **論理ID**: LabInstance
- **AMI**: ami-xxxxxxxxxxxxxxxxx (arm64, Amazon Linux 2023)
- **タイプ**: t4g.nano (2vCPU, arm64)
- **サブネット**: !Ref SubnetId
- **モニタリング**: 無効
- **EBS**:
- デバイス: /dev/xvda
- サイズ: 8GB / タイプ: gp3 / IOPS: 3000 / スループット: 125MB/s
- 暗号化: false(⚠️ Security Hub EC2.3 対象)
- DeleteOnTermination: true
- **メタデータ**:
- HttpTokens: optional(⚠️ IMDSv1 有効 / Security Hub EC2.8 対象)
- HttpPutResponseHopLimit: 2
- HttpEndpoint: enabled
- **タグ**: Name=weak-ec2-lab
## Security Hub 検出マッピング
| コントロール | 対象リソース | 脆弱設定 |
|---|---|---|
| EC2.8 | LabInstance | IMDSv2 未強制 (HttpTokens: optional) |
| EC2.3 | EBS (/dev/xvda) | 暗号化なし |
| EC2.13 | LabSecurityGroup | SSH 0.0.0.0/0 |
| EC2.19 | LabSecurityGroup | SSH 0.0.0.0/0 |
| IAM.1 | LabRole | OverlyPermissive ポリシー |
意図的に設定した脆弱項目のうち、IMDSv2未強制、EBS暗号化なし、SSHのインバウンド開放、過剰権限のIAMインラインポリシーは、逆解析でも問題として報告されていました。
なお、本文ではEC2.18(SSH全開放)を対象としていますが、逆解析が返したマッピングではEC2.13が使われています。EC2.13は「セキュリティグループで0.0.0.0/0からのSSHインバウンドを許可しない」というコントロールで、EC2.18とは検出対象が重なりますが別コントロールです。逆解析はプロンプトでコントロールIDを指定していなかったため、Kiro側の判断でEC2.13が選ばれています。
まとめ
今回の検証では、「脆弱版と修正版のペア」のように複数成果物間の整合性が必要な題材を、AI-DLCの仕様定義ファーストなプロセスで扱いました。CloudFormationテンプレート、cfn-guardルール、E2Eテストスクリプト、修正版テンプレートを同じ仕様を起点に生成しました。不足分を追記しながら進めることで、論理IDの統一や検証対象の対応関係を保ちやすくなりました。
ポイントは、要件定義書を一度作って終わりにしないことです。初回のrequirements.mdに不足があれば追記し、デプロイ検証で見つかった差分も仕様へ書き戻します。VpcId未指定のような実行時に発覚した制約を反映することで、次回の手戻りを減らしやすくなります。このサイクルで、整合性を維持しながら成果物を育てられます。
補足で試した逆解析も、この仕様開発サイクルに接続できます。今回のように、既存EC2をインスタンスID起点で解析してIaC化のたたき台を取り出せれば、既存環境についても同じ改善フローに乗せる入口を作れます。








