@shinyaz

AWS Security Agent 検証 — 自動コード修正(Code Remediation)の品質評価

目次

はじめに

第1回では REST API に対するペネトレーションテストで 5 件中 4 件の脆弱性を検出し、第2回ではソースコード提供による検出数の変化を、第3回では GraphQL API に対する検出能力を、第4回では認証フロー対応と検出範囲の変化を検証した。

第1〜4回は全て「検出」に焦点を当てていた。AWS Security Agent にはペンテストで検出した脆弱性に対して自動的にコード修正を生成し、GitHub リポジトリに PR を作成する Code Remediation 機能がある。本記事では第4回と同じ Flask アプリに対してペンテストを実行し、検出された脆弱性に対する自動修正の品質を検証する。

公式ドキュメント: Remediate a penetration test finding

前提条件と制約

Code Remediation には以下の制約がある。

  • us-east-1 のみ — Code Remediation は現時点で us-east-1 リージョンでのみ利用可能
  • GitHub 連携が必須 — AWS Security Agent GitHub App をインストールし、リポジトリを Agent Space に接続する必要がある。Private リポジトリの場合は GitHub に直接 PR が作成される。Public リポジトリの場合はセキュリティ上の理由から PR ではなく diff ファイルのダウンロードとなる
  • VPC から GitHub へのアクセスが必要 — Agent のテスト環境からソースコードを取得するため、NAT Gateway 等でインターネットアクセスを確保する必要がある

第1〜4回は ap-northeast-1 で実施したが、本記事は us-east-1 で実施する。

検証環境

第4回と同じ Flask + pyotp アプリ(5つの脆弱性を埋め込み済み)を使用する。

  • アプリ: Flask + pyotp + SQLite、TOTP 2FA 有効
  • GitHub リポジトリ: shinyaz/auth-vuln-app(Private)
  • リージョン: us-east-1
  • 認証: Secrets Manager 経由で testuser のクレデンシャルを提供
ソースコード全文(app.py)

Flask + pyotp で構築。TOTP_ENABLED=true 環境変数で 2FA を有効化する。TOTP Secret は全アカウント共通で JBSWY3DPEHPK3PXP(RFC 6238 のテスト値)を使用している。

app.py
from flask import Flask, request, jsonify, session, redirect, render_template_string
import sqlite3
import os
import pyotp
 
app = Flask(__name__)
app.secret_key = "insecure-secret-key-for-testing"
 
DB_PATH = "/tmp/auth_vuln.db"
TOTP_SECRET = "JBSWY3DPEHPK3PXP"
 
def get_db():
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    return conn
 
def init_db():
    conn = get_db()
    conn.executescript("""
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY, username TEXT UNIQUE,
            password TEXT, role TEXT DEFAULT 'user', email TEXT, phone TEXT);
        CREATE TABLE IF NOT EXISTS products (
            id INTEGER PRIMARY KEY, name TEXT, category TEXT, price REAL);
        CREATE TABLE IF NOT EXISTS comments (
            id INTEGER PRIMARY KEY, user_id INTEGER,
            content TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
        DELETE FROM users; DELETE FROM products; DELETE FROM comments;
        INSERT INTO users (id, username, password, role, email, phone) VALUES
            (1, 'admin', 'admin123', 'admin', 'admin@example.com', '555-0100'),
            (2, 'testuser', 'testpass123', 'user', 'test@example.com', '555-0200'),
            (3, 'alice', 'alice123', 'user', 'alice@example.com', '555-0300');
        INSERT INTO products (id, name, category, price) VALUES
            (1, 'Laptop', 'electronics', 999.99), (2, 'Phone', 'electronics', 699.99),
            (3, 'Desk', 'furniture', 299.99), (4, 'Chair', 'furniture', 199.99);
        INSERT INTO comments (user_id, content) VALUES
            (1, 'Welcome to the dashboard!'), (2, 'This is a test comment.');
    """)
    conn.commit()
    conn.close()
 
