Use the Conformity Knowledge Base AI to help improve your Cloud Posture

EC2 Instance Not In Public Subnet

Trend Cloud One™ – Conformity is a continuous assurance tool that provides peace of mind for your cloud infrastructure, delivering over 1000 automated best practice checks.

Risk Level: High (not acceptable risk)
Rule ID: EC2-072

Ensure that no backend Amazon EC2 instances are provisioned in public subnets in order to protect them from exposure to the Internet. Backend instances are EC2 instances that do not require direct access to the public internet such as database, API, or caching servers. To follow cloud security best practices, all Amazon EC2 instances that are not Internet-facing should run within a private subnet, behind a NAT gateway that allows downloading software updates and implementing security patches or accessing other AWS services like SQS and SNS.

This rule resolution is part of the Conformity Security & Compliance tool for AWS.

Security

By provisioning Amazon EC2 instances within a private subnet (logically isolated section of VPC) you will prevent your instances from receiving inbound traffic initiated by someone on the Internet, therefore have the guarantee that no malicious requests can reach your backend instances.

Note: For this rule, Trend Cloud One™ – Conformity assumes that your Amazon EC2 instances are running within a VPC network that has both public and private subnets.


Audit

To determine if your backend Amazon EC2 instances are running within VPC public subnets, perform the following operations:

Using AWS Console

01 Sign in to the AWS Management Console.

02 Navigate to Amazon EC2 console at https://console.aws.amazon.com/ec2/.

03 In the navigation panel, under Instances, choose Instances.

04 Select the backend Amazon EC2 instance that you want to examine.

05 Choose the Details tab from the console bottom panel to access the instance configuration details.

06 In the Instance summary section, identify the Subnet IDconfigurationattributeand copy its value.

07 Navigate to Amazon VPC console at https://console.aws.amazon.com/vpc/.

08 In the navigation panel, under VIRTUAL PRIVATE CLOUD, choose Subnets.

09 Click inside the Filter subnets box, choose Subnet ID, paste the subnet ID copied at step no. 6, then press Enter.

10 Select the VPC subnet returned as result, and choose the Route table tab from the console bottom panel. If the associated route table contains any entries with the Destination set to 0.0.0.0/0 or the Target set an Internet Gateway (i.e. igw-xxxxxxxx), the selected Amazon EC2 instance was launched inside a public subnet, therefore the backend instance is not running within a logically isolated environment that provides full protection from the Internet.

11 Repeat steps no. 4 – 10 to determine the subnet type for other backend Amazon EC2 instances available within the current AWS region.

12 Change the AWS cloud region from the navigation bar and repeat the audit process for other regions.

Using AWS CLI

01 Run describe-instances command (OSX/Linux/UNIX) with custom query filters to list the IDs of all the Amazon EC2 instances provisioned in the selected AWS region:

aws ec2 describe-instances
  --region us-east-1
  --output table
  --query 'Reservations[*].Instances[*].InstanceId'

02 The command output should return a table with the requested instance identifiers (IDs):

-------------------------
|   DescribeInstances   |
+-----------------------+
|  i-01234abcd1234abcd  |
|  i-01234567abcabcabc  |
|  i-0abcd1234abcd1234  |
+-----------------------+

03 Run describe-instances command (OSX/Linux/UNIX) using the ID of the backend Amazon EC2 instance that you want to examine as the identifier parameter and custom query filters to describe the ID of the VPC subnet associated with the selected EC2 instance:

aws ec2 describe-instances
  --region us-east-1
  --instance-ids i-01234abcd1234abcd
  --query 'Reservations[*].Instances[*].SubnetId[]'

04 The command output should return the ID of the associated VPC subnet:

[
	"subnet-abcd1234"
]

05 Run describe-route-tables command (OSX/Linux/UNIX) using the ID of the Amazon VPC subnet returned at the previous step as the identifier parameter, to describe the routes of the route table configured for the selected VPC subnet:

aws ec2 describe-route-tables
  --region us-east-1
  --filters "Name=association.subnet-id,Values=subnet-abcd1234"
  --query 'RouteTables[*].Routes[]'

06 The command output should return the requested route table routes:

[
	{
		"GatewayId": "local",
		"DestinationCidrBlock": "172.31.0.0/16",
		"State": "active",
		"Origin": "CreateRouteTable"
	},
	{
		"GatewayId": "igw-1234abcd",
		"DestinationCidrBlock": "0.0.0.0/0",
		"State": "active",
		"Origin": "CreateRoute"
	}
]

