4 of 4

EX342 Practice ExamLesson 4 of 4

Part C: Network, security and logging (100 points)

Seven tasks: a network path with four faults, a web service, central logging, AIDE, account policy, five users who cannot log in, and a PAM repair.

  • ip route get
  • getent
  • curl
  • nmcli
  • firewall-cmd
  • semanage
  • rsyslogd
  • logger
  • aide
  • faillock
  • pwscore
  • chage
  • authselect
  • journalctl -u sshd

72 min7 exercises

After this lesson you can

  • Repair the network, logging, integrity and authentication faults of the exam within about 72 minutes, each with its measurable checks.
  • Finish with the reboot of both nodes and the final checks that decide the score.

Setup for Part C

Run as root on node1 before the Part B and Part A setups (the order is C, then B, then A, because Part A ends by rebooting node1 into emergency mode). It uses ssh root@node2 while the root password of node2 is still known. As with the other parts, it is here so that you can rebuild the exam, not as a source of hints.

# --- common: rsyslog on both nodes, httpd on node1
dnf install -y rsyslog httpd
systemctl enable --now rsyslog
ssh root@node2.lab.example 'dnf install -y rsyslog && systemctl enable --now rsyslog'

# --- C1: backend address and web page on node2, and four faults
ssh root@node2.lab.example bash -s <<'EOT'
IF=$(ip -o -4 route show to 192.168.50.0/24 | awk '{print $3}' | head -1)
CON=$(nmcli -g GENERAL.CONNECTION device show "$IF")
nmcli connection modify "$CON" +ipv4.addresses 192.168.50.22/24
nmcli device reapply "$IF"
mkdir -p /srv/backend && echo "backend ok" > /srv/backend/index.html
cat > /etc/systemd/system/backend-web.service <<'EOS'
[Unit]
Description=Backend web
After=network-online.target
[Service]
WorkingDirectory=/srv/backend
ExecStart=/usr/bin/python3 -m http.server 8080 --bind 192.168.50.22
[Install]
WantedBy=multi-user.target
EOS
systemctl daemon-reload && systemctl enable --now backend-web
echo "192.168.50.111 node1.lab.example node1" >> /etc/hosts
EOT
echo "192.168.50.23 backend.lab.example backend" >> /etc/hosts
IF=$(ip -o -4 route show to 192.168.50.0/24 | awk '{print $3}' | head -1)
CON=$(nmcli -g GENERAL.CONNECTION device show "$IF")
nmcli connection modify "$CON" +ipv4.routes "192.168.50.22/32 192.168.50.99"
nmcli device reapply "$IF"

# --- C2: Apache on a port that SELinux, the firewall and the listen address all refuse
echo "lab web ok" > /var/www/html/index.html
sed -i 's/^Listen 80$/Listen 127.0.0.1:8500/' /etc/httpd/conf/httpd.conf
systemctl enable httpd

# --- C4: the directory to watch
mkdir -p /opt/vault/conf /opt/vault/cache
echo "setting=1" > /opt/vault/conf/app.conf
echo "k3y-material" > /opt/vault/conf/secret.key; chmod 600 /opt/vault/conf/secret.key
echo "cache-1" > /opt/vault/cache/c1.tmp

# --- C6: five users with five different reasons
for u in dana eli fay gus hana; do useradd -m "$u"; echo "$u:Ex342!Lab" | chpasswd; done
usermod -L dana
chage -E 2020-01-01 eli
usermod -s /sbin/nologin fay
echo "DenyUsers gus" > /etc/ssh/sshd_config.d/05-restrict.conf
chage -d 2024-01-01 -M 90 -I 0 hana
systemctl reload sshd

# --- C5/C7: faillock enabled in the stack, then a hand edit that authselect does not own
authselect current >/dev/null 2>&1 || authselect select local --force
authselect enable-feature with-faillock
authselect apply-changes
sed -i '/pam_pwquality.so/ s/^/#/' /etc/pam.d/system-auth

