Practical embedding in GoLang

Eric Urban has written a phenomenal blog post about embedding in Golang:

http://www.hydrogen18.com/blog/golang-embedding.html

Embedding in Golang is an important concept to  grasp, especially when coming from an object-oriented paradigm, since as Eric describes, embedding in Golang is superficially similar to OO’s inheritance concept. I came across a practical need for doing this when dealing with Golang’s ListenAndServeTLS [1]

The problem in my case is that by default ListenAndServeTLS only accepts external files representing the TLS server certificate and private key [2]. However, this Golang TLS server needed to be self-contained, as it was going to be installed in a semi-hostile environment where I did not want the casual user to be able to read the TLS certificate and key. So ideally we modify the original ListenAndServeTLS method to accept normal variable/constant strings as certificate and key, which can then be compiled straight into an executable. Modifying this default ListenAndServeTLS is where Golang embedding comes into play.

Below is my implementation of this:


package main
import (
"io"
"net/http"
"log"
"crypto/tls"
"net"
"sixpmplc.com/golang/license_server/tls_common"
)
type embeddedServer struct {
/*
custom struct that embeds golang's standard http.Server type
another way of looking at this is that embeddedServer "inherits" from http.Server,
though this is not strictly accurate. Have a look at note below for additional information
*/
http.Server
webserverCertificate string
webserverKey string
}
func (srv *embeddedServer) ListenAndServeTLS(addr string) error {
/*
This is where we "hide" or "override" the default "ListenAndServeTLS" method so we modify it to accept
hardcoded certificates and keys rather than the default filenames
The default implementation of ListenAndServeTLS was obtained from:
https://github.com/zenazn/goji/blob/master/graceful/server.go#L33
and tls.X509KeyPair (http://golang.org/pkg/crypto/tls/#X509KeyPair) is used,
rather than the default tls.LoadX509KeyPair
*/
config := &tls.Config{
MinVersion: tls.VersionTLS10,
}
if srv.TLSConfig != nil {
*config = *srv.TLSConfig
}
if config.NextProtos == nil {
config.NextProtos = []string{"http/1.1"}
}
var err error
config.Certificates = make([]tls.Certificate, 1)
config.Certificates[0], err = tls.X509KeyPair([]byte(srv.webserverCertificate), []byte(srv.webserverKey))
if err != nil {
return err
}
conn, err := net.Listen("tcp", addr)
if err != nil {
return err
}
tlsListener := tls.NewListener(conn, config)
return srv.Serve(tlsListener)
}
func hello(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Hello world!")
}
func main() {
// instantiating an embeddedServer type, and assigning hardcoded TLS certificate and key
embeddedTLSserver := &embeddedServer{
webserverCertificate: tls_common.WebserverCertificate,
webserverKey: tls_common.WebserverPrivateKey,
}
// reference: https://golang.org/doc/articles/wiki/
http.HandleFunc("/", hello)
http.HandleFunc("/decrypt", decryptMessage)
http.HandleFunc("/verify", verifyMessage)
/*
reference: http://golang.org/pkg/net/http/#ListenAndServeTLS
Normally we would call an TLS server using the ListenAndServeTLS as described above, resulting in a call similar to:
http.ListenAndServeTLS(":10443", "server.crt", "server.key", nil)
However, we have a problem with that because it references external files, which we dont want. Since this executable
is going to be installed in a "hostile" environment, we want to make sure that all sensitive information like
certificates and private keys are hardcoded into the executable.
Unfortunately, we can't simply set the TLSConfig attribute of http.Server (why golang gods, why?!)
So we need to write our own implementation of ListenAndServeTLS.
This is where "embeddedServer" type comes in. Note that we define "type embeddedServer struct" towards the beginning
of this program. We then proceed to use a feature of golang called "embedding". Please refer to the following link
for more information:
http://www.hydrogen18.com/blog/golang-embedding.html
*/
log.Fatal(embeddedTLSserver.ListenAndServeTLS(":10443"))
}

Aside: the tls_common package is simply a file containing the necessary certificate and key, as follows:


package tls_common
/*
IMPORTANT: due to golang's encapsulation directives, variables in this file MUST start with a capital letter, else
they will not be visible from other packages
reference: http://golangtutorials.blogspot.com/2011/06/structs-in-go-instead-of-classes-in.html
*/
/*
The following SSL/TLS certificate and key were generated for the embedded TLS server, by following the procedure
outlined here:
http://www.akadia.com/services/ssh_test_certificate.html
*/
var WebserverCertificate = `—–BEGIN CERTIFICATE—–
MIID…
—–END CERTIFICATE—–`
var WebserverPrivateKey = `—–BEGIN RSA PRIVATE KEY—–
MII…
—–END RSA PRIVATE KEY—–`

view raw

tls_common

hosted with ❤ by GitHub

We can see in practice that embedding is quite simple. We start by first defining a custom struct which we call “embeddedServer”, defined as follows:

type embeddedServer struct {
   http.Server
   webserverCertificate string
   webserverKey string
}

Note that we embedded the default “http.Server” that is included in Go HTTP package, therefore we can expect to have access to all http.Server’s methods and attributes. We are only really interested in “overriding” one of them, which is in fact ListenAndServeTLS. If we look at the source code for ListenAndServeTLS [3] we see that the certificate and key are loaded into the program by means of tls.LoadX509KeyPair [4], which expects files. We need to modify ListenAndServeTLS to instead use “X509KeyPair” which accepts byte arrays.  

So we define a method which receives our custom struct of embeddedServer, with the same name of ListenAndServeTLS and modify it to use X509KeyPair:

func (srv *embeddedServer) ListenAndServeTLS(addr string) error {

   config := &tls.Config{
      MinVersion: tls.VersionTLS10,
   }
   if srv.TLSConfig != nil {
      *config = *srv.TLSConfig
   }
   if config.NextProtos == nil {
      config.NextProtos = []string{"http/1.1"}
   }

   var err error
   config.Certificates = make([]tls.Certificate, 1)
   config.Certificates[0], err = tls.X509KeyPair([]byte(srv.webserverCertificate), []byte(srv.webserverKey))
   if err != nil {
      return err
   }

   conn, err := net.Listen("tcp", addr)
   if err != nil {
      return err
   }

   tlsListener := tls.NewListener(conn, config)
   return srv.Serve(tlsListener)
}

The most important change is in bold above. This now allows us to pass variables into the struct (webServerCertificate and webserverKey) which are then used by our modified ListenAndServeTLS.

 

References

 

[1] Golang, ‘Package http’, [online] <Available from: http://golang.org/pkg/net/http/#ListenAndServeTLS>

[2] Kaihag, ‘External Assets, Working Directories, and Go’ [online] <Available from: https://www.kaihag.com/external-assets-working-directories-and-go/>

[3] Github, ‘zenazn/goji’ [online] <Available from: https://github.com/zenazn/goji/blob/master/graceful/server.go#L33>

[4] Golang, ‘Package tls’, [online] <Available from: https://golang.org/pkg/crypto/tls/#LoadX509KeyPair>

Advertisement
Privacy Settings