Skip to content

konstruktoid/hardening

Repository files navigation

Ubuntu Hardening

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
Read the code and do not run this script without first testing in a non-operational environment. The code is not idempotent, use the Ansible role in production environments instead.
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

  1. Start the server installation.

  2. Pick language and keyboard layout.

  3. Select "Ubuntu Server (minimized)".

  4. Configure network connections.

  5. Partition the system, see below for recommendations.

  6. Do not install the OpenSSH server, "Featured Server Snaps", or any other packages.

  7. Finish the installation and reboot.

  8. Log in.

  9. If wanted, set a Grub2 password with grub-mkpasswd-pbkdf2. See https://help.ubuntu.com/community/Grub2/Passwords for more information.

  10. Install necessary packages: sudo apt-get -y install git net-tools procps --no-install-recommends.

  11. Download the script: git clone https://github.com/konstruktoid/hardening.git.

  12. Change the configuration options in the ubuntu.cfg file.

  13. Run the script: sudo bash ubuntu.sh.

  14. Reboot.

/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'
  1. The IP addresses that will be able to connect with SSH, separated by spaces.

  2. Which group the users have to be member of in order to acess via SSH, separated by spaces.

  3. Configure SSH port.

  4. Stricter sysctl settings.

  5. Auditd failure mode. 0=silent 1=printk 2=panic.

  6. Auditd rules.

  7. Logrotate settings.

  8. NTP server pool.

  9. Add a specific time zone or use the system default by leaving it empty.

  10. If you want all the details or not.

  11. Let the script guess the FW_ADMIN and SSH_GRPS settings.

  12. Add a valid email address, so PSAD can send notifications.

  13. If 'Y' then the snapd package will be held to prevent removal.

  14. Add something just to verify that you actually glanced the code.

  15. 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 fstab function is located in ./scripts/fstab.

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.

aptget

Upgrades installed packages.

The aptget 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.

psad

Installs and configures PSAD

The psad function is located in ./scripts/psad.

coredump

Writes Storage=none and ProcessSizeMax=0 to $COREDUMPCONF.

The coredump function is located in ./scripts/coredump.

usbguard

Installs and configures USBGuard.

The usbguard function is located in ./scripts/usbguard.

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

The apport function is located in ./scripts/apport.

motdnews

Disables apt_news and motd-news.

The motdnews function is located in ./scripts/motdnews.

rkhunter

Sets CRON_DAILY_RUN="yes", APT_AUTOGEN="yes" in $RKHUNTERCONF.

The rkhunter function is located in ./scripts/rkhunter.

sshconfig

Sets HashKnownHosts yes, Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr and MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,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 chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr
ClientAliveCountMax 3
ClientAliveInterval 200
Compression no
GSSAPIAuthentication no
HostbasedAuthentication no
IgnoreUserKnownHosts yes
KbdInteractiveAuthentication no
KerberosAuthentication no
KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256
LogLevel VERBOSE
LoginGraceTime 20
Macs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,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

Disables atd and only allow root to use at or cron.

The cron function is located in ./scripts/cron.

ctrlaltdel

The ctrlaltdel function is located in ./scripts/ctraltdel.

auditd

Configures auditd.

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.

lockroot

Locks root account

The lockroot function is located in ./scripts/lockroot.

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.

umask

Sets the default umask to 077

The umask function is located in ./scripts/umask.

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_post

Creates a new AIDE database.

The aide_post function is located in ./scripts/aide.

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.

Running bash ./runHostTestsCsv.sh, located in the tests directory, will generate a TESTRESULTS-<HOSTNAME>.csv report.

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.