LAYOUT_HEAD = """<!DOCTYPE html><html><head><title>Auth Vuln App</title></head><body>
<nav>
  <a href="/">Home</a> | <a href="/search">Search</a> |
  <a href="/api/products">Products API</a> |
  {% if session.get('user_id') %}
    <a href="/dashboard">Dashboard</a> |
    <a href="/profile/{{ session['user_id'] }}">Profile</a> |
    {% if session.get('role') == 'admin' %}<a href="/admin/users">Admin</a> |{% endif %}
    <a href="/logout">Logout ({{ session['username'] }})</a>
  {% else %}<a href="/login">Login</a>{% endif %}
</nav><hr>"""
LAYOUT_FOOT = "</body></html>"
 
HOME_PAGE = LAYOUT_HEAD + """
<h1>Auth Vuln App</h1>
<h2>Public Pages</h2>
<ul>
  <li><a href="/search">Search Products</a></li>
  <li><a href="/api/products">Products API</a></li>
  <li><a href="/api/products?category=electronics">Electronics</a></li>
</ul>
<h2>Authenticated Pages</h2>
<ul>
  <li><a href="/dashboard">Dashboard</a> (requires login)</li>
  <li><a href="/profile/1">User Profile</a> (requires login)</li>
  <li><a href="/admin/users">Admin Panel</a> (requires admin)</li>
</ul>""" + LAYOUT_FOOT
 
LOGIN_PAGE = LAYOUT_HEAD + """
<h1>Login</h1>
{% if error %}<p style="color:red">{{ error }}</p>{% endif %}
<form method="POST" action="/login">
  <label>Username: <input type="text" name="username"></label><br>
  <label>Password: <input type="password" name="password"></label><br>
  {% if totp_required %}
    <label>2FA Code: <input type="text" name="totp_code"></label><br>
  {% endif %}
  <button type="submit">Login</button>
</form>""" + LAYOUT_FOOT
 
SEARCH_PAGE = LAYOUT_HEAD + """
<h1>Search Products</h1>
<form method="GET" action="/search">
  <input type="text" name="q" value="{{ query }}">
  <button type="submit">Search</button>
</form>
{% if query %}
  <h2>Results for: {{ query | safe }}</h2>
  <ul>{% for p in results %}<li>{{ p['name'] }} - ${{ p['price'] }}</li>{% endfor %}</ul>
{% endif %}""" + LAYOUT_FOOT
 
DASHBOARD_PAGE = LAYOUT_HEAD + """
<h1>Dashboard</h1>
<p>Welcome, {{ session['username'] }}!</p>
<h2>Comments</h2>
<form method="POST" action="/dashboard/comment">
  <textarea name="content" rows="3" cols="40"></textarea><br>
  <button type="submit">Post Comment</button>
</form>
<ul>{% for c in comments %}<li>{{ c['content'] | safe }}</li>{% endfor %}</ul>
""" + LAYOUT_FOOT
 
PROFILE_PAGE = LAYOUT_HEAD + """
<h1>User Profile</h1>
<table>
  <tr><td>ID</td><td>{{ user['id'] }}</td></tr>
  <tr><td>Username</td><td>{{ user['username'] }}</td></tr>
  <tr><td>Email</td><td>{{ user['email'] }}</td></tr>
  <tr><td>Phone</td><td>{{ user['phone'] }}</td></tr>
  <tr><td>Role</td><td>{{ user['role'] }}</td></tr>
</table>""" + LAYOUT_FOOT
 
ADMIN_PAGE = LAYOUT_HEAD + """
<h1>Admin - User Management</h1>
<table border="1">
  <tr><th>ID</th><th>Username</th><th>Email</th><th>Role</th></tr>
  {% for u in users %}
  <tr><td>{{ u['id'] }}</td><td><a href="/profile/{{ u['id'] }}">{{ u['username'] }}</a></td>
      <td>{{ u['email'] }}</td><td>{{ u['role'] }}</td></tr>
  {% endfor %}
</table>""" + LAYOUT_FOOT
 
