【小ネタ】ip-ranges.jsonが更新された際に実際のAWSリソースと比較する

2021.05.17

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

おんづか(恩塚)です。

AWSの各サービスで使用されているIPアドレスは下記で公開されていますよね。

https://ip-ranges.amazonaws.com/ip-ranges.json

またこのjsonが更新された際に通知するためのSNSトピックが公式で用意されています。

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

このSNSトピックをトリガーにLambdaを起動したいときは下記のようにトピックのARNを入力してあげればOKです!

↓トリガー追加画面で入力して

↓こうなってればok

以上です!!

では流石にアレなのでLambdaで欲しい部分のIPだけ取得して自分のアカウントにあるリソースの設定値と比較してみましょう。

サンプル

今回はAWS Client VPN EndpointのAuthorizationに指定しているap-northeast-1(東京)のS3の範囲が最新のip-ranges.jsonと比較してOKかどうかをチェックする、というユースケースを想定してサンプルコードを書きました。

 

サンプルコード(python)

#!/usr/bin/env python

import boto3
import json
import urllib.request

URL = 'https://ip-ranges.amazonaws.com/ip-ranges.json'
SNS_TOPIC_ARN = 'arn:aws:sns:region:xxxxxxxxxxxx:yyyyyyyyyyyy'
CVPN_ENDPOINT_ID = 'cvpn-endpoint-aaaaaaaaaaaaaaaa'

REGION = 'ap-northeast-1'
SERVICE = 'S3'

sns_client = boto3.client('sns')
ec2_client = boto3.client('ec2')
ec2 = boto3.resource('ec2')


def lambda_handler(event, context):
    req = urllib.request.Request(URL)
    with urllib.request.urlopen(req) as res:
        ipranges = json.load(res)

    s3_ips_tokyo = []
    for key in ipranges['prefixes']:
        if key['region'] == REGION and key['service'] == SERVICE:
            s3_ips_tokyo.append(key['ip_prefix'])

    client_vpn_endpoints = ec2_client.describe_client_vpn_authorization_rules(
        ClientVpnEndpointId=CVPN_ENDPOINT_ID)
    authorization_rules = client_vpn_endpoints.get(
        "AuthorizationRules", "NotFound")
    cidr_blocks = []
    for rule in authorization_rules[0:]:
        cidr = rule.get("DestinationCidr", "NotFound")
        cidr_blocks.append(str(cidr))

    ng_found = False
    judges = []
    for s3_ip in s3_ips_tokyo[0:]:
        if s3_ip in cidr_blocks:
            judges.append('OK: ' + str(s3_ip) +
                          ' is included in ' + CVPN_ENDPOINT_ID)
        else:
            judges.append('<NG>: ' + str(s3_ip) +
                          ' is not included in ' + CVPN_ENDPOINT_ID)
            ng_found = True

    messages = []
    messages.append('ip-ranges.json が更新されました')
    messages.append(URL)

    messages.append('')
    messages.append('<<<' + REGION + 'の' + SERVICE + ' IPアドレス範囲>>>')
    for s3_ip in s3_ips_tokyo:
        messages.append(s3_ip)

    messages.append('')
    messages.append('<<<' + CVPN_ENDPOINT_ID + 'のIPアドレス範囲 >> >')
    for cidr in cidr_blocks:
        messages.append(cidr)

    messages.append('')
    messages.append('<<<チェック>>>')
    for j in judges:
        messages.append(j)

    messages.append('')
    messages.append('<<<結果>>>')
    if ng_found:
        messages.append('【アラート】NGが1つ以上あります。')
    else:
        messages.append('全てOKです。')

    response = sns_client.publish(
        TopicArn=SNS_TOPIC_ARN,
        Message='\r\n'.join(messages),
        Subject='ip-ranges.json が更新されました'
    )

    if ng_found:
        response = sns_client.publish(
            TopicArn=SNS_TOPIC_ARN,
            Message='\r\n'.join(messages),
            Subject='【アラート】' + REGION + 'の' + SERVICE + ' IPアドレス範囲のチェックでNGが出ています。'
        )

    return response

今回はとりあえずメールで送ってみましたが文面はこんな感じです。

補足

  • Lambdaには適切な権限を与えましょう。
    • 上記のサンプルコードではSNSトピックを使って送信する権限と対象のClient VPN Endpointへの参照権限を与えています。
  • セキュリティグループ・ルートテーブルでS3のIPアドレス範囲を指定するといったケースはマネージドプレフィックスで指定すればチェック自体不要のはずなので先にこちらを確認しましょう。

後は適宜、リージョンやサービスやメッセージ内容は編集して使っていただければと思います!

少しでもどなたかのお役に立てれば幸いです。

参考