Week 04 - HTTP and Web Services¶
Topic¶
Setting up Apache web server with virtual hosts, deploying a static company website, reverse proxying a Python API with bearer token authorization, deploying WordPress, and configuring Apache modules (forensic logging, ModSecurity).
Company Requests¶
Ticket #401: Company Front Page
"Marketing has prepared a static website for Tartu Bubbles. Deploy an Apache web server and host it at
<vm_name>.sysadm.ee. Replace the placeholder contact addresses with ones using your VM name."
Ticket #402: Inventory API
"The warehouse team built a small Python inventory API. Deploy it behind Apache at
inventory.<vm_name>.sysadm.ee. Access must be restricted — only requests with the correct bearer token should get through."
Ticket #403: Company Blog
"We need a blog for the marketing team. Set up WordPress at
blog.<vm_name>.sysadm.ee. Make sure the database is secure."
Ticket #404: Security & Compliance
"The security team requires forensic logging on all websites and a web application firewall to block common attack patterns."
Testing without DNS
DNS is covered in Week 05, so *.sysadm.ee domains won't resolve yet. To test your virtual hosts:
- From your personal machine: add entries to your
/etc/hostsfile (orC:\Windows\System32\drivers\etc\hostson Windows) mapping your VM's IP to the domain names:<vm_ip> <vm_name>.sysadm.ee blog.<vm_name>.sysadm.ee inventory.<vm_name>.sysadm.ee - From the command line: use
curlwith the--resolveflag or-H "Host:"header:curl --resolve <vm_name>.sysadm.ee:80:<vm_ip> http://<vm_name>.sysadm.ee/ curl -H "Host: <vm_name>.sysadm.ee" http://<vm_ip>/ - The scoring server uses the same technique, so DNS is not required for checks to pass.
Scoring Checks¶
- Check 4.1: Port 80 is open and reachable from the scoring server.
- Method: The scoring server opens a TCP connection to your VM on port 80.
- Expected: Connection succeeds.
- Check 4.2:
<vm_name>.sysadm.eereturns the front page containing the VM name.- Method: The scoring server sends an HTTP request to your VM's IP with the
Host: <vm_name>.sysadm.eeheader. - Expected: HTTP 200 response with the body containing your VM's short name (e.g. if your VM is
student-xyz.cloud.ut.ee, the page must containstudent-xyz).
- Method: The scoring server sends an HTTP request to your VM's IP with the
- Check 4.3:
inventory.<vm_name>.sysadm.eedenies requests without credentials (HTTP 401/403) and allows requests with the correct bearer token (HTTP 200).- Method: The scoring server sends two HTTP requests to
inventory.<vm_name>.sysadm.ee/api/v1/inventory:- One without an
Authorizationheader. - One with the header
Authorization: Bearer 845e6732f32b81dd778972703474ccbb.
- One without an
- Expected: HTTP 401 or 403 for the first request, HTTP 200 for the second.
- Method: The scoring server sends two HTTP requests to
- Check 4.4:
blog.<vm_name>.sysadm.eeserves a WordPress site.- Method: The scoring server sends an HTTP request to your VM's IP with the
Host: blog.<vm_name>.sysadm.eeheader. - Expected: HTTP response body contains
wordpress,wp-content, orwp-includes.
- Method: The scoring server sends an HTTP request to your VM's IP with the
- Check 4.5: Forensic logging module is loaded and configured.
- Method: The scoring server logs in via SSH and checks that
mod_log_forensicis loaded (httpd -M) and that aForensicLogdirective exists in the Apache configuration. - Expected:
mod_log_forensicis loaded, and aForensicLogdirective exists.
- Method: The scoring server logs in via SSH and checks that
- Check 4.6: ModSecurity blocks requests to
/etc/passwd(HTTP 403 or 406).- Method: The scoring server sends an HTTP request to
http://<vm_name>.sysadm.ee/etc/passwd. - Expected: HTTP response code 403 or 406 (ModSecurity block).
- Method: The scoring server sends an HTTP request to
Tasks¶
Task 1: Install and Configure Apache¶
Complete
- Install
httpdsoftware. - Enable and start the service.
- Open port 80 in
firewalld(and your cloud security group if needed). - Verify the default test page is accessible on your VM's IP.
Reference: Technologies: Apache HTTPD
Task 2: Deploy the Front Page¶
The marketing team has prepared a static site. You need to deploy it and customize it for your VM.
Complete
- Download the static site source from the company repository.
- Replace the
tartububbles.localplaceholder email addresses with your<vm_name>.sysadm.eedomain. - Create a virtual host for
<vm_name>.sysadm.eeserving this content. - Verify the page loads and contains your VM name.
Reference: SOP: Web Server Management - Create a Virtual Host
Task 3: Deploy the Inventory API¶
The warehouse team's Python API needs to be deployed behind Apache with bearer token authorization.
Complete
- Install Python and Flask (
dnf install python3-pip && pip3 install flask). - Download the inventory API source from the company repository.
- Place the application in a sensible location (e.g.
/usr/local/lib/inventory/app.py) and create asystemdservice to run it. - Create a reverse proxy virtual host for
inventory.<vm_name>.sysadm.eeforwarding to the Flask app (port 5000). - Configure Apache to require a bearer token on this virtual host. Requests without the correct
Authorization: Bearerheader should be denied. The token is:845e6732f32b81dd778972703474ccbb.
Reference: SOP: Web Server Management - Set Up a Reverse Proxy, SOP: Web Server Management - Configure Bearer Token Authorization, SOP: Service Management - Create a Custom systemd Unit File
Task 4: Deploy WordPress¶
The marketing team needs a blog, and they have decided to use WordPress, which you'll need to install and configure.
Complete
- Install MariaDB, secure it with
mysql_secure_installation, and create a database and user for WordPress. - Install PHP prerequisites (
php-mysqlnd php-fpm php-json). - Download and extract WordPress to a document root.
- Set file ownership (
apache:apache) and SELinux context (httpd_sys_rw_content_t). - Create a virtual host for
blog.<vm_name>.sysadm.ee. - Complete the WordPress web installer.
Reference: Technologies: WordPress, Technologies: MariaDB
Task 5: Configure Forensic Logging¶
Complete
- Load
mod_log_forensicin Apache. - Add a
ForensicLogdirective to your virtual host configurations. - Verify forensic log entries appear after accessing your sites.
Reference: SOP: Web Server Management - Configure Forensic Logging, Concepts: Web Application Security - Forensic Logging
Task 6: Configure ModSecurity¶
The security team wants to defend against a certain attack related to being able to download a file through the web server. You'll need to block it to make them happy.
Complete
- Install
mod_security. - Configure a rule to block requests containing
etc/passwdin the URI. - Verify that accessing
http://<vm_name>.sysadm.ee/etc/passwdreturns HTTP 403 or 406.
Reference: SOP: Web Server Management - Configure ModSecurity Rules, Concepts: Web Application Security
Ansible Tips¶
This section covers tips for automating the tasks in this lab with Ansible. You are not required to automate everything this week, but starting early will save you time later.
Tags¶
Use tags to run specific roles instead of the whole playbook:
- { role: apache, tags: apache }
- { role: wordpress, tags: wordpress }
Run only the Apache role: ansible-playbook --tags=apache playbook.yml
See the Ansible documentation on tags for more.
Handlers¶
Use handlers to restart services only when configuration actually changes. Define a handler in roles/<rolename>/handlers/main.yml:
- name: restart httpd
systemd:
daemon_reload: yes
name: httpd
state: restarted
enabled: yes
Notify the handler from any task that modifies an Apache config file:
- name: Deploy virtual host config
template:
src: vhost.conf.j2
dest: /etc/httpd/conf.d/site.conf
notify: restart httpd
Apache will only be restarted when the config file actually changes — not on every playbook run.
WordPress wp-config.php Template¶
Instead of using the web-based installer, create wp-config.php as an Ansible template. Start from the sample file and use Jinja2 variables for the database settings:
{# wp-config.php.j2 — key lines to template #}
define( 'DB_NAME', '{{ wp_db_name }}' );
define( 'DB_USER', '{{ wp_db_user }}' );
define( 'DB_PASSWORD', '{{ wp_db_password }}' );
define( 'DB_HOST', 'localhost' );
Deploy it with the template module:
- name: Deploy wp-config.php
template:
src: wp-config.php.j2
dest: /var/www/html/wordpress/wp-config.php
owner: apache
group: apache
mode: '0640'
See also Technologies: WordPress — Manual wp-config.php Creation for the manual approach.
Automating mysql_secure_installation¶
The interactive mysql_secure_installation script cannot be run from Ansible directly. Instead, use the mysql_user and mysql_db modules to achieve the same result:
- name: Remove anonymous MySQL users
mysql_user:
name: ''
host_all: yes
state: absent
- name: Remove test database
mysql_db:
name: test
state: absent
- name: Create WordPress database
mysql_db:
name: wordpress
state: present
- name: Create WordPress database user
mysql_user:
name: admin
password: "{{ wp_db_password }}"
priv: "wordpress.*:ALL"
state: present
Note
You may need to install the PyMySQL Python library (pip3 install PyMySQL) on the target host for the mysql_* modules to work.
Useful Modules¶
These Ansible modules map directly to tasks in this lab:
dnf— install packagespip— install Python packages (Flask)template— deploy configuration files with variables (virtual hosts, wp-config.php, systemd units)copy/unarchive— deploy static files or extract archives (WordPress tarball)mysql_db/mysql_user— manage databases and usersseboolean— set SELinux booleans (e.g.httpd_can_network_connect)sefcontext— set SELinux file contexts (e.g.httpd_sys_rw_content_t)systemd— manage servicesfile— set ownership and permissions