From 10b4cd07f0588c825c8ac00ac8ddc5af77616b15 Mon Sep 17 00:00:00 2001 From: Suyash Kumar Date: Fri, 12 Oct 2018 01:00:55 -0400 Subject: [PATCH] Add ability to autogenerate & serve LetsEncrypt SSL/TLS Certificate (#4) This change uses the autocert package to add initial support for generating and serving a real live SSL certificate from LetsEncrypt via `ssl-proxy`. Now, serving your application with SSL is as easy as: ```sh ./ssl-proxy -from=0.0.0.0:443 -to=localhost:8080 -domain=mydomain.com ``` this will attempt to fetch a certificate for mydomain.com from LetsEncrypt (port 443 must be bindable by this process) and then begin proxying HTTPS traffic on :443 to any server running on :8080. Easy. This closes #1. --- Gopkg.lock | 18 ++++++++++++++++++ Gopkg.toml | 34 ++++++++++++++++++++++++++++++++++ main.go | 40 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 Gopkg.lock create mode 100644 Gopkg.toml diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000..21c99f3 --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,18 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "master" + name = "golang.org/x/crypto" + packages = [ + "acme", + "acme/autocert" + ] + revision = "7c1a557ab941a71c619514f229f0b27ccb0c27cf" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "46f1afba4d3f1fbad0de1917c45e25b020122869163df72cf8a274f67994e625" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..47863a9 --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,34 @@ +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + branch = "master" + name = "golang.org/x/crypto" + +[prune] + go-tests = true + unused-packages = true diff --git a/main.go b/main.go index 1abba8c..ea3d9e8 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/suyashkumar/ssl-proxy/gen" + "golang.org/x/crypto/acme/autocert" ) var ( @@ -19,6 +20,7 @@ var ( fromURL = flag.String("from", "127.0.0.1:4430", "the tcp address and port this proxy should listen for requests on") certFile = flag.String("cert", "", "path to a tls certificate file. If not provided, ssl-proxy will generate one for you in ~/.ssl-proxy/") keyFile = flag.String("key", "", "path to a private key file. If not provided, ssl-proxy will generate one for you in ~/.ssl-proxy/") + domain = flag.String("domain", "", "domain to mint letsencrypt certificates for. Usage of this parameter implies acceptance of the LetsEncrypt terms of service.") ) const ( @@ -31,7 +33,12 @@ const ( func main() { flag.Parse() - if *certFile == "" || *keyFile == "" { + validCertFile := *certFile != "" + validKeyFile := *keyFile != "" + validDomain := *domain != "" + + // Determine if we need to generate self-signed certs + if (!validCertFile || !validKeyFile) && !validDomain { // Use default file paths *certFile = DefaultCertFile *keyFile = DefaultKeyFile @@ -66,13 +73,40 @@ func main() { log.Println("Assuming -to URL is using http://") } + // Parse toURL as a URL toURL, err := url.Parse(*to) if err != nil { log.Fatal("Unable to parse 'to' url: ", err) } + // Setup ServeMux localProxy := httputil.NewSingleHostReverseProxy(toURL) - http.Handle("/", localProxy) + mux := http.NewServeMux() + mux.Handle("/", localProxy) + log.Printf("Proxying calls from https://%s (SSL/TLS) to %s", *fromURL, toURL) - log.Fatal(http.ListenAndServeTLS(*fromURL, *certFile, *keyFile, nil)) + + // Determine if we should serve with autogenerated LetsEncrypt certificates or not + if validDomain { + // Domain is present, use autocert + // TODO: validate domain (though, autocert may do this) + // TODO: for some reason this seems to only work on :443 + log.Printf("Domain specified, using LetsEncrypt to autogenerate and serve certs for %s\n", *domain) + if !strings.HasSuffix(*fromURL, ":443") { + log.Println("WARN: Right now, you must serve on port :443 to use autogenerated LetsEncrypt certs using the -domain flag, this may NOT WORK") + } + m := &autocert.Manager{ + Cache: autocert.DirCache("certs"), + Prompt: autocert.AcceptTOS, + HostPolicy: autocert.HostWhitelist(*domain), + } + s := &http.Server{ + Addr: *fromURL, + TLSConfig: m.TLSConfig(), + } + s.Handler = mux + s.ListenAndServeTLS("", "") + } else { + log.Fatal(http.ListenAndServeTLS(*fromURL, *certFile, *keyFile, mux)) + } }