@app.route("/")
def index():
    return render_template_string(HOME_PAGE)
 
@app.route("/search")
def search():
    query = request.args.get("q", "")
    results = []
    if query:
        conn = get_db()
        results = conn.execute("SELECT * FROM products WHERE name LIKE ?", (f"%{query}%",)).fetchall()
        conn.close()
    return render_template_string(SEARCH_PAGE, query=query, results=results)
 
@app.route("/api/products")
def api_products():
    category = request.args.get("category", "")
    conn = get_db()
    if category:
        query = f"SELECT * FROM products WHERE category = '{category}'"
        try:
            results = conn.execute(query).fetchall()
        except Exception as e:
            conn.close()
            return jsonify({"error": str(e)}), 500
    else:
        results = conn.execute("SELECT * FROM products").fetchall()
    conn.close()
    return jsonify([dict(r) for r in results])
 
TOTP_ENABLED = os.environ.get("TOTP_ENABLED", "false").lower() == "true"
 
@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "GET":
        return render_template_string(LOGIN_PAGE, error=None, totp_required=TOTP_ENABLED)
    username = request.form.get("username", "")
    password = request.form.get("password", "")
    conn = get_db()
    user = conn.execute("SELECT * FROM users WHERE username = ? AND password = ?",
                        (username, password)).fetchone()
    conn.close()
    if not user:
        return render_template_string(LOGIN_PAGE, error="Invalid credentials",
                                      totp_required=TOTP_ENABLED), 401
    if TOTP_ENABLED:
        totp_code = request.form.get("totp_code", "")
        totp = pyotp.TOTP(TOTP_SECRET)
        if not totp.verify(totp_code, valid_window=1):
            return render_template_string(LOGIN_PAGE, error="Invalid 2FA code",
                                          totp_required=True), 401
    session["user_id"] = user["id"]
    session["username"] = user["username"]
    session["role"] = user["role"]
    return redirect("/dashboard")
 
@app.route("/logout")
def logout():
    session.clear()
    return redirect("/")
 
@app.route("/profile/<int:user_id>")
def profile(user_id):
    if "user_id" not in session:
        return redirect("/login")
    conn = get_db()
    user = conn.execute("SELECT * FROM users WHERE id = ?", (user_id,)).fetchone()
    conn.close()
    if not user:
        return "User not found", 404
    return render_template_string(PROFILE_PAGE, user=dict(user))
 
@app.route("/dashboard")
def dashboard():
    if "user_id" not in session:
        return redirect("/login")
    conn = get_db()
    comments = conn.execute("SELECT * FROM comments ORDER BY created_at DESC").fetchall()
    conn.close()
    return render_template_string(DASHBOARD_PAGE, comments=comments)
 
@app.route("/dashboard/comment", methods=["POST"])
def post_comment():
    if "user_id" not in session:
        return redirect("/login")
    content = request.form.get("content", "")
    conn = get_db()
    conn.execute("INSERT INTO comments (user_id, content) VALUES (?, ?)",
                 (session["user_id"], content))
    conn.commit()
    conn.close()
    return redirect("/dashboard")
 
@app.route("/admin/users")
def admin_users():
    if "user_id" not in session:
        return redirect("/login")
    conn = get_db()
    users = conn.execute("SELECT * FROM users").fetchall()
    conn.close()
    return render_template_string(ADMIN_PAGE, users=users)
 
@app.route("/.well-known/aws/securityagent-domain-verification.json")
def verify():
    return jsonify({"token": "<verification-token>"})
 
if __name__ == "__main__":
    init_db()
    app.run(host="0.0.0.0", port=5000, debug=False)
