AWS – Creating security groups

How to configure nginx for Joomla

AWS describes security groups as virtual firewalls. While this analogy helps newcomers to the EC2 platform understand their purpose and function, it’s probably more accurate to describe them as a firewall-like method of authorizing traffic. They don’t offer all the functionality you’d find in a traditional firewall, but this simplification makes them much easier to use and troubleshoot since they do just a single job and do it reliably.

We’re going to go through a basic scenario involving a web server and a load balancer. Load balancers are vital components of a scalable web application as they allow requests to be spread out over a fleet of instances, instead of sending traffic to a single point of failure. We want the load balancer to respond to HTTP requests from everywhere, and we want to isolate the web server from everything except the load balancer. This is a good security practice as it shields instances from direct external connections.

Getting ready

Before we get started, there’s a small list of things you’ll need to have ready:

  • AmiIdThis is the ID of an AMI in your region. For this recipe, we’d recommend using an AWS Linux AMI because our instance will attempt to run some yum commands on startup.
  • VPCID: This is the ID of the VPC you wish to launch the EC2 server into.
  • SubnetIDs: These are the subnets that our EC2 instance can launch in.

How to do it…

Follow these steps to create a CloudFormation template that launches a stack with a new security group, a load balancer, and an EC2 instance:

  1. Open up your text editor and create a new CloudFormation template. We’re going to start by adding a few Parametersas follows:
AWSTemplateFormatVersion: '2010-09-09' 
      Type: AWS::EC2::AMI::Id 
      Description: AMI ID to launch instances from 
      Type: AWS::EC2::VPC::Id 
      Description: VPC where load balancer and instance will launch 
      Type: List<AWS::EC2::Subnet::Id> 
      Description: Subnets (pick at least 2)
  1. Let’s take a look at a security group we’ll be applying to a public load balancer:
    Type: AWS::EC2::SecurityGroup 
      GroupDescription: Security Group for example ELB 
        - IpProtocol: tcp 
          FromPort: 80 
          ToPort: 80

Anything that resides in this security group will allow inbound TCP connections on port 80 from anywhere ( Note that a security group can contain more than one rule; we’d almost certainly want to also allow HTTPS (443), but we’ve left it out to simplify this recipe.

  1. Now, let’s look at a security group for a web server sitting behind our load balancer:
    Type: AWS::EC2::SecurityGroup 
      GroupDescription: Security Group for example Instance 
        - IpProtocol: tcp 
            Ref: ExampleELBSecurityGroup 
          FromPort: 80 
          ToPort: 80

Here, you can see that we aren’t specifying a source IP range. Instead, we’re specifying a source security group, which we will accept connections from. In this case, we’re saying that we want to allow anything from our ELB security group to connect to anything in our EC2 instance security group on port 80.
Since this is the only rule we’re specifying, our web server will not accept connections from anywhere except our load balancer, to port 80 or otherwise. Our web server isn’t wide open to the internet, and it is even isolated from other instances in our VPC.

Remember that multiple instances can reside in a security group. In a scenario where you have multiple web servers attached to this load balancer, it would be unnecessary, inefficient, and somewhat of an anti-pattern to create a new security group for each web server. Given that all the web servers attached to this load balancer would be serving the same role or function, it makes sense to apply the same security group to them.

This is where the power of security groups really comes in. If an EC2 instance is serving multiple roles – let’s say you have an outbound HTTP proxy server in your VPC that you also want to act as an SMTP relay – then you can simply apply multiple security groups to it.

  1. Next, we need to add our load balancer. This is probably the most basic load balancer configuration you’ll come across. The following code will give you a load balancer and a listener:
        - Fn::Select: [ 0, Ref: SubnetIDs ] 
        - Fn::Select: [ 1, Ref: SubnetIDs ] 
        - Fn::GetAtt: ExampleELBSecurityGroup.GroupId 
    Type: AWS::ElasticLoadBalancingV2::Listener 
        Ref: ExampleLoadBalancer 
        - Type: forward 
            Ref: ExampleTargetGroup 
      Port: 80 
      Protocol: HTTP 
  1. Then, we need to add the target group:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup 
      Port: 80 
      Protocol: HTTP 
          Ref: VPCID 
        - Id: 
            Ref: ExampleEC2Instance
  1. The last resource we’ll add to our template is an EC2 server. This server will install and start nginx when it boots:
    Type: AWS::EC2::Instance 
      InstanceType: t2.nano 
          Fn::Sub: | 
            #!/bin/bash -ex 
            yum install -y nginx 
            service nginx start 
            exit 0 
      Ref: AmiId 
      - Fn::GetAtt: ExampleEC2InstanceSecurityGroup.GroupId 
      Fn::Select: [ 0, Ref: SubnetIDs ]
  1. Lastly, we’re going to add some Outputs to the template to make it a little more convenient to use our ELB and EC2 instances after the stack is created:
        Fn::GetAtt: [ ExampleEC2Instance, PublicDnsName ] 
          - '' 
          - [ 'http://', { 'Fn::GetAtt': [ ExampleLoadBalancer, DNSName ] }, '/' ]
  1. Go ahead and launch this template using the CloudFormation web console or the AWS CLI.

There’s more…

You’ll eventually run into circular dependency issues when configuring security groups using CloudFormation. Let’s say you want all the servers in ExampleEC2InstanceSecurityGroup to be able to access each other on port 22 (SSH). To achieve this, you would need to add this rule as the separate resource type, AWS::EC2::SecurityGroupIngress. This is because a security group can’t refer to itself in CloudFormation when it hasn’t been created yet. This is what the extra resource type looks like:

        Type: AWS::EC2::SecurityGroupIngress 
          IpProtocol: tcp 
            Ref: ExampleEC2InstanceSecurityGroup 
            Ref: ExampleEC2InstanceSecurityGroup 
          FromPort: 22 
          ToPort: 22

Unfortunately, circular dependencies sometimes crop up with CloudFormation, but there is usually an effective workaround.

Differences from traditional firewalls

The following are some of the ways in which a security group differs from traditional firewalls:

  • Security groups can’t be used to explicitly block traffic. Only rules of a permissive kind can be added; deny style rules are not supported. Essentially, all inbound traffic is denied unless you explicitly allow it.
  • Your rules may not refer to source ports; only destination ports are supported.
  • When security groups are created, they will contain a rule that allows all outbound connections. If you remove this rule, new outbound connections will be dropped. It’s a common pattern to leave this rule in place and filter all your traffic using inbound rules only.
  • If you do replace the default outbound rule, it’s important to note that only new outbound connections will be filtered. Any outbound traffic being sent in response to an inbound connection will still be allowed. This is because security groups are stateful.
  • Unlike security groups, network ACLs are not stateful and support DENY rules. You can use them as a complementary layer of security inside your VPC, especially if you need to control traffic flow between subnets.

Comments are closed.