Check the "GatewayId" and "DestinationCidrBlock" configuration attribute values returned by the describe-route-tables command output. If the route table contains any entries with the "GatewayId" value set to "igw-xxxxxxxx" or the "DestinationCidrBlock" value set to "0.0.0.0/0", as shown in the output example above, the selected Amazon EC2 instance was provisioned within a public subnet, therefore the backend instance is not running inside a logically isolated environment that provides full protection from the Internet.

07 Repeat steps no. 3 – 6 to determine the VPC subnet type for other backend EC2 instances available in the selected AWS region.

08 Change the AWS cloud region by updating the --region command parameter value and repeat the audit process for other regions.

Remediation / Resolution

To move your backend Amazon EC2 instances from public subnets to private subnets, you must re-create these instances within private VPC subnets. To implement the migration process, perform the following operations:

Using AWS CloudFormation

01 CloudFormation template (JSON):

{
	"AWSTemplateFormatVersion": "2010-09-09",
	"Parameters": {
		"SSHKeyName": {
			"Type": "AWS::EC2::KeyPair::KeyName",
			"Description": "Instance SSH key"
		},
		"SecurityGroupId": {
			"Type": "AWS::EC2::SecurityGroup::Id",
			"Description": "Security group ID"
		}
	},
	"Resources": {
		"EC2Instance": {
			"Type": "AWS::EC2::Instance",
			"Properties": {
				"ImageId": "ami-0123456789abcdefa",
				"InstanceType": "c5.xlarge",
				"KeyName": {
					"Ref": "SSHKeyName"
				},
				"SubnetId": "subnet-0123456789abcdef0",
				"SecurityGroupIds": [
					{
						"Ref": "SecurityGroupId"
					}
				],
				"BlockDeviceMappings": [
					{
						"DeviceName": "/dev/xvda",
						"Ebs": {
							"VolumeSize": "30",
							"VolumeType": "gp2"
						}
					}
				],
				"NetworkInterfaces": [
					{
						"DeviceIndex": 0,
						"SubnetId": "subnet-0123456789abcdef0",
						"DeleteOnTermination": true,
						"AssociatePublicIpAddress": false
					}
				]
			}
		}
	}
}

02 CloudFormation template (YAML):

AWSTemplateFormatVersion: '2010-09-09'
	Parameters:
	SSHKeyName:
		Type: AWS::EC2::KeyPair::KeyName
		Description: Instance SSH key
	SecurityGroupId:
		Type: AWS::EC2::SecurityGroup::Id
		Description: Security group ID
	Resources:
	EC2Instance:
		Type: AWS::EC2::Instance
		Properties:
		ImageId: ami-0123456789abcdefa
		InstanceType: c5.xlarge
		KeyName: !Ref 'SSHKeyName'
		SubnetId: subnet-0123456789abcdef0
		SecurityGroupIds:
			- !Ref 'SecurityGroupId'
		BlockDeviceMappings:
			- DeviceName: /dev/xvda
			Ebs:
				VolumeSize: '30'
				VolumeType: gp2
		NetworkInterfaces:
			- DeviceIndex: 0
			SubnetId: subnet-0123456789abcdef0
			DeleteOnTermination: true
			AssociatePublicIpAddress: false

Using Terraform (AWS Provider)

01 Terraform configuration file (.tf):

terraform {
	required_providers {
		aws = {
			source  = "hashicorp/aws"
			version = "~> 4.0"
		}
	}
	required_version = ">= 0.14.9"
}
provider "aws" {
	profile = "default"
	region  = "us-east-1"
}
resource "aws_instance" "ec2-instance" {
	ami = "ami-0123456789abcdefa"
	instance_type = "c5.xlarge"
	key_name = "ssh-key"
	subnet_id = "subnet-0123456789abcdef0"
	vpc_security_group_ids = [ "sg-0123456789abcdefa" ]
	ebs_block_device {
		device_name = "/dev/xvda"
		volume_size = 30
		volume_type = "gp2"
	}
	associate_public_ip_address = false
}

Using AWS Console

01 Sign in to the AWS Management Console.

02 Navigate to Amazon EC2 console at https://console.aws.amazon.com/ec2/.

03 In the navigation panel, under Instances, choose Instances.

04 Select the backend Amazon EC2 instance that you want to move to the private subnet.

05 Click on the Actions dropdown menu from the console top menu, select Image and templates, and choose Create image.

