VMware Hands-on Labs

HOL Three-Tier Application, Part 3 – App Server

This is the third post in the series about building a three-tier application for demonstration, lab, and education purposes. At this point, you have downloaded Photon OS and prepared the base template according to the specifications and have built and tested the database server, db-01a.

Working from the bottom-up, we move to the application server, which sits between the database and the web tier. As with the database, this is nothing fancy, but it gets the job done.

The Application Server (app-01a)

The application server sits in the middle of our stack and handles the formatting of data that is returned from the database. We will again use Apache and Python because we know how to use those tools from the database server build. This time, the CGI script presents an HTML form and table containing data pulled from the database via HTTP.

To add a bit of realism and fun to the process, we will configure SSL on the application server. The basic configuration is pretty simple once you have the certificate and key, and it is good to know how to do. Sure, we will be using our own self-signed certificate, but this is a lab!

The red box in the following diagram highlights the component that we are building in this post.

app-server

Note that the first steps look quite a bit like the steps we performed for the database server. I’ll outline them here. Details can be found in my previous post. Let’s get started!

  1. Deploy a copy of the base Photon template you created by following the steps in my first post. Name it something that makes sense to you for your app server. I called mine app-01a
  2. Power it up and log in as the root user
  3. Change the hostname in /etc/hostname and in /etc/hosts
  4. Change the IP address in /etc/systemd/network/10-static-eth0.network
  5. Use a SSH client to access the machine as root (this makes pasting commands easier)
  6. Install the Apache web server

Install the Apache server here, just like on the database server

# tdnf install httpd

Due to the SSL requirement on this machine, there are more files to edit than in the database server build, so get your editor ready!

Prepare to create the certificate and key

Creating the certificate is easiest if we pass it a file containing all of the answers needed by openssl. Navigate to /tmp and create the file app-01a.conf

# cd /tmp 
# vi app-01a.conf

Put the following contents into the file. Substitute values appropriate for your environment. The CN and the values under the [alt_names] section are the most important for our purposes here.

