Table of Contents

Webserver configuration

This is a document detailing our current webserver setup. It is intended to help anyone wishing to improve or maintain our online infrastructure.

Overview

Base Debian setup

sshd config tweaks

Firewall configuration

NFTables setup

#!/usr/sbin/nft -f

flush ruleset

table inet filter {
        chain input {
                type filter hook input priority filter;

                # Allow loopback (local connections)
                iifname lo accept

                # Allow established/related
                ct state established,related accept

                # Allow incoming pings
                ip protocol icmp limit rate 2/second accept
                meta l4proto ipv6-icmp limit rate 2/second accept

                # Allow SSH and HTTP(s)
                tcp dport { ssh,http,https } accept

                # Drop everything else
                drop


        }
        chain forward {
                type filter hook forward priority filter;

                # Disallow forwarding
                drop
        }
        chain output {
                type filter hook output priority filter;

                # Allow all outgoing traffic
                accept
        }
}

Crowdsec setup

Generally follow the Crowdsec install instructions https://docs.crowdsec.net/docs/getting_started/install_crowdsec/.

Install the NFTables firewall bouncer sudo apt install crowdsec-firewall-bouncer-nftables

Caddy setup

Install following the standard Debian instructions https://caddyserver.com/docs/install#debian-ubuntu-raspbian.

Database setup

Configure PostgreSQL to listen on a unix socket:

postgresql.conf

port = 5432
unix_socket_directories = '/var/run/postgresql'
unix_socket_group = 'pguser'
unix_socket_permissions = 0770

Check that local connections are using peer authentication:

pg_hba.conf

# "local" is for Unix domain socket connections only
local   all             all                                     peer

Adding new webservers

Setting up a directory

  1. Create a user for that service. For example the Wiki has a user called wiki
  2. Create a folder in /srv with the same user and group.
  3. Set the SGID bit so the group is inherited e.g. sudo chmod g+s /srv/wiki
  4. Set an ACL to set the default group permissions to rwx with the following sudo setfacl -Rdm g::rwx /srv/wiki

Once created, the service can be cloned or copied into this folder.

Setting up a SystemD socket (for python gunicorn based services)

There are two files required, a .socket and a .service file. These will be placed in /etc/systemd/system/

Example /etc/systemd/system/website.socket

[Unit]
Description=gunicorn socket

[Socket]
ListenStream=/run/website.sock
# Our service won't need permissions for the socket, since it
# inherits the file descriptor by socket activation.
# Only Caddy will need access to the socket:
SocketUser=caddy
SocketGroup=caddy
# Once the user/group is correct, restrict the permissions:
SocketMode=0660

[Install]
WantedBy=sockets.target

Example /etc/systemd/system/website.service

[Unit]
Description=website daemon
Requires=website.socket
After=network.target

[Service]
# gunicorn can let systemd know when it is ready
Type=notify
NotifyAccess=main
# the specific user that our service will run as
User=website
Group=website
RuntimeDirectory=website
WorkingDirectory=/srv/website
ExecStart=/srv/website/.venv/bin/gunicorn -w 2 'hackspace_website:create_app()'
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
# if your app does not need administrative capabilities, let systemd know
# ProtectSystem=strict

[Install]
WantedBy=multi-user.target

Setting up phpfpm (for php based services)

; Start a new pool named 'wiki'.
; the variable $pool can be used in any directive and will be replaced by the
; pool name ('wiki' here)
[wiki]

; Unix user/group of the child processes.
user = wiki
group = wiki

; The address on which to accept FastCGI requests.
listen = /run/php/wiki.sock

; Set permissions for unix socket, if one is used.
listen.owner = caddy
listen.group = caddy
;listen.mode = 0660

; Choose how the process manager will control the number of child processes.
pm = dynamic

; The number of child processes to be created when pm is set to 'static' and the
; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'.
pm.max_children = 5

; The number of child processes created on startup.
pm.start_servers = 2

; The desired minimum number of idle server processes.
pm.min_spare_servers = 1

; The desired maximum number of idle server processes.
pm.max_spare_servers = 3

; The number of rate to spawn child processes at once.
;pm.max_spawn_rate = 32

; Redirect worker stdout and stderr into main error log.
catch_workers_output = yes

;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com
;php_flag[display_errors] = off
php_admin_value[error_log] = /var/log/fpm-php.wiki.log
;php_admin_flag[log_errors] = on
;php_admin_value[memory_limit] = 32M

Adding to Caddy

Simple gunicorn based services can have a minimal reverse proxy:

bristolhackspace.org {
    log
    reverse_proxy unix//run/website.sock
}

PHP services require a little more setup:

mosparo.bristolhackspace.org {
  encode gzip zstd
  root * /srv/mosparo/public
  
  route {
    try_files {path} /index.php
  }
  
  php_fastcgi unix//var/run/php/mosparo.sock
  file_server
}

Some services such as the Wiki will require more complicated rewrite rules (generally services originally designed to run under Apache)

Postgresql database connection

Some services will require a connection to the postgresql database. This is done via a unix socket with a set group. To give a service access:

  1. Add the service user to the pguser group.
  2. For python/sqlalchemy services use a connection string like so postgresql+psycopg2://website@/website replacing website with your service name.
  3. For other services you may need to set the database host to /var/run/postgresql and port to 5432.