ip-ranges.json の更新に追従せよ ― Direct Connect 環境の Prefix List 運用と自動化

記事タイトルとURLをコピーする

この記事で分かること

  • DX 環境で Prefix List(経路フィルター)を運用する際の課題
  • ip-ranges.json の更新を SNS で検知する仕組み
  • Lambda で Prefix List を自動更新する設計案
  • CFn テンプレートで一発デプロイする手順

はじめに

ある構築案件で、Direct Connect 経由で Amazon Connect に接続するため、閉域網接続サービスの Prefix List に AWS サービスの IP レンジを登録しました。

初期構築時は ip-ranges.json から IP レンジを取得して登録しましたが、問題は今後の運用をどうするかになります。 ip-ranges.json は随時更新されます。Prefix List が古いままだと、AWS 側の IP 変更時に通信断のリスクがあるわけです。

今回は、Prefix List の運用課題と自動化の設計案を整理します。

課題:ip-ranges.json は変わる

AWS は自社サービスの IP アドレス範囲を ip-ranges.json として公開しており、随時更新しています。

Amazon Connect の CCP ネットワーク設定に関する公式ドキュメント には、以下の記載があります。

Amazon Connect でサポートされる新しい IP アドレス範囲がある場合、一般公開されている ip-ranges.json ファイルに追加されます。追加されたアドレス範囲は、サービスで使用される前に最低 30 日間保持されます。30 日後、新しい IP アドレス範囲を通過するソフトフォントラフィックは、その後 2 週間増加します。2 週間後には、トラフィックは、すべての利用可能な範囲と同等の新しい範囲を介してルーティングされます。

つまり Amazon Connect の場合、ip-ranges.json が更新されてから 最短30日後にソフトフォントラフィックが新 IP レンジを使い始め、その後約2週間かけて段階的に移行します。この期間中に Prefix List を更新しないと、新しい IP レンジへの通信ができず音声通話やCCPの通信断につながる可能性があります。30日と聞くと余裕がありそうですが、DX 環境では閉域網サービスを提供するベンダーとの調整が必要になるケースも多く、実際にはそこまで余裕がなかったりしますよね。

なお、この30日+2週間の挙動は Amazon Connect 固有のものです。他の AWS サービスでは異なるタイミングで IP レンジが使用開始される場合があるので、サービスごとの公式ドキュメントを確認してください。

手動運用はつらい

手動で ip-ranges.json の変更を監視・追従する場合の運用フローは以下の通りです。

  1. ip-ranges.json を定期的にダウンロードして差分を確認
  2. 対象サービス(AMAZON_CONNECT, CLOUDFRONT 等)の IP レンジに変更があるか確認
  3. 変更があれば Prefix List を更新
  4. 更新後の動作確認

これを手動で、30日以内に、漏れなく行う必要があります。頻度が低いとはいえ、忘れたときのインパクト(通信断)が大きいので、手作業では不安です。

自動化案:SNS + Lambda

AWS は ip-ranges.json が更新されたときに SNS 通知を送信する仕組みを提供しています。 こちらを活用すればip-ranges.jsonの変更をトリガーとした半自動化が可能です。

アーキテクチャ

ステップ 1:SNS トピックの購読

AWS は以下の SNS トピックで ip-ranges.json の更新を通知しています。

arn:aws:sns:us-east-1:806199016981:AmazonIpSpaceChanged

Lambda 関数をこのトピックのサブスクライバーとして登録します。リージョンが us-east-1 固定なので、Lambda 関数も us-east-1 に作成する必要がある点に注意です。

また、公式ドキュメントには「通知はエンドポイントの可用性によって異なるため、JSON ファイルを定期的に確認して常に最新の範囲を入手した方がよい」との注意書きがあります。これは SNS 自体が通知を送らないという意味ではなく、受信側(Lambda 等)が一時的に利用不可だった場合に通知を受け取れない可能性があるという趣旨です。Lambda + SNS の組み合わせなら Lambda の可用性は AWS が担保してくれるので実運用上はほぼ問題ないはずですが、より堅牢にしたい場合は EventBridge の定期実行と組み合わせるのも一案ですね。

詳細は AWS IP アドレスの範囲の通知 - Amazon VPC を参照してください。

ステップ 2:Lambda 関数の処理

Lambda 関数では以下の処理を行います。

import json
import os
import urllib.request
import boto3

s3 = boto3.client("s3")
sns = boto3.client("sns")

S3_BUCKET = os.environ["S3_BUCKET"]
S3_KEY = os.environ["S3_KEY"]
SNS_TOPIC_ARN = os.environ["SNS_TOPIC_ARN"]
TARGET_SERVICES = os.environ["TARGET_SERVICES"].split(",")
TARGET_REGIONS = os.environ["TARGET_REGIONS"].split(",")

