Narrowing security group rules

Before Reading

Best practice to create security groups

  • Reference security group IDs instead of individual IPs (State of the art)
  • If there’s a need for IPs, be as specific as possible, e.g. IPs with /32 range

Tech Stacks

Similar to the previous automation articles, the tech stack to accomplish this tool is the following:

  • AWS Lambda
  • Python using Boto3
  • AWS S3

Again, I will try to steer away from the grainy details since it is not the scope of the article.There are two major resources which need to be handled in this automation piece, EC2 and S3. The main functions that will be used are:

  • ec2.describe_security_groups(),
  • ec2.revoke_security_group_ingress()
  • s3.put_object().

I will leave references to each of these functions below.

Goal

The objective for this automation piece is to close all the potential threatening rules that might jeopardize the security of the servers. In my experience working with security groups, I have had to be making tests by adding and removing open rules, such as 0.0.0.0/0, in order to make sure that connectivity can be established into a server. However, there were times where I missed closing these open rules, leaving a risky entry points for malicious attacks.

Therefore, I decided to create this tool to automate the closure of wide open rules in security groups.

This is particularly useful when there are multiple security groups and there are rules that need to be deleted from all.

Here’s an example:

Before:

Security Group Rules wide open

After:

Security Group Rules after function was executed

Logic

There are three fundamental for loops that need to be laid out before executing the main logic of the function.

# 1st: goes around security groups
for sg in response['SecurityGroups']:

# 2nd: goes around the rules for security groups
for ip in sg['IpPermissions']:

# 3rd: goes around the IP Ranges || identifying wide open rules
for cidr in ip['IpRanges']:

When calling the Boto3 function describe_security_groups(), the response is going to look something like the following:

{
'SecurityGroups': [
{
...
'IpPermissions': [
...
'IpRanges': [
'CidrIp': 'string' ## key component
...
]
...
]
...
}
],
...
}

Hence, this is the reason why there are three different for loops. The tool needs to drill into the root of the security group to find the potential wide open IPs that might be vulnerability for potential attacks.

After understanding this, three simple steps need to be followed in this code:

Step 1: Identifying

The first step is to identify which security groups have rules that are too open and need closed down. This logic will be encapsulated in an if-statement where the cidr[‘CidrIp’] is going to be evaluated to the predetermined value of open_rules, in this case 0.0.0.0/0.

open_rules='0.0.0.0/0' # this would be depending on your needs
if cidr['CidrIp'] == open_rules:

Important Note: there has to be another if statement right before the logic above in order to determine whether there are any identifiable ports in the security group. Otherwise the function is going to error out.

exFromPort = 'FromPort'
if exFromPort in ip:

Step 2: Logging

The second step is to log the actual rules that are going to be closed. This is particularly crucial when trying to revert changes that were made to a security group.

For this, the code is writing to a text file inside of the lambda function with the security groups (groupdId, groupName, fromPort, toPort, ipProtocol). After writing to the file only the wide open rules, the file gets sent to an S3 bucket for durable storage.

# declaring variables for S3 copy
bucket_name = "YOUR_BUCKET"
file_name = "YOUR_FILE_NAME.txt"
lambda_path = "/tmp/" + file_name # mandatory- stores file in lambda
s3_path = "/" + file_name # mandatory- specify the path in S3
# making the string
string = writeToFile(groupId, groupName, fromPort, toPort, ipProtocol, open_rules)
finalString += string
# sending data to S3
encoded_string = finalString.encode("utf-8")
s3 = boto3.resource("s3")
s3.Bucket(bucket_name).put_object(Key=s3_path, Body=encoded_string)

Step 3: Closing

Last but certainly not least, closing the open rules will reside inside of the if-statement logic from step 1. This is the most important component of the whole automation piece since it is in charge of closing the rules that we have identified and logged in the previous two steps.

client.revoke_security_group_ingress(
CidrIp=cidrIp,
GroupId=groupId,
IpProtocol=ipProtocol,
FromPort=fromPort,
ToPort=toPort
)

Complete code 💻

References

AWS Lambda: https://docs.aws.amazon.com/lambda/latest/dg/welcome.html

Boto3:

Github: https://github.com/edreinoso/aws_devops/tree/master/security-group-checker

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Ed Reinoso

Ed Reinoso

76 Followers

Cloud Engineer with a particular passion for AWS automation