環境構築手順(EC2 + GitHub 連携 + NAT Gateway)
Terminal
REGION=us-east-1
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
AGENT_SPACE_ID=<your-agent-space-id>
 
# 1. GitHub リポジトリ作成・プッシュ
gh repo create auth-vuln-app --private
cd /tmp && mkdir auth-vuln-app-repo && cd auth-vuln-app-repo
git init
# app.py と requirements.txt を配置
git add . && git commit -m "Initial commit"
git remote add origin git@github.com:<your-org>/auth-vuln-app.git
git push -u origin main
 
# 2. EC2 起動(パブリックサブネット)
AMI_ID=$(aws ec2 describe-images --region $REGION --owners amazon \
  --filters "Name=name,Values=al2023-ami-2023*-x86_64" "Name=state,Values=available" \
  --query "sort_by(Images, &CreationDate)[-1].ImageId" --output text)
 
SG_ID=$(aws ec2 create-security-group --region $REGION \
  --group-name "remediation-test-sg" --description "Remediation test" \
  --vpc-id <your-vpc-id> --query "GroupId" --output text)
 
aws ec2 authorize-security-group-ingress --region $REGION \
  --group-id "$SG_ID" --protocol tcp --port 80 --cidr <vpc-cidr>
 
# SG 自己参照ルール(Agent コンテナ → EC2)
aws ec2 authorize-security-group-ingress --region $REGION \
  --group-id "$SG_ID" --protocol tcp --port 80 --source-group "$SG_ID"
 
INSTANCE_ID=$(aws ec2 run-instances --region $REGION \
  --image-id "$AMI_ID" --instance-type t3.small \
  --subnet-id <your-public-subnet-id> --security-group-ids "$SG_ID" \
  --iam-instance-profile Name=<your-ssm-profile> \
  --associate-public-ip-address \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=remediation-test-target}]' \
  --query "Instances[0].InstanceId" --output text)
 
# 3. アプリデプロイ(SSM 経由)
aws ssm send-command --region $REGION --instance-ids "$INSTANCE_ID" \
  --document-name "AWS-RunShellScript" \
  --parameters commands='["dnf install -y python3-pip",
    "pip3 install flask pyotp gunicorn",
    "mkdir -p /opt/app",
    "# app.py を base64 でデコードして配置",
    "cd /opt/app && TOTP_ENABLED=true python3 -c \"from app import init_db; init_db()\"",
    "cd /opt/app && TOTP_ENABLED=true gunicorn -w 4 -b 0.0.0.0:80 --timeout 120 -e TOTP_ENABLED=true --daemon app:app"]'
 
# 4. NAT Gateway 作成(Agent コンテナから GitHub へのアクセス用)
EIP_ALLOC=$(aws ec2 allocate-address --region $REGION --domain vpc --query "AllocationId" --output text)
NAT_GW=$(aws ec2 create-nat-gateway --region $REGION \
  --subnet-id <your-public-subnet-id> --allocation-id "$EIP_ALLOC" \
  --query "NatGateway.NatGatewayId" --output text)
aws ec2 wait nat-gateway-available --region $REGION --nat-gateway-ids "$NAT_GW"
 
# 5. プライベートサブネット作成(Agent コンテナ用)
PRIVATE_SUBNET=$(aws ec2 create-subnet --region $REGION \
  --vpc-id <your-vpc-id> --cidr-block "172.31.128.0/24" \
  --availability-zone <your-az> \
  --query "Subnet.SubnetId" --output text)
 
PRIVATE_RT=$(aws ec2 create-route-table --region $REGION \
  --vpc-id <your-vpc-id> --query "RouteTable.RouteTableId" --output text)
aws ec2 create-route --region $REGION \
  --route-table-id "$PRIVATE_RT" --destination-cidr-block "0.0.0.0/0" \
  --nat-gateway-id "$NAT_GW"
