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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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—–` |
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>