courses

Lab — Week 6: Policy, Access Control, and Host Firewall
Due: 14 August 2026
Submission: GitLab repo secdevops-s26-<CECS>

Overview

Add the final layer of host-side controls: policy-as-code, least-privilege access, and a host firewall with automated response.

Prerequisites


Part 1: Secrets Detection — Your Course Repo

  1. Run gitleaks across the full history of your course repo:
    cd ~/path/to/secdevops-s26-<CECS>
    gitleaks git . \
      --report-format json \
      --report-path lab09/gitleaks-report.json
    

    ℹ️ gitleaks git scans commit history; gitleaks dir scans only the working tree. Older handouts and blog posts use gitleaks detect --source ., which was renamed in gitleaks 8.19 — the old form still runs but is hidden from --help.

Part 2: Policy-as-Code with auditd and AppArmor

Security controls written as code are enforced continuously, are version-controlled, and are testable. This part implements auditd rules that log privileged operations on ubuntu-server, connects those events to the Wazuh dashboard, and confines nginx with an AppArmor profile.

auditd Rules

  1. Enable and start auditd: sudo systemctl enable --now auditd

  2. Write /etc/audit/rules.d/dvwa.rules with the following rules. Each rule must include a -k key tag:
    • Watch /etc/shadow for read access
    • Watch /etc/sudoers and /etc/sudoers.d/ for any write
    • Audit all executions by UID 0 (syscall execve)
    • Watch /var/ossec/etc/ossec.conf for any modification
    • Watch the Docker socket /var/run/docker.sock for access
  3. Load the rules: sudo augenrules --load

  4. Verify the rules are active: sudo auditctl -l

  5. Trigger each rule deliberately and confirm events appear:
    sudo cat /etc/shadow               # triggers shadow_access key
    sudo touch /etc/sudoers.d/test && sudo rm /etc/sudoers.d/test   # triggers sudoers key
    sudo ls /                          # triggers root_exec key
    
  6. Search for triggered events:
    sudo ausearch -k shadow_access --start today
    sudo ausearch -k root_exec --start today
    
  7. Confirm the same events appear in the Wazuh dashboard under Security Events, filtered by rule.groups: audit. Take a screenshot.

  8. Commit etc/audit/rules.d/dvwa.rules (the file itself, not the system path) to your repo.

AppArmor Profile for nginx

  1. Generate an AppArmor profile for nginx using complain mode:
    sudo aa-genprof nginx
    

    While aa-genprof is running, send a few HTTP requests through nginx so the tool can observe normal behavior: curl http://localhost

  2. Review and refine the generated profile with: sudo aa-logprof

  3. Switch nginx to enforce mode:
    sudo aa-enforce /etc/apparmor.d/usr.sbin.nginx
    
  4. Verify nginx still serves DVWA correctly: curl -s -o /dev/null -w "%{http_code}" http://localhost

  5. Confirm the profile is in enforce mode: sudo aa-status | grep nginx

  6. Save the profile to your repo as lab11/nginx-apparmor-profile.

Threat Model Update

  1. In lab02/threat-model.md, mark threats mitigated by auditd and AppArmor. Add a note for each: what STRIDE category it addresses and the specific rule or profile that enforces it.

Deliverables

Create lab11/ in your repo containing:


Part 3: IAM, Host Firewall, and Automated Response

Least privilege applied at three levels in one part: service account restrictions limit what the DVWA process can do if compromised, nftables enforces which network paths are permitted, and fail2ban automatically blocks brute-force sources — all before a human can react.

Part 1 must be complete — auditd running on ubuntu-server.

Service Account Hardening

  1. Create a dedicated service account for DVWA with no login shell and no home directory:
    sudo useradd --system --no-create-home --shell /usr/sbin/nologin dvwa-svc
    
  2. Create /etc/sudoers.d/dvwa that grants dvwa-svc the single ability to restart its own container and nothing else:
    dvwa-svc ALL=(root) NOPASSWD: /usr/bin/systemctl restart dvwa
    
  3. Verify the restriction: sudo -l -U dvwa-svc The output must show exactly that one command and no others.

  4. Attempt a restricted command and confirm it fails:
    sudo -u dvwa-svc sudo bash   # must be denied
    

