aboutsummaryrefslogtreecommitdiff
path: root/blog/2022-03-24-server-hardening.org
blob: 88fd44eae04a5bdc949703a7f05e59008eda3b94 (plain) (blame)
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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
#+title: Hardening a Public-Facing Home Server
#+date:  2022-03-24

** Post Updates
:PROPERTIES:
:CUSTOM_ID: post-updates
:END:

#+begin_quote
After reviewing this post today (2022-10-04), I noticed quite a few gaps
in my write-up and wanted to add a few things, even though this blog is
really just a retrospective and knowledge dump for myself. I left things
intact and simply crossed them out (+like this+) for posterity.

#+end_quote

** Planning Data Flows & Security
:PROPERTIES:
:CUSTOM_ID: planning-data-flows-security
:END:
*** My Personal Data Flow
:PROPERTIES:
:CUSTOM_ID: my-personal-data-flow
:END:
#+begin_src txt
                                                          ┌───────┐   ┌─────────────────┐
                                                       ┌──► VLAN1 ├───► Private Devices │
                                                       │  └───────┘   └─────────────────┘
┌──────────┐   ┌────────┐   ┌──────────┐   ┌────────┐  │
│ Internet ├───► Router ├───► Firewall ├───► Switch ├──┤
└──────────┘   └────────┘   └──────────┘   └────────┘  │
                                                       │  ┌───────┐   ┌───────────────┐
                                                       └──► VLAN2 ├───► Public Server │
                                                          └───────┘   └───────────────┘
#+end_src

*** Thought Process
:PROPERTIES:
:CUSTOM_ID: thought-process
:END:
To serve content from your home server and harden your security posture,
you have to think about the transport of data from =server= to =client=.

Let's start with the actual server itself. Think about the following:

- Do I have a firewall enabled? Do I need to update this to allow new
  ports or IPs?
- Do I have an IPS/IDS that may prevent outside traffic?
- Do I have any other security software installed?
- Are the services hosted inside Docker containers, behind a reverse
  proxy, or virtualized? If so, are they configured to allow outside
  traffic?

Once the data leaves the server, where does it go? In my case, it goes
to a managed switch. In this case, I asked the following:

- What configurations is the switch using?
- Am I using VLANs?
  - Yes, I am using 802.1Q VLANs.
