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

RDS 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: RDS-039

Ensure that no Amazon RDS database instances are provisioned inside VPC public subnets in order to protect them from direct exposure to the Internet. Because database instances are not Internet-facing and their management (running software updates, implementing security patches, etc) is performed by AWS, the database instances should run only in private subnets.

This rule resolution is part of the Conformity solution.

Security

By provisioning your Amazon RDS instances within private subnets (logically isolated sections of AWS VPC), you will prevent these resources from receiving inbound traffic from the public Internet, therefore you can have the guarantee that no malicious requests can reach your database instances from the Internet.


Audit

To determine if your Amazon RDS database instances are currently running within VPC public subnets, perform the following actions:

Using AWS Console

01 Sign in to the AWS Management Console.

02 Navigate to Amazon RDS console at https://console.aws.amazon.com/rds/.

03 In the navigation panel, under Amazon RDS, choose Databases.

04 Click on the name (link) of the Amazon RDS database instance that you want to examine. To identify RDS database instances, check the resource role available in the Role column (i.e. Instance).

05 Select the Connectivity & security tab and click on the identifier (ID) of the VPC subnet associated with the database instance, listed under Subnets. The AWS Management Console will redirect you to the VPC Subnets page.

06 Select the associated VPC subnet 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 RDS database instance was launched inside a public subnet, therefore the database instance is not running within a logically isolated environment that provides full protection from the Internet.

07 Repeat steps no. 5 and 6 for each VPC subnet associated with the selected database instance.

08 Repeat steps no. 4 – 7 for each Amazon RDS database instance available within the current AWS region.

09 Change the AWS cloud region from the navigation bar and repeat the Audit process for other regions.

Using AWS CLI

01 Run describe-db-instances command (OSX/Linux/UNIX) with custom query filters to list the names of the Amazon RDS database instances provisioned in the selected AWS region:

aws rds describe-db-instances
  --region us-east-1
  --output table
  --query 'DBInstances[*].DBInstanceIdentifier'

02 The command output should return a table with the requested database instance names:

--------------------------------
|     DescribeDBInstances      |
+------------------------------+
|  cc-project5-mysql-database  |
|  cc-prod-postgres-database   |
+------------------------------+

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

aws rds describe-db-instances
  --region us-east-1
  --db-instance-identifier cc-project5-mysql-database
  --query 'DBInstances[*].DBSubnetGroup.Subnets[*].SubnetIdentifier[]'

04 The command output should list the subnets available in the selected database subnet group:

[
	"subnet-abcd1234",
	"subnet-1234abcd"
]

05 Run describe-route-tables command (OSX/Linux/UNIX) using the ID of the VPC subnet that you want to examine 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 RDS database instance was provisioned within a public subnet, therefore the database instance is not running inside a logically isolated environment that provides full protection from the Internet.

07 Repeat steps no. 5 and 6 for each VPC subnet associated with the selected database instance.

08 Repeat steps no. 3 – 7 for each Amazon RDS database instance available in the selected AWS region.

09 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 Amazon RDS database instances from public subnets to private subnets, you must replace their current subnet groups with the ones that contain VPC private subnets only. To run the migration process, perform the following actions:

Note: For this rule Trend Cloud One™ – Conformity assumes that you have private RDS subnet groups already defined within your VPC. A private RDS subnet group is a collection of private subnets that you create in your VPC to use with your RDS database instances.

Using AWS CloudFormation

01 CloudFormation template (JSON):

