For AI agents: A markdown version of this page is available at https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/terraform-aws-instance-with-no-vpc.md. A documentation index is available at /llms.txt.
This product is not supported for your selected Datadog site. ().

Metadata

Id: terraform-aws-instance-with-no-vpc

Provider: AWS

Platform: Terraform

Severity: Low

Category: Insecure Configurations

Learn More

Description

Amazon EC2 instances should always be provisioned within a Virtual Private Cloud (VPC) to leverage the network isolation, traffic filtering, and granular access controls that VPCs provide. If EC2 instances are created without specifying a subnet_id or vpc_security_group_ids (as shown in the configuration below), they may default to legacy EC2-Classic networks or lack critical network restrictions, increasing the risk of unauthorized access and exposure to attacks. Using a VPC ensures all traffic to and from instances can be centrally managed, monitored, and audited, reducing the attack surface.

Insecure example:

resource "aws_instance" "example" {
  ami           = "ami-003634241a8fcdec0"
  instance_type = "t2.micro"
}

Secure example:

resource "aws_instance" "example" {
  ami                    = "ami-003634241a8fcdec0"
  instance_type          = "t2.micro"
  subnet_id              = aws_subnet.example.id
  vpc_security_group_ids = [aws_security_group.example.id]
}

Compliant Code Examples

# vpc_security_group_ids is set to a literal string that merely looks like
# a resource reference ("aws_security_group.instance.id" is quoted, not an
# actual HCL reference expression), so Terraform keeps it as a known literal
# in planned_values and the rule correctly stays silent on both the HCL and
# plan-JSON fixtures. This does NOT exercise the real cross-resource-
# reference case — see negative4.tf and negative5.json for a genuine
# vpc_security_group_ids = aws_security_group.instance.id reference, where
# the plan JSON leaves the field absent from planned_values (unknown until
# apply) but _dd_tfplan_meta.configuration_expressions still shows it was
# configured.
resource "aws_instance" "negative1" {
  ami = "ami-003634241a8fcdec0"

  instance_type = "t2.micro"

  vpc_security_group_ids = ["aws_security_group.instance.id"]

}
module "ec2_instance" {
  source  = "terraform-aws-modules/ec2-instance/aws"
  version = "~> 3.0"

  name = "single-instance"

  ami                    = "ami-ebd02392"
  instance_type          = "t2.micro"
  key_name               = "user1"
  monitoring             = true
  vpc_security_group_ids = ["sg-12345678"]
  subnet_id              = "subnet-eddcdzz4"

  tags = {
    Terraform   = "true"
    Environment = "dev"
  }
}
module "ec2_instance" {
  source  = "terraform-aws-modules/ec2-instance/aws"
  version = "~> 3.0"

  name = "single-instance"

  ami                    = "ami-ebd02392"
  instance_type          = "t2.micro"
  key_name               = "user1"
  monitoring             = true
  subnet_id              = "subnet-eddcdzz4"

  tags = {
    Terraform   = "true"
    Environment = "dev"
  }
}

Non-Compliant Code Examples

resource "aws_instance" "positive1" {
  ami = "ami-003634241a8fcdec0"

  instance_type = "t2.micro"

}
# Explicit null still leaves the instance on default networking; the
# resulting configuration_expressions entries (constant_value: null) must
# not be treated as evidence of a real cross-resource reference.
resource "aws_instance" "positive2" {
  ami = "ami-003634241a8fcdec0"

  instance_type = "t2.micro"

  subnet_id               = null
  vpc_security_group_ids  = null
}