initial import
This commit is contained in:
parent
e3e0eb7656
commit
e8fb7b288e
43 changed files with 14946 additions and 0 deletions
373
md/debian_server_setup.md
Normal file
373
md/debian_server_setup.md
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
# 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue