DEV Community

Joel Oguntoye
Joel Oguntoye

Posted on

Deploying AWS Solution for a company's Websites

Objective:

  • Build a secure infrastructure in AWS VPC for a company that uses Wordpress CMS for its main company site and a tooling website for its DevOps team.
  • Reverse proxy technology with Nginx has been selected with the aim to improve security and performance.
  • Cost, reliability and scalability are the major considerations for the company, to make the infrastructure resilient to server failures, accomodate increased traffic and have reasonable cost.

Infrastructure:

project-15-infrastructure IMG
Credits: Darey.io

Initial setup

  • Create a subaccount in AWS to manage all resources for the company's AWS solution and assign any appropriate name e.g 'DevOps'
  • From the root account, create an organization unit (OU) and move the subaccount into the OU. We will launch Dev resources in the subaccount

creating_organization_unit_ou_and_Adding_devops_account

  • Create a domain name for the company website on domain name providers. You can obtain a free domain name from freenom website
  • Create a hosted zone in AWS and map the hosted zone name servers to the domain name.

creating_route_53_hosted_zone_in_aws

mapping_hosted_zone_name_Servers_to_domain_name

Setup a Virtual Private Cloud on AWS

  • Create a VPC
  • Create subnets (public and private subnets) as shown in the architecture. The subnets Ips are CIDR IPs. We can use utility sites like IPinfo.io to see the range of IP addresses associated with each subnet.

Public-subnet-in-two-AZs

  • Create private and public route tables and associate it with with the private and public subnets respectively

edit_route_in_public_routetable_to_allow_subnets_access_the_internet

  • Edit a route in public route table, and associate it with the Internet Gateway. This allows the public subnet to access the internet

edit_route_in_public_routetable_to_allow_subnets_access_the_internet

  • Create a NAT gateway and assign an elastic IP to it. You can use a NAT gateway so that instances in a private subnet can connect to services outside your VPC but external services cannot initiate a connection with those instances.

edit_route_in_private_routetable_to_allow_nat_Gateway_access_the_internet

  • Create security groups for:
    • Nginx servers: To allow access to from external application load balancer to the Nginx server
    • Bastion servers: Access to the Bastion servers should be allowed only from workstations that need to SSH into the bastion servers.
    • External Load Balancer: The external application load balancer will be accessible from the internet
    • Internal load balancer: The internal load balancer will allow https and http access from the Nginx server and SSH access from the bastion server
    • Web servers: The webservers will allow https and http access from the internal load balancer and SSH access from the bastion server
    • Data layer security group: The access to the data layer for the appliation (consisting of both the Amazon RDS storage and Amazon EFS as shown in the architecture), will consist of webserver access to the RDS storage and both webserver and Nginx access to the Amazon EFS file system.

security_grp_rule_for_bastion_host

security_grp_rule_for_External_ALB

Create a SSL/TLS certificate using Amazon Certificate Manager (ACM) to be used by the external and internal Application Load balancers (ALB)

  • Create a wild card SSL/TLS certificate to be used when creating the external ALB. We want to ensure connection to the external ALB is secured and data sent over the internet is encrypted. Since the external ALB will listen for client requests to both the tooling webserver and the wordpress server, we'll create a wild card TLS certificate. Select DNS validation

creating_public_wild-card-TLS_certificate_for_ALBs

Create Amazon EFS

  • Create Amazon Elastic file System (EFS) to be used by the web servers for files storage. The mount targets to be specified fot the elastic file system will be the subnets for the webservers. Specifying the mount targets makes the EFS storage available to the webservers

creating-Amazon-EFS-for-the-wordpress-and-tooling-servers-to-access

  • Also, we specify access points on the EFS we created for the web servers. Amazon EFS access points are application-specific entry points into a shared file system. In this project, we create two access points on the EFS one for each web servers, each with its own root directory path specified. Set the POSIX user and user group ID to root user and the root directory path to /wordpress and /tooling respectively. - The root directory creation permission is set to 0755 to allow read write permissions on the file system by the clients

tooling-access-point-created on-EFS-for-the-tooling-webserver

Create KMS key to be used for RDS

  • Next, navigate to AWS KMS page to create a cryptographic key to be used to secure the MySQL relational database for the project.
  • Create a symmetric key
  • Set the admin user for the key. You can leave the 'key usage permission' with the default settings

