papyri/md/debian_server_setup.md
2024-03-20 11:40:22 -05:00

373 lines
12 KiB
Markdown

# Debian Server Setup
## Contents
- [Server Installation](#server-installation)
- [Server User Setup](#server-user-setup)
- [Disable root Login](#disable-root-login)
- [Server Hardening](#server-hardening)
- [fail2ban Setup](#fail2ban-setup)
- [Apache Webserver](#apache-webserver)
- [Apache iptables Ports](#apache-iptables-ports)
- [Apache Default Template](#apache-default-template)
- [Apache 80 Template](#apache-80-template)
- [Apache 443 Template](#apache-443-template)
## Server Installation
Install **Debian 10** using the Minimal setup method, add the SSH server option during the final steps on the installation. This is the default image delivered from many cloud providers; it may use the default hostname `localhost` - if desired, set a new one:
```
hostnamectl set-hostname myhostname
```
Ensure the hostname resolves locally - it does not have to be `127.0.0.1` (localhost) nor a FQDN, for example this works:
```
127.0.0.1 localhost
127.0.1.1 myhostname
```
Adjust as needed based on how `/etc/hosts` is already configured from the installation.
## Server User Setup
> **Use a very secure password** - at a minimum use `pwgen -sB 15`, strong password security encouraged!
Set up a non-root user and add to the `sudo` group, then add a password. Use this user for SSH access and become root once logged in with sudo; if you have used SSH keys to log in as root, copy to this new user's setup as well if needed:
```
apt-get update
apt-get install sudo
export MYUSER="frankthetank"
useradd -m -d /home/${MYUSER} -s /bin/bash -g users -G sudo ${MYUSER}
passwd ${MYUSER}
```
If you are unable to use `ssh-copy-id` from your workstation to add a new SSH key, perform the work manually:
```
mkdir /home/${MYUSER}/.ssh
cp /root/.ssh/authorized_keys /home/${MYUSER}/.ssh/
chmod 0700 /home/${MYUSER}/.ssh
chmod 0600 /home/${MYUSER}/.ssh/authorized_keys
chown -R ${MYUSER}:users /home/${MYUSER}/.ssh
```
> **SSH in as this user and test `sudo` several times** - log out completely between tests
### Disable root Login
**If the above is successful** and you are capable of gaining full root privileges via the non-root SSH session using sudo, now disable root logins in SSH from the outside world for an additional security layer. The `root` account still remains usable, just not via _direct_ SSH access.
The task is to set `PermitRootLogin no` - the setting varies from one provider to another, sometimes it's already set (either yes or no), sometimes it's commented out. This small scriptlet should handle these 2 most common cases, **be careful** and investigate for yourself:
```
_SCFG="/etc/ssh/sshd_config"
if $(grep -iEq '^PermitRootLogin[[:space:]]+yes' "${_SCFG}"); then
sed -i.bak -e 's/^PermitRootLogin.*/PermitRootLogin no/gi' "${_SCFG}"
else
sed -i.bak -e 's/^#PermitRootLogin.*/PermitRootLogin no/gi' "${_SCFG}"
fi
```
**After confirming the change is correct**, restart the SSH core daemon (it will not log you out):
```
systemctl restart sshd
```
**Test logging in again** to ensure the changes are as expected. Do not log out of the active, working SSH session as root until you've confirmed in _another_ session you can log in as your non-root user and still gain `sudo` to root.
## Server Hardening
**1.** The Debian default vimrc (`set mouse=a`, `/usr/share/vim/vim80/defaults.vim`) messes up middle-mouse click paste when remote via SSH, override the setting to just disable the mouse:
```
echo 'set mouse=' >> ~/.vimrc
```
**2.** Install a few basic packages to make life a little nicer; typically the minimal install / cloud instances are stripped down and need a few things added, both for security and ease of use. Adjust as desired:
```
apt-get update
echo "iptables-persistent iptables-persistent/autosave_v4 boolean true" | debconf-set-selections
echo "iptables-persistent iptables-persistent/autosave_v6 boolean true" | debconf-set-selections
echo "unattended-upgrades unattended-upgrades/enable_auto_updates boolean true" | debconf-set-selections
apt-get install sysstat unattended-upgrades iptables-persistent man less vim rsync bc net-tools git strace
```
The `smem` package will pull in a lot of X dependencies due to an embedded recommendation, install it while disabling that feature. This utility can be used to quickly query memory usage (including swap) on the memory constrained cloud server:
```
apt-get install smem --no-install-recommends
```
**3.** Enable `journald` to store logs on disk instead of just RAM. By default, the `journald` system is in automatic mode - on boot it will create the ephemeral tmpfs `/run` out of RAM, but will _only_ transition to storing the journal on disk (out of RAM) if this directory exists.
```
mkdir /var/log/journal
```
**4.** Enable _sysstat_ for ongoing statistics capture of your instance (use `sar` to view):
```
sed -i.bak -e 's|^ENABLED=".*"|ENABLED="true"|g' /etc/default/sysstat
```
**5.** Enable _unattended-upgrades_ to ensure that all Security updates are applied:
```
cat << 'EOF' > /etc/apt/apt.conf.d/02periodic
APT::Periodic::Enable "1";
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "5";
APT::Periodic::Unattended-Upgrade "1";
EOF
```
**6.** Enable the basic _iptables_ rules to allow only port 22:
```
cat << 'EOF' > /etc/iptables/rules.v4
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT
EOF
cat << 'EOF' > /etc/iptables/rules.v6
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p ipv6-icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
-A INPUT -j REJECT --reject-with icmp6-adm-prohibited
-A FORWARD -j REJECT --reject-with icmp6-adm-prohibited
COMMIT
EOF
```
**7.** Add a bit of swap if needed - using swap is not bad in and of itself, the Linux kernel will attempt to cache it's small bits of data if available. A cloud instance may not be delivered with any swap configured.
```
# 128M swap file
dd if=/dev/zero of=/swap.file bs=1024 count=128000
chmod 0600 /swap.file
mkswap /swap.file
echo '/swap.file none swap defaults 0 0' >> /etc/fstab
swapon /swap.file
```
**8.** Finally, ensure all the services are enabled and apply all outstanding updates; reboot as needed for a new kernel. If you don't reboot here, you'll need to `service` _foo_ `restart` each one individually (just reboot, it's easier):
```
systemctl disable remote-fs.target
systemctl enable sysstat unattended-upgrades netfilter-persistent
apt-get dist-upgrade -y
reboot
```
### fail2ban Setup
Recommended: configure fail2ban to keep an eye on the SSH port for brute force attacks.
> **Note**: `fail2ban` tends to consume a fair amount of memory the longer it runs; if the cloud server is memory constrained, you may wish to skip this step or disable the service later. Use `smem` to monitor it periodically.
```
apt-get install fail2ban sqlite3
cat << 'EOF' > /etc/fail2ban/jail.local
[DEFAULT]
ignoreip = 127.0.0.1/8
bantime = 600
maxretry = 3
backend = auto
destemail = root@localhost
EOF
systemctl enable --now fail2ban
```
Additionally, add a weekly `cron` task to purge the database of old IPs (bug in 0.9.x series) and to restart the daemon to free up it's RAM usage:
```
cat << 'EOF' > /etc/fail2ban/dbpurge.sql
delete from bans where timeofban <= strftime('%s', date('now', '-7 days'));
vacuum;
.quit
EOF
cat << 'EOF' > /etc/cron.weekly/f2b-cleanup
#!/bin/sh
if [ -x /usr/bin/sqlite3 ]; then
sqlite3 /var/lib/fail2ban/fail2ban.sqlite3 < /etc/fail2ban/dbpurge.sql
fi
systemctl restart fail2ban.service
EOF
chown root:root /etc/cron.weekly/f2b-cleanup
chmod 0755 /etc/cron.weekly/f2b-cleanup
```
## Apache Webserver
Optional: adding a webserver might be desired, the method of obtain the SSL certificate is not covered here.
### Apache Installation
The Debian package includes the SSL libraries, a few extra modules need to be enabled to support the extra security tuning in the templates.
```
apt-get update
apt-get install apache2
a2enmod ssl
a2enmod reqtimeout
a2enmod rewrite
a2enmod headers
a2enmod expires
```
### Apache iptables Ports
Ensure the ports for 80 and 443 are added to `/etc/iptables/rules.v4` and `/etc/iptables/rules.v6`, typically near where the SSH port has been opened:
```
-A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
```
Restart the daemon: `systemctl restart netfilter-persistent`
### Apache Default Template
This is the main template setting up parameters for all virtualhosts; the choice to include the virtual hosts in this template is not required, only a stylistic choice of the author. Save this to `/etc/apache2/sites-available/00_main.conf` (or use a symlink):
```
Timeout 60
KeepAlive Off
MaxKeepAliveRequests 100
KeepAliveTimeout 15
ServerName localhost
ServerTokens OS
TraceEnable off
<IfModule prefork.c>
StartServers 3
MinSpareServers 2
MaxSpareServers 4
ServerLimit 9
MaxClients 9
MaxRequestsPerChild 2000
</IfModule>
<IfModule mod_reqtimeout.c>
RequestReadTimeout header=20-40,MinRate=500 body=20,MinRate=500
</IfModule>
<Directory "/path/to/www">
AllowOverride None
Require all granted
</Directory>
# Port 80
Include /path/to/port_80.conf
# Port 443
Include /path/to/port_443.conf
```
Disable the Debian default website and enable the new one created above:
```
a2dissite 000-default
a2ensite 00_main
```
...or just manually change symlinks in `/etc/apache2/sites-enabled/` as desired.
### Apache 80 Template
Included above as `/path/to/port_80.conf`
```
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
ServerAdmin root@example.com
ErrorLog /var/log/apache2/example-error.log
CustomLog /var/log/apache2/example-access.log combined
DocumentRoot /path/to/www/html
<Directory /path/to/www/html>
Options FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
```
### Apache 443 Template
Included above as `/path/to/port_443.conf`
```
<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com
ServerAdmin root@example.com
ErrorLog /var/log/apache2/example-error.log
CustomLog /var/log/apache2/example-access.log combined
SSLEngine on
SSLHonorCipherOrder on
SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
SSLHonorCipherOrder on
SSLCompression off
SSLSessionTickets off
SSLCertificateFile /path/to/sslkeys/2020-example.crt
SSLCertificateKeyFile /path/to/sslkeys/2020-example.key
SSLCACertificateFile /path/to/sslkeys/2020-ssl-issuer-CA.pem
Header always set Strict-Transport-Security "max-age=15768000"
<Files ~ "\.(cgi|shtml|phtml|php3?)$">
SSLOptions +StdEnvVars
</Files>
SetEnvIf User-Agent ".*MSIE.*" \
nokeepalive ssl-unclean-shutdown \
downgrade-1.0 force-response-1.0
DocumentRoot /path/to/www/html
<Directory /path/to/www/html>
Options FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
```
Note the above 443 template does not enable HSTS on all subdomains by design, add as required.