← Back to DevBytes

VPC Security: IAM Policies and Network Security

VPC Security: IAM Policies and Network Security

Securing a Virtual Private Cloud (VPC) is one of the most critical responsibilities for any cloud engineer. A VPC isolates your workloads from the public internet, but isolation alone is not enough. You must layer identity-based controls (IAM policies) with network-based controls (security groups, network ACLs, route tables) to build defense in depth. This tutorial walks through both layers with practical, production-ready examples.

What Is VPC Security?

VPC security is the combination of controls that govern who can interact with your cloud network and what traffic is allowed to flow within it. It rests on two pillars:

Together, these controls answer two distinct questions: "Can this principal manage this resource?" (IAM) and "Can this packet reach this resource?" (network). Both must pass for a connection to succeed.

Why It Matters

Misconfigured VPCs are a leading cause of cloud breaches. A single overly permissive security group rule can expose a database to the internet, while an overly broad IAM policy can let a compromised role destroy your entire network infrastructure. Treating IAM and network security as separate concerns leaves gaps; treating them as a unified strategy closes them. Proper VPC security also supports compliance frameworks such as PCI-DSS, HIPAA, and SOC 2, which explicitly require least-privilege access and network segmentation.

Understanding IAM Policies for VPC Resources

IAM Policy Basics

An IAM policy is a JSON document that defines permissions. Each statement contains an Effect, an Action, a Resource, and optional Condition keys. For VPC resources, actions use the ec2 service prefix because VPC components are managed under Amazon EC2's API namespace.

The following policy grants a developer read-only access to describe VPCs, subnets, and route tables, but prevents any modifications:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DescribeVpcResources",
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeVpcs",
        "ec2:DescribeSubnets",
        "ec2:DescribeRouteTables",
        "ec2:DescribeSecurityGroups",
        "ec2:DescribeNetworkAcls"
      ],
      "Resource": "*"
    }
  ]
}

Describe actions in EC2 do not support resource-level permissions, so Resource must be "*". For mutating actions, you should restrict the resource ARN.

Restricting Actions to Specific VPCs

To allow a user to launch instances only into a specific VPC, combine resource-level permissions with condition keys. The ec2:Vpc condition key lets you scope actions to a particular VPC ARN:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "LaunchInstancesInSpecificVpc",
      "Effect": "Allow",
      "Action": [
        "ec2:RunInstances",
        "ec2:StartInstances",
        "ec2:StopInstances"
      ],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "ec2:Vpc": "arn:aws:ec2:us-east-1:123456789012:vpc/vpc-0abc123def456789a"
        }
      }
    }
  ]
}

Enforcing Security Group Rules via IAM

You can prevent users from creating security groups that allow unrestricted inbound traffic by using the ec2:AuthorizeSecurityGroupIngress action with a condition that denies 0.0.0.0/0. This is a powerful way to enforce network policy at the identity layer:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyInboundOpenToWorld",
      "Effect": "Deny",
      "Action": "ec2:AuthorizeSecurityGroupIngress",
      "Resource": "arn:aws:ec2:*:*:security-group/*",
      "Condition": {
        "StringEquals": {
          "ec2:SourceIp": "0.0.0.0/0"
        }
      }
    }
  ]
}

This policy ensures that even if a user has broader permissions elsewhere, they cannot open a security group to the entire internet on ports that would expose sensitive services.

Network Security Controls

Security Groups

Security groups are stateful, instance-level firewalls. They allow you to specify allow rules for inbound and outbound traffic. By default, all inbound traffic is denied and all outbound traffic is allowed. Security groups evaluate all rules before deciding whether to allow traffic, and you cannot write explicit deny rules.

The following AWS CLI command creates a security group for a web tier and adds an inbound rule allowing HTTPS from anywhere, plus HTTP from a specific corporate CIDR:

# Create the security group
aws ec2 create-security-group \
  --group-name web-tier-sg \
  --description "Security group for public web tier" \
  --vpc-id vpc-0abc123def456789a

# Allow HTTPS from anywhere
aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 443 \
  --cidr 0.0.0.0/0

# Allow HTTP from corporate network only
aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 80 \
  --cidr 203.0.113.0/24

