summaryrefslogtreecommitdiff
path: root/guides/sysadmin/cloud/vps.org
blob: 986e373f0590132c7596472d4bb826677b5ad66e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
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.