diff --git a/go.mod b/go.mod index d4e28fb..3f62af1 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,8 @@ require ( github.com/google/go-cmp v0.5.9 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/stretchr/testify v1.8.1 // indirect - golang.org/x/tools v0.6.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/text v0.8.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) @@ -37,7 +38,7 @@ require ( github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect - golang.org/x/crypto v0.7.0 // indirect + golang.org/x/crypto v0.7.0 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect golang.org/x/sys v0.7.0 // indirect diff --git a/go.sum b/go.sum index c9773e0..d04c688 100644 --- a/go.sum +++ b/go.sum @@ -353,6 +353,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -423,6 +425,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -468,7 +472,6 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/ndt-server.go b/ndt-server.go index c5f37cd..f0578c6 100644 --- a/ndt-server.go +++ b/ndt-server.go @@ -27,6 +27,8 @@ import ( "github.com/m-lab/ndt-server/platformx" "github.com/m-lab/ndt-server/version" "github.com/m-lab/tcp-info/eventsocket" + "golang.org/x/crypto/acme" + "golang.org/x/crypto/acme/autocert" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" @@ -43,15 +45,19 @@ var ( certFile = flag.String("cert", "", "The file with server certificates in PEM format.") keyFile = flag.String("key", "", "The file with server key in PEM format.") tlsVersion = flag.String("tls.version", "", "Minimum TLS version. Valid values: 1.2 or 1.3") - dataDir = flag.String("datadir", "/var/spool/ndt", "The directory in which to write data files") - htmlDir = flag.String("htmldir", "html", "The directory from which to serve static web content.") - compress = flag.Bool("compress-results", true, "Whether to compress result files") - deploymentLabels = flagx.KeyValue{} - tokenVerifyKey = flagx.FileBytesArray{} - tokenRequired5 bool - tokenRequired7 bool - isLameDuck bool - tokenMachine string + autocertEnabled = flag.Bool("autocert.enabled", false, "Whether to use automatic TLS certificate generation.") + autocertHostname = flagx.FileBytes{} + autocertDir = flag.String("autocert.dir", "autocert", "The directory in which to write autocert files.") + + dataDir = flag.String("datadir", "/var/spool/ndt", "The directory in which to write data files") + htmlDir = flag.String("htmldir", "html", "The directory from which to serve static web content.") + compress = flag.Bool("compress-results", true, "Whether to compress result files") + deploymentLabels = flagx.KeyValue{} + tokenVerifyKey = flagx.FileBytesArray{} + tokenRequired5 bool + tokenRequired7 bool + isLameDuck bool + tokenMachine string // A metric to use to signal that the server is in lame duck mode. lameDuck = promauto.NewGauge(prometheus.GaugeOpts{ @@ -69,6 +75,7 @@ func init() { flag.BoolVar(&tokenRequired7, "ndt7.token.required", false, "Require access token in NDT7 requests") flag.StringVar(&tokenMachine, "token.machine", "", "Use given machine name to verify token claims") flag.Var(&deploymentLabels, "label", "Labels to identify the type of deployment.") + flag.Var(&autocertHostname, "autocert.hostname", "File containing the public hostname to request TLS certs for") } func catchSigterm() { @@ -117,6 +124,12 @@ func httpServer(addr string, handler http.Handler) *http.Server { MinVersion: tls.VersionTLS12, } } + + if *autocertEnabled { + // Include ALPN protocol name used by LE's tls-apln-01 challenges. + tlsconf.NextProtos = append(tlsconf.NextProtos, acme.ALPNProto) + } + return &http.Server{ Addr: addr, Handler: handler, @@ -263,7 +276,6 @@ func main() { rtx.Must(listener.ListenAndServeAsync(ndt7ServerCleartext), "Could not start ndt7 cleartext server") defer ndt7ServerCleartext.Close() - // Only start TLS-based services if certs and keys are provided if *certFile != "" && *keyFile != "" { // The ndt5 protocol serving WsS-based tests. ndt5WssMux := http.NewServeMux() @@ -286,7 +298,27 @@ func main() { rtx.Must(listener.ListenAndServeTLSAsync(ndt7Server, *certFile, *keyFile), "Could not start ndt7 server") defer ndt7Server.Close() } else { - log.Printf("Cert=%q and Key=%q means no TLS services will be started.\n", *certFile, *keyFile) + // Use the autocert package to get TLS certificates if autocert is enabled. + if *autocertEnabled && autocertHostname.String() != "" { + log.Printf("Setting up autocert for hostname %s\n", autocertHostname.String()) + m := &autocert.Manager{ + Prompt: autocert.AcceptTOS, + HostPolicy: autocert.HostWhitelist(autocertHostname.String()), + Cache: autocert.DirCache(*autocertDir), + } + + // The ndt7 listener serving up WSS based tests + ndt7Server := httpServer( + *ndt7Addr, + ac7.Then(logging.MakeAccessLogHandler(ndt7Mux)), + ) + ndt7Server.TLSConfig.GetCertificate = m.GetCertificate + log.Println("About to listen for ndt7 tests on " + *ndt7Addr) + rtx.Must(listener.ListenAndServeTLSAsync(ndt7Server, *certFile, *keyFile), "Could not start ndt7 server") + defer ndt7Server.Close() + } else { + log.Printf("cert/key empty and autocert is disabled, no TLS services will be started.\n") + } } // Set up handler for /health endpoint.