· 25 5 how to golang tls

https.quine.space: A Simple TLS Server Written in Go

This is the second project in my current 25:5 process, which you can follow with the 25:5 tag. This post is primarily about how to do https in Golang, and not really about the project itself.

https.quine.space

This project is live! And it has been for a while, honestly. It’s fairly simple, and most of the necessary information came from a single blog post on gopher academy.

If you want to see the source code, just go to https.quine.space! Though I guess there’s also a github.

So anyway, what’s all this do?

I’m going to gloss over some of the details, like the Go basics of package and imports, and mostly talk about the specifics of https and Go servers.

Let’s go through the setup.

httpPort := os.Getenv("HTTP_PORT")
httpsPort := os.Getenv("HTTPS_PORT")

These lines are setting up bits of configuration. Normally, your HTTP port is 80, and your HTTPS port is 443. But if you’re testing locally, you may want those to be different, so we make them configurable. Having a default would be nice, but that’s more work, and this is somewhat intended to be a reference, so keeping it brief is best.

secretDir := os.Getenv("SECRET_DIR")
autocertWhitelist := os.Getenv("HOST_WHITELIST")
if autocertWhitelist == "" {
	autocertWhitelist = "quine.space"
}

// Setup and handle autocert - a.k.a. Let's Encrypt
m := &autocert.Manager{
	Cache:      autocert.DirCache(secretDir),
	Prompt:     autocert.AcceptTOS,
	HostPolicy: autocert.HostWhitelist(autocertWhitelist),
}

The secret dir is just where the cert will be stored on the file system, and the whitelist ensures that you don’t attempt to request a certificate for a domain that isn’t yours. So we set this as “quine.space”, and pass all of that to the manager, which will eventually be inserted into the GetCertificate section of a standard TLSConfig.

// Redirect to https
httpRedirectHandler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
	w.Header().Set("Connection", "close")
	url := "https://" + req.Host + req.URL.String()
	http.Redirect(w, req, url, http.StatusTemporaryRedirect)
})

// Run the http autocert server with https redirect fallback
go http.ListenAndServe(":"+httpPort, m.HTTPHandler(httpRedirectHandler))

This section sets up a redirect from regular http to https. The reason we need this is that anyone who attempted to go to http://https.quine.space would get a 404, because the server hasn’t been told to do anything with those requests. Instead, now that we have this, they’ll simply be redirected to the same page but over https instead of http. The go keyword before our http.ListenAndServe ensures that this redirect server runs, but allows us to continue to setup our https server afterwards.

// TLS Config based on https://blog.gopheracademy.com/advent-2016/exposing-go-on-the-internet/
tlsConfig := &tls.Config{
	CipherSuites: []uint16{
		tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
		tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
		tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
		tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
		tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
		tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
	},
	CurvePreferences: []tls.CurveID{
		tls.CurveP256,
		tls.X25519,
	},
	// GetCertificate provided by the autocert manager
	GetCertificate:           m.GetCertificate,
	PreferServerCipherSuites: true,
	MinVersion:               tls.VersionTLS12,
}

Admittedly, this section had the most copying from the gopher academy blog post. I don’t know most of what is going on, other than we’re setting up which types of cyphers and curves and TLS versions we’re cool with using.

But I do know that you’ll want to confer with a cryptographer about it. Regularly, if you want to use this for a real system.

Though the thing I can point out here is the GetCertificate section. m.GetCertificate comes from the autocert manager we setup earlier, and is the magic behind getting a real certificate from Let’s Encrypt, or whatever other ACME based certificate authority you want (though I don’t personally know of any others).

// Setup the TLS server
srv := &http.Server{
	Addr:         ":" + httpsPort,
	ReadTimeout:  5 * time.Second,
	WriteTimeout: 10 * time.Second,
	IdleTimeout:  120 * time.Second,
	TLSConfig:    tlsConfig,
	Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		w.Header().Set("Content-Type", "text/plain")
		fmt.Fprintf(w, quine, backtick+quine+backtick, backtick)
	}),
}

The final configuration piece! This is the actual https server configuration. Most of the timeout information also comes from the referenced blog post. But the important part is, we’ve added the tlsConfig we created above and the httpsPort configuration (which is usually 443), to ensure this is a TLS server.

note: You may have noticed that quine and backtick haven’t yet been defined. That’s true, and relates specifically to quine.space things. If you want this example to actually work, just replace everything after the w in the fmt.Fprintf with a string of your choice, and it’ll all work.

log.Println(srv.ListenAndServeTLS("", ""))

The last step is to run ListenAndServeTLS instead of the regular ListenAndServe. We pass in empty values for the certFile and keyFile, because the TLSConfig.GetCertificate handles that stuff for us.

What’s all the rest of the stuff at https.quine.space?

Because reasons (a.k.a. for fun), things on quine.space will show the source code used to serve the page you see. In order to do that, I essentially have to create a string which is almost exactly the program already, and print it out. So the undefined variables I mentioned before, quine and backtick? They’re essentially just the entire program again, plus the ability to print it out. You don’t actually need any of that if all you want to do is run a TLS server.

Anything else you’d like to share?

Yeah!

The first time someone connects to this server, the autocert that was setup will have to talk to Let’s Encrypt and actually get the certificate. For whatever reason, this code will fail to serve the page when that occurs. So the very first person to look at this site will see nothing. But everyone after that should be fine :)

Also, though I’ve linked to it several times in this post already, I wouldn’t have been able to write this without the gopher academy blog post. It was an essential resource for this project and this post.

Next project: ʅʕ•ᴥ•ʔʃ

There are three projects left:

All of them have been started in some way, but none of them are particularly far along. So I’m not sure which one will be next, but I know I’ll write something up about it, and will likely have little posts in between for smaller learnings.

← json.pictures for third party api testing Quick Raspberry Pi Tips →