A best practice is to reference other security groups by ID rather than CIDR blocks when traffic flows between tiers. This keeps rules tightly coupled to workload identity rather than IP addresses that may change:

# Allow app tier to reach database tier on 5432
aws ec2 authorize-security-group-ingress \
  --group-id sg-database-tier \
  --protocol tcp \
  --port 5432 \
  --source-security-group-id sg-app-tier

Network ACLs

Network ACLs (NACLs) are stateless, subnet-level firewalls. Unlike security groups, they support both allow and deny rules and they evaluate rules in order, stopping at the first match. NACLs are useful for blocking known malicious IP ranges and for adding a second layer of defense at the subnet boundary.

Here is an example of creating a custom NACL that denies traffic from a suspicious CIDR and allows all other inbound traffic:

# Create a network ACL in a VPC
aws ec2 create-network-acl \
  --vpc-id vpc-0abc123def456789a

# Deny inbound traffic from a malicious range (rule number 100)
aws ec2 create-network-acl-entry \
  --network-acl-id acl-0abc123def456789a \
  --rule-number 100 \
  --protocol -1 \
  --rule-action deny \
  --cidr-block 198.51.100.0/24 \
  --ingress

# Allow all other inbound traffic (rule number 200)
aws ec2 create-network-acl-entry \
  --network-acl-id acl-0abc123def456789a \
  --rule-number 200 \
  --protocol -1 \
  --rule-action allow \
  --cidr-block 0.0.0.0/0 \
  --ingress

Because NACLs are stateless, you must also create corresponding outbound rules. Forgetting the return traffic rule is a common mistake that breaks connectivity even when the inbound rule is correct.

Route Tables and Segmentation

Route tables determine where traffic from a subnet goes. By carefully designing route tables, you can enforce segmentation between tiers. A common pattern is to use a public subnet for load balancers, private subnets for application servers, and isolated subnets for databases with no route to the internet.

# Create a route table for an isolated database subnet
aws ec2 create-route-table \
  --vpc-id vpc-0abc123def456789a

# Notice: no route to 0.0.0.0/0 is added.
# Only local VPC traffic exists by default, keeping the subnet isolated.

VPC Endpoints

VPC endpoints let you connect to AWS services privately without traversing the public internet. Gateway endpoints for S3 and DynamoDB are free and route traffic through the AWS backbone. Interface endpoints use PrivateLink and assign private IP addresses within your subnets. Using endpoints reduces the need for NAT gateways and removes a potential exfiltration path.

# Create a gateway endpoint for S3
aws ec2 create-vpc-endpoint \
  --vpc-id vpc-0abc123def456789a \
  --service-name com.amazonaws.us-east-1.s3 \
  --route-table-ids rtb-0abc123def456789a

Bringing IAM and Network Security Together

The strongest VPC security posture combines both layers. For example, you can use a service control policy (SCP) or IAM policy to require that all new security groups be tagged with an owner, and use a network configuration pipeline to validate that no security group allows SSH from 0.0.0.0/0. The IAM layer prevents unauthorized changes, while the network layer limits the blast radius if a change slips through.

A practical example is requiring that all EC2 instances use a pre-approved security group. The following IAM policy denies RunInstances unless the specified security group is used:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "RequireApprovedSecurityGroup",
      "Effect": "Deny",
      "Action": "ec2:RunInstances",
      "Resource": "arn:aws:ec2:*:*:instance/*",
      "Condition": {
        "StringNotEquals": {
          "ec2:SecurityGroup": "arn:aws:ec2:us-east-1:123456789012:security-group/sg-approved"
        }
      }
    }
  ]
}

Best Practices

Conclusion

VPC security is not a single feature but a layered discipline that unifies identity and network controls. IAM policies define who can shape your network and how they can configure it, while security groups, network ACLs, route tables, and VPC endpoints govern the actual flow of packets. By applying least-privilege IAM, default-deny network rules, careful segmentation, and continuous validation, you build a VPC that resists both accidental misconfiguration and deliberate attack. Treat these two layers as one strategy, automate its enforcement, and revisit it regularly as your workloads evolve.

— Ad —

Google AdSense will appear here after approval

← Back to all articles