Crafted Connections

Crafted Connections

Motivation: No Authenticated Minecraft?

A few months ago, I added another NUC to my home lab, finally giving me the resources to run more than just a few web services. Initially, I planned to add the NUC as a node in my Kubernetes cluster and possibly spin up a Splunk instance or manage another service that directly relates to my work. But one day, while pondering what I should do for my next personal project, my coworkers and I were sitting around the lunch table talking about how we should start a team Minecraft server. Having just acquired some new hardware, I figured it could be something fun to host while I figured out what I wanted to do with the NUC. However, as someone who has to watch for malicious cyber activity professionally, I am extremely paranoid about opening up anything on my home lab to the internet. Naturally, I looked up the best options for Minecraft server authentication, and honestly, it was looking like if I did spin up a server, a nation state would be using my NUC as a relay box within the hour. I realized this would be a great mini project while I brainstormed what I wanted to do with my home lab next.

Why MTLS Minecraft

I thought of a few possible solutions before settling on an MTLS tunnel. The first thing I considered was giving all my coworkers access to my network via my Tailscale VPN, and then using Tailscale policy to limit their access to just the Minecraft server. I didn't want to do this for 2 reasons, the first mainly being I didn't want users to have to connect to a VPN to play on the server. The second reason is I do not trust my very smart coworkers with any access to my network. One mistake in the Tailscale ACL, and one of them is bound to start messing with my stuff.

With that out the window, I thought about trying to make a Minecraft mod so the Java server would accept TLS connections, but I decided I was not interested in writing Java to extend Minecraft. Plus, I would still have to open my network to the internet, which I was trying to avoid as best I could.

The solution that best fit was an MTLS tunnel, which would connect to a proxy load balancing server sitting in my Oracle free tier cloud (Oracle Cloud is so shit I can't believe people pay for it at all). The cloud load balancer would handle MTLS authentication and then forward the traffic to the Minecraft server on my home lab. In order to reduce the server's exposure to the internet, firewall rules would be in place to drop all connections besides those from the cloud proxy. With the architecture laid out, I could start building the tech stack.

Creating A CA and Certificates

The first step of being able to use MTLS is, of course, having certificates issued by your CA to prove the authenticity of your client and server. Given that I had always used Let's Encrypt's Certbot to provision certificates, this was new to me and a great learning opportunity during this project. To get started, I created two new OpenSSL configs just for this project, one for my root CA, and one for my issuing intermediate CA. In the openssl config, you must specify a section like v3_ca, which will include information telling openssl that this certificate and key pair will be used as a CA. My intermediate CA config was almost exactly the same aside from the name and specifying pathlen:0 in the v3_ca section. pathlen:0 signifies that the CA should only issue certificates, and was not for signing other CAs. I then generated the root and intermediate CAs along with the client and server certificates using some bash scripts I made. This was an easy process to automate away given that generating a certificate is a combination of the same commands: openssl genrsa -out ... for the key, openssl req -config {CA Config} -extensions v3_ca ... or openssl ca -config {CA Config} -extensions v3_ca ... for a CA depending if it is root or intermediary, openssl req -new ... for the CSR, and openssl ca -config {Signing CA Config} -extensions {certificate extension} ... for the certificate. This was a good skill to learn since I had never created a CA before, and it was simple enough.

Go Client

A big part of doing this project was my desire to learn a new programming language. Before I had decided on making an MTLS tunnel, I was thinking I should learn TypeScript to become more well rounded. Thankfully, this project came along, giving me an easy way to avoid my worst enemy: anything that has to do with a frontend. I took this project as an opportunity to start learning Go, which I had been meaning to look into. To get up to speed, I spent a few hours going through Go tutorials on the Go website until I became comfortable with the syntax and documentation. For me, Go seemed like a nice combination of Python and C++, which I really liked. I appreciated how even though it is a strongly typed language, the syntax is still very flexible and easy to pick up. Go routines were another plus as an easy way to handle concurrency made this project a lot easier to build.

Once I felt I could competently write a few functions, I decided to give the MTLS client a go (get it, go?!, alright my bad). Using the net library, I was easily able to create a TCP listener on 127.0.0.1:25565, the default Minecraft port on localhost. On connection, the handler function would attempt to create an MTLS connection to the proxy server, and then copy any data received from the raw TCP listener into the MTLS connection. To do this, I first embedded the client certificate and CA bundle into the binary using //go:embed (very nice feature). I then used X509.NewCertPool to create a CertPool object, which allows you to add a certificate bundle using AppendCertsFromPem to specify a CA for Go to use in order to verify the server's certificate. The next step was to use the tls library function X509KeyPair to create a tls.Certificate object from the client certificate and key to be used in the connection. Finally, I created a tls.Config object to configure the CA pool and client certificate, and a net.Dialer which allows you to specify timeouts and keep alive options for the connection. Using tls.DialWithDialer, I was able to create a tls.Conn object which I could then use to copy data from the raw TCP listener over to the MTLS connection. To ensure data was copied bidirectionally, I created two go routines with io.Copy to copy data from the localhost connection to the MTLS connection and vice versa. It was necessary to use go routines since io.Copy is a blocking function. To complete my use of Go specific features, I also created a channel to signal when the connection was closed, along with sync.Once, to ensure both connections were closed properly when the client disconnected. With this done, all I needed was a working proxy and I could play some MTLS Minecraft.

Haproxy

Now I know what you are thinking, you made the client in Go, why not make the proxy yourself in Go as well? My reasoning is that I did not have the confidence with a mere hours of Go experience to create a production ready internet facing proxy. Although this is definitely something I would look to do in the future, I did not want to expose possibly exploitable code to the world. I opted to use Haproxy based on some intense reasoning: it is a TCP proxy, it seemed easy enough to configure, I had heard of it before. The haproxy was, in fact, incredibly easy to set up with the help of their blog, which walked me through the configuration of an MTLS proxy. Once the proxy was configured to authenticate the client and pass through the raw TCP traffic, there was only one thing left to do.

Minecraft!

I connected to my server and spawned in a tree to kick off the world. After playing for a while to ensure there would be no latency issues, I wanted to ensure that I could not connect to the server with a faulty certificate or bypass the proxy. I created 2 new binaries, one with a bad certificate and the other configured to connect directly to my home lab; after running both of them, I confirmed that all the connections were unsuccessful.

I really enjoyed creating the server while learning more about MTLS, creating a CA, and, of course, learning Go. I hope to have more blogs coming soon in the next few months as I figure out what I want to do with my home lab and discover more interests in security. If you want to come play on the server, look at the codebase, or have any suggestions, feel free to hit me up!