[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
C = US
ST = California
L = Palo Alto
O = VMware
OU = Hands-on Labs
CN = app-01a.corp.local
[v3_req]
keyUsage = keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = app-01a.corp.local
DNS.2 = app-01a
IP.1 = 192.168.120.20

Save and close the file.

Generate the key and certificate from the configuration file

Note that this is a long command. You may need to scroll all the way to the right to get all of it.

# openssl req -x509 -nodes -days 1825 -newkey rsa:2048 -keyout app.key -out app.pem -config app-01a.conf

(optional) Validate that the PEM file “looks right”

# openssl x509 -in app.pem -noout -text

Read through the text output and make sure the certificate looks as you expect. If you filled out the configuration file properly, it should be fine.

Leave those files where they are for now. We’ll need them in a few minutes.

Configure the web server to run SSL on port 8443

SSL over the default port is so yesterday. Besides, we want something that differentiates this traffic from the port 443 SSL traffic that comes from the client to the web server. Changing the port makes the firewall rules more interesting and easier to distinguish. Besides, this is not normally user-facing, so there is no complexity with requiring normal users to append “:8443” to the end of the host portion of the URL.

Open the configuration file

Use the “+52” to jump to the line we want.

# vi +52 /etc/httpd/httpd.conf

Disable listening on port 80

We don’t need this server listening on the default HTTP port, so put a # in front of the “Listen 80” on line 52)

#Listen 80

Load the SSL module

Is this thing on? We need to turn on the SSL functionality, which is disabled in the default configuration. Uncomment (remove the # from the beginning of) line 143:

LoadModule ssl_module /usr/lib/httpd/modules/mod_ssl.so

Load the CGI module

Just like on the database server, we have to enable the CGI module. Add the following text on its own line at the end of the module loading, above the <IfModule unixd_module> text. I use line 176.

LoadModule cgi_module /usr/lib/httpd/modules/mod_cgi.so

Include the SSL configuration file

Near the bottom of the file (around line 508), un-comment the line

Include conf/extra/httpd-ssl.conf

Save the file and exit.

Configure SSL Settings

Open the SSL “extra settings” file.

# vi +36 /etc/httpd/conf/extra/httpd-ssl.conf

On line 36, locate the Listen line and change the port number from 443 to 8443

Listen 8443

To make things work, I also had to comment out line 92:

#SSLSessionCache "shmcb:/etc/httpd/logs/ssl_scache(512000)"

Change the virtual host’s port number from 443 to 8443 on line 121

<VirtualHost _default_:8443>

On lines 144 and 154, ensure that the following are set (these should be the defaults) and note the paths:

SSLCertificateFile "/etc/httpd/conf/server.crt"
SSLCertificateKeyFile "/etc/httpd/conf/server.key"

Put the certificates in the right place

Remember the files you created at the beginning? Now is the time to move them to where they are expected. Just overwrite anything is already there.

# mv /tmp/app.key /etc/httpd/conf/server.key
# mv /tmp/app.pem /etc/httpd/conf/server.crt

Start the web server and set it to startup when the VM boots

# systemctl start httpd
# systemctl enable httpd

Everything should start up cleanly. If not, you may have a typo somewhere. The following command provides some good information to help locate the issue:

# systemctl status -l httpd.service

Once you have the web server started properly, take a little break. Now is just the configuration part.

Create the Python script that is our “application”

As I have written previously, Python is not my native language, but it is already there and I can convince it to do what I need. This CGI script takes the text data off the HTTP connection from the database server and formats it. It also has a simple filter field and button to change the data view a bit. Each button click submits a new request to the database and shows that the connection is still possible.

I have added some complexity here because it can provide useful information in some demonstration cases. If the IP address of the source webserver is in the webservers table, its name will be displayed. Otherwise, the IP address is used when the Accessed via: line is printed. In certain situations, this can be useful for demonstrating that load balancing is working.

Create the script

# vi /etc/httpd/cgi-bin/app.py

Add the following contents to app.py, remembering that spacing is important:

#!/usr/bin/env python
import os, sys, cgi
import requests

webservers = {
  '192.168.120.30':'web-01a',
  '192.168.120.31':'web-02a',
  '192.168.120.32':'web-03a'
 }

print "Content-type:text/html\n\n";
print "<head><title>Customer Database</title></head>\n"
print "<body>\n"
print "<h1>Customer Database Access</h1>\n"

remote = os.getenv("REMOTE_ADDR")
form = cgi.FieldStorage()
querystring = form.getvalue("querystring")

if remote in webservers :
   accessName = webservers[remote]
else :
   accessName = remote

print "Accessed via:",accessName,"\n<p>"

if querystring != None:
   url = 'http://db-01a.corp.local/cgi-bin/data.py?querystring=' + querystring
else:
   url = 'http://db-01a.corp.local/cgi-bin/data.py'
   querystring = ""

r = requests.get(url)

print '<form action="/cgi-bin/app.py">'
print ' Name Filter (blank for all records):'
print ' <input type="text" name="querystring" value="'+querystring+'">'
print ' <input type="submit" value="Apply">'
print '</form>'

print "\n<table border=1 bordercolor=black cellpadding=5 cellspacing=0>"

print "\n<th>Rank</th><th>Name</th><th>Universe</th><th>Revenue</th>"

#deal with the data coming across the wire
a = r.text.split("|\n#")
for row in a:
   if len(row) != 1:
      print "<tr>"
      splitrow = row.split("|")
      for item in splitrow:
         if item != None:
            print "<td>",item,"</td>"
      print "</tr>\n"
   print "</body></html>\n"

Save and close the file.

Set execute permissions

# chmod 755 /etc/httpd/cgi-bin/app.py

Verify

Now, as long as your database server is online and reachable, accessing the script via https on port 8443, will produce the data formatted as HTML:

# curl -k https://app-01a:8443/cgi-bin/app.py

If you access it via a graphical browser like Chrome or Firefox, you can and witness the fruits of your labors thus far. Note that you will have to accept the self-signed certificate, at least temporarily. The -k switch to curl tells it to ignore the untrusted SSL certificate and continue in “insecure” mode.

Here is what it looks like from Chrome. I used the IP address in the URL here because I have not created a DNS entry for app-01a. My plan is to provide access to this application only via the web front-end and I am only accessing the app server directly as part of the build.

demo-app-chrome

The good news is that we are almost finished with the application. As you can see, you can use this as it stands as a “two-tier application” by creating a DNS record for the app-01a server, but I promised you three tiers. Stick with me. We cover that in the next post.

Thanks for reading!