def to_key(entry):
    return (entry["service"], entry["region"], entry["ip_prefix"])

def lambda_handler(event, context):
    # 1. ip-ranges.json をダウンロード
    url = "https://ip-ranges.amazonaws.com/ip-ranges.json"
    with urllib.request.urlopen(url) as resp:
        ip_ranges = json.loads(resp.read())

    # 2. 対象サービス・リージョンの IP レンジを抽出(サービス名・リージョン付き)
    current = sorted(
        [
            {"service": p["service"], "region": p["region"], "ip_prefix": p["ip_prefix"]}
            for p in ip_ranges["prefixes"]
            if p["service"] in TARGET_SERVICES
            and p["region"] in TARGET_REGIONS
        ],
        key=lambda x: to_key(x),
    )

    # 3. S3 から前回の IP レンジを取得
    try:
        obj = s3.get_object(Bucket=S3_BUCKET, Key=S3_KEY)
        previous = json.loads(obj["Body"].read())
    except s3.exceptions.NoSuchKey:
        previous = []

    # 4. 差分検出
    current_keys = set(to_key(e) for e in current)
    previous_keys = set(to_key(e) for e in previous)
    added_keys = sorted(current_keys - previous_keys)
    removed_keys = sorted(previous_keys - current_keys)

    # 5. 差分があれば SNS で通知(サービス名・リージョン付き)
    if added_keys or removed_keys:
        lines = ["ip-ranges.json has been updated.\n"]
        if added_keys:
            lines.append("Added:")
            for svc, region, prefix in added_keys:
                lines.append(f"  + {prefix}  ({svc} / {region})")
            lines.append("")
        if removed_keys:
            lines.append("Removed:")
            for svc, region, prefix in removed_keys:
                lines.append(f"  - {prefix}  ({svc} / {region})")
            lines.append("")
        lines.append("Please update the Prefix List accordingly.")

        sns.publish(
            TopicArn=SNS_TOPIC_ARN,
            Subject="[ip-ranges.json] Prefix change detected",
            Message="\n".join(lines),
        )

    # 6. 今回の結果を S3 に保存(次回比較用)
    s3.put_object(
        Bucket=S3_BUCKET,
        Key=S3_KEY,
        Body=json.dumps(current),
        ContentType="application/json",
    )

    # ※ 本サンプルは IPv4(prefixes) のみ対象。
    #    IPv6 が必要な場合は ipv6_prefixes も処理すること。

    return {
        "statusCode": 200,
        "prefixes": len(current),
        "added": [f"{p} ({s} / {r})" for s, r, p in added_keys],
        "removed": [f"{p} ({s} / {r})" for s, r, p in removed_keys],
    }

差分検出のために、前回取得した IP レンジを S3 に保存しておき、次回実行時に比較する設計です。S3 のバージョニングを有効にしておけば変更履歴も残るので、「いつ何が変わったか」のトレーサビリティも確保できますね。

ステップ 3:更新方法の選択

Prefix List の更新方法は、利用している閉域網サービスによって異なります。

方式 内容 適したケース
全自動 Lambda から閉域網サービスの API を呼んで Prefix List を直接更新 API が提供されている場合
半自動 Lambda で差分を検出し、管理者に通知。更新は手動 API がない場合、変更管理が必要な場合
通知のみ 変更を検知して通知するだけ まずは小さく始めたい場合

今回の案件では閉域網接続サービス側の API 連携が難しかったため、半自動(通知 + 手動更新) が現実的な落としどころでした。全自動にできればベストですが、まずはここからですかね。

発展:CSV 出力で申込書作成を効率化

閉域網サービスの Prefix List 変更は、ベンダーへの申込書(Excel)で依頼するケースが多いです。Lambda の出力を CSV 形式にしておけば、通知メールの内容をそのまま Excel に取り込み → 申込書完成という運用も可能です。

例えば通知メッセージを以下のような CSV 形式にするだけです。

操作,IPレンジ,サービス,リージョン
追加,15.193.0.0/19,AMAZON_CONNECT,GLOBAL
追加,13.224.0.0/14,CLOUDFRONT,GLOBAL
削除,52.xx.xx.xx/xx,CLOUDFRONT,GLOBAL

Lambda のコードを少し変更するだけで対応できるので、ベンダーへの申請フローに合わせてカスタマイズしてみてください。

デプロイして試す

上記の構成を CFn テンプレート1枚にまとめました。us-east-1 にデプロイすれば、SNS 購読 → Lambda → S3(差分保存)→ メール通知まで構築できます。

テンプレートは GitHub に置いています:ip-ranges-prefix-list-checker.yaml

