{"id":366,"date":"2016-06-06T04:02:49","date_gmt":"2016-06-06T04:02:49","guid":{"rendered":"http:\/\/tuxlabs.com\/?p=366"},"modified":"2016-06-16T05:19:00","modified_gmt":"2016-06-16T05:19:00","slug":"how-to-use-boto-to-audit-your-aws-ec2-instance-security-groups","status":"publish","type":"post","link":"https:\/\/tuxlabs.com\/?p=366","title":{"rendered":"How to use Boto to Audit your AWS EC2 instance security groups"},"content":{"rendered":"<p>Boto is a Software Development Kit for accessing the AWS API&#8217;s using Python.<\/p>\n<p><a href=\"https:\/\/github.com\/boto\/boto3\">https:\/\/github.com\/boto\/boto3<\/a><\/p>\n<p>Recently, I needed to determine how many of my EC2 instances were spawned in a public subnet, that also had security groups with wide open access on any port via any protocol to the instances. Because I have an IGW (Internet Gateway) in my VPC&#8217;s and properly setup routing tables, instances launched in the public subnets with wide open security groups (allowing ingress traffic from any source) is a really bad thing \ud83d\ude42<\/p>\n<p>Here is the code I wrote to identify these naughty instances. It will require slight modifications in your own environment, to match your public subnet IP Ranges, EC2 Tags, and Account names.<\/p>\n<pre class=\"lang:python decode:true \">#!\/usr\/bin\/env python\r\n__author__ = 'Jason Riedel'\r\n__description__ = 'Find EC2 instances provisioned in a public subnet that have security groups allowing ingress traffic from any source.'\r\n__date__ = 'June 5th 2016'\r\n__version__ = '1.0'\r\n\r\nimport boto3\r\n\r\ndef find_public_addresses(ec2):\r\n    public_instances = {}\r\n    instance_public_ips = {}\r\n    instance_private_ips = {}\r\n    instance_ident = {}\r\n    instances = ec2.instances.filter(Filters=[{'Name': 'instance-state-name', 'Values': ['running'] }])\r\n\r\n    # Ranges that you define as public subnets in AWS go here.\r\n    public_subnet_ranges = ['10.128.0', '192.168.0', '172.16.0']\r\n\r\n    for instance in instances:\r\n        # I only care if the private address falls into a public subnet range\r\n        # because if it doesnt Internet ingress cant reach it directly anyway even with a public IP\r\n        if any(cidr in instance.private_ip_address for cidr in public_subnet_ranges):\r\n            owner_tag = \"None\"\r\n            instance_name = \"None\"\r\n            if instance.tags:\r\n                for i in range(len(instance.tags)):\r\n                    #comment OwnerEmail tag out if you do not tag your instances with it.\r\n                    if instance.tags[i]['Key'] == \"OwnerEmail\":\r\n                        owner_tag = instance.tags[i]['Value']\r\n                    if instance.tags[i]['Key'] == \"Name\":\r\n                        instance_name = instance.tags[i]['Value']\r\n            instance_ident[instance.id] = \"Name: %s\\n\\tKeypair: %s\\n\\tOwner: %s\" % (instance_name, instance.key_name, owner_tag)\r\n            if instance.public_ip_address is not None:\r\n                values=[]\r\n                for i in range(len(instance.security_groups)):\r\n                    values.append(instance.security_groups[i]['GroupId'])\r\n                public_instances[instance.id] = values\r\n                instance_public_ips[instance.id] = instance.public_ip_address\r\n                instance_private_ips[instance.id] = instance.private_ip_address\r\n\r\n    return (public_instances, instance_public_ips,instance_private_ips, instance_ident)\r\n\r\ndef inspect_security_group(ec2, sg_id):\r\n    sg = ec2.SecurityGroup(sg_id)\r\n\r\n    open_cidrs = []\r\n    for i in range(len(sg.ip_permissions)):\r\n        to_port = ''\r\n        ip_proto = ''\r\n        if 'ToPort' in sg.ip_permissions[i]:\r\n            to_port = sg.ip_permissions[i]['ToPort']\r\n        if 'IpProtocol' in sg.ip_permissions[i]:\r\n            ip_proto = sg.ip_permissions[i]['IpProtocol']\r\n            if '-1' in ip_proto:\r\n                ip_proto = 'All'\r\n        for j in range(len(sg.ip_permissions[i]['IpRanges'])):\r\n            cidr_string = \"%s %s %s\" % (sg.ip_permissions[i]['IpRanges'][j]['CidrIp'], ip_proto, to_port)\r\n\r\n            if sg.ip_permissions[i]['IpRanges'][j]['CidrIp'] == '0.0.0.0\/0':\r\n                #preventing an instance being flagged for only ICMP being open\r\n                if ip_proto != 'icmp':\r\n                    open_cidrs.append(cidr_string)\r\n\r\n    return open_cidrs\r\n\r\n\r\nif __name__ == \"__main__\":\r\n    #loading profiles from ~\/.aws\/config &amp; credentials\r\n    profile_names = ['de', 'pe', 'pde', 'ppe']\r\n    #Translates profile name to a more friendly name i.e. Account Name\r\n    translator = {'de': 'Platform Dev', 'pe': 'Platform Prod', 'pde': 'Products Dev', 'ppe': 'Products Prod'}\r\n    for profile_name in profile_names:\r\n        session = boto3.Session(profile_name=profile_name)\r\n        ec2 = session.resource('ec2')\r\n\r\n        (public_instances, instance_public_ips, instance_private_ips, instance_ident) = find_public_addresses(ec2)\r\n\r\n        for instance in public_instances:\r\n            for sg_id in public_instances[instance]:\r\n                open_cidrs = inspect_security_group(ec2, sg_id)\r\n                if open_cidrs: #only print if there are open cidrs\r\n                    print \"==================================\"\r\n                    print \" %s, %s\" % (instance, translator[profile_name])\r\n                    print \"==================================\"\r\n                    print \"\\tprivate ip: %s\\n\\tpublic ip: %s\\n\\t%s\" % (instance_private_ips[instance], instance_public_ips[instance], instance_ident[instance])\r\n                    print \"\\t==========================\"\r\n                    print \"\\t open ingress rules\"\r\n                    print \"\\t==========================\"\r\n                    for cidr in open_cidrs:\r\n                        print \"\\t\\t\" + cidr\r\n<\/pre>\n<p>To run this you also need to setup your .aws\/config and .aws\/credentials file.<\/p>\n<p><a href=\"http:\/\/docs.aws.amazon.com\/cli\/latest\/userguide\/cli-chap-getting-started.html#cli-config-files\">http:\/\/docs.aws.amazon.com\/cli\/latest\/userguide\/cli-chap-getting-started.html#cli-config-files<\/a><\/p>\n<p>Email me tuxninja [at] tuxlabs.com if you have any issues.<br \/>\nBoto is awesome \ud83d\ude42 Note so is the AWS CLI, but requires some shell scripting as opposed to Python to do cool stuff.<\/p>\n<p>The github for this code here\u00a0<a href=\"https:\/\/github.com\/jasonriedel\/AWS\/blob\/master\/sg-audit.py\">https:\/\/github.com\/jasonriedel\/AWS\/blob\/master\/sg-audit.py<\/a><\/p>\n<p>Enjoy !<\/p>\n","protected":false},"excerpt":{"rendered":"<a href=\"https:\/\/tuxlabs.com\/?p=366\" rel=\"bookmark\" title=\"Permalink to How to use Boto to Audit your AWS EC2 instance security groups\"><p>Boto is a Software Development Kit for accessing the AWS API&#8217;s using Python. https:\/\/github.com\/boto\/boto3 Recently, I needed to determine how many of my EC2 instances were spawned in a public subnet, that also had security groups with wide open access on any port via any protocol to the instances. Because I have an IGW (Internet [&hellip;]<\/p>\n<\/a>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[131,130,8,9,78,12],"tags":[23,138,141,139,55,140],"class_list":{"0":"post-366","1":"post","2":"type-post","3":"status-publish","4":"format-standard","6":"category-aws","7":"category-cloud","8":"category-programming","9":"category-python","10":"category-security","11":"category-systems-administration","12":"tag-aws","13":"tag-boto","14":"tag-boto3","15":"tag-ec2","16":"tag-python","17":"tag-security-groups","18":"h-entry","19":"hentry"},"_links":{"self":[{"href":"https:\/\/tuxlabs.com\/index.php?rest_route=\/wp\/v2\/posts\/366","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tuxlabs.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tuxlabs.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tuxlabs.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tuxlabs.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=366"}],"version-history":[{"count":7,"href":"https:\/\/tuxlabs.com\/index.php?rest_route=\/wp\/v2\/posts\/366\/revisions"}],"predecessor-version":[{"id":376,"href":"https:\/\/tuxlabs.com\/index.php?rest_route=\/wp\/v2\/posts\/366\/revisions\/376"}],"wp:attachment":[{"href":"https:\/\/tuxlabs.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=366"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tuxlabs.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=366"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tuxlabs.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=366"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}