====== 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 =====
* The webserver is based on Debian
* NFTables for our firewall
* Crowdsec for IP banning
* Caddy for our frontend web proxy
* SystemD and linux groups to manage our web services
* PostgreSQL as our database
===== Base Debian setup =====
==== sshd config tweaks ====
* Disable password authentication
===== 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 ====
- Create a user for that service. For example the Wiki has a user called ''wiki''
- Create a folder in ''/srv'' with the same user and group.
- Set the SGID bit so the group is inherited e.g. ''sudo chmod g+s /srv/wiki''
- 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:
- Add the service user to the ''pguser'' group.
- For python/sqlalchemy services use a connection string like so ''%%postgresql+psycopg2://website@/website%%'' replacing ''website'' with your service name.
- For other services you may need to set the database host to ''/var/run/postgresql'' and port to 5432.