Now to the last OPTIONAL part, hardening Lighttpd with apparmor.
Like mentioned before :
While using an apparmor profile you can harden your webserver even further. This mainly helps to limit the attack surface in cases you misconfigured the webserver somehow or there is an unfixed zero-day-exploit.
The basic idea is to limit access to resources (files, signals, etc.) to what a particular service actualy needs and not more. And in case there is a breach an attacker has only access to this resources.
I mainly used the following and execellent guides :
In the latter guide there is the basic process described to create and / or refine your own profile for Lighttpd. So after installing the apparmor-profiles-extra I started with the profile /usr/share/apparmor/extra-profiles/usr.sbin.lighttpd as a template (copy it to /etc/apparmor.d/). Often you need to alter these profiles while they only fit to a particular installation / configuration.
If not using auditd, remember and temporarily disable kernel rate limiting on logs:
sudo printk_ratelimit=$(sysctl kernel.printk_ratelimit)
sudo echo $printk_ratelimit
sudo sysctl -w kernel.printk_ratelimit=0
Set the profile in complain mode, to see access "violations" in the logs, but not forbid the access :
sudo aa-complain /etc/apparmor.d/usr.sbin.lighttpd
In the logs like /var/log/kern.log you then can see apparmor messages
(if you use (r)syslog and configured it this way) :
kernel: [ 8799.555371] audit: type=1400 audit(1711407851.216:81): apparmor="DENIED" operation="file_mmap" profile="/usr/sbin/lighttpd///usr/bin/php-cgi8.2" name="/" pid=35598 comm="php-cgi" requested_mask="wr" denied_mask="wr" fsuid=33 ouid=33
Btw, this is the only complain from apparmor I couldn't get rid off and also shouldn't. Because it's quite questionable why php-cgi tries to create a memory mapped file access on the "/" root folder, even worse with read-write access. I guess this is a bug in php-cgi8.2 and isn't needed that everything works properly.
It's good to use 2 terminal / shells while creating / refining your profile.
terminal 1 - stop lighttpd to make sure that you trace the startup of the service also :
sudo systemctl stop lighttpd.service
terminal 2 :
sudo aa-genprof /usr/sbin/lighttpd
terminal 1 - start lighttpd, then do all things that you usually do with your webservices and finally switch to terminal 2 answering the questions of aa-genprof :
sudo systemctl start lighttpd.service
sudo lnav /var/log/kern.log
lnav is an excellent log file viewer for the console, you can use commands like less or tail as well
terminal 2 - after completing aa-genprof and manual profile refinement, reload the updated profile
sudo nano /etc/apparmor.d/usr.sbin.lighttpd
sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.lighttpd
start over again with step 1 until you addressed all complains in the logfiles or are happy with the results and reset the kernel rate limit :
sudo sysctl -w "$printk_ratelimit"
Optionally you can put your profile in enforce mode (caution: this will deny access to everything that is not explicity allowed in your profile) or leave it in complain mode for further observations :
sudo aa-enforce /etc/apparmor.d/usr.sbin.lighttpd
And here comes my Lighttpd profile as an inspiration / starting-point (you can need to adapt it to you installation / configuration using above process).
aa-genprof was not able to find / mitigate some complains like signal sending between lighttpd and the child-processes php-cgi, so I needed to add them by hand :
# Last Modified: Fri Mar 15 23:13:32 2024
abi <abi/3.0>,
include <tunables/global>
/usr/sbin/lighttpd {
include <abstractions/base>
include <abstractions/dovecot-common>
include <abstractions/nameservice>
include <abstractions/nis>
include <abstractions/openssl>
include <abstractions/perl>
include <abstractions/ssl_certs>
include <abstractions/totem>
include <abstractions/web-data>
capability dac_read_search,
capability net_bind_service,
capability setgid,
capability setuid,
capability sys_resource,
# allow sending any signals to child processes, inclusive terminating
signal peer=/usr/sbin/lighttpd///usr/bin/php-cgi8.2,
/etc/group r,
/etc/ld.so.cache r,
/etc/letsencrypt/archive/**/* r,
/etc/lighttpd/** r,
/etc/lighttpd/**/ r,
/etc/mime.types r,
/etc/nsswitch.conf r,
/etc/passwd r,
/etc/roundcube/* r,
/proc/sys/kernel/random/* r,
/run/lighttpd/* w,
/run/systemd/*/ r,
/usr/bin/dash mrix,
/usr/bin/php-cgi8.2 Cx,
/usr/lib/lighttpd/*.so mr,
/usr/lib64/lighttpd/*.so mr,
/usr/sbin/lighttpd mix,
/usr/share/lighttpd/* mrix,
/var/cache/lighttpd/ r,
/var/cache/lighttpd/** rwl,
/var/cache/lighttpd/uploads/* rw,
/var/log/lighttpd/* w,
/var/www/** r,
/var/www/**/ r,
/{,var/}run/lighttpd.pid rwl,
@{PROC}/loadavg r,
profile /usr/bin/php-cgi8.2 flags=(attach_disconnected) {
include <abstractions/base>
include <abstractions/nameservice>
include <abstractions/php-worker>
include <abstractions/php>
include <abstractions/ssl_keys>
include <abstractions/user-tmp>
signal (read, receive) peer="/usr/sbin/lighttpd",
/etc/gai.conf r,
/etc/host.conf r,
/etc/hosts r,
/etc/ld.so.cache r,
/etc/locale.alias r,
/etc/mime.types r,
/etc/nsswitch.conf r,
/etc/resolv.conf r,
/proc/*/maps r,
/proc/*/status r,
/sys/devices/system/cpu/possible r,
/usr/bin/php-cgi8.2 mr,
/var/www/** r,
/var/www/**/ r,
}
}