前提条件

  • AWS CLI がインストール・設定済みであること
  • デプロイ先の IAM ユーザー/ロールに cloudformation:*, lambda:*, s3:*, sns:*, iam:* の権限があること

CFn パラメータ

パラメータ デフォルト値 説明
NotificationEmail (必須) 変更通知の送信先メールアドレス
TargetServices AMAZON_CONNECT,CLOUDFRONT 監視対象の AWS サービス名(カンマ区切り)
TargetRegions ap-northeast-1,GLOBAL 監視対象のリージョン(カンマ区切り)

Amazon Connect 以外のサービスを監視したい場合は、TargetServices を変更してください。サービス名は ip-ranges.jsonservice フィールドに対応しています。

デプロイ手順

# 1. テンプレートをダウンロード
curl -O https://raw.githubusercontent.com/ryoupr/ip-ranges-prefix-list-checker/main/cfn/ip-ranges-prefix-list-checker.yaml

# 2. デプロイ(AmazonIpSpaceChanged が us-east-1 のため --region を明示)
aws cloudformation deploy \
  --region us-east-1 \
  --stack-name ip-ranges-prefix-checker \
  --template-file ip-ranges-prefix-list-checker.yaml \
  --parameter-overrides \
    NotificationEmail=your@email.com \
    TargetServices=AMAZON_CONNECT,CLOUDFRONT \
    TargetRegions=ap-northeast-1,GLOBAL \
  --capabilities CAPABILITY_IAM

デプロイ後、指定したメールアドレスに SNS サブスクリプションの確認メール が届きます。メール内の Confirm subscription リンクをクリックしないと通知が届かないので、忘れずに承認してください。

動作確認

ip-ranges.json の更新は不定期なので、デプロイ直後に動作確認したい場合は Lambda を手動実行します。

aws lambda invoke \
  --region us-east-1 \
  --function-name $(aws cloudformation describe-stacks \
    --region us-east-1 \
    --stack-name ip-ranges-prefix-checker \
    --query "Stacks[0].Outputs[?OutputKey=='FunctionArn'].OutputValue" \
    --output text | xargs -I{} basename {}) \
  --payload '{}' \
  /dev/stdout

初回実行時は S3 に前回分がないため、現在の全 IP レンジが「追加」として検出され、通知メールが届きます。2回目以降は差分がある場合のみ通知されます。

差分検出の動作も確認したい場合は、S3 に保存された前回分の JSON から IP を1つ削除してから Lambda を再実行してみてください。削除した IP が「追加」として検出され、通知が届くはずです。

# S3 から前回分を取得
BUCKET=$(aws cloudformation describe-stacks \
  --region us-east-1 \
  --stack-name ip-ranges-prefix-checker \
  --query "Stacks[0].Outputs[?OutputKey=='BucketName'].OutputValue" \
  --output text)

aws s3 cp s3://${BUCKET}/ip-ranges/previous_prefixes.json previous_prefixes.json

# 先頭の IP を1つ削除して再アップロード
cat previous_prefixes.json | python3 -c "import sys,json; d=json.load(sys.stdin); d.pop(0); print(json.dumps(d))" > modified.json
aws s3 cp modified.json s3://${BUCKET}/ip-ranges/previous_prefixes.json

# Lambda を再実行 → 削除した IP が「追加」として通知される

クリーンアップ

不要になったら以下で削除できます。S3 バケットはバージョニングが有効なので、オブジェクトとバージョンの両方を削除する必要があります。

# バケット名を取得
BUCKET=$(aws cloudformation describe-stacks \
  --region us-east-1 \
  --stack-name ip-ranges-prefix-checker \
  --query "Stacks[0].Outputs[?OutputKey=='BucketName'].OutputValue" \
  --output text)

# オブジェクトと全バージョンを削除
aws s3api list-object-versions --bucket ${BUCKET} \
  --query '{Objects: [].{Key:Key,VersionId:VersionId}}' \
  --output json | \
  aws s3api delete-objects --bucket ${BUCKET} --delete file:///dev/stdin

# スタック削除
aws cloudformation delete-stack \
  --region us-east-1 \
  --stack-name ip-ranges-prefix-checker

まとめ

  • ip-ranges.json は随時更新される。Amazon Connect の場合、更新後 30日+約2週間 で新 IP レンジへ完全移行するため、その前に Prefix List の追従が必要
  • SNS トピック AmazonIpSpaceChanged を購読して変更を自動検知できる
  • Lambda と組み合わせて、差分検出 → 通知の仕組みを構築可能

参考

手嶋 凌一朗 (記事一覧)

クロスインダストリー第1本部クラウドモダナイズ課

書きたい心はあるんです。

趣味はテニスと釣り