Final reboot and checks

When every task is done and C7 is the last change you made:

  1. Reboot node2, then node1, and wait until both answer ssh.
  2. On node1, run the check lines of A2, A3 (kdumpctl status), B5, B6, C2 and C4 again: services running, ports listening, timers enabled.
  3. On node2, confirm ss -tln | grep ':514 ' and the log file of C3 receives a new logger message from node1.
  4. Check that getenforce is Enforcing and firewall-cmd --state is running on both machines.

A task whose check fails after the reboot earns no points: that is the habit the exam rewards, so make the reboot part of your plan, not an afterthought.

Part C marking

TaskPointsEarned
C1 Network path18
C2 Web service14
C3 Central logging18
C4 AIDE12
C5 Account policy14
C6 Five users14
C7 PAM repair10
Part C100

Practice

0 of 7 done

Do these on your own system. They get harder. Check each result, then tick it off.

  1. Warm-up

    C1 · A path that does not work (18 points, 12 min)

    The application team says: "From node1, http://backend.lab.example:8080/ does not answer, and node2 cannot use the name node1.lab.example." The backend runs on node2 with the extra address 192.168.50.22. Find every fault between the two machines (there are four, on both nodes) and fix them so that:

    1. On node1, backend.lab.example resolves to 192.168.50.22 and curl http://backend.lab.example:8080/ prints the page.
    2. On node2, node1.lab.example resolves to 192.168.50.11.
    3. All fixes survive a reboot, with the firewall still running on both nodes and SELinux still enforcing.

    Work from the evidence in this order: name, route, then the firewall at the far end.

    Check your result
    • node1: getent hosts backend.lab.example prints 192.168.50.22; ip route get 192.168.50.22 shows no via and the interface directly.
    • node1: curl -s --max-time 5 http://backend.lab.example:8080/ prints backend ok.
    • node1: nmcli -g ipv4.routes connection show "$(nmcli -g GENERAL.CONNECTION device show $(ip -o route get 192.168.50.22 | awk '{print $3}'))" does not contain 192.168.50.22.
    • node2: getent hosts node1.lab.example prints 192.168.50.11.
    • node2: sudo firewall-cmd --permanent --list-ports (or --list-services) permits 8080/tcp; sudo firewall-cmd --state is running.
    Hint

    A name that resolves to the wrong address is not a DNS problem: getent hosts shows what the system uses, and /etc/hosts is read first. ip route get ADDRESS shows the route that would be used; a via address nobody answers for makes Destination Host Unreachable. When ping works but curl times out, test the port from node2 itself (curl to its own address works), then look at its firewall.

    Solution
    # node1: name and route
    getent hosts backend.lab.example
    sudo sed -i 's/^192.168.50.23 /192.168.50.22 /' /etc/hosts
    ip route get 192.168.50.22
    IF=$(ip -o route get 192.168.50.22 | awk '{print $3}')
    CON=$(nmcli -g GENERAL.CONNECTION device show "$IF")
    sudo nmcli connection modify "$CON" -ipv4.routes "192.168.50.22/32 192.168.50.99"
    sudo nmcli device reapply "$IF"
    curl -s --max-time 5 http://backend.lab.example:8080/ ; echo "rc=$?"
    
    # node2: firewall for 8080 and the wrong name for node1
    ssh node2.lab.example
    sudo sed -i 's/^192.168.50.111 /192.168.50.11 /' /etc/hosts
    sudo firewall-cmd --permanent --add-port=8080/tcp && sudo firewall-cmd --reload
    exit
    curl -s http://backend.lab.example:8080/
  2. Practice

    C2 · A web service nobody can reach (14 points, 10 min)

    The Apache web server on node1 must serve the page /var/www/html/index.html on TCP port 8500, for the whole lab network, and start at boot. It currently does not run. Do not disable SELinux or the firewall and do not use permissive mode.

    Prove it from node2: curl http://node1.lab.example:8500/ prints the page.

    Check your result
    • systemctl is-enabled httpd is enabled; systemctl is-active httpd is active.
    • sudo semanage port -l | grep '^http_port_t' lists 8500.
    • sudo ss -tlnp | grep ':8500' shows 0.0.0.0:8500 or *:8500 (not 127.0.0.1).
    • sudo firewall-cmd --permanent --list-ports contains 8500/tcp (or an equivalent permanent rule); getenforce is Enforcing.
    • From node2: curl -s http://node1.lab.example:8500/ prints lab web ok.
    • sudo ausearch -m avc -ts recent shows no new denial for httpd after your last change.
    Hint

    journalctl -u httpd -b names the error. Permission denied ... could not bind with SELinux enforcing is a port label problem (ausearch -m avc). Once it starts, ss -tlnp shows the address it listens on. Last, check the firewall, because a service that answers locally and not from outside is the firewall or the listen address.

    Solution
    sudo journalctl -u httpd -b --no-pager | tail -5
    sudo ausearch -m avc -ts recent | tail -3
    sudo semanage port -a -t http_port_t -p tcp 8500
    sudo grep -n '^Listen' /etc/httpd/conf/httpd.conf
    sudo sed -i 's/^Listen 127.0.0.1:8500/Listen 8500/' /etc/httpd/conf/httpd.conf
    sudo systemctl enable --now httpd
    sudo firewall-cmd --permanent --add-port=8500/tcp && sudo firewall-cmd --reload
    ss -tln | grep 8500
    ssh node2.lab.example curl -s http://node1.lab.example:8500/
  3. Challenge

    C3 · Central logging (18 points, 14 min)

    Make node2 the log server and node1 a client, with rsyslog:

    1. node2 receives syslog over TCP 514. Messages from remote hosts are written to /var/log/remote/<sending host>/<program>.log and are not mixed into node2's own local files.
    2. The firewall on node2 accepts port 514/tcp only from node1 (192.168.50.11); the port must not be open to the whole zone. SELinux stays enforcing.
    3. node1 forwards everything of facility local5 and everything of facility authpriv to node2 over TCP.
    4. Messages are not lost when node2's rsyslog is stopped for a minute: they are queued on node1 and arrive after the restart.
    5. Everything starts at boot on both nodes.

    You may work on node2 as root (the password you set in A5) or as alex with sudo.

    Check your result
    • node2: sudo ss -tln | grep ':514 ' shows a listener; sudo rsyslogd -N1 ends without errors on both nodes.
    • node2: sudo firewall-cmd --permanent --list-rich-rules contains a rule for source 192.168.50.11 and port 514 tcp; sudo firewall-cmd --permanent --list-ports does not list 514/tcp.
    • node1: logger -t c3test -p local5.notice "ex342-c3-one"; on node2 sudo grep -r ex342-c3-one /var/log/remote/ finds it in /var/log/remote/<node1 name>/c3test.log.
    • node1: logger -t c3auth -p authpriv.info "ex342-c3-auth" arrives the same way.
    • Resilience: sudo systemctl stop rsyslog on node2; on node1 logger -t c3test -p local5.notice "ex342-c3-queued"; start rsyslog on node2; within about a minute the message is in the file.
    • systemctl is-enabled rsyslog is enabled on both nodes; getenforce is Enforcing on both.
    Hint

    Server: load imtcp, a template with %HOSTNAME% and %PROGRAMNAME%, and a rule that tests $fromhost-ip so local messages are left alone. Client: an omfwd action with protocol="tcp", action.resumeRetryCount="-1" and a queue with a queue.filename. A firewall rich rule limits the source. 514 is already syslogd_port_t in the SELinux policy.

    Solution
    # node2 (server)
    sudo tee /etc/rsyslog.d/10-remote.conf <<'EOT'
    module(load="imtcp")
    input(type="imtcp" port="514")
    template(name="PerHost" type="string" string="/var/log/remote/%HOSTNAME%/%PROGRAMNAME%.log")
    if $fromhost-ip != '127.0.0.1' then {
        action(type="omfile" dynaFile="PerHost")
        stop
    }
    EOT
    sudo rsyslogd -N1 && sudo systemctl enable --now rsyslog && sudo systemctl restart rsyslog
    sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.50.11" port port="514" protocol="tcp" accept'
    sudo firewall-cmd --reload
    
    # node1 (client)
    sudo tee /etc/rsyslog.d/20-forward.conf <<'EOT'
    if ($syslogfacility-text == 'local5' or $syslogfacility-text == 'authpriv') then {
        action(type="omfwd" target="node2.lab.example" port="514" protocol="tcp"
               action.resumeRetryCount="-1"
               queue.type="LinkedList" queue.size="10000"
               queue.filename="fwd2" queue.saveOnShutdown="on")
    }
    EOT
    sudo rsyslogd -N1 && sudo systemctl enable --now rsyslog && sudo systemctl restart rsyslog
    logger -t c3test -p local5.notice "ex342-c3-one"
    ssh node2.lab.example 'sudo grep -r ex342-c3-one /var/log/remote/'
  4. Practice

    C4 · File integrity with AIDE (12 points, 9 min)

    The directory /opt/vault on node1 holds configuration and keys. Install aide and watch only that directory with a separate configuration (/etc/aide-vault.conf) and a separate database directory (/var/lib/aide-vault), so the default system watch is untouched and the check does not report the rest of the exam's changes.

    1. The rules must detect a changed content (hash), changed permissions, changed owner/group and new or removed files under /opt/vault, but must ignore everything below /opt/vault/cache.
    2. Create the baseline and activate it.
    3. A systemd timer aide-vault.timer runs the check daily through aide-vault.service; the service must succeed when nothing changed and fail when something did.
    Check your result
    • rpm -q aide; sudo ls -l /var/lib/aide-vault/aide.db.gz exists.
    • sudo aide -c /etc/aide-vault.conf --check; echo $? prints 0 on the untouched directory.
    • After echo x | sudo tee -a /opt/vault/conf/app.conf, the check exits non-zero and lists /opt/vault/conf/app.conf. After sudo chmod 644 /opt/vault/conf/secret.key it lists that file with the permission change. After sudo touch /opt/vault/new.file it lists an added entry. (Revert each change.)
    • After sudo touch /opt/vault/cache/anything, the check still exits 0.
    • systemctl is-enabled aide-vault.timer is enabled; systemctl list-timers aide-vault.timer --no-pager shows a next run; sudo systemctl start aide-vault.service succeeds on a clean directory.
    Hint

    A rule group is NAME = p+u+g+s+m+sha256; a path line is /opt/vault NAME; an exclusion is !/opt/vault/cache. Use database_in, database_out and report_url in the custom file. aide --init writes the new database; copy it over the name that database_in reads. The service is Type=oneshot and runs aide -c /etc/aide-vault.conf --check.

    Solution
    sudo dnf install -y aide
    sudo mkdir -p /var/lib/aide-vault
    sudo tee /etc/aide-vault.conf <<'EOT'
    database_in=file:/var/lib/aide-vault/aide.db.gz
    database_out=file:/var/lib/aide-vault/aide.db.new.gz
    gzip_dbout=yes
    report_url=stdout
    VAULT = p+u+g+s+m+sha256
    /opt/vault VAULT
    !/opt/vault/cache
    EOT
    sudo aide -c /etc/aide-vault.conf --init
    sudo cp -p /var/lib/aide-vault/aide.db.new.gz /var/lib/aide-vault/aide.db.gz
    sudo aide -c /etc/aide-vault.conf --check; echo "rc=$?"
    sudo tee /etc/systemd/system/aide-vault.service <<'EOT'
    [Unit]
    Description=AIDE check of /opt/vault
    [Service]
    Type=oneshot
    ExecStart=/usr/sbin/aide -c /etc/aide-vault.conf --check
    EOT
    sudo tee /etc/systemd/system/aide-vault.timer <<'EOT'
    [Unit]
    Description=Daily AIDE check of /opt/vault
    [Timer]
    OnCalendar=daily
    Persistent=true
    [Install]
    WantedBy=timers.target
    EOT
    sudo systemctl daemon-reload && sudo systemctl enable --now aide-vault.timer
  5. Practice

    C5 · Account policy (14 points, 9 min)

    On node1, set a local account policy that survives reboots and applies without editing files by hand that authselect owns:

    1. Lockout: lock an account after 4 failed logins within 10 minutes; the lock lasts 5 minutes; failures are written to the audit log; root is not locked by this policy.
    2. Password quality: at least 14 characters, at least 3 character classes, no more than 2 identical characters in a row, and the dictionary check on.
    3. Aging defaults for accounts created later: maximum age 60 days, minimum 2, warning 10. Apply the same three values to the existing user alex.
    Check your result
    • grep -E '^(deny|fail_interval|unlock_time|audit|even_deny_root)' /etc/security/faillock.conf shows deny = 4, fail_interval = 600, unlock_time = 300, audit, and no even_deny_root.
    • grep -E '^(minlen|minclass|maxrepeat|dictcheck)' /etc/security/pwquality.conf shows minlen = 14, minclass = 3, maxrepeat = 2, dictcheck = 1; echo 'Short1!' | pwscore fails and echo 'Tr1cky-Lab-Pass-2026' | pwscore prints a number.
    • grep -E '^PASS_(MAX|MIN|WARN)' /etc/login.defs shows 60, 2, 10; a new sudo useradd c5test; sudo chage -l c5test shows Maximum 60, Minimum 2, warning 10. sudo chage -l alex shows the same values. (Delete c5test afterwards.)
    • Lockout works: four wrong passwords for c5test (su - c5test from another user, or ssh) lock the account; sudo faillock --user c5test lists the failures; sudo faillock --user c5test --reset unlocks it.
    Hint

    faillock is already enabled in the PAM stack (see C7); you set its numbers in /etc/security/faillock.conf. Flag options such as audit stand alone on a line. login.defs only affects accounts that are created later, so alex needs chage. Quality rules apply when a password is changed through PAM.

    Solution
    sudo sed -i -E 's/^#?\s*deny\s*=.*/deny = 4/; s/^#?\s*fail_interval\s*=.*/fail_interval = 600/; s/^#?\s*unlock_time\s*=.*/unlock_time = 300/; s/^#?\s*audit\s*$/audit/' /etc/security/faillock.conf
    sudo sed -i -E 's/^\s*even_deny_root/# even_deny_root/' /etc/security/faillock.conf
    grep -vE '^\s*(#|$)' /etc/security/faillock.conf
    sudo sed -i -E 's/^#?\s*minlen\s*=.*/minlen = 14/; s/^#?\s*minclass\s*=.*/minclass = 3/; s/^#?\s*maxrepeat\s*=.*/maxrepeat = 2/; s/^#?\s*dictcheck\s*=.*/dictcheck = 1/' /etc/security/pwquality.conf
    echo 'Short1!' | pwscore
    sudo sed -i -E 's/^PASS_MAX_DAYS.*/PASS_MAX_DAYS   60/; s/^PASS_MIN_DAYS.*/PASS_MIN_DAYS   2/; s/^PASS_WARN_AGE.*/PASS_WARN_AGE   10/' /etc/login.defs
    sudo chage -M 60 -m 2 -W 10 alex
    sudo useradd c5test && sudo chage -l c5test | sed -n 5,7p; sudo userdel -r c5test
  6. Challenge

    C6 · Five users who cannot log in (14 points, 10 min)

    The users dana, eli, fay, gus and hana cannot log in to node1 over ssh with their password (Ex342!Lab). Each has a different reason. Find each from the evidence (logs, passwd -S, chage -l, getent passwd, sshd -T) and fix it with the smallest change.

    Rules: do not change any of the five passwords, do not turn off password authentication, and do not remove PasswordAuthentication or other settings that apply to everyone.

    Check your result
    • From node2: ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no USER@192.168.50.11 id prints the user's uid= for each of the five, typing Ex342!Lab.
    • sudo passwd -S dana second field is P; sudo chage -l eli shows Account expires : never; getent passwd fay ends in a real shell (/bin/bash).
    • sudo sshd -T | grep -i denyusers does not list gus; the other DenyUsers/AllowUsers settings of the lab are not changed.
    • sudo chage -l hana shows no past Password inactive date and a password expiry in the future.
    • If you ran into the lock-out of C5 while testing, the failure records are cleared: sudo faillock lists nothing.
    Hint

    sudo grep sshd /var/log/secure | tail -30 and journalctl -u sshd -b give a different sentence for each user. account is locked, Your account has expired, This account is currently not available, not allowed because listed in DenyUsers, Your account has expired; please contact your system administrator (inactive). Drop-ins in /etc/ssh/sshd_config.d/ are read in file-name order. Testing wrong passwords can trigger the faillock of C5: faillock --user NAME --reset.

    Solution
    sudo grep sshd /var/log/secure | tail -30
    sudo passwd -S dana eli fay gus hana
    sudo chage -l eli | head -5; sudo chage -l hana | head -6
    getent passwd fay
    sudo sshd -T | grep -i denyusers; ls /etc/ssh/sshd_config.d/
    
    sudo usermod -U dana                       # locked account
    sudo chage -E -1 eli                       # account expired
    sudo usermod -s /bin/bash fay              # nologin shell
    sudo rm /etc/ssh/sshd_config.d/05-restrict.conf && sudo sshd -t && sudo systemctl reload sshd   # DenyUsers gus
    sudo chage -I -1 -d "$(date +%F)" hana     # inactive days exceeded
    sudo faillock --reset
  7. Practice

    C7 · Repair the PAM configuration (10 points, 8 min)

    A contractor hand-edited the PAM configuration on node1, and a security scan says that authselect no longer manages it. Return to the supported configuration of authselect, keeping the profile that is in use and every feature that is enabled (the lockout of C5 depends on one of them). Afterwards, ordinary users must again be refused weak passwords when they change their own password.

    Do this task last, after all other tasks, and then reboot both nodes (see below).

    Check your result
    • sudo authselect check ends with Current configuration is valid. and exit status 0.
    • ls -l /etc/pam.d/system-auth is a symbolic link into /etc/authselect/.
    • authselect current lists the same profile as before and still lists with-faillock; grep -c pam_faillock /etc/pam.d/system-auth is at least 2.
    • grep -v '^#' /etc/pam.d/system-auth | grep -c pam_pwquality is at least 1.
    • As a normal user (create c7test with password Ex342!Lab), passwd with the new password Short1! is refused with a BAD PASSWORD message about the length.
    Hint

    authselect check tells you which file is not a link or differs. Write down authselect current first: authselect select PROFILE FEATURES --force returns to the supported files but replaces the feature list with what you type.

    Solution
    sudo authselect check
    sudo authselect current            # note the profile and the features
    sudo authselect select local with-faillock --force    # use YOUR profile and ALL features shown above
    sudo authselect check
    ls -l /etc/pam.d/system-auth
    grep -c pam_faillock /etc/pam.d/system-auth
    sudo useradd c7test && echo 'c7test:Ex342!Lab' | sudo chpasswd
    sudo -u c7test passwd        # current: Ex342!Lab ; new: Short1!  -> BAD PASSWORD
    sudo userdel -r c7test