2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2020

09/03/2017: List Deleteable Security Groups

I inherited an AWS VPC that grew organically. This meant about 150 security groups with no clear way to know which ones are important. Until I learned about the describe_network_interfaces function, creating a report about security group usage seemed like too much work.

Even with knowledge of that function, this bit of python was more work than I thought. Therefore, I’m sharing it.

import boto3
security_groups = find_deleteable_security_groups(boto3.client('config'), boto3.client('cloudformation'))
for security_group in security_groups:
  print(security_group)

def find_deleteable_security_groups(ec2Client, cfmClient):
  # Will hold security group information.
  security_groups = {}
  # Will hold network interface information.
  network_interfaces = {}
  # Will hold backward references of security group. For example, securiy group A
  # might have no assets but still be referenced by security group B.
  backward_references = {}

  for security_group in ec2Client.describe_security_groups()['SecurityGroups']:
      sg_id = security_group['GroupId']
      network_interfaces[sg_id] = ec2Client.describe_network_interfaces(Filters=[{'Name': 'group-id', 'Values': [ sg_id ] }])['NetworkInterfaces']
      security_group['referenced_groups'] = {}
      for permission in security_group['IpPermissions']:
          userid_group_pairs = permission['UserIdGroupPairs']
          for group in userid_group_pairs:
              group_id = group['GroupId']
              security_group['referenced_groups'][group_id] = 1
              if group_id not in backward_references:
                  backward_references[group_id] = {}
              backward_references[group_id][sg_id] = 1
      security_groups[sg_id] = security_group

  results = {}
  for sg_id in sorted(security_groups):
      network_interface = network_interfaces[sg_id]
      interface_count = len(network_interface)
      if security_groups[sg_id]['GroupName'] == 'default':
          results[sg_id] = { 'Count' : interface_count,  'Message' : 'Default' }
      else:
          printed = False
          sg = security_groups[sg_id]
          referenced_group_count = len(sg['referenced_groups'])

          backward_reference_count = 0
          if sg_id in backward_references:
              backward_reference_count = len(backward_references[sg_id])

          if interface_count > 0:
              results[sg_id] = { 'Count' : interface_count }
              printed = True
          else:
              response = ec2Client.describe_tags(Filters=[ { 'Name': 'resource-id', 'Values': [ sg_id ] }, { 'Name': 'key', 'Values': [ 'aws:cloudformation:stack-id' ] } ])
              if 'Tags' in response:
                  if len(response['Tags']) > 0:
                      components = response['Tags'][0]['Value'].split('/')
                      stack_name = components[1]
                      stack_id = cfmClient.Stack(stack_name).stack_id
                      results[sg_id] = { 'Count' : interface_count, 'StackName' : stack_name, 'StackId' : stack_id }
                      printed = True

          if printed == False and referenced_group_count > 0:
              results[sg_id] = { 'Count' : interface_count, 'References' : sg['referenced_groups'] }
              printed = True

          if printed == False and backward_reference_count > 0:
              results[sg_id] = { 'Count' : interface_count, 'ReferencedBy' : backward_references[sg_id] }
              printed = True

          if printed == False:
              results[sg_id] = { 'Count' : interface_count, 'Message' : 'Deleteable' }

  return results

04/14/2017: Unable to Connect with AWS Managed Elasticseach - SOLVED

TR;DR - You don’t need to specify a port number when connecting to the AWS managed Elasticsearch service.

That was an easy issue to resolve. While you’re here, let me show you the CloudFormation template that I am using to create a three-node AWS-managed Elasticsearch cluster.

AWSTemplateFormatVersion: 2010-09-09
Description: Elasticsearch Stack

Parameters:
  pAwsAccountId:
    Type: String
    Default: 929717587211
    Description: AWS Account Id
  pSourceIP:
    Type: String
    Default: 34.201.116.224
    Description: IP of the server accessing Elasticsearch

Resources:
  rElasticsearch:
    Type: AWS::Elasticsearch::Domain
    Properties:
      AccessPolicies:
        Version: 2012-10-17
        Statement:
          -
            Effect: Allow
            Principal:
              AWS: "*"
            Action: "es:*"
            Condition:
              IpAddress:
                aws:SourceIp:
                  - !Ref pSourceIP
            Resource: !Join [ "", [ "arn:aws:es:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId", ":domain/", !Ref "AWS::StackName", "/*"  ] ]
      AdvancedOptions:
        rest.action.multi.allow_explicit_index: true
        indices.fielddata.cache.size: ""
      DomainName: !Ref "AWS::StackName"
      EBSOptions:
        EBSEnabled: true
        Iops: 0
        VolumeSize: 20
        VolumeType: gp2
      ElasticsearchClusterConfig:
        InstanceCount: 3
        InstanceType: t2.medium.elasticsearch
      ElasticsearchVersion: 5.1
      Tags:
        - Key: Name
          Value: !Join [ ":", [ !Ref "AWS::StackName", elasticsearch ] ]

Outputs:
  oElasticsearchId:
    Description: the id of elasticsearch
    Value: !Ref rElasticsearch
    Export:
      Name: !Join [ ":", [ !Ref "AWS::StackName", elasticsearch-id ] ]
  oElasticsearchArn:
    Description: the arn of elasticsearch
    Value: !GetAtt rElasticsearch.DomainArn
    Export:
      Name: !Join [ ":", [ !Ref "AWS::StackName", elasticsearch-arn ] ]
  oElasticsearchEndpoint:
    Description: the endpoint of elasticsearch
    Value: !GetAtt rElasticsearch.DomainEndpoint
    Export:
      Name: !Join [ ":", [ !Ref "AWS::StackName", elasticsearch-endpoint ] ]

As soon as the cluster is in the active state, I can use curl (from the source ip server) to access it.

Get the Endpoint from the Elasticseach console.

export ES_ENDPOINT=https://search-app-05-elasticsearch-stack-foy7lzlceajw4walf5yape6iru.us-east-1.es.amazonaws.com
curl -XGET $ES_ENDPOINT/_cat/indices?v