Skip to content

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/hosts file (or C:\Windows\System32\drivers\etc\hosts on 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 curl with the --resolve flag 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.ee returns 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.ee header.
    • 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 contain student-xyz).
  • Check 4.3: inventory.<vm_name>.sysadm.ee denies 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 Authorization header.
      • One with the header Authorization: Bearer 845e6732f32b81dd778972703474ccbb.
    • Expected: HTTP 401 or 403 for the first request, HTTP 200 for the second.
  • Check 4.4: blog.<vm_name>.sysadm.ee serves a WordPress site.
    • Method: The scoring server sends an HTTP request to your VM's IP with the Host: blog.<vm_name>.sysadm.ee header.
    • Expected: HTTP response body contains wordpress, wp-content, or wp-includes.
  • Check 4.5: Forensic logging module is loaded and configured.
    • Method: The scoring server logs in via SSH and checks that mod_log_forensic is loaded (httpd -M) and that a ForensicLog directive exists in the Apache configuration.
    • Expected: mod_log_forensic is loaded, and a ForensicLog directive exists.
  • 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).

Tasks

Task 1: Install and Configure Apache

Complete

  1. Install httpd software.
  2. Enable and start the service.
  3. Open port 80 in firewalld (and your cloud security group if needed).
  4. 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

  1. Download the static site source from the company repository.
  2. Replace the tartububbles.local placeholder email addresses with your <vm_name>.sysadm.ee domain.
  3. Create a virtual host for <vm_name>.sysadm.ee serving this content.
  4. 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

  1. Install Python and Flask (dnf install python3-pip && pip3 install flask).
  2. Download the inventory API source from the company repository.
  3. Place the application in a sensible location (e.g. /usr/local/lib/inventory/app.py) and create a systemd service to run it.
  4. Create a reverse proxy virtual host for inventory.<vm_name>.sysadm.ee forwarding to the Flask app (port 5000).
  5. Configure Apache to require a bearer token on this virtual host. Requests without the correct Authorization: Bearer header 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

  1. Install MariaDB, secure it with mysql_secure_installation, and create a database and user for WordPress.
  2. Install PHP prerequisites (php-mysqlnd php-fpm php-json).
  3. Download and extract WordPress to a document root.
  4. Set file ownership (apache:apache) and SELinux context (httpd_sys_rw_content_t).
  5. Create a virtual host for blog.<vm_name>.sysadm.ee.
  6. Complete the WordPress web installer.

Reference: Technologies: WordPress, Technologies: MariaDB

Task 5: Configure Forensic Logging

Complete

  1. Load mod_log_forensic in Apache.
  2. Add a ForensicLog directive to your virtual host configurations.
  3. 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

  1. Install mod_security.
  2. Configure a rule to block requests containing etc/passwd in the URI.
  3. Verify that accessing http://<vm_name>.sysadm.ee/etc/passwd returns 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 packages
  • pip — 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 users
  • seboolean — set SELinux booleans (e.g. httpd_can_network_connect)
  • sefcontext — set SELinux file contexts (e.g. httpd_sys_rw_content_t)
  • systemd — manage services
  • file — set ownership and permissions