Last active
July 4, 2019 22:42
-
-
Save JenniferMack/351b5ad4789e1a548f5062d9956807f7 to your computer and use it in GitHub Desktop.
Sample TLS server in #go
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
// Redirecting (http => https) server, will finish request before shutting down. | |
// All-in-one file for example purposes | |
// Everything below main would normally go into a `server.go` file to reduce clutter. | |
// Redirection is normally on, and can be disabled with `-redir=false` on the command line. | |
// Use with dummy certs for testing. | |
package main | |
import ( | |
"context" | |
"crypto/tls" | |
"flag" | |
"fmt" | |
"log" | |
"net" | |
"net/http" | |
"os" | |
"os/signal" | |
"sync" | |
"time" | |
) | |
// Env is a basic DI container for handlers | |
type Env struct { | |
log *log.Logger | |
//*sql.DB | |
} | |
var ( | |
flagHTTP = flag.String("http", ":80", "http server port") | |
flagTLS = flag.String("tls", ":443", "https server port") | |
flagCert = flag.String("cert", "server.crt", "server tls certificate") | |
flagKey = flag.String("key", "server.key", "server tls key") | |
flagRedir = flag.Bool("redir", true, "run the redirection server") | |
) | |
func init() { | |
flag.Parse() | |
} | |
func main() { | |
wg := sync.WaitGroup{} | |
endProcess := make(chan struct{}) | |
go catchSignal(endProcess) | |
// Setup environment | |
e := &Env{ | |
log: log.New(os.Stderr, "[demo] ", 0), | |
} | |
// HTTP server and redirection | |
if *flagRedir { | |
httpMux := http.NewServeMux() | |
httpServ := newServer(*flagHTTP, httpMux) | |
httpMux.Handle("/", redirHTTP(e)) | |
wg.Add(1) | |
go func() { | |
defer wg.Done() | |
shutDown(httpServ, endProcess) | |
}() | |
go serveHTTP(httpServ, endProcess) | |
} | |
// TLS server | |
tlsMux := http.NewServeMux() | |
tlsServ := newServer(*flagTLS, tlsMux) | |
tlsServ.TLSConfig = setupTLS() | |
wg.Add(1) | |
go func() { | |
defer wg.Done() | |
shutDown(tlsServ, endProcess) | |
}() | |
// TLS routing | |
tlsMux.Handle("/", serveIndex(e)) | |
if err := tlsServ.ListenAndServeTLS(*flagCert, *flagKey); err != http.ErrServerClosed { | |
log.Printf("TLS server: %v", err) | |
close(endProcess) | |
} | |
wg.Wait() | |
} | |
// Sample slow function w/ DI | |
func serveIndex(env *Env) http.Handler { | |
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
for _, v := range []byte("098765431") { | |
_, err := fmt.Fprint(w, string(v)) | |
if err != nil { | |
env.log.Println(err) | |
return | |
} | |
time.Sleep(500 * time.Millisecond) | |
} | |
}) | |
} | |
// Run HTTP server in Go routine | |
func serveHTTP(s *http.Server, ch chan struct{}) { | |
if err := s.ListenAndServe(); err != http.ErrServerClosed { | |
log.Printf("HTTP server: %v", err) | |
close(ch) | |
} | |
} | |
// Redirect handler used by the HTTP server | |
func redirHTTP(env *Env) http.Handler { | |
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
u := r.URL | |
u.Scheme = "https" | |
host := r.Host | |
if *flagHTTP != ":80" { | |
h, _, err := net.SplitHostPort(r.Host) | |
if err != nil { | |
fmt.Fprintf(w, "malformed URL: %s", err) | |
if err != nil { | |
env.log.Println(err) | |
} | |
return | |
} | |
host = h | |
} | |
u.Host = host + *flagTLS | |
http.Redirect(w, r, u.String(), http.StatusMovedPermanently) | |
}) | |
} | |
// Interrupt catcher | |
func catchSignal(ch chan struct{}) { | |
sigint := make(chan os.Signal, 1) | |
signal.Notify(sigint, os.Interrupt) | |
<-sigint | |
close(ch) | |
} | |
// Shutdown handler, will let pending requests complete | |
func shutDown(s *http.Server, sig chan struct{}) { | |
log.Println("HTTP/S server starting on", s.Addr) | |
<-sig | |
if err := s.Shutdown(context.Background()); err != nil { | |
log.Printf("Server shutdown: %v", err) | |
} | |
log.Println("HTTP/S server has shut down on", s.Addr) | |
} | |
// New server setup, tuned a bit tighter than | |
// https://blog.cloudflare.com/exposing-go-on-the-internet/ | |
func newServer(p string, m *http.ServeMux) *http.Server { | |
srv := &http.Server{ | |
Addr: p, | |
Handler: m, | |
ReadTimeout: 5 * time.Second, | |
WriteTimeout: 10 * time.Second, | |
IdleTimeout: 25 * time.Second, | |
} | |
return srv | |
} | |
// Using "modern" tls setup, IE 11+ etc. | |
// https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility | |
// https://blog.cloudflare.com/exposing-go-on-the-internet/ | |
func setupTLS() *tls.Config { | |
tls := &tls.Config{ | |
// Causes servers to use Go's default ciphersuite preferences, | |
// which are tuned to avoid attacks. | |
PreferServerCipherSuites: true, | |
// Only use curves which have assembly implementations | |
CurvePreferences: []tls.CurveID{ | |
tls.CurveP256, | |
tls.X25519, // Go 1.8 only | |
}, | |
MinVersion: tls.VersionTLS12, | |
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, // Go 1.8 only | |
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, // Go 1.8 only | |
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, | |
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, | |
}, | |
} | |
return tls | |
} |
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 ( | |
"net/http" | |
"net/http/httptest" | |
"testing" | |
) | |
func TestRedir8080(t *testing.T) { | |
req := httptest.NewRequest(http.MethodGet, "http://localhost:8080/foo.html", nil) | |
res := httptest.NewRecorder() | |
*flagHTTP = ":8080" | |
*flagTLS = ":443" | |
redirHTTP(&Env{}).ServeHTTP(res, req) | |
got := res.Header().Get("Location") | |
want := "https://localhost:443/foo.html" | |
if got != want { | |
t.Error(got) | |
} | |
} | |
func TestRedir4433(t *testing.T) { | |
req := httptest.NewRequest(http.MethodGet, "http://localhost:8080/foo.html", nil) | |
res := httptest.NewRecorder() | |
*flagHTTP = ":8080" | |
*flagTLS = ":4433" | |
redirHTTP(&Env{}).ServeHTTP(res, req) | |
got := res.Header().Get("Location") | |
want := "https://localhost:4433/foo.html" | |
if got != want { | |
t.Error(got) | |
} | |
} | |
func TestRedirReverse(t *testing.T) { | |
req := httptest.NewRequest(http.MethodGet, "http://localhost/foo.html", nil) | |
res := httptest.NewRecorder() | |
*flagHTTP = ":80" | |
*flagTLS = ":4433" | |
redirHTTP(&Env{}).ServeHTTP(res, req) | |
got := res.Header().Get("Location") | |
want := "https://localhost:4433/foo.html" | |
if got != want { | |
t.Error(got) | |
} | |
} | |
func TestRedirDefault(t *testing.T) { | |
req := httptest.NewRequest(http.MethodGet, "http://localhost/foo.html", nil) | |
res := httptest.NewRecorder() | |
*flagHTTP = ":80" | |
*flagTLS = ":443" | |
redirHTTP(&Env{}).ServeHTTP(res, req) | |
got := res.Header().Get("Location") | |
want := "https://localhost:443/foo.html" | |
if got != want { | |
t.Error(got) | |
} | |
} |
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
-----BEGIN CERTIFICATE----- | |
MIIDBjCCAe6gAwIBAgIBezANBgkqhkiG9w0BAQsFADAzMQswCQYDVQQGEwJnYjEN | |
MAsGA1UEChMEVGVzdDEVMBMGA1UEAxMMVGVzdCBSb290IENBMB4XDTE5MDYwODAw | |
NDcyN1oXDTE5MDcwODAwNDcyN1owMDELMAkGA1UEBhMCZ2IxDTALBgNVBAoTBFRl | |
c3QxEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC | |
AQoCggEBAOBlFsqA/7VdVleYmR8Liyta86xbe6R9wc3Z6GaufJ59duXoNyi+vk4H | |
EZQ4/C6sglIK9KFSoVC5xyIM8HJ8eDLkl4eRajKnKielq4KlMQz7y3TWeCpWBwCs | |
D0LNHRRgsrLe8DU+3CX4eblKQrTlEWT8qTkhTFLbXSdwDi2JTI9vP6uYkGIa5nYP | |
nMiM3Hkt6GoQsAJNKY1zuDoGlEIvvjv2+YT9u8XcdfZvb2xUmrTAw5XEtRomEvqi | |
5B+tjx0UsPLQQ/F7kY1WMqQlwyd3koP+QTnOj9rZWZnFU0C+pCugQ1/dMNLsXYcD | |
Xre3eE6Qa+sKchv55BZiwJq3bUFI+ykCAwEAAaMoMCYwDgYDVR0PAQH/BAQDAgOI | |
MBQGA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEACvlXnLVn | |
CRZDwZyioehXatKxtekLUiEYIyDfm3BS4qx94i/xBZNyRKYs9VVLtorfYhtAAyyh | |
ySe+1YMlDtgi+58wmnGbvtQiYXvV8S+CydvEOj0X3jW1/arhjvF4wh08aLa2pgh+ | |
3LiXZXiU45Z0YDxHlsz0eunln38mOV/z89P2hEY3GosWPH6Aqj3lTkHw+wN5vH/o | |
PtKdQJ3Is43hALi5Ni7hcfhXMhKx+0jU75gSQQ2orCa22dpkhyIupp6fUv8rjt/b | |
WAIBhcJnYtrycmePtjQ2VgfOLboNWOJqjeb5kBADhIyMB4grI9oRWACzGRkrJOQG | |
yNy3WaLCGHTL6A== | |
-----END CERTIFICATE----- |
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
-----BEGIN PRIVATE KEY----- | |
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDgZRbKgP+1XVZX | |
mJkfC4srWvOsW3ukfcHN2ehmrnyefXbl6Dcovr5OBxGUOPwurIJSCvShUqFQucci | |
DPByfHgy5JeHkWoypyonpauCpTEM+8t01ngqVgcArA9CzR0UYLKy3vA1Ptwl+Hm5 | |
SkK05RFk/Kk5IUxS210ncA4tiUyPbz+rmJBiGuZ2D5zIjNx5LehqELACTSmNc7g6 | |
BpRCL7479vmE/bvF3HX2b29sVJq0wMOVxLUaJhL6ouQfrY8dFLDy0EPxe5GNVjKk | |
JcMnd5KD/kE5zo/a2VmZxVNAvqQroENf3TDS7F2HA163t3hOkGvrCnIb+eQWYsCa | |
t21BSPspAgMBAAECggEBAJ4EsgsC0o+eXfornNCu6V8rmmMqvSQ15u+WX0FH6LwX | |
cE4wu/82a385HRj7FCOuGcu6qVCdhrn5SZDh+cU1f9OfBFJUhauL6nSnBuNmfuc8 | |
vabWjSKLGD1R7SFGng7GlbC+q/ti+9bFZrqj39vRX/F0t5pip4PFtJDcKS/J+x8Z | |
j0n/PTHWUKgqd/uPuti8CUXpr0W+cnTACBb+Pzjg7Ex8oz7g3TTbrLehP2miGACA | |
FSsJ7ulXM1A5DYBSqzrfAL0X2e0y8w7aYq/byvrwFFTKGs45WrRRN/0mWelfGAy7 | |
NqDbm/IbMkJ9cKYgPJrlgy2ls7l5zfGaFYJHpx+PvM0CgYEA/z1wSnq6mRXPnd/i | |
6ClV53hISfnsW1BED4U6dnHeAuTZMkIUU6bBuLEkC86pfJYFUr8oQI9X5/EBCCTv | |
D5EgfY9z+CRZUqt3nYV+qQbhAKD9o1eA+DEKy1KGtPpVLLYvVZpSMJYEYHXMt3yO | |
L/6ZKLWEz+EyOFHdVZ3Y69H80icCgYEA4RAjXOa62ru4LpRlM9P/ENCStq2ltDND | |
8f+sriyILLl0PEt7NC5MYcKOpuQDRdJ+XrtyZ1VQnnldFslp5GY/5bq+rYAgFh+9 | |
A7nJ8CvU5QJmz7/xrGCuWot7ZYDBYPu152L7fs+myuKb87LzFYo0CP90utt6u0wK | |
PCFSva38Ki8CgYBEu1axt7rn3me6K7/+pKLtYgaVZSewrzyksUu8+Yy/WsDiN8kh | |
fZZNFeaPB2e8lOl6Dt/YAsG5Q7zzZH67wmjtZuPjvmYPTP04/enNunx8nt2uTrH8 | |
I7i0Z9S+h2rIT4cPli7rxnoHP7GQ94mSgwzbWYYaNJpBcEoZ9Bel6TYboQKBgED3 | |
jZ21pN6bLZGUmJobaheKfa9p9NlNqyFiMpCcnjp61kjJaSko7DeUF+WpECDJoFzs | |
MxwNDpaQZgS742tg8LT58SzYPWrlgoRezyIbJPtudAsoVzTxO6M7fEVSo3/BaUL/ | |
2aVDf9w1CduRHoZrPJYUV7fQv17tlk7BN8c1QANVAoGBAJXm7kPZqWZkIEdT1sHw | |
V1QoobZ7CGLlXxy4y4bXDSke5SZzgn73CC2SLljpn7t6SZeM+ubku8CUpJ/7txqt | |
IZ2w1RDEJFq63uYI5TBQdqA4zN4rpLJH9PbB+GK5magHHUrWjQG15vXFVDB54p6A | |
KQKmDgMcMFWmfIdaIVyxOM2i | |
-----END PRIVATE KEY----- |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment