LFCS — Linux Foundation System Administrator
SELinux and firewalld by Hand — and Users, ACL, sshd, dnf
Goal
On an AlmaLinux 9 VM, you actually apply SELinux booleans, file contexts, and port contexts, work by hand with firewalld's runtime/permanent, zones, and rich rules, and configure users, sudoers, profile.d, limits, ACLs, an sshd drop-in, and dnf locking so that the results remain.
Why it matters
The Operations domain of the LFCS explicitly says "manage access with SELinux", and the Networking domain requires packet filtering and OpenSSH. A Pod has neither SELinux nor firewalld, so these items only brushed past in the practice exams. This VM is Enforcing and firewalld is alive, so if you leave out the port context, httpd really does not start, and if you leave out permanent, the rule disappears the moment you reload.
Three things recur. First, the value now and the permanent value live in different places — -P of setsebool, chcon and semanage fcontext, and firewalld's runtime and permanent. Second, the effective value is told to you by a tool, not by a file — sshd -T, getsebool, sudo -l -U, getfacl. Third, check first so you do not lock yourself out — visudo -cf, sshd -t, apachectl configtest.
⚠️ The grading agent comes in on 8899/tcp. When you touch firewalld, if you remove this port or change the default zone to drop, grading cannot reach the VM and from the next step you get failures of unknown cause. A session starts at 60 minutes and can be extended up to 180 minutes, and the VM disappears when the session ends.
Steps
- Save the
sestatusoutput to/root/lfcs/selinux/status.txt(it must be Enforcing). Then turn on the booleanhttpd_can_network_connectso that it persists after a reboot. - Write
selinux okin/srv/web/index.html, make everything under/srv/webhave thehttpd_sys_content_tcontext permanently, and then apply it (semanage fcontext + restorecon). Register tcp 8081 ashttp_port_t, make httpd serve/srv/webon 8081 with/etc/httpd/conf.d/lfcs.conf, and enable and start it.curl http://127.0.0.1:8081/must returnselinux ok. - In the default zone (public), open the service
httpand the port8081/tcpin both runtime and permanent. The grading port8899/tcpmust remain as it is. Finally, save the output offirewall-cmd --list-allto/root/lfcs/firewall/public.txt. - Create a new zone
laband assign the source198.51.100.0/24to that zone. In thelabzone, put the servicesshand a rich rule that allows tcp 8081 coming from198.51.100.0/24(rule family="ipv4" source address="198.51.100.0/24" port port="8081" protocol="tcp" accept). Write everything as permanent and reload so that it shows in runtime too. - Create a group
opsand a userdeploy(with a home directory, shell/bin/bash), and put it inopsas a supplementary group. In/etc/sudoers.d/ops, write a rule that lets theopsgroup run only/usr/bin/systemctlwith sudo without a password, and set the permissions to0440. The syntax must passvisudo -cf. - In
/etc/profile.d/lfcs.sh, export the environment variableLFCS_ENVaslab. In/etc/security/limits.d/90-lfcs.conf, write a hard limit of4096on the number of open files (nofile) for the userdeploy, and a soft limit of2048on the number of processes (nproc) for the groupops. When you open a login shell withsu - deploy, the variable must be visible inenvandulimit -Hnmust be 4096. - Create the
/srv/web/uploaddirectory owned by root with permissions750(do not widen it with chmod), give the userdeployrwxwith an ACL, and give a default ACL so that files newly created inside this directory inheritr-xfor the groupops. Save the output ofgetfacl /srv/web/uploadto/root/lfcs/acl/upload.acl. (After you give the ACL, the group position ofls -ldshows the mask (rwx) — that is normal.) - In the
/etc/ssh/sshd_config.d/50-lfcs.confdrop-in, writeMaxAuthTries 3,ClientAliveInterval 300, andPermitRootLogin no, and restart sshd. The final values seen withsshd -Tmust be those values for all three. - Install the
treepackage and lock it with versionlock or put it inexclude=of/etc/dnf/dnf.confso that it is excluded from upgrades. Save the output ofdnf history listto/root/lfcs/pkg/history.txtand the output ofrpm -q treeto/root/lfcs/pkg/tree.txt.
Notes
- SELinux:
getenforce,setsebool -P,semanage fcontext -a -t … '/경로(/.*)?'(replace the path placeholder with the real path) →restorecon -Rv,semanage port -a -t http_port_t -p tcp 8081. The denial record is inausearch -m avc -ts recent. - firewalld: write with
--permanentand--reload. You see a whole zone at once with--zone=lab --list-all. - Users:
useradd -m -s /bin/bash -G ops deploy,visudo -cf /etc/sudoers.d/ops,sudo -l -U deploy. - limits are applied at the moment of login. Looking at
ulimitin an already open shell shows no change. - Common mistake 1: changing the context with
chconand stopping there — a singlerestoreconreverts it. - Common mistake 2: putting a firewalld rule only in runtime and then erasing it with
--reload(reload rereads permanent). - Common mistake 3: leaving out
exportin profile.d so it remains only a shell variable.
Checking the SELinux mode, and booleans
Save the sestatus output to /root/lfcs/selinux/status.txt (it must be Enforcing). Then turn on the boolean httpd_can_network_connect so that it persists after a reboot.
If you add -P to setsebool, it is written to the policy store. To check, use getsebool and semanage boolean -l -C (show only local changes).
Putting httpd on 8081 with file and port contexts
Write selinux ok in /srv/web/index.html, make everything under /srv/web have the httpd_sys_content_t context permanently, and then apply it (semanage fcontext + restorecon). Register tcp 8081 as http_port_t, make httpd serve /srv/web on 8081 with /etc/httpd/conf.d/lfcs.conf, and enable and start it. curl http://127.0.0.1:8081/ must return selinux ok.
chcon disappears when you restorecon — semanage fcontext -a -t '/srv/web(/.*)?' is the permanent one. If you do not register the port, httpd cannot bind 8081 and the start itself fails (check with journalctl -u httpd and ausearch -m avc).
firewalld services and ports — runtime and permanent
In the default zone (public), open the service http and the port 8081/tcp in both runtime and permanent. The grading port 8899/tcp must remain as it is. Finally, save the output of firewall-cmd --list-all to /root/lfcs/firewall/public.txt.
With --permanent it applies only to the files, and without it only to the rules running now. Do both, or write with --permanent and --reload. To check, --list-services / --permanent --list-services.
A new zone and a rich rule
Create a new zone lab and assign the source 198.51.100.0/24 to that zone. In the lab zone, put the service ssh and a rich rule that allows tcp 8081 coming from 198.51.100.0/24 (rule family="ipv4" source address="198.51.100.0/24" port port="8081" protocol="tcp" accept). Write everything as permanent and reload so that it shows in runtime too.
A new zone can be created only with --permanent --new-zone=lab and appears in runtime after a reload. --zone=lab --add-source=..., --add-service=..., --add-rich-rule='...'. To check, --zone=lab --list-all.
Group, user, and sudoers rule
Create a group ops and a user deploy (with a home directory, shell /bin/bash), and put it in ops as a supplementary group. In /etc/sudoers.d/ops, write a rule that lets the ops group run only /usr/bin/systemctl with sudo without a password, and set the permissions to 0440. The syntax must pass visudo -cf.
useradd -m -s /bin/bash -G ops deploy. The sudoers line: %ops ALL=(ALL) NOPASSWD: /usr/bin/systemctl. If the syntax is wrong, all of sudo is locked, so always check with visudo -cf. See the result with sudo -l -U deploy.
Login environment and resource limits
In /etc/profile.d/lfcs.sh, export the environment variable LFCS_ENV as lab. In /etc/security/limits.d/90-lfcs.conf, write a hard limit of 4096 on the number of open files (nofile) for the user deploy, and a soft limit of 2048 on the number of processes (nproc) for the group ops. When you open a login shell with su - deploy, the variable must be visible in env and ulimit -Hn must be 4096.
profile.d is read by the login shell as it passes through /etc/profile. Without export, it is not visible to child processes (env). A limits line has four fields, 'target type item value', and a group is @ops. pam_limits applies it at login.
Granting permission beyond ownership with an ACL
Create the /srv/web/upload directory owned by root with permissions 750 (do not widen it with chmod), give the user deploy rwx with an ACL, and give a default ACL so that files newly created inside this directory inherit r-x for the group ops. Save the output of getfacl /srv/web/upload to /root/lfcs/acl/upload.acl. (After you give the ACL, the group position of ls -ld shows the mask (rwx) — that is normal.)
setfacl -m u:deploy:rwx , and the default ACL is setfacl -d -m g:ops:rx (or -m d:g:ops:rx). If + is attached in ls -l, an ACL is present. The mask line trims the effective permissions.
The sshd drop-in and sshd -T
In the /etc/ssh/sshd_config.d/50-lfcs.conf drop-in, write MaxAuthTries 3, ClientAliveInterval 300, and PermitRootLogin no, and restart sshd. The final values seen with sshd -T must be those values for all three.
The Include in sshd_config is at the very top, so the drop-in is read before the main body, and sshd uses the first value of the same key. Syntax with sshd -t, effective values with sshd -T. Console access has nothing to do with sshd, so there is no worry about locking yourself out.
dnf install, lock, and history
Install the tree package and lock it with versionlock or put it in exclude= of /etc/dnf/dnf.conf so that it is excluded from upgrades. Save the output of dnf history list to /root/lfcs/pkg/history.txt and the output of rpm -q tree to /root/lfcs/pkg/tree.txt.
The versionlock plugin is already installed: dnf versionlock add tree / dnf versionlock list. exclude is exclude=tree in the [main] section of dnf.conf. history is per transaction, so the install shows as one line.