aws ec2 associate-route-table --region $REGION \
  --route-table-id "$PRIVATE_RT" --subnet-id "$PRIVATE_SUBNET"
 
# 6. Target Domain 作成・検証
PRIVATE_DNS=$(aws ec2 describe-instances --region $REGION \
  --instance-ids "$INSTANCE_ID" \
  --query "Reservations[0].Instances[0].PrivateDnsName" --output text)
aws securityagent create-target-domain --region $REGION \
  --target-domain-name "$PRIVATE_DNS" --verification-method HTTP_ROUTE
# Private VPC ドメインの場合、UNREACHABLE が正常な検証状態
 
# 7. Secrets Manager
aws secretsmanager create-secret --region $REGION \
  --name "security-agent/remediation-test/testuser" \
  --secret-string '{"username":"testuser","password":"testpass123","totpSecret":"JBSWY3DPEHPK3PXP"}'
 
# 8. Agent Space 更新(プライベートサブネット + Secret + Target Domain)
aws securityagent update-agent-space --region $REGION \
  --agent-space-id "$AGENT_SPACE_ID" \
  --target-domain-ids <target-domain-id> \
  --aws-resources '{
    "vpcs": [{"vpcArn":"<vpc-id>","securityGroupArns":["'$SG_ID'"],"subnetArns":["'$PRIVATE_SUBNET'"]}],
    "secretArns": ["<secret-arn>"],
    "iamRoles": ["<role-arn>"]
  }'
 
# 9. GitHub 連携(AWS Console で実施)
# - Security Agent → Agent Space → Integrations → Connect GitHub
# - GitHub App インストール → リポジトリ選択
# - Penetration test タブ → Pentest remediation enabled
 
# 10. ペンテスト作成(GitHub リポジトリ + AUTOMATIC Remediation)
aws securityagent create-pentest --region $REGION \
  --agent-space-id "$AGENT_SPACE_ID" \
  --title "remediation-test-auth" \
  --code-remediation-strategy AUTOMATIC \
  --assets '{
    "endpoints": [{"uri": "http://'"$PRIVATE_DNS"'"}],
    "actors": [{"identifier":"testuser","uris":["http://'"$PRIVATE_DNS"'"],
      "authentication":{"providerType":"SECRETS_MANAGER","value":"<secret-arn>"},
      "description":"Navigate to /login. Enter username and password. Enter TOTP code. Click Login."}],
    "integratedRepositories": [{"integrationId":"<integration-id>","providerResourceId":"<github-repo-id>"}]
  }' \
  --service-role "<role-arn>" \
  --vpc-config '{"vpcArn":"<vpc-id>","securityGroupArns":["'$SG_ID'"],"subnetArns":["'$PRIVATE_SUBNET'"]}'
 
# 11. ペンテストジョブ開始
aws securityagent start-pentest-job --region $REGION \
  --agent-space-id "$AGENT_SPACE_ID" --pentest-id <pentest-id>

セットアップで遭遇した問題

環境構築では以下の問題に遭遇した。

  1. DB 未初期化 — gunicorn 経由では init_db() が実行されない。手動で python3 -c "from app import init_db; init_db()" を実行する必要がある。これを忘れると全 DB 依存エンドポイントが 500 エラーを返し、ペンテストが FAILED になる
  2. VPC から GitHub へのアクセス — Agent のテスト環境は Agent Space に設定したサブネットで起動する。このサブネットから GitHub にアクセスできないとソースコードを取得できず、Unable to reach GitHub repository エラーでジョブが失敗する。今回はプライベートサブネットを作成し、NAT Gateway 経由でインターネットアクセスを確保した
  3. ペンテスト作成時にリポジトリを紐づける必要があるcreate-pentestassets.integratedRepositories でリポジトリを指定する。後から update-pentest で追加しても、既に実行中のジョブには反映されない

検証結果

