Amir Keshavarz
Posted on August 4, 2019
Go has become extremely popular between server programmers in recent years and as this rising popularity continues some specific needs emerge.
In this article, we’re going to talk about the use of Go in https servers, reverse proxies and the problems that appear when hosting a large number of domains.
Problem
Let’s assume we’re going to build a reverse proxy which hosts a large number of domains like edge servers for a CDN.
the plain HTTP is a lot easier to handle than HTTPS so I’m not going to talk about it in this article.
With previous knowledge of how TLS handshake works, we know that at some point our server sends a public key to the client. This key is specific to the domain which the client sees.
So the question is how to serve a large number of websites with different certificates on a single IP/Port.
Solution
Before SNI was a thing we were limited to an IP or Port to host a website ( Yes, multi-domain certificates are available but they’re limited and not scalable).
what is SNI? Basically, we get the Server Name before TLS handshake is completed. So now we know what certificate to send based on the Server Name we received. Let’s get to coding.
The “crypto/tls” package in Go has provided us with everything we need in this scenario. The first thing we do is making a TLS listener with a custom config.
config := &tls.Config{
GetCertificate: returnCert,
}
ln, err := tls.Listen("tcp", ":443", config)
As you can see we have provided a function called “returnCert” to GetCertificate in Config type. This basically tells the TLS listener to callback our function when a TLS handshake starts so we can provide you with the right certificate.
After that, we can simply create an https server on top of the TLS listener.
http.HandleFunc("/", func(w http.ResponseWriter, r \*http.Request) {
fmt.Fprintf(w, "Hello TLS!")
})
http.Serve(ln, nil)
Now we implant the “returnCert” function. our callback function receives ClientHelloInfo which contains information about Cipher suites supported by the client and most importantly ServerName.
I’m not going into the details of how to store and load the cert files but a simple scenario would be to store cert files in a database or even filesystem and then cache them when loaded so we won’t have to load them every time.
In this sample code, I directly load the files from the filesystem. This is not a good design for production though!
func returnCert(helloInfo \*tls.ClientHelloInfo) (\*tls.Certificate, error) {
//helloInfo.ServerName ( This contains our Server Name )
cer, err := tls.LoadX509KeyPair("server.crt", "server.key")
if err != nil {
log.Println(err)
return nil, nil
}
return &cer, nil
}
This is the entire code of this article:
I hope you enjoyed this article.
Links
Posted on August 4, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.