Secure web application architecture

In this tutorial we’re going to take a look in an important topic, secure server deployment. We’re going to create a web application that handles API requests using Apache 2, PHP 7 and MySQL. Our architecture is made of one API server and one database server.

1. Prepare your system
Always keep your system up to date. This ensures you get all latest security patches.

sudo apt-get u
pdate
sudo apt-get upgrade

2. Install Apache2
The Apache web server is among the most popular web servers in the world. It has been in wide use for much of the history of the web and it performs really well.

sudo apt-get apache2

After installation has been successful we can configure our server name.

sudo nano /etc/apache2/apache2.conf

Find ServerName in the configuration file and edit it like this

ServerName your_domainName_or_IP

Next, check for syntax errors by typing:

sudo apache2ctl configtest

If everything went well, it should output Syntax OK.

3. Install PHP7
sudo apt install php

Always sanitize user input for our API using PHP’s built in mysql_real_escape_string method!

4. Install Fail2Ban
While connecting to your server through SSH can be very secure, the SSH daemon itself is a service that must be exposed to the internet to function properly. This comes with some inherent risk.

A service called fail2ban can mitigate this problem by creating rules that can automatically alter your iptables firewall configuration based on a predefined number of unsuccessful login attempts.

sudo apt-get install fail2ban

The fail2ban service keeps its configuration in /etc/fail2ban/jail.conf file.

Since this file can be modified by package upgrades, we should not edit this file in-place, but rather copy it so that we can make our changes safely.

awk '{ printf "# "; print; }' /etc/fail2ban/jail.conf | sudo tee /etc/fail2ban/jail.local

Settings located under the [DEFAULT] section will be applied to all services enabled for fail2ban that are not overridden in the service’s own section.

To edit our configuration file type

sudo nano /etc/fail2ban/jail.conf

Our configuration file should look like this

[DEFAULT]
ignoreip = 127.0.0.1/8
bantime = 172800
findtime = 60
maxretry = 5

The ignoreip setting configures the source addresses that fail2ban ignores. By default, it is configured to not ban any traffic coming from the local machine.

The bantime parameter sets length of time in seconds that a client will be banned when they have failed to authenticate correctly. In our example a client will be banned for 48 hours.

The next two parameters that you want to pay attention to are findtime and maxretry. These work together to establish the conditions under which a client is found to be an illegitimate user that should be banned. The maxretry variable sets the number of tries a client has to authenticate within a window of time defined by findtime, before being banned. In our example a client will be banned if it fails to login 5 times in 60 seconds.

5. Configuring Iptables
Iptables is a standard firewall included in most Linux distributions by default (a modern variant called nftables will begin to replace it). Iptables are reloaded on every boot, to prevent that we’ going to install iptables-persistent package.

sudo apt-get install iptables-persistent

Next we’re going to start adding our rules.

sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
sudo iptables -A INPUT -j DROP

You can save the firewalls so that they survive a reboot by typing:
sudo dpkg-reconfigure iptables-persistent

With this configuration we’re only accepting incoming traffic on our management SSH port 22, web server port 80 and our SSL web server port 443 (SSL).

Often, services on the server communicate with each other by sending network packets to each other. We want this type of behaviour to be allowed

sudo iptables -A INPUT -i lo -j ACCEPT

sudo service fail2ban start

Because we’re running our server on VPC instance for DDoS protection we’ll be using Alibaba Cloud Anti-DDoS.

6. Configuring Apache settings
We’re going to configure some additional Apache settings, such as hiding our server version, and disabling ETags. Open your Apache configuration and edit it:

sudo nano /etc/apache2/httpd.conf

<Directory /var/www/your_api_folder>
Options FollowSymLinks
AllowOverride None
Require all granted
</Directory>

Header unset Pragma
FileETag None
Header unset ETag
ServerSignature Off
ServerTokens Prod

We’re also going to hide Powerd-by header. Open your php.ini

sudo nano /etc/php/7.0/apache2/php.ini

edit the following line so it looks like this expose_php = Off

7. Database deployment
Install MySQL server

sudo apt-get install mysql-server

Since we’re using another VPC instance for our database server, we need to repeat steps 1, 4 and 5. We’re going to add different set of Iptables rules for database server.

iptables -A INPUT -p tcp -s **SERVER_IP** --dport 3306 -j ACCEPT
iptables -A INPUT -p tcp -s 127.0.0.1 --dport 3306 -j ACCEPT
iptables -A INPUT -p tcp -s **SERVER_IP** --dport 22 -j ACCEPT
iptables -A INPUT -p tcp --dport 3306 -j DROP

Our database server only accepts connections on port 3306 and 22 from our API server IP. If we want to manage our database server we need to connect to it via our API server since it only allows connections from its IP.

Next, we want our MySQL server to accept remote connections

sudo nano */etc/mysql/mysql.conf.d/mysqld.cnf*

find this line bind-address = 127.0.0.1 and replace 127.0.0.1 with our API server IP.

It’s also a good practice to create a new MySQL user that won’t have root privileges, we’re going to use newly created user to run all our queries.

Conclusion
In this tutorial we covered some basic server security practices. It’s also strongly recommended to use a SSL certificate and also pin it to your apps that are using this API. If everything went fine we should be able to start Apache and MySQL server.

I see at least one glaring example of ill advice:

IMHO, one should not use obsolete mysql_* but should instead take advantage of using prepared statements that are available with mysqli_* / PDO

In fact, if one were to
3. Install PHP7
they would find that mysql_real_escape_string does not exist.

1 Like

That doesn’t make sense. You can’t bind to the IP of another machine. You can only bind to IPs of the local machine, i.e., the machine MySQL is running on.

The default value, 127.0.0.1, only accepts local connections, and not from remote machines, so the default should be replaced to either a remote IP of the MySQL machine if you know it, or to 0.0.0.0 to bind to all available addresses.

Also, why did you choose to run a MySQL machine yourself, instead of the managed MySQL service Alibaba offers, namely ApsaraDB for RDS?

1 Like

Thanks for the heads up, it’s a typo (missing an “i” in mysqli). :blush: Also as you mentioned mysql_real_escape_string has been deprecated since PHP 5.5.

It’s a typo, it should be find this line bind-address = 127.0.0.1 and replace 127.0.0.1 with our MySQL server IP. Thanks for letting me know for that mistake. :blush:

I decided to use a dedicated MySQL machine because this tutorial is security oriented and in order to show how to set up a secure instance of MySQL server we have to do it ourselves. Also running our own MySQL instance gives as more options for customization of our server hardware and generally speaking, in the long run, it’s cheaper.