06 On the Create image setup page, provide the following information:

  1. In the Image name box, enter a unique name for the new AMI.
  2. (Optional) In the Image description box, provide a short description that reflects the usage of the selected backend EC2 instance.
  3. Deselect Enable under No reboot so that Amazon EC2 service can guarantee the file system integrity for the new AMI.
  4. (Optional) For Tags, chooseTag image and snapshots together and use the Add tag button to create and apply user-defined tags to the new image.
  5. Choose Create image to create your new AMI.

07 Once the new image is ready, use it to relaunch your backend EC2 instance within a private VPC subnet. On the Instances listing page, choose Launch instances and perform the following operations:

  1. For Step 1: Choose an Amazon Machine Image (AMI), choose My AMIs tab, and select the Amazon Machine Image (AMI) created at step no. 6.
  2. For Step 2: Choose an Instance Type, select the required instance type (must match the instance type used by the source, non-compliant instance). Choose Next: Configure Instance Details to continue the setup process.
  3. For Configure Instance Details, perform the following actions:
    • Select the private subnet that you want to use from the Subnet dropdown list or choose Create new subnet to create a new private VPC subnet.
    • Select Disable from the Auto-assign Public IP dropdown list to launch the new backend instance without a public IP address.
    • Configure the identity management, behavior, and metadata settings for the new instance. The new instance configuration must match the source instance configuration. Choose Next: Add Storage to continue the setup process.
  4. For Step 4: Add Storage, configure the storage device settings, then click Next: Add Tags to set up the instance tags.
  5. For Step 5: Add Tags, use the Add tag button to create and apply user-defined tags to the new backend EC2 instance. You can track compute cost and other criteria by tagging your instance. Choose Configure Security Group to continue the setup process.
  6. For Step 6: Configure Security Group, chooseSelect an existing security groupand select the security group(s) associated with the source instance. Choose Review and Launch to continue.
  7. For Step 7: Review Instance Launch, review your EC2 instance configuration details, then choose Launch.
  8. In the Select an existing key pair or create a new key pair configuration box, select Choose an existing key pair and use the same key pair as the source backend instance. Select the I acknowledge that I have access to the selected private key file (<key-name>.pem), and that without this file, I won't be able to log into my instance checkbox for confirmation, then choose Launch Instances to launch your new backend Amazon EC2 instance.
  9. Choose View Instances to return to the Instances page.

08 (Optional) After the new backend instance is deployed to the specified private subnet, you can terminate the source (non-compliant) instance in order to stop adding charges for that EC2 resource. To shut down the required instance, perform the following actions:

  1. In the navigation panel, under Instances, choose Instances.
  2. Select the Amazon EC2 instance that you want to terminate.
  3. Choose Instance state and select Terminate instance.
  4. In the Terminate instance? confirmation box, review the instance details, then choose Terminate to shut down the selected EC2 instance.

09 Repeat steps no. 4 – 8 to migrate other backend Amazon EC2 instances to private VPC subnets, available within the current AWS region.

10 Change the AWS cloud region from the navigation bar and repeat the remediation process for other regions.

Using AWS CLI

01 Run describe-instances command (OSX/Linux/UNIX) to list the configuration information available for the backend Amazon EC2 instance that you want to move to a private VPC subnet:

aws ec2 describe-instances
  --region us-east-1
  --instance-ids i-01234abcd1234abcd
  --query 'Reservations[*].Instances[]'

02 The command output should return an array with the requested configuration information:

