Lab — Week 2: OS Hardening and Secrets Management
Due: 17 July 2026
Submission: GitLab repo secdevops-s26-<CECS>
Overview
Reduce the attack surface of the freshly provisioned Ubuntu server — removing what isn’t needed, locking down what remains — then replace plaintext credentials with a proper secrets manager.
- Part 1 — OS Hardening reduces the attack surface of
ubuntu-serverby removing unnecessary services, hardening SSH, auditing file permissions, and deploying fail2ban. Every change should be traceable to a threat in your threat model. - Part 2 — Secrets Management with OpenBao replaces plaintext credentials with an OpenBao instance, demonstrates the policy model that enforces least-privilege access to secrets, and verifies that no credentials exist in plaintext anywhere on the system.
Prerequisites
- Week 1 Part 1 complete — ubuntu-server running and reachable
- Week 1 Part 2 complete — threat model exists in
lab02/threat-model.md - OpenBao installed on ubuntu-server (by Ansible) — for Part 2
Part 1: OS Hardening
A freshly installed Ubuntu 26.04 server is configured for convenience, not security. This part reduces the attack surface of ubuntu-server by removing unnecessary services, hardening SSH, auditing file permissions, and deploying fail2ban. Every change you make here should be traceable to a threat in your threat model.
SSH Hardening
- Edit
/etc/ssh/sshd_configon ubuntu-server to implement at minimum:PasswordAuthentication noPermitRootLogin noAllowUsers dmcgrathMaxAuthTries 3LoginGraceTime 30X11Forwarding no
-
Test the configuration before reloading:
sudo sshd -t -
Reload sshd:
sudo systemctl reload sshd - Verify from Kali that password authentication is rejected:
ssh -o PreferredAuthentications=password dmcgrath@10.10.10.20 # Must fail with: Permission denied (publickey)
Service Minimization
-
List all running services:
systemctl list-units --type=service --state=running -
Identify and disable at least three services that are not needed for the course workload (nginx, MariaDB, Docker are needed — everything else is a candidate). Document each with: service name, why it was disabled, and what attack surface it removes.
-
Mask any services that should never start:
sudo systemctl mask <service>
Audit Script
- Write
scripts/harden-audit.shon ubuntu-server (commit the script to your repo). The script must check all of the following and print PASS or FAIL for each:PermitRootLoginisnoPasswordAuthenticationisno- No world-writable files exist in
/etc - No unexpected SUID binaries exist in
/tmpor/var/tmp - The
dmcgrathaccount has a valid SSH key in~/.ssh/authorized_keys
- Run the script and fix all FAIL results. Re-run to confirm all checks pass.
fail2ban
-
Configure
/etc/fail2ban/jail.localto protect SSH with:maxretry = 3,findtime = 600,bantime = 3600. Use thenftables-multiportbanaction. -
Enable and start fail2ban:
sudo systemctl enable --now fail2ban -
From Kali, trigger the ban by attempting five failed SSH logins. Confirm the Kali IP appears in fail2ban’s ban list:
sudo fail2ban-client status sshd
Threat Model Update
- In
lab02/threat-model.md, mark any threats that are now mitigated. Update their status fromOPENtoMITIGATEDand add the control applied.
Deliverables
Create lab03/ in your repo containing:
sshd_configdiff —diff /etc/ssh/sshd_config.orig /etc/ssh/sshd_configor equivalent showing your changes- Service audit — table listing each disabled service, the reason, and the attack surface removed
scripts/harden-audit.sh— the script itself- Audit script output — screenshot or paste showing all checks passing
- fail2ban configuration — contents of
/etc/fail2ban/jail.local - fail2ban test — screenshot of
fail2ban-client status sshdshowing the Kali IP banned - Updated
lab02/threat-model.md— with newly mitigated threats marked
All findings and changes must be documented in lab03/lab03.md with a brief explanation of why each change was made, referencing specific threat model entries.
Part 2: Secrets Management with OpenBao
Hardcoded credentials are one of the most common and consequential security failures. This part replaces plaintext credentials on ubuntu-server with an OpenBao instance, demonstrates the policy model that enforces least-privilege access to secrets, and verifies that no credentials exist in plaintext anywhere on the system.
OpenBao is the open-source (Linux Foundation) fork of HashiCorp Vault; its CLI is bao and its API is Vault-compatible.
OpenBao Setup
- Create
/etc/openbao/openbao.hclwith a file storage backend listening on127.0.0.1:8200(TLS disabled for lab use):ui = true storage "file" { path = "/opt/openbao/data" } listener "tcp" { address = "127.0.0.1:8200"; tls_disable = true } api_addr = "http://127.0.0.1:8200" - Create storage directory and set ownership:
sudo mkdir -p /opt/openbao/data sudo chown openbao:openbao /opt/openbao/data -
Enable and start OpenBao:
sudo systemctl enable --now openbao - Initialize OpenBao with 5 key shares and a threshold of 3:
export BAO_ADDR='http://127.0.0.1:8200' bao operator init -key-shares=5 -key-threshold=3Save the unseal keys and root token somewhere secure (a local file is fine for the lab).
-
Unseal OpenBao using 3 of the 5 keys:
bao operator unseal <key> - Enable the audit log:
bao audit enable file file_path=/var/log/openbao/audit.log
Secrets and Policies
-
Enable the KV-v2 secrets engine:
bao secrets enable -path=secret kv-v2 - Store the DVWA database credentials:
bao kv put secret/dvwa/db username=dvwauser password=$(openssl rand -base64 16) -
Write a read-only policy file
policy/dvwa-read.hclthat allowsreadonsecret/data/dvwa/*andlistonsecret/metadata/dvwa/*. - Load the policy and create a 24-hour service token scoped to it:
bao policy write dvwa-read policy/dvwa-read.hcl bao token create -policy=dvwa-read -ttl=24h -display-name=dvwa-app - Verify the token can read but not write:
BAO_TOKEN=<service-token> bao kv get secret/dvwa/db # must succeed BAO_TOKEN=<service-token> bao kv put secret/dvwa/db x=y # must fail
Python Integration
-
Write
scripts/get-secret.pythat reads thepasswordfield fromsecret/dvwa/dbusing thehvaclibrary and prints it. The script must readBAO_ADDRandBAO_TOKENfrom environment variables — no hardcoded values. -
Run it:
BAO_TOKEN=<service-token> python3 scripts/get-secret.py
Verify No Plaintext Credentials
- Search the system for plaintext credentials:
sudo grep -rn --include="*.conf" --include="*.yml" --include="*.yaml" \ -E '(password|passwd|secret)\s*[:=]\s*\S+' /etc /opt 2>/dev/null \ | grep -v '^\s*#'Any result that contains a real credential (not a template placeholder) must be remediated.
Deliverables
Create lab04/ in your repo containing:
/etc/openbao/openbao.hcl— your OpenBao configuration (strip any sensitive values)policy/dvwa-read.hcl— the policy file- Token verification output — paste showing the service token succeeding on read and failing on write
scripts/get-secret.py— the Python script- Script output — showing successful secret retrieval
- Plaintext credential scan — output of the grep command, confirming zero findings
lab04/lab04.md— writeup documenting the secrets architecture: what is stored in OpenBao, which token has access, and how the application retrieves its credentials at runtime
Submission
Commit all files for both parts — lab03/ and lab04/ — and push to your GitLab repo. Do not commit the root OpenBao token, unseal keys, or any actual secret values. The lab is considered submitted when the commit appears in GitLab before the due date.