7 件の Finding を検出し、全件に対して GitHub PR が自動生成された。 code-remediation-strategyAUTOMATIC に設定したため、Finding が確認されるたびに PR が順次生成された。ペンテストジョブの完了を待つ必要はなく、Finding の検出から数分後に対応する PR が GitHub リポジトリに作成された。

#Findingリスクタイプ深刻度確信度PR
1SQL Injection in /api/productsSQL_INJECTIONCRITICALHIGH#1
2Reflected XSS in /searchCROSS_SITE_SCRIPTINGMEDIUMLOW#2
3Hardcoded Flask Secret KeySESSION_TOKEN_VULNERABILITIESCRITICALHIGH#3
4Default Credentials + Plaintext PasswordsDEFAULT_CREDENTIALSCRITICALHIGH#4
5IDOR on /profileINSECURE_DIRECT_OBJECT_REFERENCEMEDIUMHIGH#5
6Privilege Escalation /admin/usersPRIVILEGE_ESCALATIONMEDIUMHIGH#6
7Stored XSS in CommentsCROSS_SITE_SCRIPTINGMEDIUMLOW#7

第4回(条件 B)では 6 件だったが、今回は 7 件検出された。第4回では検出されなかった Hardcoded Secret Key(#3)と Default Credentials(#4)が新たに検出されている。今回は GitHub リポジトリとしてソースコードが提供されており、コードレベルの分析が加わった可能性がある。

PR の修正内容

各 PR の修正内容を分析する。

PR #1: SQL Injection(CRITICAL)— f-string → パラメータ化クエリ(+4/-4)

修正前
query = f"SELECT * FROM products WHERE category = '{category}'"
results = conn.execute(query).fetchall()
修正後
results = conn.execute(
    "SELECT * FROM products WHERE category = ?", (category,)
).fetchall()

f-string による文字列結合を SQLite のパラメータ化クエリ(? プレースホルダ)に置き換えている。加えて、例外ハンドラの str(e) を汎用メッセージに変更し、エラー詳細の漏洩も防いでいる。教科書的な修正であり、そのまま適用可能。

なお、各 PR の description には Pentest ID、Finding ID、CWE 番号、根本原因の説明、修正内容の詳細、影響範囲が構造化されて記載されている。コードレビュー時に Finding の詳細を別途確認する必要がなく、PR 単体で修正の妥当性を判断できる。

PR #2: Reflected XSS(MEDIUM)— |safe フィルタ除去(+1/-1)

修正前
<h2>Results for: {{ query | safe }}</h2>
修正後
<h2>Results for: {{ query }}</h2>

Jinja2 の |safe フィルタを除去し、デフォルトの自動エスケープを有効にしている。1行の変更で正確に脆弱性を解消している。

PR #3: Hardcoded Secret Key(CRITICAL)— 環境変数 / ランダム生成(+5/-1)

ハードコードされた app.secret_key = "insecure-secret-key-for-testing" を、環境変数 FLASK_SECRET_KEY から取得するように変更。未設定の場合は os.urandom(32) でランダム生成し、警告を出力する。セッション偽造を防ぐ実用的な修正。

PR #4: Default Credentials + Plaintext(CRITICAL)— 包括的リファクタリング(+70/-30)

最も大規模な修正。以下の変更を含む。

  • 平文パスワード → werkzeug.security.generate_password_hash によるハッシュ化
  • 共通 TOTP Secret → pyotp.random_base32() による per-user TOTP Secret 生成
  • 起動時の毎回シード → SELECT COUNT(*) で空テーブルの場合のみシード
  • ログイン処理を check_password_hash に変更
  • users テーブルに totp_secret カラムを追加

単一の脆弱性修正を超えて、認証基盤全体をリファクタリングしている。

PR #5: IDOR(MEDIUM)— 所有者チェック追加(+2/-1)

修正後(追加部分)
if session["user_id"] != user_id and session.get("role") != "admin":
    return "Forbidden", 403

自分自身のプロフィールまたは admin ロールの場合のみアクセスを許可する認可チェックを追加。admin の例外を考慮している点が実用的。

PR #6: Privilege Escalation(MEDIUM)— ロールベース認可チェック(+3/-1)

修正後(追加部分)
if session.get("role") != "admin":
    return "Forbidden", 403

/admin/users エンドポイントに admin ロールチェックを追加。シンプルだが正確な修正。

PR #7: Stored XSS(MEDIUM)— |safe フィルタ除去(+1/-1)

PR #2 と同じパターン。コメント表示の {{ c['content'] | safe }} から |safe を除去。

修正品質の評価

#脆弱性深刻度修正の正確性そのまま適用可能か変更量
1SQL InjectionCRITICAL✅ 正確+4/-4
2Reflected XSSMEDIUM✅ 正確+1/-1
3Hardcoded Secret KeyCRITICAL✅ 正確+5/-1
4Default CredentialsCRITICAL✅ 正確⚠️ 要テスト+70/-30
5IDORMEDIUM✅ 正確+2/-1
6Privilege EscalationMEDIUM✅ 正確+3/-1
7Stored XSSMEDIUM✅ 正確+1/-1

全 7 件の修正が技術的に正確だった。PR #4(Default Credentials)のみ +70/-30 の大規模変更であり、テーブルスキーマの変更を含むため適用前にテストが必要だが、修正の方向性は適切である。

まとめ

  • 全 Finding に対して PR が自動生成された — 7 件の脆弱性に対して 7 件の PR が作成され、全て技術的に正確な修正だった。XSS の |safe 除去(1行)から認証基盤のリファクタリング(70行)まで、脆弱性の複雑さに応じた修正が生成された
  • 修正は「検出」の延長線上にある — PR の description には Finding のタイトル、CWE 番号、根本原因、修正内容、影響範囲が記載されており、コードレビューに必要な情報が揃っている。ペンテストの Finding を読んで手動で修正するよりも効率的である
  • NAT Gateway が必要 — Agent のテスト環境はプライベートサブネットで起動するため、GitHub へのアクセスには NAT Gateway が必要。これは追加コスト(NAT Gateway の時間課金 + データ転送)を伴う
  • us-east-1 限定は実用上の制約 — 既存の Agent Space が別リージョンにある場合、Remediation のためだけに us-east-1 で環境を再構築する必要がある

クリーンアップ

リソース削除手順
Terminal
REGION=us-east-1
 
# EC2
aws ec2 terminate-instances --region $REGION --instance-ids <instance-id>
 
# NAT Gateway + EIP
aws ec2 delete-nat-gateway --region $REGION --nat-gateway-id <nat-gw-id>
# NAT Gateway 削除完了を待ってから EIP 解放
aws ec2 release-address --region $REGION --allocation-id <eip-alloc-id>
 
# プライベートサブネット + ルートテーブル
aws ec2 delete-subnet --region $REGION --subnet-id <private-subnet-id>
aws ec2 delete-route-table --region $REGION --route-table-id <private-rt-id>
 
# Security Group
aws ec2 delete-security-group --region $REGION --group-id <sg-id>
 
# Secrets Manager
aws secretsmanager delete-secret --region $REGION \
  --secret-id <secret-name> --force-delete-without-recovery
 
# Target Domain
aws securityagent delete-target-domain --region $REGION \
  --target-domain-id <target-domain-id>
 
# GitHub リポジトリ
gh repo delete <your-org>/auth-vuln-app --yes

共有する

田原 慎也

田原 慎也

ソリューションアーキテクト @ AWS

AWS ソリューションアーキテクトとして金融業界のお客様を中心に技術支援をしており、クラウドアーキテクチャや AI/ML に関する学びをこのサイトで発信しています。このサイトの内容は個人の見解であり、所属企業の公式な意見や見解を代表するものではありません。

関連記事