{
	"AWSTemplateFormatVersion": "2010-09-09",
	"Description": "Database Instance Not in Public Subnet",
	"Parameters": {
		"DBInstanceName": {
			"Default": "mysql-database-instance",
			"Description": "RDS database instance name",
			"Type": "String",
			"MinLength": "1",
			"MaxLength": "63",
			"AllowedPattern": "^[0-9a-zA-Z-/]*$",
			"ConstraintDescription": "Must begin with a letter and must not end with a hyphen or contain two consecutive hyphens."
		},
		"DBInstanceClass": {
			"Default": "db.t2.micro",
			"Description": "DB instance class/type",
			"Type": "String",
			"ConstraintDescription": "Must provide a valid DB instance type."
		},
		"DBAllocatedStorage": {
			"Default": "20",
			"Description": "The size of the database (GiB)",
			"Type": "Number",
			"MinValue": "20",
			"MaxValue": "65536",
			"ConstraintDescription": "Must be between 20 and 65536 GiB."
		},
		"DBName": {
			"Default": "mysqldb",
			"Description": "Database name",
			"Type": "String",
			"MinLength": "1",
			"MaxLength": "64",
			"AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*",
			"ConstraintDescription": "Must begin with a letter and contain only alphanumeric characters."
		},
		"DBUsername": {
			"Description": "Master username for database access",
			"Type": "String",
			"MinLength": "1",
			"MaxLength": "16",
			"AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*",
			"ConstraintDescription": "Must begin with a letter and contain only alphanumeric characters."
		},
		"DBPassword": {
			"NoEcho": "true",
			"Description": "Password for database access",
			"Type": "String",
			"MinLength": "8",
			"MaxLength": "41",
			"AllowedPattern": "[a-zA-Z0-9]*",
			"ConstraintDescription": "Must contain only alphanumeric characters."
		},
		"SecurityGroupName": {
			"Default": "default",
			"Description": "DB security group name",
			"Type": "String",
			"ConstraintDescription": "Must provide an existing DB security group name."
		},
		"DBSubnetGroupName": {
			"Default": "default",
			"Description": "DB subnet group name",
			"Type": "String",
			"ConstraintDescription": "Must provide a valid DB subnet group name."
		}
	},
	"Resources": {
		"RDSInstance": {
			"Type": "AWS::RDS::DBInstance",
			"Properties": {
				"DBInstanceIdentifier": {
					"Ref": "DBInstanceName"
				},
				"DBName": {
					"Ref": "DBName"
				},
				"MasterUsername": {
					"Ref": "DBUsername"
				},
				"MasterUserPassword": {
					"Ref": "DBPassword"
				},
				"DBInstanceClass": {
					"Ref": "DBInstanceClass"
				},
				"AllocatedStorage": {
					"Ref": "DBAllocatedStorage"
				},
				"VPCSecurityGroups": [
					{
						"Ref":"SecurityGroupName"
					}
				],
				"DBSubnetGroupName": {
					"Ref": "DBSubnetGroupName"
				},
				"Engine": "MySQL",
				"EngineVersion": "5.7.36",
				"PubliclyAccessible": false
			}
		}
	}
}

02 CloudFormation template (YAML):

