はじめに
SRE1課の石井です。 現在プロジェクトで稼働しているCLBを全てALBに置き換える作業を行っています。
現環境ではインターネットからの通信をALB、またはCLBで受けSecurityGroupsで通信を制御しています。 ALB置き換え後はAWS WAFのIP制限を行うルールに移行してSecurityGroupsでの制御を廃止する予定です。
そのため、ALB,CLB,終点となるEC2のSecurityGroupsのインバウンドルールの内容をIPSetsに移行しなければならないのですが、移行台数が多くマネージドコンソールから手作業で調査を進めるとミスが多発しそうです。
今回、上記の理由からboto3を使ってALB,CLB,両者に紐づく終点となるEC2のSecurityGroupsからインバウンドルールのIPを出力するスクリプトを組んで見ました。 同じような移行作業を行う人の参考になればと思います。
※下記のような通信を行う環境が10個以上あるイメージです。

対象者
本記事は以下の方を対象としています。
- SecurityGroupsが大量に存在する環境からIPSetsへ移行したい人
- boto3の使い方がなんとなくわかる人
前提
- 本記事はスクリプトの内容の解説はしません。
- 本記事のスクリプトは0.0.0.0/0の全開放のインバウンドルールは除外します。
- 本記事のスクリプトは下記の環境で動作確認をしています。
- AWS CloudShell
使い方
- 下記のファイル「SG_Check.py」及び「config.yml」をAWS CloudShell上に作成してください。
- config.ymlに調査する「ALB名」,「CLB名」,「ALBに紐づくターゲットグループ名」をデフォルトの記載サンプルを参考に入力してください。
- 以下のコマンドを実行してください。
python3 SG_Check.py
config.ymlの編集の仕方
調査する下記要素のリソースを記載してください。
- CLB_Name : Classic Loadbarancerの名前
- ALB_Name : Application Loadbarancerの名前
- TargetGroup_Name : ALB_Nameに記載したALBに紐づくTargetGroupの名前
調査対象がCLBのみの場合はALB_Name及びTargetGroup_Nameは削除してください。
- 調査対象がALBのみの場合はALB_Name及びTargetGroup_Nameを記載しCLB_Nameは削除してください。
※一つのdictにALBとCLBを両方記載できますが、私の作業の都合上の理由です。
ファイル名:SG_Check.py
# -*- coding: utf-8 -*-
import boto3
import yaml
from botocore.exceptions import ClientError
def main(data):
#ALBまたはCLBに紐づくEC2のリスト
ec2_id_list=[]
#SGのリスト
sgid_list=[]
#SGのインバウンドルールに記載中のIPリスト
iplist=[]
#文字出力用のリスト
output_text_list=[]
ec2_resource = boto3.resource('ec2')
ec2_client = boto3.client('ec2')
alb_client = boto3.client('elbv2')
clb_client = boto3.client('elb')
#CLB_Nameに紐づくインスタンスIDとソースSGを取得
if data.get('CLB_Name') is not None:
describe_clb_list=clb_client.describe_load_balancers()
for clb in describe_clb_list['LoadBalancerDescriptions']:
if data.get('CLB_Name') in clb['LoadBalancerName']:
sgid_list=clb['SecurityGroups']
for i in clb['Instances']:
x=i.get('InstanceId')
ec2_id_list.extend( x if type(x) == list else [x] )
else:
if 'x' in locals():
x='CLB_Name:' + data.get('CLB_Name')
output_text_list.extend( x if type(x) == list else [x] )
else:
x='存在しないCLB_Name:' + data.get('CLB_Name')
output_text_list.extend( x if type(x) == list else [x] )
if data.get('ALB_Name') is not None:
#ALB_Nameに紐づくインスタンスIDとソースSGを取得
describe_alb_list=alb_client.describe_load_balancers()
for alb in describe_alb_list['LoadBalancers']:
if data.get('ALB_Name') in alb['LoadBalancerName']:
x=alb.get('SecurityGroups')
sgid_list.extend( x if type(x) == list else [x] )
try:
describe_tgg = alb_client.describe_target_groups(Names=[data['TargetGroup_Name']])
for d in describe_tgg['TargetGroups']:
tgg_health = alb_client.describe_target_health(TargetGroupArn=d['TargetGroupArn'])
for o in tgg_health['TargetHealthDescriptions']:
x=o['Target'].get('Id')
ec2_id_list.extend( x if type(x) == list else [x] )
#ターゲットグループがなければdescribe_target_groupsがエラーになるのでキャッチ
except ClientError as e:
if e.response['Error']['Code'] == 'TargetGroupNotFound':
print('!!存在しないターゲットグループ!!:' + data.get('TargetGroup_Name'))
except KeyError:
print('!!ALBに対応するターゲットグループを指定してください!!:' + data.get('ALB_Name'))
break
finally:
pass
else:
if 'd' in locals():
x='ALB_Name:' + data.get('ALB_Name')
output_text_list.extend( x if type(x) == list else [x] )
else:
x='存在しないALB_Name:' + data.get('ALB_Name')
output_text_list.extend( x if type(x) == list else [x] )
#CLBまたはALBに紐づくインスタンスのSGを取得
for i in ec2_id_list:
describe_instance=ec2_client.describe_instances(InstanceIds=[i])
for u in describe_instance['Reservations']:
for d in u['Instances']:
for c in d['SecurityGroups']:
x=c.get('GroupId')
sgid_list.extend( x if type(x) == list else [x] )
#取得したsgid_listからインバウンドの定義をiplistへ格納する
for sgid in sgid_list:
security_group = ec2_resource.SecurityGroup(sgid)
for u in security_group.ip_permissions:
for i in u['IpRanges']:
x=i.get('CidrIp')
iplist.extend( x if type(x) == list else [x] )
uniq_iplist = set(iplist)
#全開放のインバウンドルールは除外する
if '0.0.0.0/0' in uniq_iplist:
uniq_iplist.remove('0.0.0.0/0')
if not uniq_iplist:
uniq_iplist = 'インバウンドルールなし、または0.0.0.0/0が設定中'
return uniq_iplist , output_text_list
if __name__ == '__main__':
with open('lb_config.yml', 'r') as yml:
config = yaml.safe_load(yml)
for data in config:
print('----------')
iplist , output_text_list = main(data)
print(' , '.join(output_text_list))
print(iplist)
ファイル名:config.yml
---
-
CLB_Name: AAA_CLB
-
ALB_Name: BBB_ALB
TargetGroup_Name: BBB_TargetGroup
-
CLB_Name: CCC_CLB
ALB_Name: CCC_ALB
TargetGroup_Name: CCC_TargetGroup
-
CLB_Name: HOGE
ALB_Name: HUGA
TargetGroup_Name: HUGA_TargetGroup
出力結果サンプル
CLB_Name:CLB-XXX
{'192.168.1.1/32', '10.0.0.0/8'}
----------
ALB_Name:ALB-YYY
インバウンドルールなし、または0.0.0.0/0が設定中
----------
CLB_Name:CLB-ZZZ,ALB_Name:ALB-UUU
{'192.168.1.1/32', '10.0.0.0/8'}
----------
存在しないCLB_Name:HOGE , 存在しないALB_Name:HUGA
インバウンドルールなし、または0.0.0.0/0が設定中