I am considering my obligations to get UK ‘Cyber Essentials’ that includes secure configuration of all servers and workstations. That means, in broad terms, to not install software or services that is not needed (to do your job), change any default passwords, secure known weaknesses etc etc. It’s good to NOT do stupid things, yes?
The UK National Cyber Security Centre (NCSC) have released a script to secure Ubuntu after initial installation. I wondered what folks here think of it?
The article refers to a Github page that hosts a bash script, which you can get directly to here:
I have posted this to Tom Lawrence forums also, so I hope I do not cause offence by cross-posting. Actually, I only thought of LLTV after the initial post to LTS. I can/will delete the LTS forum post if anyone objects.
It is night now, I felt like opening new web pages.
Here is the source, for anyone not interested to go there.
#!/bin/bash
HIGHLIGHT='\033[1;32m'
NC='\033[0m'
function promptPassphrase {
PASS=""
PASSCONF=""
while [ -z "$PASS" ]; do
read -s -p "Passphrase: " PASS
echo ""
done
while [ -z "$PASSCONF" ]; do
read -s -p "Confirm passphrase: " PASSCONF
echo ""
done
echo ""
}
function getPassphrase {
promptPassphrase
while [ "$PASS" != "$PASSCONF" ]; do
echo "Passphrases did not match, try again..."
promptPassphrase
done
}
if [[ $UID -ne 0 ]]; then
echo "This script needs to be run as root (with sudo)."
exit 1
fi
# Get the admin user.
users=($(ls /home))
echo "Existing users:"
echo
for index in ${!users[*]}
do
echo -e "\t[$index]: " ${users[$index]}
done
echo
while [ -z "$SELECTION" ]; do read -p "Please select the user you created during the Ubuntu installation: " SELECTION; done
ADMINUSER=${users[$SELECTION]}
if [ -z "$ADMINUSER" ]; then
echo "Invalid user selected. Please run the script again."
exit
fi
# Get the username for the primary user.
echo
echo "Please enter a username for the primary device user that will be created by this script."
while [ -z "$ENDUSER" ]; do read -p "Username for primary device user: " ENDUSER; done
if [ -d "/home/$ENDUSER" ]; then
if [ "$ENDUSER" == "$ADMINUSER" ]; then
echo "Primary user cannot be the same as the admin user."
exit
fi
read -p "The username you entered already exists. Do you want to continue? [y/n]: " CONFIRM
if [ "$CONFIRM" != "y" ]; then
exit
fi
fi
echo "If you are not using the default internet repositories you should configure this before running this script."
echo "You should also have an active network connection to the repositories."
read -p "Continue? [y/n]: " CONFIRM
if [ "$CONFIRM" != "y" ]; then
exit
fi
echo -e "${HIGHLIGHT}Running system updates...${NC}"
# Update.
apt-get update
# Upgrade.
apt-get dist-upgrade -y
# Remove packages.
apt-get remove -y popularity-contest
# And install required packages.
apt-get install -y apparmor-profiles apparmor-utils auditd
# Configuring mount and grub. We need to make sure the script is running for the first time.
echo -e "${HIGHLIGHT}Configuring fstab...${NC}"
read -p "Is this the first time you run the post-install script? [y/n]: " CONFIRM
if [ "$CONFIRM" == "y" ]; then
# Update fstab.
echo -e "${HIGHLIGHT}Writing fstab config...${NC}"
sed -ie '/\s\/home\s/ s/defaults/defaults,noexec,nosuid,nodev/' /etc/fstab
EXISTS=$(grep "/tmp/" /etc/fstab)
if [ -z "$EXISTS" ]; then
echo "none /tmp tmpfs rw,noexec,nosuid,nodev 0 0" >> /etc/fstab
else
sed -ie '/\s\/tmp\s/ s/defaults/defaults,noexec,nosuid,nodev/' /etc/fstab
fi
echo "none /run/shm tmpfs rw,noexec,nosuid,nodev 0 0" >> /etc/fstab
# Bind /var/tmp to /tmp to apply the same mount options during system boot
echo "/tmp /var/tmp none bind 0 0" >> /etc/fstab
# Temporarily make the /tmp directory executable before running apt-get and remove execution flag afterwards. This is because
# sometimes apt writes files into /tmp and executes them from there.
echo -e "DPkg::Pre-Invoke{\"mount -o remount,exec /tmp\";};\nDPkg::Post-Invoke {\"mount -o remount /tmp\";};" >> /etc/apt/apt.conf.d/99tmpexec
chmod 644 /etc/apt/apt.conf.d/99tmpexec
fi
# Set grub password.
echo -e "${HIGHLIGHT}Configuring grub...${NC}"
echo "Please enter a grub sysadmin passphrase..."
getPassphrase
echo "set superusers=\"sysadmin\"" >> /etc/grub.d/40_custom
echo -e "$PASS\n$PASS" | grub-mkpasswd-pbkdf2 | tail -n1 | awk -F" " '{print "password_pbkdf2 sysadmin " $7}' >> /etc/grub.d/40_custom
sed -ie '/echo "menuentry / s/echo "menuentry /echo "menuentry --unrestricted /' /etc/grub.d/10_linux
sed -ie '/^GRUB_CMDLINE_LINUX_DEFAULT=/ s/"$/ module.sig_enforce=yes"/' /etc/default/grub
echo "GRUB_SAVEDEFAULT=false" >> /etc/default/grub
update-grub
# Set permissions for admin user's home directory.
chmod 700 "/home/$ADMINUSER"
# Configure automatic updates.
echo -e "${HIGHLIGHT}Configuring automatic updates...${NC}"
EXISTS=$(grep "APT::Periodic::Update-Package-Lists" /etc/apt/apt.conf.d/20auto-upgrades)
if [ -z "$EXISTS" ]; then
sed -i '/APT::Periodic::Update-Package-Lists/d' /etc/apt/apt.conf.d/20auto-upgrades
echo "APT::Periodic::Update-Package-Lists \"1\";" >> /etc/apt/apt.conf.d/20auto-upgrades
fi
EXISTS=$(grep "APT::Periodic::Unattended-Upgrade" /etc/apt/apt.conf.d/20auto-upgrades)
if [ -z "$EXISTS" ]; then
sed -i '/APT::Periodic::Unattended-Upgrade/d' /etc/apt/apt.conf.d/20auto-upgrades
echo "APT::Periodic::Unattended-Upgrade \"1\";" >> /etc/apt/apt.conf.d/20auto-upgrades
fi
EXISTS=$(grep "APT::Periodic::AutocleanInterval" /etc/apt/apt.conf.d/10periodic)
if [ -z "$EXISTS" ]; then
sed -i '/APT::Periodic::AutocleanInterval/d' /etc/apt/apt.conf.d/10periodic
echo "APT::Periodic::AutocleanInterval \"7\";" >> /etc/apt/apt.conf.d/10periodic
fi
chmod 644 /etc/apt/apt.conf.d/20auto-upgrades
chmod 644 /etc/apt/apt.conf.d/10periodic
# Prevent standard user executing su.
echo -e "${HIGHLIGHT}Configure su execution...${NC}"
dpkg-statoverride --update --add root adm 4750 /bin/su
# Protect user home directories.
echo -e "${HIGHLIGHT}Configuring home directories and shell access...${NC}"
sed -ie '/^DIR_MODE=/ s/=[0-9]*\+/=0700/' /etc/adduser.conf
sed -ie '/^UMASK\s\+/ s/022/077/' /etc/login.defs
read -p "User shell configuration:
[0] Set shell to /bin/passwd (allows password changes, bypassing Gnome bug) (default)
[1] Set shell to /sbin/nologin (prevents shell access)
[2] Set shell to /bin/bash (allows bash shell access)
" LEVEL
if [ "$LEVEL" == "1" ]; then
# Disable shell access for new users (not affecting the existing admin user).
sed -ie '/^SHELL=/ s/=.*\+/=\/usr\/sbin\/nologin/' /etc/default/useradd
sed -ie '/^DSHELL=/ s/=.*\+/=\/usr\/sbin\/nologin/' /etc/adduser.conf
elif [ "$LEVEL" == "2" ]; then
# Keep shell access for new users (not affecting the existing admin user).
sed -ie '/^SHELL=/ s/=.*\+/=\/bin\/bash/' /etc/default/useradd
sed -ie '/^DSHELL=/ s/=.*\+/=\/bin\/bash/' /etc/adduser.conf
else
# Keep shell access for new users to prevent Gnome bug re. passwords (not affecting the existing admin user). Default option.
sed -ie '/^SHELL=/ s/=.*\+/=\/bin\/passwd/' /etc/default/useradd
sed -ie '/^DSHELL=/ s/=.*\+/=\/bin\/passwd/' /etc/adduser.conf
fi
# Installing libpam-pwquality
echo -e "${HIGHLIGHT}Configuring minimum password requirements...${NC}"
apt-get install -f libpam-pwquality
# Create the standard user.
adduser "$ENDUSER"
# Set some AppArmor profiles to enforce mode.
echo -e "${HIGHLIGHT}Configuring apparmor...${NC}"
aa-enforce /etc/apparmor.d/usr.bin.firefox
aa-enforce /etc/apparmor.d/usr.sbin.avahi-daemon
aa-enforce /etc/apparmor.d/usr.sbin.dnsmasq
aa-enforce /etc/apparmor.d/bin.ping
aa-enforce /etc/apparmor.d/usr.sbin.rsyslogd
# Setup auditing.
echo -e "${HIGHLIGHT}Configuring system auditing...${NC}"
if [ ! -f /etc/audit/rules.d/tmp-monitor.rules ]; then
echo "# Monitor changes and executions within /tmp
-w /tmp/ -p wa -k tmp_write
-w /tmp/ -p x -k tmp_exec" > /etc/audit/rules.d/tmp-monitor.rules
fi
if [ ! -f /etc/audit/rules.d/admin-home-watch.rules ]; then
echo "# Monitor administrator access to /home directories
-a always,exit -F dir=/home/ -F uid=0 -C auid!=obj_uid -k admin_home_user" > /etc/audit/rules.d/admin-home-watch.rules
fi
augenrules
systemctl restart auditd.service
# Configure the settings for the "Welcome" popup box on first login.
echo -e "${HIGHLIGHT}Configuring user first login settings...${NC}"
mkdir -p "/home/$ENDUSER/.config"
echo yes > "/home/$ENDUSER/.config/gnome-initial-setup-done"
chown -R "$ENDUSER:$ENDUSER" "/home/$ENDUSER/.config"
sudo -H -u "$ENDUSER" ubuntu-report -f send no
# Disable error reporting services
echo -e "${HIGHLIGHT}Configuring error reporting...${NC}"
systemctl stop apport.service
systemctl disable apport.service
systemctl mask apport.service
systemctl stop whoopsie.service
systemctl disable whoopsie.service
systemctl mask whoopsie.service
if [ ! -f "/etc/dconf/profile/user" ]; then
touch /etc/dconf/profile/user
fi
EXISTS=$(grep "user-db:user" /etc/dconf/profile/user)
if [ -z "$EXISTS" ]; then
echo "user-db:user" >> /etc/dconf/profile/user
fi
EXISTS=$(grep "system-db:local" /etc/dconf/profile/user)
if [ -z "$EXISTS" ]; then
echo "system-db:local" >> /etc/dconf/profile/user
fi
# Optionally disable bluetooth
read -p "Do you want to disable bluetooth for all users? [y/n]: " CONFIRM
if [ "$CONFIRM" == "y" ]; then
systemctl disable bluetooth.service
fi
# Lockdown Gnome screensaver lock settings
echo -e "${HIGHLIGHT}Configuring Gnome screensaver lock settings...${NC}"
mkdir -p /etc/dconf/db/local.d/locks
echo "[org/gnome/login-screen]
disable-user-list=true
[org/gnome/desktop/session]
idle-delay=600
[org/gnome/desktop/screensaver]
lock-enabled=true
lock-delay=0
ubuntu-lock-on-suspend=true" > /etc/dconf/db/local.d/00_custom-lock
echo "/org/gnome/desktop/session/idle-delay
/org/gnome/desktop/screensaver/lock-enabled
/org/gnome/desktop/screensaver/lock-delay
/org/gnome/desktop/screensaver/ubuntu-lock-on-suspend
/org/gnome/login-screen/disable-user-list" > /etc/dconf/db/local.d/locks/00_custom-lock
# Remove user list from login page
sed -ie '/^\# disable-user-list\=true/ s/#//' /etc/gdm3/greeter.dconf-defaults
read -p "Do you want to disable lockscreen notifications? [y/n]: " CONFIRM
if [ "$CONFIRM" == "y" ]; then
echo "
[org/gnome/desktop/notifications]
show-in-lock-screen=false
[org/gnome/login-screen]
banner-message-enable=false" >> /etc/dconf/db/local.d/00_custom-lock
echo "/org/gnome/desktop/notifications/show-in-lock-screen
/org/gnome/login-screen/banner-message-enable" >> /etc/dconf/db/local.d/locks/00_custom-lock
fi
# Optionally Disable Location Services
read -p "Do you want to disable location services? [y/n]: " CONFIRM
if [ "$CONFIRM" == "y" ]; then
echo "
[org/gnome/system/location]
max-accuracy-level='country'
enabled=false" >> /etc/dconf/db/local.d/00_custom-lock
echo "/org/gnome/system/location/max-accuracy-level
/org/gnome/system/location/enabled" >> /etc/dconf/db/local.d/locks/00_custom-lock
fi
# Further Privacy Setting
echo "
[org/gnome/desktop/privacy]
report-technical-problems=false" >> /etc/dconf/db/local.d/00_custom-lock
echo "/org/gnome/desktop/privacy/report-technical-problems" >> /etc/dconf/db/local.d/locks/00_custom-lock
# Optionally Set USB Restrictions (located within org/gnome/desktop/privacy)
read -p "Do you want to restrict USB usage? [y/n]: " CONFIRM
if [ "$CONFIRM" == "y" ]; then
echo "usb-protection=true" >> /etc/dconf/db/local.d/00_custom-lock
read -p "Set the restriction level:
[0] block USB on lockscreen (default)
[1] always block USB
" LEVEL
if [ "$LEVEL" == "1" ]; then
echo "Setting USB lockdown mode to: always"
echo "usb-protection-level='always'" >> /etc/dconf/db/local.d/00_custom-lock
echo "/org/gnome/desktop/privacy/usb-protection-level" >> /etc/dconf/db/local.d/locks/00_custom-lock
if [ ! -f "/etc/modprobe.d/blacklist.conf" ]; then
touch /etc/modprobe.d/blacklist.conf
fi
if [ ! -f "/etc/rc.local" ]; then
touch /etc/rc.local
echo "#!/bin/bash" >> /etc/rc.local
fi
echo "blacklist usb_storage
blacklist uas" >> /etc/modprobe.d/blacklist.conf
echo "modprobe -r uas
modprobe -r usb_storage" >> /etc/rc.local
rmmod usb_storage
else
echo "Setting USB lockdown mode to: lockscreen"
echo "usb-protection-level='lockscreen'" >> /etc/dconf/db/local.d/00_custom-lock
echo "/org/gnome/desktop/privacy/usb-protection-level" >> /etc/dconf/db/local.d/locks/00_custom-lock
fi
fi
dconf update
# Fix dconf permissions, otherwise option locks don't apply upon subsequent script executions
chmod 644 -R /etc/dconf/db/
chmod a+x /etc/dconf/db/local.d/locks
chmod a+x /etc/dconf/db/local.d
chmod a+x /etc/dconf/db
# Disable apport (error reporting)
sed -ie '/^enabled=1$/ s/1/0/' /etc/default/apport
sudo -H -u "$ENDUSER" dbus-launch gsettings set com.ubuntu.update-notifier show-apport-crashes false
# Fix some permissions in /var that are writable and executable by the standard user.
echo -e "${HIGHLIGHT}Configuring additional directory permissions...${NC}"
chmod o-w /var/crash
chmod o-w /var/metrics
chmod o-w /var/tmp
# Setting up firewall without any rules.
echo -e "${HIGHLIGHT}Configuring firewall… ${NC}"
ufw enable
echo -e "${HIGHLIGHT}Installation complete.${NC}"
read -p "Reboot now? [y/n]: " CONFIRM
if [ "$CONFIRM" == "y" ]; then
reboot
fi
The script:
verifies that you are the root user, or running it with sudo, or quits
it gets the users from the /home directory (kinda inefficient, because your users may have other locations, like /opt or /export/home, better to get it from /etc/passwd and see what users have id starting with 1000 and up)
shows you the list of users and asks you to insert the number for the user which you used when installing ubuntu
asks you to insert a username as an end user, that is not the admin user (for security purposes, you’re not supposed to use an admin account for your daily activities, you’re supposed to su - into it, then sudo)
if the username exists, asks for confirmation if you’re sure
runs updates and installs apparmor
it creates some tmpfs mountpoints in fstab like /tmp and bind mounts /tmp to /var/tmp (which I find dumb, they should be different - the idea of /var/tmp being a tmpfs is fine, just it shouldn’t be combined with /tmp)
reads a pasphrase twice and makes sure it’s not empty
if the passphrases didn’t match, asks again until you get it right
sets that password for grub in order to allow boot probably (if your system is not encrypted, this doesn’t do anything than maybe prevent end-users from adding a boot flag to boot to rescue mode and reset the root password, but even then, if booting other OS is allowed in the bios, this doesn’t do anything either)
configures automatic, unattended updates
shows a prompt for each user from /home to set their shells accordingly (passwd, nologin or bash)
installs require to set better passwords and set expiration policies
adds some apparmor profiles
enables some audits for changes and executions from /tmp
I believe it monitors admin access over /home directories, probably to prevent admin abuse, I suppose
configures first login and sets the gnome ubuntu-reporting thing to no by default, so you bypass the automatic data sending to canonical
disables error reporting services (idk why, doesn’t sound good to me when you want to debug stuff, although many exploits are done through error reporting software)
sets some dconf profiles
asks if you want to disable bluetooth
sets the gnome screen locking to 10 minutes of idle (I would go for 5 if I really wanted to lock down things) and sets suspend on lockon
disables the user list in the login screen (you have to type it manually)
asks if you want lock screen notifications
asks if you want location services
sets privacy settings
asks if you want to block usb devices (blocking people from copying files to and from thumb drives)
removes write permissions for others to /var/{crash,metrics,tmp} (why would they do it for /var/tmp and /tmp, for /tmp it makes no sense, which is the reason why /tmp shouldn’t be bind mounted to /var/tmp)
asks to reboot
Decent little script, especially the apparmor stuff, but you can do without it. It seems mostly for desktop use anyway (with all the gnome and firefox garbage policies). I wouldn’t use it on a server, but maybe I’d implement some of the stuff, like pam and apparmor, or rather, selinux, because I prefer rhel-based systems, even fedora, to ubuntu or suse that use apparmor - just preference. But if I was to really be able to pick a server os that should be rock-solid and secure, I’d choose Alpine diskless install and keep things minimal, although I’d have to use apparmor.
@ThatGuyB, my apologies. I did not include the code explicitly, but the link to the GitHub page caused the forum software to auto-add the extract. I agree that GitHub formatting is not so easy on the eye. I see you have added the script below, and a synopsis of what its doing - my thanks for the nice contributiont that I think others will find very useful.
I have often installed Linux with my usual username, and continue to use it thereafter and sudo to run something privileged. The note you made about not daily driving with the installation user causes me concern - so I will look in to that. I do create keys to ssh to a host (i.e. not using a password, as they are not secure) but as soon as I need to use sudo on the remote I have to enter the password. Is there a better (more secure) way to authenticate in a terminal?
My goal is to develop a Linux operational and configuration security policy for my business. I would like to have a credable response to a challenge that I do not install antivirus for my Linux servers, as is the norm for Windows. Steve Gibson has commented on the fact that, for Windows at least, antimalware vendors have often reduced system security and created instability due to the nature of what they are doing - by hooking the OS at a deep level - and Microsoft has struggled and reacted to control delinquent third-party antimalware services.
Steve Gibson: Not so fun is the growing problem that we are seeing with third-party antivirus, at least on Windows. And we’ve talked about the cause of this. The cause is that today’s AV, in order to continue to demonstrate its value to its customers, really has to hook its claws deeply into Windows. Microsoft now is arguably competing. We’re seeing instances where third-party AV is causing Microsoft trouble. It’s always been sort of controversial because, in order to get the access that antivirus software needs, it needs essentially to kind of be a rootkit. It needs to go in and make modifications to the underlying kernel level code in order to essentially insert taps into Windows for the things it wants to do. Well, the problem is, you know, I said it was rootkit-like. Well, Windows is trying to prevent rootkits. So, I mean, there really is a fundamental schism here which we’re now increasingly reporting. And it happened again last Tuesday, a week ago, in an interesting way. https://www.grc.com/sn/sn-738.htm
I think I will experiment with the useful parts of the script - your analysis is very useful! Thanks.
Well… you could technically use a 2FA in the terminal too, like a fido2 key or TOTP, but that’s a hassle. Easiest way to keep things secure:
edit sshd config to only allow certain users to authenticate via ssh keys. Disabling password authentication does the trick most of the time though
when you ssh to a server, ssh to a non-sudoer, then su - into the sudoer and run your sudo commands
try to limit su - and su - root to prevent people from logging in as the root user. It is more convenient and if you know what you are doing, there’s nothing wrong with it, but it’s better you have them run commands with sudo and monitor their activity
if you aren’t using an AAA server, have each sudoer account have a unique, RNG long password and for sharing the password, use VaultWarden, or if you can afford it, Bitwarden (they both use the same GUIs and plugins / extensions, but vaultwarden is FOSS and gratis, while I believe bitwarden has some proprietary bits and peaces and has or used to have a requirement on MS SQL server, which was bonkers, which is why I went with vaultwarden, or bitwarden_rs as it was used to be named, until I think the dev was asked to change the name to avoid trademark reasons)
The Enterprise Linux Security series (53 episodes and counting) is great. It’s not a technical tutorial or review but more of a conversation about things to consider. Sometimes it’s best to get an overview of things before reaching out for one specific tool, at least this helps me when I’m trying to find things online or ask questions about specific topics.
${!users[*]}
What does this syntax mean? Expand items in the array?
it appears to be an array of items, made into a list (kind of like “cat users.txt” or “index in [1…29]”. I never used something like this, I prefer to use files, but I suppose bash array variables aren’t that uncommon. Files are just easier to work with, but sometimes keeping things in RAM is more efficient (unless you just use /tmp, which normally is a tmpfs anyway, then you only get a slight overhead of writing the file to a RAM FS, instead of directly to RAM, but it’s not that big for small files).
#!/usr/bin/bash
array=(one two three)
# ${!array[@]} is the list of all the indexes set in the array
for i in ${!array[@]}; do
echo "$i, ${array[$i]}"
done
I know this thread is 7 months old, but I would like to point out something from this quote:
it gets the users from the /home directory (kinda inefficient, because your users may have other locations, like /opt or /export/home, better to get it from /etc/passwd and see what users have id starting with 1000 and up)