- Knowledge Base
- Amazon Web Services
- Amazon EC2
- EC2 Instances with Public IP Addresses or Available in Public Subnets
Ensure there are no backend instances deployed within public subnets or backend instances that have public IP addresses assigned, in order to protect them from exposure to the Internet. Backend instances are Amazon EC2 instances that do not need direct access to the public Internet and therefore do not require running within public subnets or having public IP addresses.
As a cloud security best practice, all backend Amazon EC2 instances such as database, API, or caching servers should run within private subnets, without using public IP addresses, or behind a NAT gateway that allows downloading software updates, security patches, or access to other AWS resource. By deploying backend EC2 instances within private VPC subnets, you can prevent your instances from receiving inbound traffic initiated by someone on the Internet, and therefore protect your cloud resources against attacks and other malicious activities.
Audit
Case A: To determine if your backend Amazon EC2 instances are running within VPC public subnets, perform the following actions:
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 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-0abcabcabc1234567 | | i-01234567abcabcabc | | i-0abcd1234abcd1234 | +-----------------------+
03 Run describe-instances command (OSX/Linux/UNIX) using the ID of the backend 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 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 Amazon 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.
Case B: To determine if your backend Amazon EC2 instances have public IP addresses assigned, perform the following actions:
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, check the Public IPv4 address configuration attribute value. If the Public IPv4 address attribute value is set to an IPv4 address, the selected backend Amazon EC2 instance is using a public IP address that is reachable from the Internet.
07 Repeat steps no. 4 – 6 for each backend Amazon EC2 instance available within the current AWS region.
08 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) using custom query filters to list the IDs of all the Amazon EC2 instances available 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 IDs:
------------------------- | DescribeInstances | +-----------------------+ | i-01234abcd1234abcd | | i-0abcabcabc1234567 | | i-01234567abcabcabc | | i-0abcd1234abcd1234 | +-----------------------+
03 Run describe-instances command (OSX/Linux/UNIX) using the ID of the backend EC2 instance that you want to examine as the identifier parameter and custom query filters to determine whether the selected EC2 instance is associated with a public or an Elastic IP address:
aws ec2 describe-instances --region us-east-1 --instance-ids i-01234abcd1234abcd --query "Reservations[*].Instances[*].NetworkInterfaces[*].Association.IpOwnerId[] | []"
04 The command output should return an empty array – if the verified instance has no public IP address assigned, "amazon" – if the instance has a public IP address, or the AWS account ID of the owner – if the selected instance is associated with an Elastic IP address:
[ "amazon" ]
If the describe-instances command output returns an AWS account ID (e.g. "123456789012") or "amazon" (as shown in the output example above), the selected backend Amazon EC2 instance is using a public or an Elastic IP address that is reachable from the Internet.
05 Repeat step no. 3 and 4 for each backend Amazon EC2 instance available in the selected AWS region.
06 Change the AWS cloud region by updating the --region command parameter value and repeat steps no. 1 – 5 to perform the audit process for other regions.
Remediation / Resolution
To migrate your backend EC2 instances from public subnets to private subnets, you must relaunch these instances within private subnets using the appropriate network interface configuration (i.e. without public IP addresses). To run the EC2 instance migration process, perform the following actions:
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-0abcdabcdabcdabcd", "SecurityGroupIds": [ { "Ref": "SecurityGroupId" } ], "BlockDeviceMappings": [ { "DeviceName": "/dev/xvda", "Ebs": { "VolumeSize": "30", "VolumeType": "gp2" } } ], "NetworkInterfaces": [ { "DeviceIndex": 0, "SubnetId": "subnet-0abcdabcdabcdabcd", "AssociatePublicIpAddress": false, "DeleteOnTermination": true } ] } } } }
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-0abcdabcdabcdabcd SecurityGroupIds: - !Ref 'SecurityGroupId' BlockDeviceMappings: - DeviceName: /dev/xvda Ebs: VolumeSize: '30' VolumeType: gp2 NetworkInterfaces: - DeviceIndex: 0 SubnetId: subnet-0abcdabcdabcdabcd AssociatePublicIpAddress: false DeleteOnTermination: true
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-0abcdabcdabcdabcd" vpc_security_group_ids = [ "sg-0123456789abcdefa" ] associate_public_ip_address = false ebs_block_device { device_name = "/dev/xvda" volume_size = 30 volume_type = "gp2" } }
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 EC2 instance that you want to move to the private subnet (see the Audit section part I section to identify the right resource).
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:
- In the Image name box, enter a unique name for the new AMI.
- (Optional) In the Image description box, provide a short description that reflects the usage of the selected backend EC2 instance.
- Deselect Enable under No reboot so that Amazon EC2 service can guarantee the file system integrity for the new AMI.
- (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.
- 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 subnet, without attaching a public IP address. On the Instances listing page, choose Launch instances and perform the following operations:
- 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.
- 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.
- For Configure Instance Details, perform the following actions:
- Select the private subnet that you want to use from the Subnetdropdown list or choose Create new subnet to create a new private VPC subnet. A private subnet is a VPC subnet with no Internet Gateway (IGW) attached.
- 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. The new instance configuration must match the source, non-compliant instance configuration. Choose Next: Add Storage to continue the setup process.
- For Step 4: Add Storage, configure the storage device settings, then click Next: Add Tags to set up the instance tags.
- 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.
- For Step 6: Configure Security Group, chooseSelect an existing security group and select the security group(s) associated with the source, non-compliant EC2 instance. Choose Review and Launch to continue.
- For Step 7: Review Instance Launch, review your EC2 instance configuration details, then choose Launch.
- 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. - Choose View Instances to return to the Instances page.
08 (Optional) Once the new backend EC2 instance is deployed to the specified private subnet, you can terminate the source, non-compliant instance in order to stop adding charges for that resource. To shut down the required instance, perform the following actions:
- In the navigation panel, under Instances, choose Instances.
- Select the Amazon EC2 instance that you want to terminate.
- Choose Instance state and select Terminate instance.
- 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 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 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 backend EC2 instance (see Audit section part II to identify the right resource). 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 EC2 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 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 EC2 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) Once the new backend 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 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
- AWS Documentation
- Amazon EC2 FAQs
- Scenario 2: VPC with Public and Private Subnets (NAT)
- Creating an Amazon EBS-Backed Linux AMI
- Launching an Instance
- Instance Lifecycle
- Terminate Your Instance
- Elastic IP addresses
- IP addressing for your VPCs and subnets
- AWS Command Line Interface (CLI) Documentation
- ec2
- describe-instances
- describe-route-tables
- create-image
- run-instances
- terminate-instances