creating-symmetric-key-for-encrypting-and-decrypting-the-DB

kms-key

Create DB subnet group

  • A DB subnet group is a collection of subnets (typically private) that you create for a VPC and that you then designate for your DB instances.
  • From project architecture, specify the appropriate private subnets (private subnet 3 and 4) for the DB.

creating_subnet_group_for_RDS

Create AWS RDS

  • Select MySQL engine for the RDS
  • Select Dev/Test template. This is an expensive service. For this purpose of this project we can still use free tier template, however, we will not be able to encrypt the database using the KMS key we created.
  • set the DB name
  • Set master username and password
  • Select the VPC for the DB
  • Ensure the DB is not publicly accessible
  • Select the appropriate security group for the DB
  • set the initial database name

creating_DB

Create compute resources

#### Setup compute resources for Nginx

  • provision EC2 instance for Nginx
  • Install the following packages

    epel-release
    python
    htop
    ntp
    net-tools
    vim
    wget
    telnet
    
  • We also need to install a self signed SSL certificate on the Nginx AMI. The Nginx AMI will be attached to a target group that uses HTTPs protocol and health checks. The load balancer establishes TLS connections with the targets using certificates that you install on the targets

  • Nginx instance installations:

  yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm
  yum install -y dnf-utils http://rpms.remirepo.net/enterprise/remi-release-8.rpm
  yum install wget vim python3 telnet htop git mysql net-tools chrony -y
  systemctl start chronyd
  systemctl enable chronyd

  #configure SELinux policies
  setsebool -P httpd_can_network_connect=1
  setsebool -P httpd_can_network_connect_db=1
  setsebool -P httpd_execmem=1
  setsebool -P httpd_use_nfs 1

  #install Amazon efs client utils
  git clone https://github.com/aws/efs-utils
  cd efs-utils
  yum install -y make
  yum install -y rpm-build
  make rpm 
  yum install -y  ./build/amazon-efs-utils*rpm

  #setup self-signed certificate for the Nginx AMI
  sudo mkdir /etc/ssl/private
  sudo chmod 700 /etc/ssl/private
  openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/kiff.key -out /etc/ssl/certs/kiff.crt
  sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048
Enter fullscreen mode Exit fullscreen mode
  • We will reference the SSL key and cert in the Nginx Reverse.conf configuration file. Also we will specify host header in the config file to forward traffic to the tooling server.

Nginx reverse.conf file

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;


    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

     server {
        listen       80;
        listen       443 http2 ssl;
        listen       [::]:443 http2 ssl;
        root          /var/www/html;
        server_name  *.kiff-web.space;


        ssl_certificate /etc/ssl/certs/kiff.crt;
        ssl_certificate_key /etc/ssl/private/kiff.key;
        ssl_dhparam /etc/ssl/certs/dhparam.pem;



        location /healthstatus {
        access_log off;
        return 200;
       }


        location / {
            proxy_set_header             Host $host;
            proxy_pass                   https://internal-Kiff-internal-ALB-1756005909.eu-west-3.elb.amazonaws.com/; 
           }
    }
}

Enter fullscreen mode Exit fullscreen mode
  • We perform the above installations on the EC2 instance for the AMI step by step instead of adding them all to the launch template's user data, to reduce to size of
    the user data

  • Create an AMI from the instance

  • Create a Nginx target group of Instance type. Targets in the Nginx target groups will be accessed by the external Load balancer.

  • Prepare a launch template from the AMI instance

  • From EC2 Console, click Launch Templates from the left pane

    • Choose the Nginx AMI
    • Select the instance type (t2.micro)
    • Select the key pair
    • Select the security group
    • Add resource tags
    • Click Advanced details, scroll down to the end and configure the user data script to update the yum repo and install nginx. The userdata for Nginx:
   #!/bin/bash
   yum install -y nginx
   systemctl start nginx
   systemctl enable nginx
   git clone https://github.com/joeloguntoyeACS-project-config.git
   mv /ACS-project-config/reverse.conf /etc/nginx/
   mv /etc/nginx/nginx.conf /etc/nginx/nginx.conf-distro
   cd /etc/nginx/
   touch nginx.conf
   sed -n 'w nginx.conf' reverse.conf
   systemctl restart nginx
   rm -rf reverse.conf
   rm -rf /ACS-project-config
