EC2 Instances and Resource Security
This is the continuation of a AWS Terraform demo to create a VPC in AWS with an EC2 instance connected to MariaDB database running in RDS using a single Terraform plan.
Prerequisites and source code:
- Terraform Setup and AWS VPC Subnet Creation (1/5)
- VPC Subnet Routing and Internet Access with Terraform (2/5)
- AWS Route 53, AMI Lookup and EC2 Creation with Terraform (3/5)
- Creating AWS RDS Database with Terraform (4/5)
Creating AWS EC2 Instances with Terraform
EC2 instances are defined using the terraform.tfvars, some values (ami, vpc_security_group_ids and subnet_id) are derived from modules output so the definition is in the aws_ec2_pro_wp.tf file as terraform.tfvars doesn’t allow interpolation.
#------------------------ # WP PRO #------------------------ aws_ec2_pro_pub_wp_01 = { name = "ditwl-ec2-pro-pub-wp01" ami = "" #Uses data.aws_ami.ubuntu1604.id instance_type = "t2.micro" #AWS Free Tier: 750 hours per month of Linux, RHEL, or SLES t2.micro instance usage availability_zone = "us-east-1a" key_name = "ditwl_kp_infradmin" # vpc_security_group_ids = SEE TF file # subnet_id = SEE TF file associate_public_ip_address = true root_block_device_size = 8 # See https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSVolumeTypes.html root_block_device_volume_type = "gp2" tag_private_name = "ditwl-ec2-pro-pub-wp-01" tag_public_name = "www" tag_app = "wp" tag_app_id = "wp-01" tag_os = "ubuntu" tag_os_id = "ubuntu-16" tags_environment = "pro" tag_cost_center = "ditwl-permanent" tags_volume = "ditwl-ec2-pro-pub-wp-01-root" } #------------------------ # WP PRO Security Group #------------------------ aws_sg_ec2_pro_pub_wp_01 = { sec_name = "ditwl-sg-ec2-pro-pub-01" sec_description = "ditwl - WP server access rules - Pub, Env: PRO" allow_all_outbound = false } aws_sr_ec2_pro_pub_wp_01_internet_to_80 = { type = "ingress" from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = "0.0.0.0/0" description = "Access from Internet to port 80" } aws_sr_ec2_pro_pub_wp_01_internet_to_443 = { type = "ingress" from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = "0.0.0.0/0" description = "Access from Internet to port 443" }
All EC2 instance names and its Security Rules and Groups follow a naming pattern:
- Cloud: a prefix specifying the unique name of this cloud across all available clouds and providers. In this case the prefix will be: ditwl that stands for Demo ITWonder Lab in lowercase.
- Resource: a short name identifying the resource, in this case:
- ec2: for EC2 Instances
- sg-ec2: for an EC2 Security Group
- sr-ec2: for an EC2 Security Rule
- Environment: for resources that are not to be shared between environments, a 3 letter acronym for the environment:
- pro: production
- pre: preproduction
- dev: development
- Visibility: for resources that can be either public or private, a 3 letter acronym for the visibility:
- pub: for public resources
- pri: for private resources
- Name/ID: optional a name or ID that describes the usage of the resource or the number of the resource instance, for example:
- EC2 resource number: this will be an EC2 instance for WordPress wp number 01 for Public Zones and Pro Environment. We might have new EC2 instances for WP in the future, adding a number now will make it easier to grow the infrastructure later on.
- EC2 security group number: this will be the EC2 security group 01 for Public Zones and Pro Environment for wp. We might have new security groups on the future, adding a number now will make it easier to grow the infrastructure later on.
- Description of the purpose of the rule: using a description like instances_to_instance_port explains the intended usage of the rule. In this case the rules allow access from the Internet to ports 80 and 443. (as we are not using a Load Balancer, Internet connections are directly to instances)
Private Key for the EC2 Instance
In order to access the created Linux instances in AWS you will need an SSH client. Authentication will use a private key, and in the case of Ubuntu a username named “ubuntu”.
The private key needs to be registered in AWS EC2 console, it can be uploaded to the console or created using a wizard.
- Visit the AWS console
- Select the region where instances will be created (as Key Pais are unique to each region),
- Go to EC2 AWS web console
- Go to Network & Security and Key Pairs.
- Create a new Key Pair and name it ditwl_kp_infradmin. AWS generates a PEM file that you should store in a safe place.
- Save the downloaded pem file in ${HOME}/keys/ditwl_kp_infradmin.pem. It will be used by Ansible in the next tutorial.
Launching AWS EC2 Instances with Terraform
Having configured most of the values for the instance in the terraform.tfvars, now the file aws_ec2_pro_wp.tf makes use of Terraform modules to create the resources.
The Terraform module /modules/aws/ec2/instance/add is used to create the EC2 instance. Most of the variables come from the aws_ec2_pro_pub_wp_01 variable definition from terraform.tfvars and the rest are interpolations to other resources:
- ami = “${data.aws_ami.ubuntu1604.id}”. Uses the output from the data source aws_ami. See Route 53 and AMI Lookup for an explanation.
- disable_api_termination = “${var.is_production ? true : false}”. Uses the value from is_production variable to prevent EC2 instance destroy. See Avoiding AWS instance destroy with Terraform for an explanation.
- vpc_security_group_ids = [“${module.aws_sec_group_ec2_default.id}”,”${module.aws_sg_ec2_pro_pub_wp_01.id}”] is a list of security groups that will allow access to the instance.
- register_dns_private = true and register_dns_public = true are used for Instance registration in private and public DNS. See Registering EC2 instances in Route 53 for an explanation.
# Create WP instance module "aws_ec2_pro_pub_wp_01" { source = "./modules/aws/ec2/instance/add" name = var.aws_ec2_pro_pub_wp_01["name"] ami = data.aws_ami.ubuntu1604.id instance_type = var.aws_ec2_pro_pub_wp_01["instance_type"] availability_zone = var.aws_ec2_pro_pub_wp_01["availability_zone"] key_name = var.aws_ec2_pro_pub_wp_01["key_name"] disable_api_termination = var.is_production ? true : false vpc_security_group_ids = [module.aws_sg_ec2_default.id,module.aws_sg_ec2_pro_pub_wp_01.id] subnet_id = module.aws_sn_za_pro_pub_32.id associate_public_ip_address = var.aws_ec2_pro_pub_wp_01["associate_public_ip_address"] instance_tags = {} tag_private_name = var.aws_ec2_pro_pub_wp_01["tag_private_name"] tag_public_name = var.aws_ec2_pro_pub_wp_01["tag_public_name"] tag_app = var.aws_ec2_pro_pub_wp_01["tag_app"] tag_app_id = var.aws_ec2_pro_pub_wp_01["tag_app_id"] tag_os = var.aws_ec2_pro_pub_wp_01["tag_os"] tag_os_id = var.aws_ec2_pro_pub_wp_01["tag_os_id"] tags_environment = var.aws_ec2_pro_pub_wp_01["tags_environment"] tag_cost_center = var.aws_ec2_pro_pub_wp_01["tag_cost_center"] register_dns_private = true route53_private_zone_id = module.aws_route53_public.id register_dns_public = true route53_public_zone_id = module.aws_route53_public.id root_block_device = { volume_size = var.aws_ec2_pro_pub_wp_01["root_block_device_size"] volume_type = var.aws_ec2_pro_pub_wp_01["root_block_device_volume_type"] delete_on_termination = var.is_production ? false : true #If production, Do not delete! } volume_tags = { Name = var.aws_ec2_pro_pub_wp_01["name"] } ignore_changes = ["ami"] }
Resource Security in AWS with Terraform
Securing AWS VPC resources with Terraform makes use of 3 modules:
- modules/aws/security/group: creates a security group and returns the id to be used in rules and instance association.
- modules/aws/security/rule/cidr_blocks: creates a security rule associated to a previously created security group, the cidr_blocks module uses an IP address range as source for the traffic.
- modules/aws/security/rule/source_group: creates a security rule associated to a previously created security group, thesource_group module uses another security group as source. See RDS creation for an example of its usage.
Best Practice: AWS Security Groups
Create a small number of security groups than can be combined together to create the desired security configuration. We recommend having a generic group for resource each type with all common rules, and a specif group for each individual resource with the particularities.
Recommended Security Groups:
- A generic default group for each resource type: these groups are used to hold default groups that apply to the type of resource, for example the SSH access to EC2 instances from a fixed administration IP address or the access to the database port for administration from a fixed administration IP.
Examples:- ditwl-sg-rds-mariadb-def: default security group for all the MariaDB RDS resources. It is specific for MariaDB RDS resources as other type of database will use a different port.
- ditwl-sg-ec2-def: default security group for all EC2 instances. It will have the SSH access rule.
- A specific group for each resource: It is recommended to have a group for each resource, it will be named using part of the resource name and the prefix will have the cloud and the resource type. Examples:
- ditwl-aws-sg-rds-mariadb-pro-pub-01: security group for all the MariaDB RDS resources.
- ditwl-sg-ec2-pro-pub-01: security group for all the WP EC2 instances.
Avoid creating too many groups and don’t use CIDR as a source (except for Internet as a source). It is better to use groups as a source, that way an element gets access to other resources by being a member of a group, not by having a specific IP that can change.
The following illustration shows the security groups and rules applied to each AWS resource.

Continue the Terraform and Ansible demo, see:
- Terraform AWS Tutorials
- Ansible Tutorials