- Are the VLANs configured properly?
  - Yes, as shown in the [[#switch][Switch]] section below, I have a
    separate VLAN to allow outside traffic to and from the server alone.
    No other devices, except for a service port, and in that VLAN.

At this point, the data has been processed through the switch. Where
does it go next? In my case, it's pretty simple: it goes to the
router/modem device.

- Does my ISP block any ports that I need?
  - This is an important step that a lot of people run into when
    self-hosting at home. Use an online port-checker tool for your IP or
    call your ISP if you think ports are blocked.
- Is there a router firewall?
  - Yes, I checked that it's configured to allow the ports I need to run
    my services publicly. Common web servers and reverse proxies require
    ports 80 and 443, but other services like media servers or games can
    require unique ports, so be sure to check the documentation for your
    service(s).
- Are there any other settings affecting inbound/outbound traffic?
  - Schedules or access blocks
  - Static Routing
  - QoS
  - Port Forwarding
  - DMZ Hosting
  - Remote Management (this can sometimes mess with services that also
    require the use of ports 80 and 443)

Once the data leaves my router, it goes to the upstream ISP and can be
accessed publicly.

** Server
:PROPERTIES:
:CUSTOM_ID: server
:END:
+The services I run on my server are installed straight into the OS,
without any use of Docker or VMs, so I don't need any extra application
configuration to make them accessible to the outside world.+

As of 2022-10-04, the paragraph above is no longer true as I now run a
reverse proxy with Nginx and host many services inside Docker. However,
it doesn't change anything regarding this post as I still just need to
open ports 80 & 443 and create the necessary website configuration
files.

When creating new services - either installed directly on bare metal or
within something like Docker - I ensure that I read through the
documentation thoroughly to understand a few key things: - What network
activities should this app perform (if any)? Using which ports and
protocols? - Does this app require any commands/services to be run as
=root=? - Does this app log errors, authentication failures/successes,
or anything else that would be useful for an investigation?

For extra security, I use limit all incoming connections to SSH
connections through my server firewall (=ufw=) and disable common SSH
settings. After all of that, I use =fail2ban= as a preventative measure
against brute-force login attempts.

As another piece of security, you can randomize your SSH port to ensure
that random scanners or attackers can't easily try to force their way
into your network. For example, you can edit the port rules in your
server to block all connection requests to port =22= but forward all
remote connections from port =12345= to your server's port =22=. Then
you just need to SSH to your network via your randomized port.

*** =ufw=
:PROPERTIES:
:CUSTOM_ID: ufw
:END:
To see how to configure =ufw=, see my other post:
[[/blog/secure-your-network-with-the-uncomplicated-firewall/][Secure
Your Network with the Uncomplicated Firewall]].

The general notion with an on-device firewall is that you want to deny
all incoming connections by default and then selectively open certain
ports for services or users that you know need access.

If you know that you will only be logging into this server from a
certain set or list of IPs, you can always set the firewall to only
allow connections to port 22 from those IPs.

For a quick start to only allow SSH connections to the server, use this:

#+begin_src sh
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22
sudo ufw enable
#+end_src

#+caption: ufw
[[https://img.cleberg.net/blog/20220324-hardening-a-public-facing-home-server/ufw.png]]

*** =ssh=
:PROPERTIES:
:CUSTOM_ID: ssh
:END:
**** Using SSH Keys
:PROPERTIES:
:CUSTOM_ID: using-ssh-keys
:END:
First, make sure you have an SSH keypair generated on the device(s) that
you'll be using to log in to the server. If you don't have an SSH key,
run this command:

#+begin_src sh
ssh-keygen
#+end_src

Now that we have an SSH key, copy it to the server with the following
command, which will ask for the user's password before accepting the
key:

#+begin_src sh
ssh-copy-id my_user@my_server
#+end_src

If you have multiple keys, you'll need to specify which to use. After
it's complete, =ssh= back into the server as that user and make sure it
doesn't ask for a password.

**** Disable Password & Root Authentication
:PROPERTIES:
:CUSTOM_ID: disable-password-root-authentication
:END:
Now that we can access the server without a password, we will disable
password authentication and disable anyone from using =ssh= to login as
=root=.

To do this, open the =sshd_config= file:

#+begin_src sh
sudo nano /etc/ssh/sshd_config
#+end_src

You'll need to update the parameters to the values below. If one of
these rules is commented-out or doesn't exist, create the rule at the
bottom of the file.

#+begin_src config
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
#+end_src

Finally, restart the =ssh= service:

#+begin_src sh
sudo systemctl restart sshd.service
#+end_src

To test that everything's working so far, open ANOTHER terminal and try
logging in as =root= over SSH. It is very important that you keep your
current SSH session open and test with an additional session, or you
will lock yourself out at some point and will need to use a recovery
method (e.g., hooking monitor up to home server) to get yourself back
in.

**** Enable MFA for =ssh=
:PROPERTIES:
:CUSTOM_ID: enable-mfa-for-ssh
:END:
This part is optional, but I highly recommend it. So far, we've ensured
that no one can log into our user on the server without using our secret
key, and we've ensured that no one can log in remotely as =root=. Next,
you can enable MFA authentication for =ssh= connections.

This process involves editing a couple files and installing an MFA
package, so I will not include all the details in this post. To see how
to configure MFA for =ssh=, see my other post:
[[/blog/enable-totp-mfa-for-ssh/][Enabling MFA for SSH]].

#+caption: SSH MFA
[[https://img.cleberg.net/blog/20220324-hardening-a-public-facing-home-server/ssh_mfa.png]]

*** =fail2ban=
:PROPERTIES:
:CUSTOM_ID: fail2ban
:END:
I haven't written a post on how I use =fail2ban=, but it's quite simple.
I use the default =sshd= jail, but you can always create new jails for
respective applications or ports. For example, if you use Nginx as your
web server, you can use the =nginx-http-auth= jail.

In order to get it up and running, use the following commands:

#+begin_src sh
sudo apt install fail2ban
sudo fail2ban-client start sshd
sudo fail2ban-client status sshd
#+end_src

This should be used as a last-resort defense and shouldn't be a
replacement for the security measures mentioned above.

#+caption: fail2ban
[[https://img.cleberg.net/blog/20220324-hardening-a-public-facing-home-server/fail2ban.png]]

** Switch
:PROPERTIES:
:CUSTOM_ID: switch
:END:
Between the router and any local devices is my managed switch, which is
used to create VLANs. The example below shows how I would isolate the
VLANs if I were starting to host a single service at home.

*** 802.1Q VLAN Configuration
:PROPERTIES:
:CUSTOM_ID: q-vlan-configuration
:END:
In this configuration, port 8 is the public server that needs to be
accessed from the outside. Port 23 is my 'dedicated service port' for
this server. In order to SSH to this server, I need to plug my laptop
into port 23 or else I cannot SSH. Otherwise, I'd need to hook up a
monitor and keyboard directly to the server to manage it.

| VLAN ID | VLAN Name | Member Ports | Tagged Ports | Untagged Ports |
|---------+-----------+--------------+--------------+----------------|
| 1       | Default   | 1-24         |              | 1-24           |
| 2       | Server    | 1,8,23       |              | 1,8,23         |

*** 802.1Q VLAN PVID Setting
:PROPERTIES:
:CUSTOM_ID: q-vlan-pvid-setting
:END:
Once the VLAN is created, I simply add the =VLAN ID= of =2= as the
=PVID= for any related ports (in this case, see that ports =8= and =23=
have a PVID of =2=).

| Port | PVID |
|------+------|
| 1    | 1    |
| 2    | 1    |
| 3    | 1    |
| 4    | 1    |
| 5    | 1    |
| 6    | 1    |
| 7    | 1    |
| 8    | 2    |
| 9    | 1    |
| 10   | 1    |
| 11   | 1    |
| 12   | 1    |
| 13   | 1    |
| 14   | 1    |
| 15   | 1    |
| 16   | 1    |
| 17   | 1    |
| 18   | 1    |
| 19   | 1    |
| 20   | 1    |
| 21   | 1    |
| 22   | 1    |
| 23   | 2    |
| 24   | 1    |

** Router
:PROPERTIES:
:CUSTOM_ID: router
:END:
On my router, the configuration was as easy as opening the firewall
settings and unblocking the ports I needed for my services (e.g.,
HTTP/S, Plex, SSH, MySQL, etc.).

+Since I'm relying on an ISP-provided modem/router combo for now (not by
choice), I do not use any other advanced settings on my router that
would inhibit any valid traffic to these services.+

The paragraph above regarding the ISP-owned router is no longer accurate
as I now use the Ubiquiti Unifi Dream Machine Pro as my router. Within
this router, I enabled port forwarding/firewall rules, segregate the
network based on the device, and enable traffic restrictions (e.g.,
silently drop traffic from certain countries and threat categories).

If you have the option with your ISP, I recommend using a personal
router with software that you are familiar with so that you can explore
all the options available to you.

** Physical Security
:PROPERTIES:
:CUSTOM_ID: physical-security
:END:
One large piece of self-hosting that people generally don't discuss
online is physical security. However, physical security is very
important for everyone who hosts a server like this. Exactly /how/
important it is depends on the server use/purpose.

If you self-host customer applications that hold protected data (HIPAA,
GDPR, COPPA, etc.), then physical security is extremely important and
cannot be ignored. If you simply host a blog and some hobby sites, then
it's a relatively minor consideration, but one you still need to think
about.

*** Location
:PROPERTIES:
:CUSTOM_ID: location
:END:
The first consideration is quite simple: location. - Is the server
within a property you own or housed on someone else's property? - Is it
nearby (in your house, in your work office, in your neighbor's garage,
in a storage unit, etc.)? - Do you have 24/7 access to the server? - Are
there climate considerations, such as humidity, fires, tornadoes,
monsoons? - Do you have emergency equipment nearby in case of emergency?

*** Hardware Ownership
:PROPERTIES:
:CUSTOM_ID: hardware-ownership
:END:
Secondly, consider the hardware itself: - Do you own the server in its
entirety? - Are any other users able to access the server, even if your
data/space is segregated? - If you're utilizing a third party, do they
have any documentation to show responsibility? This could be a SOC 1/2/3
report, ISO compliance report, internal security/safety documentation.

*** Physical Controls
:PROPERTIES:
:CUSTOM_ID: physical-controls
:END:
Regardless of who owns the hardware, ensure that there are adequate
safeguards in place, if necessary. These usually don't apply to small
home servers and are usually covered already if you're utilizing a third
party.

These can include: - Server bezel locks - Server room locks - physical,
digital, or biometric authentication - Security cameras - Raised
floors/lowered ceilings with proper guards/gates in-place within the
floors or ceilings - Security personnel - Log sheets and/or guest badges