summaryrefslogtreecommitdiff
path: root/guides/sysadmin/cloud
diff options
context:
space:
mode:
Diffstat (limited to 'guides/sysadmin/cloud')
-rw-r--r--guides/sysadmin/cloud/vps.org198
1 files changed, 198 insertions, 0 deletions
diff --git a/guides/sysadmin/cloud/vps.org b/guides/sysadmin/cloud/vps.org
new file mode 100644
index 0000000..986e373
--- /dev/null
+++ b/guides/sysadmin/cloud/vps.org
@@ -0,0 +1,198 @@
+* Security
+** Switch APT to HTTPS
+~sudo sed -i 's/http:/https:/' /etc/apt/sources.list~
+
+Granted, the repository signature provides enough protection; still,
+no sense in wasting bandwidth and CPU if someone is meddling.
+** Tweak root access
+On OVH's Debian image:
+- The =root= account has no password.
+- =PermitRootLogin= defaults to =prohibit-password=: set it to =no=.
+** Enable fail2ban
+~lastb~ says there's about 4000 login attempts per day; that makes
+=/var/log/btmp= much bigger than it needs to be.
+
+Debian's fail2ban comes with a jail for ~sshd~, so it's just a matter
+of ~apt install fail2ban~.
+** Tweak user accounts
+=debian= seems mildly popular among bots looking for valid usernames.
+
+Ideally I'd just rename the =debian= account, but renaming does not
+seem to be a very well-defined operation: ~usermod --login $name
+--move-home --home /home/$name debian~ gets partway there, but leaves
+a bunch of miscellany to take care of (e.g. sudoers).
+
+So instead, I'll
+- create my own user account: ~sudo adduser $name~
+- add it to all groups =debian= belongs to:
+ #+begin_src sh
+ groups=$(groups | tr ' ' '\n' | grep -v debian | paste -sd,)
+ sudo usermod --append --groups ${groups} ${name}
+ #+end_src
+- only allow password authentication over SSH for this new user
+ account:
+ #+begin_src conf
+ PasswordAuthentication no
+ Match User …
+ PasswordAuthentication yes
+ #+end_src
+
+* System
+#+begin_src sh
+sudo hostnamectl set-hostname $DOMAIN
+sudo timedatectl set-timezone $tz
+#+end_src
+
+* Services
+** Web server
+Run ~sudo apt install nginx~; then, in
+=/etc/nginx/sites-available/$DOMAIN=:
+#+begin_src conf
+server {
+ listen 80;
+ listen [::]:80;
+
+ server_name $DOMAIN www.$DOMAIN;
+ access_log /var/log/nginx/$DOMAIN.access.log;
+
+ root /var/www/$DOMAIN/html;
+ index index.html;
+
+ location / {
+ try_files $uri $uri/ =404;
+ }
+}
+#+end_src
+Use one =access_log= file per site, to simplify analytics.
+
+Run ~sudo systemctl restart nginx~.
+*** fail2ban
+With the following files in =$HOME=:
+#+begin_src conf
+# nginx-botsearch.local
+[Init]
+
+block = \S*(php|wp-|wordpress|jenkins|hudson|sql|boaform)[^,]*
+
+[Definition]
+
+# Change from distro: just remove the leading slash before <block>.
+failregex = ^<HOST> \- \S+ \[\] \"(GET|POST|HEAD) <block> \S+\" 404 .+$
+ ^ \[error\] \d+#\d+: \*\d+ (\S+ )?\"\S+\" (failed|is not found) \(2\: No such file or directory\), client\: <HOST>\, server\: \S*\, request: \"(GET|POST|HEAD) \/<block> \S+\"\, .*?$
+
+# jail.local
+[nginx-http-auth]
+enabled = true
+
+[nginx-botsearch]
+enabled = true
+# Assume that each requests to $DOMAIN will be logged to "$DOMAIN.access.log".
+logpath = /var/log/nginx/*access.log
+#+end_src
+
+Then:
+#+begin_src sh
+sudo cp ~/nginx-botsearch.local /etc/fail2ban/filter.d/
+sudo cp ~/jail.local /etc/fail2ban/
+sudo systemctl restart fail2ban
+#+end_src
+
+Check how these rules fare against real bot searches with:
+#+begin_src sh
+fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-botsearch.local
+#+end_src
+
+*** HTTPS
+#+begin_src sh
+sudo apt install certbot python3-certbot-nginx
+sudo certbot --nginx -d $DOMAIN www.$DOMAIN
+sudo systemctl reload nginx
+#+end_src
+
+** Git server
+*** SSH access
+#+begin_src sh
+sudo apt install git
+sudo tee -a /etc/shells <<< $(which git-shell)
+sudo adduser git --disabled-password --shell $(which git-shell)
+sudo mkdir /srv/git
+sudo chown git:git /srv/git
+# For every new repo:
+sudo -u git git init --bare --shared=group /srv/git/${repo}
+#+end_src
+
+*** Web mirror
+With =/etc/nginx/sites-available/git.$DOMAIN=:
+#+begin_src conf
+server {
+ listen 80;
+ listen [::]:80;
+
+ server_name git.$DOMAIN;
+ access_log /var/log/nginx/git.$DOMAIN.access.log;
+
+ root /usr/share/cgit;
+ try_files $uri @cgit;
+
+ location @cgit {
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME /usr/lib/cgit/cgit.cgi;
+ fastcgi_param PATH_INFO $uri;
+ fastcgi_param QUERY_STRING $args;
+ fastcgi_param HTTP_HOST $server_name;
+ fastcgi_pass unix:/run/fcgiwrap.socket;
+ }
+}
+#+end_src
+
+And =/etc/cgitrc/=:
+#+begin_src conf
+css=/cgit.css
+logo=/cgit.png
+
+virtual-root=/
+# Change to https:// after setting up certbot:
+clone-url=http://git.$DOMAIN/$CGIT_REPO_URL
+snapshots=tar.xz
+
+enable-git-config=1
+enable-http-clone=1
+enable-index-owner=0
+
+scan-path=/srv/git
+#+end_src
+
+In each repository:
+- fill in =description=,
+- fill =[cgit]= section in =config= (=hide=, =owner=, =section=).
+
+Do:
+#+begin_src sh
+sudo apt install cgit fcgiwrap
+( cd /etc/sites-enabled/ && ln -s ../sites-avaiable/git.$DOMAIN . )
+sudo systemctl restart nginx
+# Make fail2ban notice the new log file.
+sudo systemctl restart fail2ban
+#+end_src
+
+**** "Idle" vs default branch
+cgit struggles to guess what to print for the "Idle" column on the
+index page when the default branch is not "master". [[https://lists.zx2c4.com/pipermail/cgit/2020-August/004515.html][Workarounds]]:
+
+- set =repo.defbranch=,
+- update =agefile= with [[https://git.zx2c4.com/cgit/tree/contrib/hooks/post-receive.agefile][a post-receive hook]].
+** CGI
+I like the idea of [[https://en.wikipedia.org/wiki/Common_Gateway_Interface#Using_CGI_scripts][CGI "scripts"]], i.e. having the web server fire a
+program to handle requests:
+
+- URI bits are passed through environment variables and stdin;
+- the program spits the page on stdout.
+
+Had some fun toying with Python's ~cgi~ module; sadly though the
+project has [[https://peps.python.org/pep-0594/#cgi][decided to deprecate it]]. The docs [[https://docs.python.org/3.11/library/cgi.html][suggest some migration
+paths]], and there's a [[https://pypi.org/project/legacy-cgi/][legacy-cgi package on PyPI]] if I really want to
+keep using it I guess.
+
+Also nginx has no support for CGI either, though their documentation
+explains how to combine their FastCGI support with ~fcgiwrap~ to
+enable CGI scripts.