Say you are developing – or getting yourself familiar – a web service that may have unknown number of vulnerabilities or configuration errors. One could always host a network in an isolated network in your home LAN or even run everything in a local services in your laptop. The problem with those being that you are limited to work within one location or within one computer. It would be nice to test with a mobile browser, for example. There are VPNs that can provide access to closed networks but also they are a bit hassle.
However, there is an easy option to get around it: put a HTTPS reverse proxy requiring client certificates in front of your development server. The idea I got from the presentation “Adversarial Defences: Bear-trapping Linux Servers” by Dan Tentler at Security Fest 2024.
Set up a certificate authority and issue certificates
Running PKI CA is something that requires advanced knowledge but as we are not safeguarding billion euro bank or military secrets, we can relax. We are just trying to make it slightly harder for bad actors to mess with our development system.
Easiest way to build PKI certificate hierarchy is to use easy-rsa that is part of
OpenVPN. You can download it from there or with Debian (instructions
apply to Ubuntu and other variants too) just install
sudo apt install easyrsa
.
If you installed it from Debian package, then run
make-cadir my_secret_ca
where the argument is name of
directory you want host the PKI. It should not exists. Note that you can
run your CA in your laptop, or at server. With enough tinfoil, you
create a live USB stick and run CA from there (with no network
connectivity) and keep the stick in a safe when not creating new
certs.
Now cd my_secret_ca
and you are ready to go. First you
need to create the ca. By default it creates 2048 bit RSA keys that are
just fine. You can change vars
configuration file if you
want to have more fancy certificates; see easy-rsa docs for details. So,
to create pki you need to have structure and CA.
./easyrsa init-pki
./easyrsa ca
For the second command you can add nopass
argument, so
you do not need to remember passwords. Remember, that anyone who can get
access to this directory, can create new certificates. For most people
that is the least of problems if the files are in your home directory in
your laptop. Anyway, give some nice name for your CA.
Then just create as many certificates you need. One for laptop,
another for mobile phone and third to a friend. We also create p12
packages to be imported in into devices. Again, we can use
nopass
arguments for build-client-full
if we
do not want to encrypt the private key. For export-12
we
can give an empty password or a more complex one
./easyrsa build-client-full mylaptop nopass
./easyrsa export-p12 mylaptop
./easyrsa build-client-full friend
./easyrsa export-p12 friend
./easyrsa build-client-full mobile
./easyrsa export-p12 mobile
You will find the p12 files from pki/private
folder.
Should be around 3500 bytes in size. Now just copy the files: one to
your laptop, send one as email to a friend and third to Google
Drive.
It depends on the system how you install it. In Firefox, you go
Settings -> Security -> Certificates and click “Show
Certificates”. Then select “Own certificates” and “Import”. Select the
mylaptop.p12
file.
In Android you can open the .p12 file in Google Drive and it asks you if you want to use it for application/VPN or WLAN. And you can give nice name for it.
That’s it. Now lets configure the server end. Do not forgot to copy
the pki/ca.crt
to the server!
Setting up NGINX server and getting certificates
Now back at server, we can install nginx and certbot.
sudo apt install nginx python3-certbot-nginx
sudo certbot --nginx -d bestever.example.com
That will configure nginx to use certificates to your site
bestever.example.com
the Certbot acquired from Let’s Encrypt. Note that you of
course should have that domain name pointed to your testing server.
You can check the setup from
/etc/nginx/sites-enabled/default
.
We also need to copy our CA root certificate ca.crt
to
some place where it can be found. In Debian systems, just copy it to
/usr/local/share/ca-certificates/
with some reasonable name
and update certificate database.
sudo cp ca.crt /usr/local/share/ca-certificates/my-super-ca.crt
sudo update-ca-certificates
It should say “add 1 certificate” or similar. It should be linked
from /etc/ssl/certs
as my-super-ca.pem
. If
not, check permissions and actual paths.
Let’s assume we have our experimental web service running at
localhost:8000
, i,e. it is not reachable outside of system
and it is listening on port 8000. You can try to reach it with
curl http://localhost:8000
and you should receive some HTML
most likely.
There are two points we need to configure in nginx configuration file
/etc/nginx/sites-available/default
.
So we first need to configure the proxy part by locating
server
block that has
server_name bestever.example.com;
directive. Below that
there should be a location /
block with some
try_files
statements. We’ll replace it with following:
location / {
proxy_pass http://localhost:8000;
include proxy_params;
}
Further down, still within the same server
block you
will find various ssl letsencrypt directives. Below those (and above
}
) add following lines:
ssl_client_certificate /etc/ssl/certs/my-super-ca.pem;
ssl_verify_client on;
Now all is configured and ready to go. Just
sudo systemctl restart nginx
and point your browser (with a
user certificate) towards the site. The browser should ask if you want
to use certificate to authenticate. Just select the one you installed
some time ago.
If the system does not work, like if you got some error messages from
systemctl
command, you can check error messages with
sudo journalctl -xeu nginx.service
. There maybe a missing
semicolon.
Finally, you need to check that certificate is actually required. Use some machine or browser where the user certificate is not installed. You should receive an error like:
400 Bad Request No required SSL certificate was sent
So, everything seems to be right. Happy developing!
Edit: 4th July. One extra / removed from nginx config.