====== 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.