AWSTemplateFormatVersion: '2010-09-09'
	Description: Database Instance Not in Public Subnet
	Parameters:
	DBInstanceName:
		Default: mysql-database-instance
		Description: RDS database instance name
		Type: String
		MinLength: '1'
		MaxLength: '63'
		AllowedPattern: ^[0-9a-zA-Z-/]*$
		ConstraintDescription: Must begin with a letter and must not end with a hyphen
		or contain two consecutive hyphens.
	DBInstanceClass:
		Default: db.t2.micro
		Description: DB instance class/type
		Type: String
		ConstraintDescription: Must provide a valid DB instance type.
	DBAllocatedStorage:
		Default: '20'
		Description: The size of the database (GiB)
		Type: Number
		MinValue: '20'
		MaxValue: '65536'
		ConstraintDescription: Must be between 20 and 65536 GiB.
	DBName:
		Default: mysqldb
		Description: Database name
		Type: String
		MinLength: '1'
		MaxLength: '64'
		AllowedPattern: '[a-zA-Z][a-zA-Z0-9]*'
		ConstraintDescription: Must begin with a letter and contain only alphanumeric
		characters.
	DBUsername:
		Description: Master username for database access
		Type: String
		MinLength: '1'
		MaxLength: '16'
		AllowedPattern: '[a-zA-Z][a-zA-Z0-9]*'
		ConstraintDescription: Must begin with a letter and contain only alphanumeric
		characters.
	DBPassword:
		NoEcho: 'true'
		Description: Password for database access
		Type: String
		MinLength: '8'
		MaxLength: '41'
		AllowedPattern: '[a-zA-Z0-9]*'
		ConstraintDescription: Must contain only alphanumeric characters.
	SecurityGroupName:
		Default: default
		Description: DB security group name
		Type: String
		ConstraintDescription: Must provide an existing DB security group name.
	DBSubnetGroupName:
		Default: default
		Description: DB subnet group name
		Type: String
		ConstraintDescription: Must provide a valid DB subnet group name.
	Resources:
	RDSInstance:
		Type: AWS::RDS::DBInstance
		Properties:
		DBInstanceIdentifier: !Ref 'DBInstanceName'
		DBName: !Ref 'DBName'
		MasterUsername: !Ref 'DBUsername'
		MasterUserPassword: !Ref 'DBPassword'
		DBInstanceClass: !Ref 'DBInstanceClass'
		AllocatedStorage: !Ref 'DBAllocatedStorage'
		VPCSecurityGroups:
			- !Ref 'SecurityGroupName'
		DBSubnetGroupName: !Ref 'DBSubnetGroupName'
		Engine: MySQL
		EngineVersion: 5.7.36
		PubliclyAccessible: 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_db_subnet_group" "db-subnet-group" {
	name       = "cc-private-db-subnet-group"
	subnet_ids = ["subnet-0123456789abcdefa","subnet-0123456789abcdefb"]
}

resource "aws_db_instance" "rds-database-instance" {
	allocated_storage      = 50
	engine                 = "mysql"
	engine_version         = "5.7"
	instance_class         = "db.t3.medium"
	name                   = "[database-name]"
	username               = "[master-username]"
	password               = "[master-password]"
	parameter_group_name   = "default.mysql5.7"
	vpc_security_group_ids = ["sg-0123456789abcdefa"]
	publicly_accessible    = false
	
	# Database Instance Not in Public Subnet
	db_subnet_group_name   = aws_db_subnet_group.db-subnet-group.name
}

Using AWS Console

01 Sign in to the AWS Management Console.

02 Navigate to Amazon RDS console at https://console.aws.amazon.com/rds/.

03 In the navigation panel, under Amazon RDS, choose Databases.

04 Select the Amazon RDS database instance that you want to reconfigure and choose Modify.

05 On the Modify DB instance: <instance-name> configuration page, perform the following operations:

  1. In the Connectivitysection, select the name of the private RDS subnet group that you want to use for your database instance, from the Subnet group dropdown list.
  2. Choose Continue and review the configuration changes that you want to apply, available in the Summary of modifications section.
  3. In the Scheduling of modifications section, perform one of the following actions based on your workload requirements:
    • Select Apply during the next scheduled maintenance window to apply the changes automatically during the next scheduled maintenance window.
    • Select Apply immediately to apply the changes right away. With this option any pending modifications will be asynchronously applied as soon as possible, regardless of the maintenance window configured for the selected Amazon RDS database instance. Note that any changes available in the pending modifications queue are also applied. If any of the pending modifications require downtime, choosing this option can cause unexpected downtime for your database application.
  4. Choose Modify DB instance to apply the configuration changes.

06 Repeat steps no. 4 and 5 for each Amazon RDS database instance that you want to reconfigure, available in the selected AWS region.

07 Change the AWS cloud region from the navigation bar and repeat the Remediation process for other regions.

Using AWS CLI