Enter fullscreen mode Exit fullscreen mode

Setup compute resources for Bastion server

  • Provision EC2 instance for Bastion server
  • Bastion instance installations:
yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm 
yum install -y dnf-utils http://rpms.remirepo.net/enterprise/remi-release-8.rpm 
yum install wget vim python3 telnet htop git mysql net-tools chrony -y
systemctl start chronyd
systemctl enable chronyd
Enter fullscreen mode Exit fullscreen mode

Connect to the RDS from the Bastion server and create DBs named toolingdb and wordpressdb for the two webservers

  • SSH into the RDS instance
eval `ssh-agent`
ssh-add project-key.pem
ssh -A ec2-user@ip_address
mysql -h <RDS_endpoint> -u <username> -p 
>>create database wordpressdb;
>>create database toolingdb;
Enter fullscreen mode Exit fullscreen mode

creating_databases_on_RDS_using_bastion_server_to_access_RDS

Setup compute resources for web server

  • Provision EC2 instance for web servers
  • Web server installations:
yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm
yum install -y dnf-utils http://rpms.remirepo.net/enterprise/remi-release-8.rpm
yum install wget vim python3 telnet htop git mysql net-tools chrony -y
systemctl start chronyd
systemctl enable chronyd

#configure SELinux policies
setsebool -P httpd_can_network_connect=1
setsebool -P httpd_can_network_connect_db=1
setsebool -P httpd_execmem=1
setsebool -P httpd_use_nfs 1

#install Amazon EFS client utils for mounting the targets on the EFS
git clone https://github.com/aws/efs-utils
cd efs-utils
yum install -y make
yum install -y rpm-build
make rpm 
yum install -y  ./build/amazon-efs-utils*rpm

#setup self-signed certificate for apache server
yum install -y mod_ssl
openssl req -newkey rsa:2048 -nodes -keyout /etc/pki/tls/private/kiff-web.key -x509 -days 365 -out /etc/pki/tls/certs/kiff-web.crt

# edit the ssl.conf file to specify the part to the certificate and the key
vi /etc/httpd/conf.d/ssl.conf
Enter fullscreen mode Exit fullscreen mode
  • Create an AMI from the instance
  • We will create two launch templates from this AMI, one each for the wordpress server and the tooling server. The launch templates will differ in the user data for each server. The launch tmeplate
  • Configure user data for the worpress launch template:
