source /etc/mailinabox.conf source setup/functions.sh # load our functions # Basic System Configuration # ------------------------- # ### Disable password login via SSH # We will disable password based login via ssh only if the user that logged in: # # - Is using an ssh connection # - Used a public key to authenticate # - The user still has that key in authorized_keys # # The /var/log/auth.log will contain entries for logins that used public # key authentication. We will try to find an entry for the user. If we # find a record matching the user and the current ip address we will lookup # the key finger print in the authorized keys of the user # # Sudo makes the use of $SSH_CLIENT client impossible, so we have to use pinky USERNAME_THAT_WAS_USED_TO_LOG_IN=$(logname) IP_ADDRESS_OF_USER=$(pinky -w $USERNAME_THAT_WAS_USED_TO_LOG_IN | tail -n+2 | tail -n1 | awk '{print $(NF)}') if [ ! -z "$IP_ADDRESS_OF_USER" ]; then HOMEDIRECTORY_OF_USER=$(grep "$USERNAME_THAT_WAS_USED_TO_LOG_IN" /etc/passwd | cut -d":" -f6) LOG_ENTRY_TO_SEARCH_FOR="ssh.* Accepted publickey for $USERNAME_THAT_WAS_USED_TO_LOG_IN from $IP_ADDRESS_OF_USER" SSH_PUB_KEY_USED_TO_LOG_IN=$(grep "$LOG_ENTRY_TO_SEARCH_FOR" /var/log/auth.log* | tail -n 1 | awk '{print $(NF)}') if [ ! -z "$SSH_PUB_KEY_USED_TO_LOG_IN" ]; then if [ -e "$HOMEDIRECTORY_OF_USER"/.ssh/authorized_keys ]; then if ssh-keygen -l -f "$HOMEDIRECTORY_OF_USER"/.ssh/authorized_keys | grep -q -i "$SSH_PUB_KEY_USED_TO_LOG_IN"; then # Public key used to log in is still in authorized_keys so we can safely disable # password based logins echo "Disabling password authentication for ssh" tools/editconf.py /etc/ssh/sshd_config -s \ PasswordAuthentication=yes restart_service ssh fi fi fi fi # ### Add swap space to the system # If the physical memory of the system is below 2GB it is wise to create a # swap file. This will make the system more resiliant to memory spikes and # prevent for instance spam filtering from crashing # We will create a 1G file, this should be a good balance between disk usage # and buffers for the system. We will only allocate this file if there is more # than 5GB of disk space available # The following checks are performed: # - Check if swap is currently mountend by looking at /proc/swaps # - Check if the user intents to activate swap on next boot by checking fstab entries. # - Check if a swapfile already exists # - Check if the root file system is not btrfs, might be an incompatible version with # swapfiles. User should hanle it them selves. # - Check the memory requirements # - Check available diskspace # See https://www.digitalocean.com/community/tutorials/how-to-add-swap-on-ubuntu-14-04 # for reference SWAP_MOUNTED=$(cat /proc/swaps | tail -n+2) SWAP_IN_FSTAB=$(grep "swap" /etc/fstab) ROOT_IS_BTRFS=$(grep "\/ .*btrfs" /proc/mounts) TOTAL_PHYSICAL_MEM=$(head -n 1 /proc/meminfo | awk '{print $2}') AVAILABLE_DISK_SPACE=$(df / --output=avail | tail -n 1) if [ -z "$SWAP_MOUNTED" ] && [ -z "$SWAP_IN_FSTAB" ] && [ ! -e /swapfile ] && [ -z "$ROOT_IS_BTRFS" ] && [ $TOTAL_PHYSICAL_MEM -lt 1900000 ] && [ $AVAILABLE_DISK_SPACE -gt 5242880 ] then echo "Adding a swap file to the system..." # Allocate and activate the swap file. Allocate in 1KB chuncks # doing it in one go, could fail on low memory systems dd if=/dev/zero of=/swapfile bs=1024 count=$[1024*1024] status=none if [ -e /swapfile ]; then chmod 600 /swapfile hide_output mkswap /swapfile swapon /swapfile fi # Check if swap is mounted then activate on boot if swapon -s | grep -q "\/swapfile"; then echo "/swapfile none swap sw 0 0" >> /etc/fstab else echo "ERROR: Swap allocation failed" fi fi # ### Add Mail-in-a-Box's PPA. # We've built several .deb packages on our own that we want to include. # One is a replacement for Ubuntu's stock postgrey package that makes # some enhancements. The other is dovecot-lucene, a Lucene-based full # text search plugin for (and by) dovecot, which is not available in # Ubuntu currently. # # So, first ensure add-apt-repository is installed, then use it to install # the [mail-in-a-box ppa](https://launchpad.net/~mail-in-a-box/+archive/ubuntu/ppa). if [ ! -f /usr/bin/add-apt-repository ]; then echo "Installing add-apt-repository..." hide_output apt-get update apt_install software-properties-common fi hide_output add-apt-repository -y ppa:mail-in-a-box/ppa # ### Update Packages # Update system packages to make sure we have the latest upstream versions of things from Ubuntu. echo Updating system packages... hide_output apt-get update apt_get_quiet upgrade # ### Install System Packages # Install basic utilities. # # * haveged: Provides extra entropy to /dev/random so it doesn't stall # when generating random numbers for private keys (e.g. during # ldns-keygen). # * unattended-upgrades: Apt tool to install security updates automatically. # * cron: Runs background processes periodically. # * ntp: keeps the system time correct # * fail2ban: scans log files for repeated failed login attempts and blocks the remote IP at the firewall # * netcat-openbsd: `nc` command line networking tool # * git: we install some things directly from github # * sudo: allows privileged users to execute commands as root without being root # * coreutils: includes `nproc` tool to report number of processors, mktemp # * bc: allows us to do math to compute sane defaults echo Installing system packages... apt_install python3 python3-dev python3-pip \ netcat-openbsd wget curl git sudo coreutils bc \ haveged pollinate \ unattended-upgrades cron ntp fail2ban # ### Set the system timezone # # Some systems are missing /etc/timezone, which we cat into the configs for # Z-Push and ownCloud, so we need to set it to something. Daily cron tasks # like the system backup are run at a time tied to the system timezone, so # letting the user choose will help us identify the right time to do those # things (i.e. late at night in whatever timezone the user actually lives # in). # # However, changing the timezone once it is set seems to confuse fail2ban # and requires restarting fail2ban (done below in the fail2ban # section) and syslog (see #328). There might be other issues, and it's # not likely the user will want to change this, so we only ask on first # setup. if [ -z "$NONINTERACTIVE" ]; then if [ ! -f /etc/timezone ] || [ ! -z $FIRST_TIME_SETUP ]; then # If the file is missing or this is the user's first time running # Mail-in-a-Box setup, run the interactive timezone configuration # tool. dpkg-reconfigure tzdata restart_service rsyslog fi else # This is a non-interactive setup so we can't ask the user. # If /etc/timezone is missing, set it to UTC. if [ ! -f /etc/timezone ]; then echo "Setting timezone to UTC." echo "Etc/UTC" > /etc/timezone restart_service rsyslog fi fi # ### Seed /dev/urandom # # /dev/urandom is used by various components for generating random bytes for # encryption keys and passwords: # # * TLS private key (see `ssl.sh`, which calls `openssl genrsa`) # * DNSSEC signing keys (see `dns.sh`) # * our management server's API key (via Python's os.urandom method) # * Roundcube's SECRET_KEY (`webmail.sh`) # * ownCloud's administrator account password (`owncloud.sh`) # # Why /dev/urandom? It's the same as /dev/random, except that it doesn't wait # for a constant new stream of entropy. In practice, we only need a little # entropy at the start to get going. After that, we can safely pull a random # stream from /dev/urandom and not worry about how much entropy has been # added to the stream. (http://www.2uo.de/myths-about-urandom/) So we need # to worry about /dev/urandom being seeded properly (which is also an issue # for /dev/random), but after that /dev/urandom is superior to /dev/random # because it's faster and doesn't block indefinitely to wait for hardware # entropy. Note that `openssl genrsa` even uses `/dev/urandom`, and if it's # good enough for generating an RSA private key, it's good enough for anything # else we may need. # # Now about that seeding issue.... # # /dev/urandom is seeded from "the uninitialized contents of the pool buffers when # the kernel starts, the startup clock time in nanosecond resolution,...and # entropy saved across boots to a local file" as well as the order of # execution of concurrent accesses to /dev/urandom. (Heninger et al 2012, # https://factorable.net/weakkeys12.conference.pdf) But when memory is zeroed, # the system clock is reset on boot, /etc/init.d/urandom has not yet run, or # the machine is single CPU or has no concurrent accesses to /dev/urandom prior # to this point, /dev/urandom may not be seeded well. After this, /dev/urandom # draws from the same entropy sources as /dev/random, but it doesn't block or # issue any warnings if no entropy is actually available. (http://www.2uo.de/myths-about-urandom/) # Entropy might not be readily available because this machine has no user input # devices (common on servers!) and either no hard disk or not enough IO has # ocurred yet --- although haveged tries to mitigate this. So there's a good chance # that accessing /dev/urandom will not be drawing from any hardware entropy and under # a perfect-storm circumstance where the other seeds are meaningless, /dev/urandom # may not be seeded at all. # # The first thing we'll do is block until we can seed /dev/urandom with enough # hardware entropy to get going, by drawing from /dev/random. haveged makes this # less likely to stall for very long. echo Initializing system random number generator... dd if=/dev/random of=/dev/urandom bs=1 count=32 2> /dev/null # This is supposedly sufficient. But because we're not sure if hardware entropy # is really any good on virtualized systems, we'll also seed from Ubuntu's # pollinate servers: pollinate -q -r # Between these two, we really ought to be all set. # ### Package maintenance # # Allow apt to install system updates automatically every day. cat > /etc/apt/apt.conf.d/02periodic </dev/null | grep "^port " | sed "s/port //") #NODOC if [ ! -z "$SSH_PORT" ]; then if [ "$SSH_PORT" != "22" ]; then echo Opening alternate SSH port $SSH_PORT. #NODOC ufw_allow $SSH_PORT #NODOC fi fi ufw --force enable; fi #NODOC # ### Local DNS Service # Install a local DNS server, rather than using the DNS server provided by the # ISP's network configuration. # # We do this to ensure that DNS queries # that *we* make (i.e. looking up other external domains) perform DNSSEC checks. # We could use Google's Public DNS, but we don't want to create a dependency on # Google per our goals of decentralization. `bind9`, as packaged for Ubuntu, has # DNSSEC enabled by default via "dnssec-validation auto". # # So we'll be running `bind9` bound to 127.0.0.1 for locally-issued DNS queries # and `nsd` bound to the public ethernet interface for remote DNS queries asking # about our domain names. `nsd` is configured later. # # About the settings: # # * RESOLVCONF=yes will have `bind9` take over /etc/resolv.conf to tell # local services that DNS queries are handled on localhost. # * Adding -4 to OPTIONS will have `bind9` not listen on IPv6 addresses # so that we're sure there's no conflict with nsd, our public domain # name server, on IPV6. # * The listen-on directive in named.conf.options restricts `bind9` to # binding to the loopback interface instead of all interfaces. apt_install bind9 resolvconf tools/editconf.py /etc/default/bind9 \ RESOLVCONF=yes \ "OPTIONS=\"-u bind -4\"" if ! grep -q "listen-on " /etc/bind/named.conf.options; then # Add a listen-on directive if it doesn't exist inside the options block. sed -i "s/^}/\n\tlisten-on { 127.0.0.1; };\n}/" /etc/bind/named.conf.options fi if [ -f /etc/resolvconf/resolv.conf.d/original ]; then echo "Archiving old resolv.conf (was /etc/resolvconf/resolv.conf.d/original, now /etc/resolvconf/resolv.conf.original)." #NODOC mv /etc/resolvconf/resolv.conf.d/original /etc/resolvconf/resolv.conf.original #NODOC fi # Restart the DNS services. restart_service bind9 restart_service resolvconf # ### Fail2Ban Service # Configure the Fail2Ban installation to prevent dumb bruce-force attacks against dovecot, postfix and ssh cat conf/fail2ban/jail.local \ | sed "s/PUBLIC_IP/$PUBLIC_IP/g" \ > /etc/fail2ban/jail.local cp conf/fail2ban/dovecotimap.conf /etc/fail2ban/filter.d/dovecotimap.conf restart_service fail2ban