VPC Security groups should only allow unrestricted incoming traffic for authorized ports
Security groups are the primary instance-level firewall in AWS. When a rule allows 0.0.0.0/0 on ports like 22, 3389, or 3306, you expose management interfaces and databases to the entire internet. Attackers actively scan for these ports, and a single misconfigured rule can lead to credential brute-forcing or direct data exfiltration.
Limiting unrestricted ingress to known web-serving ports (80, 443) forces teams to use bastion hosts, VPNs, or AWS Systems Manager Session Manager for administrative access. That significantly reduces the blast radius when a security group is misconfigured.
Retrofit consideration
Existing security groups with unrestricted ingress on non-authorized ports (e.g., 22 for SSH bastion hosts) will fail immediately. You must either restrict the source CIDR, migrate to Session Manager, or add the port to the authorized list if business-justified. Audit all groups with aws ec2 describe-security-groups --filters Name=ip-permission.cidr,Values=0.0.0.0/0 before enforcing.
Implementation
Choose the approach that matches how you manage Terraform.
Use the compliance.tf module to enforce this control by default. See get started with compliance.tf.
module "ec2_instance" {
source = "pcidss.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
security_group_ingress_rules = {
from_port = 80
}
}
module "ec2_instance" {
source = "nist80053.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
security_group_ingress_rules = {
from_port = 80
}
}
module "ec2_instance" {
source = "fedrampmoderate.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
security_group_ingress_rules = {
from_port = 80
}
}
module "ec2_instance" {
source = "cisv80ig1.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
security_group_ingress_rules = {
from_port = 80
}
}
module "ec2_instance" {
source = "nist800171.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
security_group_ingress_rules = {
from_port = 80
}
}
module "ec2_instance" {
source = "nydfs23.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
security_group_ingress_rules = {
from_port = 80
}
}
module "ec2_instance" {
source = "cccsmedium.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
security_group_ingress_rules = {
from_port = 80
}
}
module "ec2_instance" {
source = "iso270012013.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
security_group_ingress_rules = {
from_port = 80
}
}
module "ec2_instance" {
source = "pcidssv321.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
security_group_ingress_rules = {
from_port = 80
}
}
If you use terraform-aws-modules/ec2-instance/aws, set the right module inputs for this control. You can later migrate to the compliance.tf module with minimal changes because it is compatible by design.
module "ec2_instance" {
source = "terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
security_group_ingress_rules = {
from_port = 80
}
}
Use AWS provider resources directly. See docs for the resources involved: aws_vpc_security_group_ingress_rule.
resource "aws_vpc_security_group_ingress_rule" "this" {
cidr_ipv4 = "0.0.0.0/0"
ip_protocol = "tcp"
security_group_id = "sg-abc12345"
to_port = 443
from_port = 80
}
What this control checks
This control evaluates aws_vpc_security_group_ingress_rule resources (or the older aws_security_group_rule with type = "ingress"). A rule fails when cidr_ipv4 is "0.0.0.0/0" or cidr_ipv6 is "::/0" and the from_port/to_port range covers any port outside the authorized list. To pass, either restrict the source CIDR to a specific range (e.g., "10.0.0.0/8") or ensure the port matches an authorized value. For the default configuration, only rules targeting exactly port 80 or 443 with a world-open CIDR pass. A rule with from_port = 0 and to_port = 65535 always fails because it covers every unauthorized port. If your organization needs additional authorized ports (e.g., 8443 for an API gateway), update the control parameter authorizedTcpPorts to include them.
Common pitfalls
Inline ingress blocks can complicate policy checks
Policy enforcement against the
ingressblock insideaws_security_groupis less consistent than against standaloneaws_vpc_security_group_ingress_ruleresources. AWS Config catches the violation at runtime either way, but Terraform-level checks may miss inline blocks depending on tool support. Migrate to standalone rule resources to get reliable shift-left enforcement.Port ranges that span authorized and unauthorized ports
A rule with
from_port = 80andto_port = 90silently opens ports 81 through 90 in addition to 80. The control flags the entire range because it includes unauthorized ports. Always use exact single-port rules (from_port = 80,to_port = 80) rather than ranges that happen to include an authorized port.IPv6 CIDR often overlooked
Lock down
cidr_ipv4and forgetcidr_ipv6 = "::/0"and you still have unrestricted internet access on IPv6-enabled VPCs. Both address families are evaluated independently. If your VPC has IPv6 enabled, audit both arguments on every ingress rule.Protocol -1 (all traffic) always fails
Setting
ip_protocol = "-1"on anaws_vpc_security_group_ingress_rulewith a world-open CIDR allows all ports and protocols. There is no authorized-ports check that can save it because the rule implicitly includes every unauthorized port. Replace it with explicit protocol and port rules.Default security group with inherited rules
Get this wrong and untracked 0.0.0.0/0 rules accumulate in the default security group outside Terraform state, with nothing to flag them until an audit. Import the default security group using
aws_default_security_groupand manage its rules explicitly so any ingress added outside Terraform shows up as drift.
Audit evidence
Auditors expect Config rule evaluation results for the managed rule vpc-sg-open-only-to-authorized-ports showing all security groups as COMPLIANT. A supplementary report listing each security group with unrestricted ingress, the associated ports, and the business justification for any non-default authorized ports rounds out the documentation package. CloudTrail AuthorizeSecurityGroupIngress events show that new rules were reviewed and approved before taking effect. Screenshots from the VPC console covering inbound rules for security groups attached to public-facing instances confirm no unauthorized open ports are present.
Framework-specific interpretation
PCI DSS v4.0: Requirement 1.3 allows only necessary and authorized inbound communications to the cardholder data environment. An open port on a CDE security group is exactly the unnecessary exposure Requirement 1.3 is designed to prevent, and examiners will ask for evidence that every unrestricted rule has a documented justification.
NIST SP 800-53 Rev 5: SC-7(5) requires deny by default, allow by exception, and AC-4 extends that to information flow between security domains. This control operationalizes both at the security group level: any world-open rule on a non-approved port fails the documented authorization requirement NIST 800-53 Rev 5 expects.
FedRAMP Moderate Baseline Rev 4: SC-7 (Boundary Protection) and AC-4 (Information Flow Enforcement) both apply here. At the Moderate baseline, network boundaries must enforce explicit allow-lists, not default-open rules. An unrestricted ingress rule on an unauthorized port is a direct SC-7 violation regardless of what sits behind it.
Related controls
Tool mappings
Use these identifiers to cross-reference this control across tools, reports, and evidence.
Compliance.tf Control:
vpc_security_group_allows_ingress_authorized_portsAWS Config Managed Rule:
VPC_SG_OPEN_ONLY_TO_AUTHORIZED_PORTSPowerpipe Control:
aws_compliance.control.vpc_security_group_allows_ingress_authorized_portsAWS Security Hub Control:
EC2.18
Last reviewed: 2026-03-09