#!/bin/bash
mkdir /var/www/
sudo mount -t efs -o tls,accesspoint=fsap-0f9364679383ffbc0 fs-8b501d3f:/ /var/www/
yum install -y httpd 
systemctl start httpd
systemctl enable httpd
yum module reset php -y
yum module enable php:remi-7.4 -y
yum install -y php php-common php-mbstring php-opcache php-intl php-xml php-gd php-curl php-mysqlnd php-fpm php-json
systemctl start php-fpm
systemctl enable php-fpm
wget http://wordpress.org/latest.tar.gz
tar xzvf latest.tar.gz
rm -rf latest.tar.gz
cp wordpress/wp-config-sample.php wordpress/wp-config.php
mkdir /var/www/html/
cp -R /wordpress/* /var/www/html/
cd /var/www/html/
touch healthstatus
sed -i "s/localhost/kiff-database.cdqtynjthv7.eu-west-3.rds.amazonaws.com/g" wp-config.php 
sed -i "s/username_here/Kiffadmin/g" wp-config.php 
sed -i "s/password_here/admin12345/g" wp-config.php 
sed -i "s/database_name_here/wordpressdb/g" wp-config.php 
chcon -t httpd_sys_rw_content_t /var/www/html/ -R
systemctl restart httpd
Enter fullscreen mode Exit fullscreen mode
  • Configure user data for tooling launch template:
#!/bin/bash
mkdir /var/www/
sudo mount -t efs -o tls,accesspoint=fsap-01c13a4019ca59dbe fs-8b501d3f:/ /var/www/
yum install -y httpd 
systemctl start httpd
systemctl enable httpd
yum module reset php -y
yum module enable php:remi-7.4 -y
yum install -y php php-common php-mbstring php-opcache php-intl php-xml php-gd php-curl php-mysqlnd php-fpm php-json
systemctl start php-fpm
systemctl enable php-fpm
git clone https://github.com/Livingstone95/tooling-1.git
mkdir /var/www/html
cp -R /tooling-1/html/*  /var/www/html/
cd /tooling-1
mysql -h kiff-db.cdqpbjkethv0.us-east-1.rds.amazonaws.com -u kiffAdmin -p toolingdb < tooling-db.sql
cd /var/www/html/
touch healthstatus
sed -i "s/$db = mysqli_connect('mysql.tooling.svc.cluster.local', 'admin', 'admin', 'tooling');/$db = mysqli_connect('kiff-db.cdqpbjkethv0.us-east-1.rds.amazonaws.com ', 'kiffAdmin', 'admin12345', 'toolingdb');/g" functions.php
chcon -t httpd_sys_rw_content_t /var/www/html/ -R
systemctl restart httpd
Enter fullscreen mode Exit fullscreen mode

configuring_user_Data_for_bastion_launch_template

Target group IMG:

creating_target_groups_for_nginx_tooling_and_wordpress_servers_to_be_Targeted_by_ALBs

Create load balancers (the external load balancer and the internal load balancer)

  • Create external load balancer.

    • Assign at least two public subnets
    • set protocol as HTTPs on port 443
    • Register the Nginx target group for the external load balancer
    • select the security group for the external load balancer
    • set path for healthchecks as /healthstatus
  • Create the internal load balancer

    • Assign at least two private subnets
    • set protocol as HTTPs on port 443
    • select the security group for the internal load balancer
    • set path for healthchecks as /healthstatus
    • Register the wordpress target group as the default target for the internal load balancer
    • Configure a listener rule to allow the internal load balancer forward traffic to the tooling target group based on the rule set.
    • Since we'll configure host header in our Nginx reverse proxy server, we will specify the listener rule on the ALB to forward traffic to the tooling target if the host header is the domain name : tooling.kiff-web.space

creating_ext_and_int_load_balancers_with_listener_rule_set_for_internal_lb

configuring_listener_rule_for_internal_LB

Create Autoscaling groups for the launch templates (Bastion, Nginx, tooling and wordpress servers)

  • Configure Autoscaling for Nginx

    • Select the right launch template
    • Select the VPC
    • Select both public subnets
    • Enable Application Load Balancer for the AutoScalingGroup (ASG)
    • Select the Nginx target you created before
    • Ensure that you have health checks for both EC2 and ALB
    • The desired capacity is 2
    • Minimum capacity is 2
    • Maximum capacity is 4
    • Set scale out if CPU utilization reaches 90%
    • Ensure there is an SNS topic to send scaling notifications
  • Configure Autoscaling For Bastion

    • Select the right launch template
    • Select the VPC
    • Select both public subnets
    • Select No load balancer for bastion Autoscaling group, since the Bastion server is not targeted by any load balancer
    • Set scale out if CPU utilization reaches 90%
    • Enable health checks
    • The desired capacity is 2
    • Set Minimum capacity to 2
    • Maximum capacity to 4
    • Ensure there is an SNS topic to send scaling notifications

creating_bastion_AG_review

  • Configure Autoscaling group for tooling and wordpress webserver
    • Select the right launch template
    • Select the VPC
    • Select both private subnets 1 and 2
    • Enable Application Load Balancer for the AutoScalingGroup (ASG)
    • Select the target groups you created before
    • Ensure that you have health checks for both EC2 and ALB
    • The desired capacity is 2
    • Minimum capacity is 2
    • Maximum capacity is 4
    • Set scale out if CPU utilization reaches 90%
    • Ensure there is an SNS topic to send scaling notifications

Create A Records in the Route 53 hosted zone

  • We need to ensure that the main domain for the WordPress website can be reached, and the subdomain for Tooling website can also be reached using a browser.
  • create A record for tooling and wordpress
    • set record type to 'A-Routes to a IPV4 address'
    • Set Route Traffic to 'Alias to application load balancer and classic load balancer'
    • Set the load balancer

adding_A_record_to_DNS

Healthchecks status for wordpress targets:

showing_health_checks_for_ALB_targets

Accessing the tooling and wordpress servers:

wordpress_server_page_loaded

tooling_server_page_loaded_with_tooling_A_Record

Note:

  • Ensure that SELinux policies are properly configured on the server instances.
  • Ensure you delete all the resources you created to avoid accumulating charges.

Top comments (0)