AWSの各サービスで利用されているIPアドレスの更新差分をLambdaとSNSで通知してみる
ご機嫌いかがでしょうか、豊崎です。
前回に引き続きAWSの各サービスで利用されているIPアドレスについての記事になります。
前回のブログはこちら
AWSではAWSのサービスで利用しているIPアドレスをwebで公開しています。 https://ip-ranges.amazonaws.com/ip-ranges.json
AWSの各サービスのIPアドレスは不定期で追加されることがあり、上記URLの内容は更新されていきます。 今回は内容の差分を取得したかったので、S3+Lambda+SNSで更新差分を通知する仕組みを作ってみました。
こんな感じです。
ip-ranges.jsonファイルの更新があったタイミングでLambdaが通知を受け取り、内容の差分を確認してユーザにメール通知する想定です。 AmazonIpSpaceChanged トピックが北バージニア(us-east-1)から通知されるので、今回の作業は全て北バージニアで行なっています。
それでは早速作っていきましょう。
S3
まずはip-ranges.jsonを保存する用のS3バケットを作成します。バケット名は「demo-ipranges」としました。
特別な設定はしていませんが、テスト用にいくつかのファイルを配置しておきました。
SNS
トピックを作成し、差分内容を送信したいメールアドレスでサブスクライブしておきましょう。ここでは「demo-iprange」というトピックを作成し自分のメールアドレスを通知先としました。
Lambda
LambdaにはIAMRoleを割り当てています。S3、SNSの操作権限などが必要なためです。今回のLambda用のIAMRoleにアタッチしたポリシーは以下です。
- AWSLambdaFullAccess
- AmazonS3FullAccess
- AmazonSNSFullAccess
検証のため、大きめに権限を割り当てています。
今回のLambda関数ではjsonファイルの内容を比較して、増えたIPレンジと減ったIPレンジを出力するようにしています。減ったIPレンジは不要かもしれないと思ったのですが、使用しているIPレンジの変更やIPレンジ追加に伴うネットワークアドレスの統合表記(/24から/23など)などの可能性を考えて一応差分を確認しています。
設定したLambdaファンクションは以下です。
言語:python3.6
import urllib.request import json import boto3 import os import sys s3 = boto3.resource('s3') s3client = boto3.client('s3') sns = boto3.client('sns', region_name='us-east-1') def lambda_handler(event, context): ## Webで現在公開されているAWSサービスのIPレンジのJSONを取得 iprangesに格納 url = 'https://ip-ranges.amazonaws.com/ip-ranges.json' req = urllib.request.Request(url) with urllib.request.urlopen(req) as res: ipranges = json.load(res) ##ip-ranges.jsonをS3に保存(日付をつける) bucket_name = os.environ['BUCKET_NAME'] json_key = 'ipranges-' + ipranges['createDate'] + '.json' obj = s3.Object(bucket_name,json_key) obj.put(Body = json.dumps(ipranges)) ## 比較対象がない場合は処理を終了 s3objlist = [] s3obj=s3client.list_objects(Bucket=bucket_name) if len(s3obj['Contents']) == 1: return 'nothing old ipranges' ## 一つ前のip-ranges.jsonを読み込む for i in s3obj['Contents']: s3objlist.append(i['Key']) s3objlist.sort(reverse=True) res = s3client.get_object(Bucket=bucket_name, Key=s3objlist[1]) oldipranges = json.loads(res['Body'].read()) ## 差分(増えた内容)を比較 diff_v4ipinc = [] diff_v6ipinc = [] for new in ipranges['prefixes']: eq_flag = 0 for old in oldipranges['prefixes']: if new['region'] == old['region']: if new['service'] == old['service']: if new['ip_prefix'] == old['ip_prefix']: eq_flag = 1 if eq_flag == 0: diff_v4ipinc.append(new) for new in ipranges['ipv6_prefixes']: eq_flag = 0 for old in oldipranges['ipv6_prefixes']: if new['region'] == old['region']: if new['service'] == old['service']: if new['ipv6_prefix'] == old['ipv6_prefix']: eq_flag = 1 if eq_flag == 0: diff_v6ipinc.append(new) ## 差分(減った内容)を比較 diff_v4ipdec = [] diff_v6ipdec = [] for old in oldipranges['prefixes']: eq_flag = 0 for new in ipranges['prefixes']: if new['region'] == old['region']: if new['service'] == old['service']: if new['ip_prefix'] == old['ip_prefix']: eq_flag = 1 if eq_flag == 0: diff_v4ipdec.append(old) for old in oldipranges['ipv6_prefixes']: eq_flag = 0 for new in ipranges['ipv6_prefixes']: if new['region'] == old['region']: if new['service'] == old['service']: if new['ipv6_prefix'] == old['ipv6_prefix']: eq_flag = 1 if eq_flag == 0: diff_v6ipdec.append(old) ### 差分を整形しSNS Publish snsmsg = ipranges['createDate'] +'.json と '+ s3objlist[1] +'の比較\n\n' snsmsg = snsmsg + '---増えた内容(' + ipranges['createDate'] + '.jsonにだけ記載されている)は以下---\n\n' for msg in diff_v4ipinc: snsmsg = snsmsg + 'region : ' + msg['region'] + ' / service : ' + msg['service'] + ' / ip_prefix : ' + msg['ip_prefix'] + '\n' for msg in diff_v6ipinc: snsmsg = snsmsg + 'region : ' + msg['region'] + ' / service : ' + msg['service'] + ' / ipv6_prefix : ' + msg['ipv6_prefix'] + '\n' snsmsg = snsmsg + '\n---減った内容(' + s3objlist[1] + 'にだけ記載されている)は以下---\n\n' for msg in diff_v4ipdec: snsmsg = snsmsg + 'region : ' + msg['region'] + ' / service : ' + msg['service'] + ' / ip_prefix : ' + msg['ip_prefix'] + '\n' for msg in diff_v6ipdec: snsmsg = snsmsg + 'region : ' + msg['region'] + ' / service : ' + msg['service'] + ' / ipv6_prefix : ' + msg['ipv6_prefix'] + '\n' sns_responce = sns.publish( TopicArn = os.environ['TOPIC_ARN'], Message = snsmsg, Subject = 'diff-ipranges' ) return 'Compleate!'
ちなみにS3バケット名とSNSのTOPIC_ARNは環境変数に登録して、タイムアウトも3分にしています。
事前にS3に配置しておいたjsonファイルの中身を弄って差分(増減)がちゃんと通知されるかを確認してみました。
Lambdaを実行して届いた通知がこちら。ちゃんと届いていますね。
<2019/02/04 追記>
実際の更新で正しく通知されていることを確認しました。
さいごに
意外とIPの差分を確認したいという要望が出てくることがありましたので、作ってみました。
テストでは成功しましたが、実際の変更での通知がAmazonIpSpaceChanged トピックからまだ来てないので、もうしばらく様子見してみます。 何か問題があれば修正するかもしれません。誰かの参考になれば幸いです。