Hardening Ubuntu. Systemd edition.
A quick way to make a Ubuntu server a bit more secure.
Use the newly installed and configured system as a reference, or golden, image. Use that image as a baseline installation media and ensure that any future installation comply with benchmarks and policies using a configuration management tool, e.g Ansible or Puppet.
Tested on Ubuntu 20.04 Focal Fossa
and Ubuntu 22.04 Jammy Jellyfish
.
If you’re just interested in the security focused systemd configuration, it’s available as a separate document.
If you’re interested in testing your host settings, you’ll find the instructions here.
Note
|
This is a constant work in progress. Make sure you understand what it does. Read the code and do not run this script without first testing in a non-operational environment. The code is not idempotent. |
Note
|
There is a SLSA artifact present under the slsa workflow for file checksum verification. |
Packer template and Ansible playbook
A Packer template is available in the Packer directory.
An Ansible playbook is available in the konstruktoid/ansible-role-hardening repository.
Howto
-
Start the server installation.
-
Pick language and keyboard layout.
-
Select "Ubuntu Server (minimized)".
-
Configure network connections.
-
Partition the system, see below for recommendations.
-
Do not install the OpenSSH server, "Featured Server Snaps", or any other packages.
-
Finish the installation and reboot.
-
Log in.
-
If wanted, set a Grub2 password with
grub-mkpasswd-pbkdf2
. See https://help.ubuntu.com/community/Grub2/Passwords for more information. -
Install necessary packages:
sudo apt-get -y install git net-tools procps --no-install-recommends
. -
Download the script:
git clone github.com/konstruktoid/hardening.git
. -
Change the configuration options in the
ubuntu.cfg
file. -
Run the script:
sudo bash ubuntu.sh
. -
Reboot.
Recommended partitions and options
/boot (rw)
/home (rw,nosuid,nodev)
/var/log (rw,nosuid,nodev,noexec)
/var/log/audit (rw,nosuid,nodev,noexec)
/var/tmp (rw,nosuid,nodev,noexec)
Note that /tmp
will be added automatically by the script.
Configuration options
FW_ADMIN='127.0.0.1' // (1)
SSH_GRPS='sudo' // (2)
SSH_PORT='22' // (3)
SYSCTL_CONF='./misc/sysctl.conf' // (4)
AUDITD_MODE='1' // (5)
AUDITD_RULES='./misc/audit-base.rules ./misc/audit-aggressive.rules ./misc/audit-docker.rules' // (6)
LOGROTATE_CONF='./misc/logrotate.conf' // (7)
NTPSERVERPOOL='0.ubuntu.pool.ntp.org 1.ubuntu.pool.ntp.org 2.ubuntu.pool.ntp.org 3.ubuntu.pool.ntp.org pool.ntp.org' // (8)
TIMEDATECTL='' // (9)
VERBOSE='N' // (10)
AUTOFILL='N' // (11)
ADMINEMAIL="root@localhost" // (12)
KEEP_SNAPD='Y' // (13)
CHANGEME='' // (14)
# Configuration files // (15)
ADDUSER='/etc/adduser.conf'
AUDITDCONF='/etc/audit/auditd.conf'
AUDITRULES='/etc/audit/rules.d/hardening.rules'
COMMONPASSWD='/etc/pam.d/common-password'
COMMONACCOUNT='/etc/pam.d/common-account'
COMMONAUTH='/etc/pam.d/common-auth'
COREDUMPCONF='/etc/systemd/coredump.conf'
DEFAULTGRUB='/etc/default/grub.d'
DISABLEFS='/etc/modprobe.d/disablefs.conf'
DISABLEMOD='/etc/modprobe.d/disablemod.conf'
DISABLENET='/etc/modprobe.d/disablenet.conf'
FAILLOCKCONF='/etc/security/faillock.conf'
JOURNALDCONF='/etc/systemd/journald.conf'
LIMITSCONF='/etc/security/limits.conf'
LOGINDCONF='/etc/systemd/logind.conf'
LOGINDEFS='/etc/login.defs'
LOGROTATE='/etc/logrotate.conf'
PAMLOGIN='/etc/pam.d/login'
PSADCONF='/etc/psad/psad.conf'
PSADDL='/etc/psad/auto_dl'
RESOLVEDCONF='/etc/systemd/resolved.conf'
RKHUNTERCONF='/etc/default/rkhunter'
RSYSLOGCONF='/etc/rsyslog.conf'
SECURITYACCESS='/etc/security/access.conf'
SSHFILE='/etc/ssh/ssh_config'
SSHDFILE='/etc/ssh/sshd_config'
SYSCTL='/etc/sysctl.conf'
SYSTEMCONF='/etc/systemd/system.conf'
TIMESYNCD='/etc/systemd/timesyncd.conf'
UFWDEFAULT='/etc/default/ufw'
USERADD='/etc/default/useradd'
USERCONF='/etc/systemd/user.conf'
-
The IP addresses that will be able to connect with SSH, separated by spaces.
-
Which group the users have to be member of in order to acess via SSH, separated by spaces.
-
Configure SSH port.
-
Stricter sysctl settings.
-
Auditd failure mode. 0=silent 1=printk 2=panic.
-
Auditd rules.
-
Logrotate settings.
-
NTP server pool.
-
Add a specific time zone or use the system default by leaving it empty.
-
If you want all the details or not.
-
Let the script guess the
FW_ADMIN
andSSH_GRPS
settings. -
Add a valid email address, so PSAD can send notifications.
-
If
'Y'
then thesnapd
package will be held to prevent removal. -
Add something just to verify that you actually glanced the code.
-
Default configuration file locations.
Functions
Function list in execution order
Note that all functions has the f_
prefix in the code.
pre
Sets apt
flags and performs basic permission check.
The pre
function is located in ./scripts/pre.
kernel
Sets /sys/module/nf_conntrack/parameters/hashsize
to 1048576 if hashsize
exists and is writable.
Sets /sys/kernel/security/lockdown
to confidentiality
if lockdown
exists and is writable.
The kernel
function is located in ./scripts/kernel.
firewall
Configures UFW if installed.
Allows connections from the adresses in $FW_ADMIN
to the $SSH_PORT
.
Sets logging and IPT_SYSCTL=/etc/sysctl.conf
.
The firewall
function is located in ./scripts/ufw.
disablenet
Disables the dccp
, sctp
, rds
and tipc
kernel modules.
The disablenet
function is located in ./scripts/disablenet.
disablefs
Disables the cramfs
freevxfs
jffs2
ksmbd
hfs
hfsplus
udf
kernel
modules.
The disablefs
function is located in ./scripts/disablefs.
disablemod
Disables the bluetooth
, bnep
, btusb
, cpia2
, firewire-core
, floppy
,
n_hdlc
, net-pf-31
, pcspkr
, soundcore
, thunderbolt
, usb-midi
,
usb-storage
, uvcvideo
, v4l2_common
kernel modules.
Note that disabling the usb-storage
module will disable any usage of USB
storage devices, if such devices are needed USBGuard
should be configured
accordingly.
The disablemod
function is located in ./scripts/disablemod.
systemdconf
Sets CrashShell=no
, DefaultLimitCORE=0
, DefaultLimitNOFILE=1024
,
DefaultLimitNPROC=1024
, DumpCore=no
in $SYSTEMCONF
and $USERCONF
.
The systemdconf
function is located in ./scripts/systemdconf.
resolvedconf
Sets DNS=$dnslist
, DNSOverTLS=opportunistic
, DNSSEC=allow-downgrade
, FallbackDNS=1.0.0.1
in $RESOLVEDCONF
, where $dnslist
is an array with the nameservers present
in /etc/resolv.conf
.
The resolvedconf
function is located in ./scripts/resolvedconf.
logindconf
Sets IdleAction=lock
, IdleActionSec=15min
, KillExcludeUsers=root
,
KillUserProcesses=1
, RemoveIPC=yes
in $LOGINDCONF
.
The logindconf
function is located in ./scripts/logindconf.
journalctl
Copies ./misc/logrotate.conf to $LOGROTATE
.
Sets Compress=yes
, ForwardToSyslog=yes
, Storage=persistent
in
$JOURNALDCONF
.
Sets $FileCreateMode 0600/
in $RSYSLOGCONF
.
if RSYSLOGCONF
is writable.
The journalctl
function is located in ./scripts/journalctl.
timesyncd
Sets NTP=${SERVERARRAY}
, FallbackNTP=${FALLBACKARRAY}
, RootDistanceMaxSec=1
in $TIMESYNCD
where the arrays are up to four time servers with < 50ms
latency.
The timesyncd
function is located in ./scripts/timesyncd.
fstab
Configures the /boot
and /home
partitions with defaults,nosuid,nodev
if
they are available in /etc/fstab
.
Configures the /var/log
, /var/log/audit
and /var/tmp
partitions with
defaults,nosuid,nodev,noexec
if they are available in /etc/fstab
.
Adds /run/shm tmpfs rw,noexec,nosuid,nodev
,
/dev/shm tmpfs rw,noexec,nosuid,nodev
and
/proc proc rw,nosuid,nodev,noexec,relatime,hidepid=2
to /etc/fstab
if
the partition isn’t present in /etc/fstab
.
Removes any floppy drivers from /etc/fstab
.
Copies ./config/tmp.mount[./config/tmp.mount] to
/etc/systemd/system/tmp.mount
, removes /tmp
from /etc/fstab
and enables the tmpfs /tmp
mount instead.
The /proc
hidepid
option is described in https://www.kernel.org/doc/html/latest/filesystems/proc.html#mount-options.
The fstab
function is located in ./scripts/fstab.
prelink
Reverts binaries and libraries to their original content before they were
prelinked and uninstalls prelink
.
The prelink
function is located in ./scripts/prelink.
aptget_configure
Sets apt
options Acquire::http::AllowRedirect "false";
, APT::Get::AllowUnauthenticated "false";
,
APT::Periodic::AutocleanInterval "7";
,
APT::Install-Recommends "false";
, APT::Get::AutomaticRemove "true";
,
APT::Install-Suggests "false";
, Acquire::AllowDowngradeToInsecureRepositories "false";
,
Acquire::AllowInsecureRepositories "false";
, APT::Sandbox::Seccomp "1";
The aptget_configure
function is located in ./scripts/aptget.
hosts
Sets sshd : ALL : ALLOW
, ALL: LOCAL, 127.0.0.1
in /etc/hosts.allow
and
ALL: ALL
in /etc/hosts.deny
.
See https://manpages.ubuntu.com/manpages/jammy/man5/hosts_access.5.html for the format of host access control files.
The hosts
function is located in ./scripts/hosts.
issue
Writes a notice regarding authorized use only to /etc/issue
, /etc/issue.net
and /etc/motd
.
Removes the executable flag from every file in /etc/update-motd.d/
.
The issue
function is located in ./scripts/issue.
sudo
Restricts su
access to members of the sudo
group using
pam_wheel.
Sets !pwfeedback
, !visiblepw
, logfile=/var/log/sudo.log
, passwd_timeout=1
,
timestamp_timeout=5
, use_pty
sudo options.
The sudo
function is located in ./scripts/sudo.
logindefs
Writes LOG_OK_LOGINS yes
, UMASK 077
, PASS_MIN_DAYS 1
, PASS_MAX_DAYS 60
,
DEFAULT_HOME no
, ENCRYPT_METHOD SHA512
, USERGROUPS_ENAB no
,
SHA_CRYPT_MIN_ROUNDS 10000
, SHA_CRYPT_MAX_ROUNDS 65536
to
$LOGINDEFS
The logindefs
function is located in ./scripts/logindefs.
sysctl
Copies ./misc/sysctl.conf to $SYSCTL
.
For an explanation of the options set, see https://www.kernel.org/doc/html/latest/admin-guide/sysctl/.
The sysctl
function is located in ./scripts/sysctl.
limitsconf
Sets hard maxlogins 10
, hard core 0
, soft nproc 512
, hard nproc 1024
in
$LIMITSCONF
The limitsconf
function is located in ./scripts/limits.
adduser
Sets DIR_MODE=0750
,DSHELL=/bin/false
, and USERGROUPS=yes
in $ADDUSER
.
Sets INACTIVE=30
and SHELL=/bin/false
in $USERADD
.
The adduser
function is located in ./scripts/adduser.
rootaccess
Writes +:root:127.0.0.1/'
to $SECURITYACCESS
and console
to
/etc/securetty
.
Masks debug-shell.
The rootaccess
function is located in ./scripts/rootaccess.
package_install
Installs acct
, aide-common
, cracklib-runtime
, debsums
, gnupg2
,
haveged
, libpam-pwquality
, libpam-tmpdir
, needrestart
, openssh-server
,
postfix
, psad
, rkhunter
, sysstat
, systemd-coredump
, tcpd
,
update-notifier-common
, vlock
.
The package_install
function is located in ./scripts/packages.
coredump
Writes Storage=none
and ProcessSizeMax=0
to $COREDUMPCONF
.
The coredump
function is located in ./scripts/coredump.
postfix
Installs postfix
and sets disable_vrfy_command=yes
,
inet_interfaces=loopback-only
,
smtpd_banner="\$myhostname
,
smtpd_client_restrictions=permit_mynetworks,reject
using postconf.
The postfix
function is located in ./scripts/postfix.
apport
Disables apport, ubuntu-report and popularity-contest.
The apport
function is located in ./scripts/apport.
rkhunter
Sets CRON_DAILY_RUN="yes"
, APT_AUTOGEN="yes"
in $RKHUNTERCONF
.
The rkhunter
function is located in ./scripts/rkhunter.
sshconfig
Sets HashKnownHosts yes
, Ciphers [email protected],[email protected],aes256-ctr
and MACs [email protected],[email protected],hmac-sha2-512,hmac-sha2-256
in $SSHFILE
.
The sshconfig
function is located in ./scripts/sshdconfig.
sshdconfig
Configures the OpenSSH
daemon. The configuration changes will be placed in
the directory defined by the Include
option if present, otherwise
$SSHDFILE
will be modified.
By default /etc/ssh/sshd_config.d/hardening.conf
will contain the following:
AcceptEnv LANG LC_*
AllowAgentForwarding no
AllowGroups sudo
AllowTcpForwarding no
Banner /etc/issue.net
Ciphers [email protected],[email protected],aes256-ctr
ClientAliveCountMax 3
ClientAliveInterval 200
Compression no
GSSAPIAuthentication no
HostbasedAuthentication no
IgnoreUserKnownHosts yes
KbdInteractiveAuthentication no
KerberosAuthentication no
KexAlgorithms [email protected],ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256
LogLevel VERBOSE
LoginGraceTime 20
Macs [email protected],[email protected],hmac-sha2-512,hmac-sha2-256
MaxAuthTries 3
MaxSessions 3
MaxStartups 10:30:60
PasswordAuthentication no
PermitEmptyPasswords no
PermitRootLogin no
PermitUserEnvironment no
Port 22
PrintLastLog yes
PrintMotd no
RekeyLimit 512M 1h
StrictModes yes
TCPKeepAlive no
UseDNS no
UsePAM yes
X11Forwarding no
The sshdconfig
function is located in ./scripts/sshdconfig.
password
Copies ./config/pwquality.conf[./config/pwquality.conf] to /etc/security/pwquality.conf
,
Removes nullok
from PAM
$COMMONAUTH
.
Configures faillock or pam_tally2 depending on which is installed.
Adds a password list to cracklib.
The password
function is located in ./scripts/password.
cron
The cron
function is located in ./scripts/cron.
auditd
Configures auditd.
See ./misc/audit-base.rules, ./misc/audit-aggressive.rules and ./misc/audit-docker.rules for the rules used.
The auditd
function is located in ./scripts/auditd.
aide
Excludes /var/lib/lxcfs/cgroup
and /var/lib/docker
from AIDE.
The aide
function is located in ./scripts/aide.
rhosts
Removes any existing hosts.equiv
or .rhosts
files.
The rhosts
function is located in ./scripts/rhosts.
users
Removes the games
, gnats
, irc
, list
, news
, sync
, uucp
users.
The users
function is located in ./scripts/users.
package_remove
Removes the apport*
, autofs
, avahi*
, beep
, git
, pastebinit
,
popularity-contest
, rsh*
, rsync
, talk*
, telnet*
, tftp*
, whoopsie
,
xinetd
, yp-tools
, ypbind
packages.
The package_remove
function is located in ./scripts/packages.
suid
Ensures the executables in ./misc/suid.list don’t have suid bits set.
The suid
function is located in ./scripts/suid.
restrictcompilers
Changes mode to 0750
on any installed compilers.
The restrictcompilers
function is located in ./scripts/compilers.
path
Copies ./config/initpath.sh[./config/initpath.sh] to /etc/profile.d/initpath.sh
and sets PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
for the root
user and PATH=/usr/local/bin:/usr/sbin:/usr/bin:/bin:/snap/bin
for everyone else.
The path
function is located in ./scripts/path.
aa_enforce
Enforces available apparmor profiles.
The aa_enforce
function is located in ./scripts/apparmor.
aide_timer
Copies a systemd AIDE check service and timer to /etc/systemd/system/.
The aide_timer
function is located in ./scripts/aide.
aptget_noexec
Adds a DPkg::Pre-Invoke
and DPkg::Post-Invoke
to ensure package updates
don’t fail on a noexec
/tmp
partition.
The aptget_noexec
function is located in ./scripts/aptget.
aptget_clean
Runs apt-get clean
and autoremove
.
The aptget_clean
function is located in ./scripts/aptget.
systemddelta
Runs systemd-delta if running in verbose mode.
The systemddelta
function is located in ./scripts/systemddelta.
post
Ensures fwupdmgr and secureboot-db is installed and GRUB is updated.
The post
function is located in ./scripts/post.
checkreboot
Checks if a reboot is required.
The checkreboot
function is located in ./scripts/reboot.
Tests
There are approximately 760 Bats tests for most of the above settings available in the tests directory.
sudo apt-get -y install bats
git clone https://github.com/konstruktoid/hardening.git
cd hardening/tests/
sudo bats .
Test automation using Vagrant
Running bash ./runTests.sh
will use Vagrant to run
all above tests, Lynis and
OpenSCAP with a
CIS Ubuntu benchmark on all
supported Ubuntu versions.
The script will generate a file named TESTRESULTS.adoc
and CIS report in
HTML-format.
Testing a host
Running bash ./runHostTests.sh
, located in the tests directory,
will generate a TESTRESULTS-<HOSTNAME>.adoc
report.
Recommended reading
Contributing
Do you want to contribute? That’s great! Contributions are always welcome, no matter how large or small. If you found something odd, feel free to submit a new issue, improve the code by creating a pull request, or by sponsoring this project.
Logo by reallinfo.