SecurityGroupに対して自分で許可したIPを複数のAWSアカウント・リージョンから探すためのPythonを書いた

どこのAWSアカウント、リージョンにIPを許可したかを覚えていない。そんな状態から各AWSアカウントから許可したIPのSecurity Group Ruleを探すPythonコードを書きました。
2021.08.30

サーモン大好き横山です。

複数の共用AWSアカウントがあり、この度諸事情で離れることになりました。その際ぬくもりのある手作業にて踏み台サーバに対してSecurityGroupでIPを許可していましたので、Ruleを削除しようとしました。しかし、恥ずかしながらどこのアカウント、どこのリージョンにIPを許可したかを覚えていませんでした。そこで、今回許可したIPと自分で設定したSecurity Group RuleのDescriptionから探し出すPythonコードを書きました。

前提

Pythonのライブラリはboto3を使いますので、boto3をインストールしておいてください。比較的新しいAPI( describe_security_group_rules )を使うので、新しいバージョンを使います。

$ pip3 install boto3

あと、探したい環境のAWSCLIの複数Profileの設定をお願いします。(参考: 名前付きプロファイル - AWS Command Line Interface ユーザーガイド

[env1]
aws_access_key_id=AKIAxxxyyy
aws_secret_access_key=salmonsalmonsalmonsalmon

[env2]
aws_access_key_id=AKIAzzzppp
aws_secret_access_key=ikuraikuraikuraikuraikura

[env3]
aws_access_key_id=AKIAaaabbb
aws_secret_access_key=tarafugutarafugutara

あと、これが一番重要なんですが、自分で許可したIPやSecurityGroupRuleのDescriptionを覚えていることです。

処理内容

boto3で各AWSアカウント・リージョンのec2を操作するクライアントを作り、 describe_security_group_rules を取得し、cidrの一致 もしくは descriptionの部分一致するものを探しコンソールに出力する。


コード

変数 search_cidrs に探したいCIDR IPを、 search_descriptions にSecurity Group RuleのDescriptionに書いてあった文言一部が一致するSecurity Group Ruleを探し出して表示するPythonコードです。

from typing import List
from boto3.session import Session


def search_cidr_in_sg_rule(sg_rule, cidr) -> bool:
    """
    security group ruleの内容とcidrが一致するか確認する
    """
    if not "CidrIpv4" in sg_rule:
        return False
    return sg_rule["CidrIpv4"] == cidr


def search_description_in_sg_rule(sg_rule, search_description) -> bool:
    """
    security group ruleの内容とdescriptionが部分一致するか確認する
    """
    if not "Description" in sg_rule:
        return False
    return search_description in sg_rule["Description"]


def search_sg_rules(
    ec2_client, search_cidrs: List = [], search_descriptions: List = []
) -> List:
    """
    検索cidr、descriptionに引っかかるsecurity group ruleの一覧を取得
    """
    page_iter = ec2_client.get_paginator("describe_security_group_rules")
    sg_rules = []
    for page in page_iter.paginate(PaginationConfig=dict(PageSize=1000)):
        sg_rules.extend(page["SecurityGroupRules"])

    hit_sg_rules = {}
    for sg_rule in sg_rules:
        for ip in search_cidrs:
            if search_cidr_in_sg_rule(sg_rule, ip):
                sg_rule_id = sg_rule["SecurityGroupRuleId"]
                if sg_rule_id not in hit_sg_rules:
                    hit_sg_rules[sg_rule_id] = sg_rule

        for word in search_descriptions:
            if search_description_in_sg_rule(sg_rule, word):
                sg_rule_id = sg_rule["SecurityGroupRuleId"]
                if sg_rule_id not in hit_sg_rules:
                    hit_sg_rules[sg_rule_id] = sg_rule

    return hit_sg_rules.values()


def display_sg_group_rule(sg_rules) -> None:
    """
    ターミナルに整形して表示する
    """
    for sg_rule in sg_rules:
        print(
            f"GroupId: {sg_rule['GroupId']:<20}, Description: {sg_rule.get('Description', ''):<30}, ",
            end="",
        )
        print(
            f"Cidr: {sg_rule['CidrIpv4']:<18}, Port: {sg_rule['FromPort']} -> {sg_rule['ToPort']}"
        )


if __name__ == "__main__":
    # 検索IP
    search_cidrs = [
        "182.xxx.xxx.zzz/32",
        "114.xxx.xxx.yyy/32",
        "160.xxx.xxx.www/32",
    ]

    # 検索Description
    search_descriptions = ["yoko"]

    # AWSCLIのプロファイル名
    profiles = [
        "env1",
        "env2",
        "env3",
    ]

    # 全リージョン取得
    # 参考: https://dev.classmethod.jp/articles/iterate-all-region-with-python-boto3/
    regions = [
        region["RegionName"]
        for region in Session(profile_name=profiles[0])
        .client("ec2", region_name="ap-northeast-1")
        .describe_regions()["Regions"]
    ]

    ec2_clients = {
        profile: {
            region: Session(profile_name=profile).client("ec2", region_name=region)
            for region in regions
        }
        for profile in profiles
    }

    for profile_name, ec2_client_regions in ec2_clients.items():
        for region_name, ec2_client in ec2_client_regions.items():
            sg_rules = search_sg_rules(ec2_client, search_cidrs, search_descriptions)
            if sg_rules:
                print(f"=== {profile_name}, {region_name} ===")
                display_sg_group_rule(sg_rules)

結果

search_cidrssearch_descriptions のいずれかに該当するSecurity Group Idと Security Group Rule IdのDescription、Cidr、Portを出力します

=== env1, ap-northeast-1 ===
GroupId: sg-xxxxxxxx         , Description: yokoyama.fumihito             , Cidr: 160.xxx.xxx.www/32 , Port: 22 -> 22
GroupId: sg-xxxxxxxx         , Description: yokoyama.fumihito-tmp         , Cidr: 182.xxx.xxx.zzz/32 , Port: 22 -> 22
GroupId: sg-yyyyyyyy         , Description: home                          , Cidr: 114.xxx.xxx.yyy/32 , Port: 22 -> 22
GroupId: sg-zzzzzzzzzzzzzzzzz, Description: yoko-home                     , Cidr: 114.xxx.xxx.yyy/32 , Port: 22 -> 22
GroupId: sg-xxxxxxxx         , Description: yokoyama.fumihito             , Cidr: 114.xxx.xxx.yyy/32 , Port: 22 -> 22
=== env1, us-west-2 ===
GroupId: sg-ssssssss         , Description: yokoyama                      , Cidr: 114.xxx.xxx.yyy/32 , Port: 22 -> 22
=== env2, ap-northeast-1 ===
GroupId: sg-tttttttt         , Description: yoko home                     , Cidr: 114.xxx.xxx.yyy/32 , Port: 22 -> 22
GroupId: sg-tttttttt         , Description: tmp-deploy(yoko)              , Cidr: 138.xxx.xxx.qqq/32 , Port: 22 -> 22
=== env3, ap-northeast-1 ===
GroupId: sg-pppppppp         , Description: yoko home                     , Cidr: 114.xxx.xxx.yyy/32 , Port: 22 -> 22

まとめ

今回はRuleを削除するのが目的でしたが、削除を含めると誤って削除した場合大変なことになるので、今回は一覧を作成し該当するRuleをマネージドコンソールから削除する対応を行いました。

SecurityGroupに穴を開ける際は、CIDR IPをメモっておくか、Descriptionに決まった文言をつけて作っておけば後で削除し忘れたときにどのSecurityGroupに穴を開けたかをこのPythonコードで調べることができます。

この記事がだれかのお役に立てば幸いです。