[
	{
		"AmiLaunchIndex": 0,
		"ImageId": "ami-0abcd1234abcd1234",
		"InstanceId": "i-01234abcd1234abcd",
		"InstanceType": "t2.micro",
		"KeyName": "conformity",
		"LaunchTime": "2021-03-10T10:00:00+00:00",
		"Monitoring": {
			"State": "disabled"
		},
		"Placement": {
			"AvailabilityZone": "us-east-1a",
			"GroupName": "",
			"Tenancy": "default"
		},
		"PrivateDnsName": "ip-10-0-0-15.ec2.internal",
		"PrivateIpAddress": "10.0.0.15",
		"ProductCodes": [],
		"PublicDnsName": "ec2-10-0-1-20.compute-1.amazonaws.com",
		"PublicIpAddress": "10.0.1.20",
		"State": {
			"Code": 16,
			"Name": "running"
		},
		"StateTransitionReason": "",
		"SubnetId": "subnet-abcd1234",
		"VpcId": "vpc-1234abcd",
		"Architecture": "x86_64",
		"BlockDeviceMappings": [
			{
				"DeviceName": "/dev/xvda",
				"Ebs": {
					"AttachTime": "2021-03-10T10:00:00+00:00",
					"DeleteOnTermination": true,
					"Status": "attached",
					"VolumeId": "vol-0abcd1234abcd1234"
				}
			}
		],
		"ClientToken": "",
		"EbsOptimized": false,
		"EnaSupport": true,
		"Hypervisor": "xen",
		"IamInstanceProfile": {
			"Arn": "arn:aws:iam::123456789012:instance-profile/ec2-manager-role",
			"Id": "ABCDABCDABCDABCDABCDA"
		},
		"NetworkInterfaces": [
			{
				"Association": {
					"IpOwnerId": "amazon",
					"PublicDnsName": "ec2-10-0-1-20.compute-1.amazonaws.com",
					"PublicIp": "10.0.1.20"
				},
				"Attachment": {
					"AttachTime": "2021-03-10T10:00:00+00:00",
					"AttachmentId": "eni-attach-0abcd1234abcd1234",
					"DeleteOnTermination": true,
					"DeviceIndex": 0,
					"Status": "attached",
					"NetworkCardIndex": 0
				},
				"Description": "Primary network interface",
				"Groups": [
					{
						"GroupName": "cc-prod-security-group",
						"GroupId": "sg-01234abcd1234abcd"
					}
				],
				"Ipv6Addresses": [],
				"MacAddress": "0e:53:19:7b:62:6b",
				"NetworkInterfaceId": "eni-0abcd1234abcd1234",
				"OwnerId": "123456789012",
				"PrivateDnsName": "ip-10-0-0-15.ec2.internal",
				"PrivateIpAddress": "10.0.0.15",
				"PrivateIpAddresses": [
					{
						"Association": {
							"IpOwnerId": "amazon",
							"PublicDnsName": "ec2-10-0-1-20.compute-1.amazonaws.com",
							"PublicIp": "10.0.1.20"
						},
						"Primary": true,
						"PrivateDnsName": "ip-10-0-0-15.ec2.internal",
						"PrivateIpAddress": "10.0.0.15"
					}
				],
				"SourceDestCheck": true,
				"Status": "in-use",
				"SubnetId": "subnet-abcd1234",
				"VpcId": "vpc-1234abcd",
				"InterfaceType": "interface"
			}
		],
		"RootDeviceName": "/dev/xvda",
		"RootDeviceType": "ebs",
		"SecurityGroups": [
			{
				"GroupName": "cc-prod-security-group",
				"GroupId": "sg-01234abcd1234abcd"
			}
		],
		"SourceDestCheck": true,
		"VirtualizationType": "hvm",
		"CpuOptions": {
			"CoreCount": 2,
			"ThreadsPerCore": 4
		},
		"CapacityReservationSpecification": {
			"CapacityReservationPreference": "open"
		},
		"HibernationOptions": {
			"Configured": false
		},
		"MetadataOptions": {
			"State": "applied",
			"HttpTokens": "optional",
			"HttpPutResponseHopLimit": 1,
			"HttpEndpoint": "enabled"
		},
		"EnclaveOptions": {
			"Enabled": false
		}
	}
]

03 Run create-image command (OSX/Linux/UNIX) to create an image from the source, non-compliant backend EC2 instance. Include the --no-reboot command parameter to guarantee the file system integrity for your new AMI:

aws ec2 create-image
  --region us-east-1
  --instance-id i-01234abcd1234abcd
  --name "Backend Instance AMI"
  --description "Web API Stack Image"
  --no-reboot

04 The command output should return the ID of the new Amazon Machine Image (AMI):

{
	"ImageId": "ami-0abcdabcdabcdabcd"
}

05 Execute run-instances command (OSX/Linux/UNIX) to launch a new backend Amazon EC2 instance from the AMI created at the previous steps. Use the information returned at step no. 2 for the instance configuration parameters. Configure the --subnet-id command parameter with the ID of your private VPC subnet and include the --no-associate-public-ip-address parameter in the command request to avoid assigning automatically a public IPv4 address to the new EC2 instance:

aws ec2 run-instances
  --region us-east-1
  --image-id ami-0abcdabcdabcdabcd
  --count 1
  --instance-type t2.micro
  --key-name conformity
  --security-group-ids sg-01234abcd1234abcd
  --iam-instance-profile Name="ec2-manager-role"
  --subnet-id subnet-abcdabcd
  --no-associate-public-ip-address

06 The command output should return the configuration metadata for the newly created backend instance:

