I’ve been working on a small app written in golang, and I’d like my local-area network connections to the golang-embedded webserver to be over https. Most of the clients are going to be scripts running on various servers.
Unfortunately, since this is on a private IP range, we get into the whole certificate-verification thing. There are a couple ways to solve this problem:
- Don’t check certificates, but that hardly seems optimal
- Create my own private CA, have all clients trust it, and use it to issue certificates
#2 is probably the right way to do this.
But something occurred to me. All https really does is say “yes, somehost.com using that certificate has the private key to that certificate”. There’s extended verification blah blah but really that’s all https does. But we’re assuming that the client and the CA agree where somehost.com is. What if they don’t…
I decided to try it. I went to a server called dnsg1.lowend.party in the cloud and created an A record for server2.lowend.party that pointed to it (server1 is already in use for a different tutorial!)
Then on dnsg1 I installed certbot:
apt-get install certbot
And now using certbot, I get my Let’s Encrypt cert:
dnsg1:~ # certbot certonly --standalone -d server2.lowend.party Saving debug log to /var/log/letsencrypt/letsencrypt.log Plugins selected: Authenticator standalone, Installer None Requesting a certificate for server2.lowend.party Performing the following challenges: http-01 challenge for server2.lowend.party Waiting for verification... Cleaning up challenges IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/server2.lowend.party/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/server2.lowend.party/privkey.pem Your certificate will expire on 2022-07-03. To obtain a new or... (blah blah)
So now I have a certficate for ‘server2.lowend.party’ that is signed by Let’s Encrypt. Will it work on a private network? You know I’m going to make you hit read more to find out. And since you’re going to get a 1990s film meme I made just for this post, I don’t feel bad for it.
![Reservoire Dogs](https://lowendbox.com/wp-content/uploads/2022/04/server2.jpg)
Reservoir Dogs, 1992
On the real server2.lowend.party, I compiled some basic golang code:
package main import( "net/http" "fmt" "log" ) func main() { http.HandleFunc( "/", func( res http.ResponseWriter, req *http.Request ) { fmt.Fprint( res, "It worked!\n" ) } ) log.Fatal( http.ListenAndServeTLS( ":9000", "fullchain.pem", "privkey.pem", nil ) ) }
All this code does is launch an https server on port 9000 and respond to requests to / with “It worked\n”.
Now let’s see if, indeed, it worked. On another server, called crash, I edited /etc/hosts to set server2.lowend.party to my local IP. Now when crash tries to find an address for server2.lowend.party, the entry in /etc/hosts will override DNS.
To be perfectly accurate, /etc/nsswitch.conf is consulted, and there you will find a line like (at least in stock Debian 11):
hosts: files dns
This means consult “files” first (/etc/hosts) and then dns. You might have other things in the list, such as ldap. A comment in the file refers to the “GNU Name Service Switch” but this file existed in SunOS 4.x and probably earlier versions. Well, maybe that was supposed to be called GNU/SunOS.
Anyway, back to our test. Let’s use wget:
root@crash:~# wget https://server2.lowend.party:9000 --2022-04-04 17:43:25-- https://server2.lowend.party:9000/ Resolving server2.lowend.party (server2.lowend.party)... 192.168.1.10 Connecting to server2.lowend.party (server2.lowend.party)|192.168.1.10|:9000... connected. HTTP request sent, awaiting response... 200 OK Length: 10 [text/plain] Saving to: ‘index.html’ index.html 100%[=================================================================>] 10 --.-KB/s in 0s 2022-04-04 17:43:25 (20.3 MB/s) - ‘index.html’ saved [10/10] root@crash:~# cat index.html It worked! root@crash:~#
Of course, eventually it will expire, but it be easy enough to script a renewal and rsync down to server2. It’s cheesy, but it works!
Related Posts:
- eWallHost: 4GB VPS for Only $4.99/Month in Finland or Germany! - September 7, 2024
- Hostneva: Cheap Shared Hosting on Enterprise-Grade Fabric Around the World, Starting at Only $6/YEAR! - September 6, 2024
- Hudson Valley Host: Yearly VPS Offers That Make a 2GB VPS Cost Only $1.50/Month! - September 5, 2024
The other option (and what I do) is use the DNS-01 challenge to get a signed certificate that way. When requesting a certificate, it will add a TXT record to your DNS to check you control it. This of course can be automated if your DNS provider has an API (and almost all do).
I have a similar set up for a local install of nextcloud, it required HTTPS to be turned on to remotely upload, all the devices at home use pihole which directs the devices to the local IP. When we are out the house I have a VPS with a public IP that is globally routed to that same url. So cert bot renewing on the vps and tunnelling traffic via VPN to home instances. Every now and then I need to copy over new Certs but works well.
If your DNS provider doesn’t have an API, you can use google-dns on a low end box somewhere on the internet to act as the API for your DNS. I’ve set it up on a $10 per year VPS and it works wonderfully well.
That should read acme-dns, not google-dns….
Thank you for sharing this great article. 888b hopes you will have many more articles for everyone to read.
> But we’re assuming that the client and the CA agree where somehost.com is
There’s no verification going on. Your client may check a revocation list via CRLs but the certificate itself is verified by the signing chain, which relies on your local CA database. SSL certificate verification will work without any network connection whatsoever. Your CA also has no idea about the IP of the system using the certificate – there’s no metadata in the certificate about that. It is only tied to the unresolved hostname, called the Common Name.