nftables Host Firewall

  1. Write /etc/nftables.d/lab.nft implementing the following policy (commit it to your repo):
    • Allow established and related connections (return traffic)
    • Allow SSH only from 10.10.10.10 (Kali)
    • Allow HTTP (80) and HTTPS (443) from any source
    • Allow MariaDB (3306) from 127.0.0.1 only
    • Drop all other inbound traffic
    • Allow all outbound traffic
  2. Apply the ruleset:
    sudo nft -f /etc/nftables.d/lab.nft
    sudo nft list ruleset          # verify
    
  3. Test from Kali (10.10.10.10):
    ssh dmcgrath@10.10.10.20       # must succeed
    curl http://10.10.10.20/       # must succeed
    
  4. Verify MariaDB is unreachable from Kali:
    nc -zv 10.10.10.20 3306        # must fail / time out
    

fail2ban Configuration

  1. Configure /etc/fail2ban/jail.local:
    • SSH jail: maxretry=3, findtime=600, bantime=3600, using nftables-multiport banaction
    • nginx jail: pattern-match the DVWA login endpoint (/dvwa/login.php) for repeated HTTP 401 responses; maxretry=5, findtime=60, bantime=3600
  2. Enable and start fail2ban: sudo systemctl enable --now fail2ban

  3. From Kali, trigger the SSH ban:
    for i in {1..5}; do ssh wronguser@10.10.10.20; done
    
  4. On ubuntu-server, confirm the ban:
    sudo fail2ban-client status sshd
    # Shows: Banned IP list: 10.10.10.10
    

Confirming the Ban in Wazuh

Two separate things reach the SIEM when you trigger a ban, and you need both:

  1. On ubuntu-server, confirm fail2ban actually logged the ban locally before looking at the dashboard:
    sudo grep -E 'Ban|Found' /var/log/fail2ban.log | tail -10
    # Expect a line like: fail2ban.actions [...] NOTICE [sshd] Ban 10.10.10.10
    

    If /var/log/fail2ban.log does not exist, check logtarget in /etc/fail2ban/fail2ban.conf — on some installs it is set to SYSLOG or SYSTEMD, in which case the ban lands in the journal instead and you should point the agent at /var/log/syslog.

  2. Add the fail2ban log to the Wazuh agent’s monitored files. Edit /var/ossec/etc/ossec.conf on ubuntu-server and add the following inside <ossec_config>:
    <localfile>
      <log_format>syslog</log_format>
      <location>/var/log/fail2ban.log</location>
    </localfile>
    

    Restart the agent: sudo systemctl restart wazuh-agent

  3. Re-trigger the ban so events are generated after the agent started reading the file (unban first, since Kali is still blocked from step 12):
    sudo fail2ban-client set sshd unbanip 10.10.10.10
    

    Then repeat the failed-login loop from Kali (step 11), and re-check fail2ban-client status sshd.

  4. In the Wazuh dashboard (https://10.10.10.30), go to Modules → Security Events, select agent ubuntu-server, and set the time range to Last 15 minutes. In the search bar, apply each of these filters in turn:

    Filter What you should see
    rule.groups: authentication_failures The brute-force detection from auth.log — typically rule 5712, “sshd: brute force trying to get access to the system”
    rule.groups: fail2ban The ban action itself — a “Fail2ban: host banned” style alert sourced from /var/log/fail2ban.log
    data.srcip: 10.10.10.10 Everything the SIEM recorded about Kali during the attack, both of the above together

    If the fail2ban group filter returns nothing, drop back to a free-text search for 10.10.10.10 over the same window and look for the alert whose full_log field contains the Ban 10.10.10.10 line — that confirms the log is being ingested even if it matched a different rule group.

  5. Expand one ban alert in the dashboard and record its rule ID, rule level, and description; you will cite these in lab12.md. Take a screenshot showing the expanded alert with the source IP and rule description visible.

  6. Unban Kali before finishing: sudo fail2ban-client set sshd unbanip 10.10.10.10

Threat Model — Final Update for Preventive Controls

  1. In lab02/threat-model.md, mark all threats now addressed by service account restrictions, nftables rules, or fail2ban. This should bring nearly all of your top 5 threats to MITIGATED status.

Deliverables

Create lab12/ in your repo containing:


Submission

Commit all files for both parts — lab11/ and lab12/ — and push to your GitLab repo. The lab is considered submitted when the commit appears in GitLab before the due date.