{
	"Groups": [],
	"Instances": [
		{
			"AmiLaunchIndex": 0,
			"ImageId": "ami-0abcdabcdabcdabcd",
			"InstanceId": "i-01234123412341234",
			"InstanceType": "t2.micro",
			"KeyName": "conformity.aws",
			"LaunchTime": "2021-03-22T17:29:43+00:00",
			"Monitoring": {
				"State": "disabled"
			},
			"Placement": {
				"AvailabilityZone": "us-east-1e",
				"GroupName": "",
				"Tenancy": "default"
			},
			"PrivateDnsName": "ip-10-0-0-5.ec2.internal",
			"PrivateIpAddress": "10.0.0.5",
			"ProductCodes": [],
			"PublicDnsName": "",
			"State": {
				"Code": 0,
				"Name": "pending"
			},
			"StateTransitionReason": "",
			"SubnetId": "subnet-abcdabcd",
			"VpcId": "vpc-1234abcd",
			"Architecture": "x86_64",
			"BlockDeviceMappings": [],
			"EbsOptimized": false,
			"EnaSupport": true,
			"Hypervisor": "xen",
			"IamInstanceProfile": {
				"Arn": "arn:aws:iam::123456789012:instance-profile/ec2-manager-role",
				"Id": "ABCDABCDABCDABCDABCD"
			},
			"NetworkInterfaces": [
				{
					"Attachment": {
						"AttachTime": "2021-03-22T17:29:43+00:00",
						"AttachmentId": "eni-attach-0abcd1234abcd1234",
						"DeleteOnTermination": true,
						"DeviceIndex": 0,
						"Status": "attaching",
						"NetworkCardIndex": 0
					},
					"Description": "",
					"Groups": [
						{
							"GroupName": "cc-prod-security-group",
							"GroupId": "sg-01234abcd1234abcd"
						}
					],
					"Ipv6Addresses": [],
					"MacAddress": "06:00:c7:12:51:99",
					"NetworkInterfaceId": "eni-0abcd1234abcd1234",
					"OwnerId": "123456789012",
					"PrivateDnsName": "ip-10-0-0-5.ec2.internal",
					"PrivateIpAddress": "10.0.0.5",
					"PrivateIpAddresses": [
						{
							"Primary": true,
							"PrivateDnsName": "ip-10-0-0-5.ec2.internal",
							"PrivateIpAddress": "10.0.0.5"
						}
					],
					"SourceDestCheck": true,
					"Status": "in-use",
					"SubnetId": "subnet-abcdabcd",
					"VpcId": "vpc-1234abcd",
					"InterfaceType": "interface"
				}
			],
			"RootDeviceName": "/dev/xvda",
			"RootDeviceType": "ebs",
			"SecurityGroups": [
				{
					"GroupName": "cc-prod-security-group",
					"GroupId": "sg-01234abcd1234abcd"
				}
			],
			"SourceDestCheck": true,
			"StateReason": {
				"Code": "pending",
				"Message": "pending"
			},
			"VirtualizationType": "hvm",
			"CpuOptions": {
				"CoreCount": 1,
				"ThreadsPerCore": 1
			},
			"CapacityReservationSpecification": {
				"CapacityReservationPreference": "open"
			},
			"MetadataOptions": {
				"State": "pending",
				"HttpTokens": "optional",
				"HttpPutResponseHopLimit": 1,
				"HttpEndpoint": "enabled"
			},
			"EnclaveOptions": {
				"Enabled": false
			}
		}
	],
	"OwnerId": "123456789012",
	"ReservationId": "r-0abcd1234abcd1234"
}

07 (Optional) After the new backend Amazon EC2 instance is deployed to the specified private VPC subnet, you can terminate the source (non-compliant) instance in order to stop adding charges for that EC2 resource. To shut down the required instance, run terminate-instances command (OSX/Linux/UNIX) using the instance ID as the identifier parameter:

aws ec2 terminate-instances
  --instance-ids i-01234abcd1234abcd

08 The output should return the terminate-instances command request metadata:

{
	"TerminatingInstances": [
		{
			"InstanceId": "i-01234abcd1234abcd",
			"CurrentState": {
				"Code": 32,
				"Name": "shutting-down"
			},
			"PreviousState": {
				"Code": 16,
				"Name": "running"
			}
		}
	]
}

09 Repeat steps no. 1 – 8 to migrate other backend Amazon EC2 instances to private VPC subnets, available in the selected AWS region.

10 Change the AWS cloud region by updating the --region command parameter value and repeat steps no. 1 – 9 for other regions.

References

Publication date Dec 3, 2016