01 Run modify-db-instance command (OSX/Linux/UNIX) to replace the subnet group for the selected Amazon RDS database instance in order to migrate the database instance from public subnet(s) to private subnet(s). The following command request example makes use of --apply-immediately parameter to apply the configuration changes asynchronously and as soon as possible. Any changes available in the pending modifications queue are also applied with this request. If any of the pending modifications require downtime, choosing this option can cause unexpected downtime for your database application. If you skip adding the --apply-immediately parameter to the command request, Amazon RDS will apply your changes during the next maintenance window:

aws rds modify-db-instance
  --region us-east-1
  --db-instance-identifier cc-project5-mysql-database
  --db-subnet-group-name cc-private-db-subnet-group
  --apply-immediately

02 The command output should return the configuration metadata for the modified database instance:

{
	"DBInstance": {
		"PubliclyAccessible": true,
		"MasterUsername": "ccadmin",
		"MonitoringInterval": 0,
		"LicenseModel": "general-public-license",
		"VpcSecurityGroups": [
			{
				"Status": "active",
				"VpcSecurityGroupId": "sg-0abcd1234abcd1234"
			},
			{
				"Status": "active",
				"VpcSecurityGroupId": "sg-abcd1234"
			}
		],
		"InstanceCreateTime": "2021-05-12T08:00:00.677Z",
		"CopyTagsToSnapshot": true,
		"OptionGroupMemberships": [
			{
				"Status": "in-sync",
				"OptionGroupName": "default:mysql-5-7"
			}
		],
		"Engine": "mysql",
		"MultiAZ": false,
		"DBSecurityGroups": [],
		"DBParameterGroups": [
			{
				"DBParameterGroupName": "default.mysql5.7",
				"ParameterApplyStatus": "in-sync"
			}
		],
		"PerformanceInsightsEnabled": true,
		"AutoMinorVersionUpgrade": true,
		"PreferredBackupWindow": "06:02-06:32",
		"DBSubnetGroup": {
			"Subnets": [
				{
					"SubnetStatus": "Active",
					"SubnetIdentifier": "subnet-abcd1234",
					"SubnetOutpost": {},
					"SubnetAvailabilityZone": {
						"Name": "us-east-1a"
					}
				},
				{
					"SubnetStatus": "Active",
					"SubnetIdentifier": "subnet-1234abcd",
					"SubnetOutpost": {},
					"SubnetAvailabilityZone": {
						"Name": "us-east-1b"
					}
				}
			],
			"DBSubnetGroupName": "cc-private-db-subnet-group",
			"VpcId": "vpc-abcdabcd",
			"DBSubnetGroupDescription": "Private DB Subnet Group",
			"SubnetGroupStatus": "Complete"
		},
		"ReadReplicaDBInstanceIdentifiers": [],
		"AllocatedStorage": 70,
		"DBInstanceArn": "arn:aws:rds:us-east-1:123456789012:db:cc-project5-mysql-database",
		"BackupRetentionPeriod": 0,
		"PreferredMaintenanceWindow": "thu:03:27-thu:03:57",
		"Endpoint": {
			"HostedZoneId": "ABCDABCDABCD",
			"Port": 3306,
			"Address": "cc-project5-mysql-database.abcdabcdabcd.us-east-1.rds.amazonaws.com"
		},
		"DBInstanceStatus": "available",
		"IAMDatabaseAuthenticationEnabled": true,
		"EngineVersion": "5.7.30",
		"DeletionProtection": true,
		"AvailabilityZone": "us-east-1a",
		"DomainMemberships": [],
		"StorageType": "gp2",
		"DbiResourceId": "db-ABCDABCDABCDABCDABCDABCDAB",
		"CACertificateIdentifier": "rds-ca-2019",
		"StorageEncrypted": true,
		"AssociatedRoles": [],
		"DBInstanceClass": "db.t3.medium",
		"DbInstancePort": 0,
		"DBInstanceIdentifier": "cc-project5-mysql-database"
	}
}

03 Repeat steps no. 1 and 2 for each Amazon RDS database instance that you want to reconfigure, available in the selected AWS region.

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

References

Publication date Dec 3, 2016