From cddc5ff351eff9bf66ab6b75da657d60d193a65c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Hurtado?= Date: Thu, 23 Jan 2020 17:39:58 +0100 Subject: [PATCH] Fixes #45: Added Support fot Mutual TLS Authentication --- internal/cmd/server.go | 20 +++++++++++++++++-- internal/server/server.go | 7 +++++-- internal/server/user/server.go | 36 +++++++++++++++++++++++++++++++++- 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/internal/cmd/server.go b/internal/cmd/server.go index bdab3da1..c6a2cc31 100644 --- a/internal/cmd/server.go +++ b/internal/cmd/server.go @@ -32,8 +32,8 @@ import ( var ServerCmd = &cobra.Command{ Use: "server [optional flags] [optional pow file(s)]", Short: "Start a kapow server", - Long: `Start a Kapow server with, by default with client interface, data interface - and admin interface`, + Long: `Start a Kapow server with a client interface, a data interface and an + admin interface`, PreRunE: validateServerCommandArguments, Run: func(cmd *cobra.Command, args []string) { var sConf server.ServerConfig = server.ServerConfig{} @@ -44,6 +44,9 @@ var ServerCmd = &cobra.Command{ sConf.CertFile, _ = cmd.Flags().GetString("certfile") sConf.KeyFile, _ = cmd.Flags().GetString("keyfile") + sConf.ClientAuth, _ = cmd.Flags().GetBool("clientauth") + sConf.ClientCaFile, _ = cmd.Flags().GetString("clientcafile") + go server.StartServer(sConf) // start sub shell + ENV(KAPOW_CONTROL_URL) @@ -78,13 +81,26 @@ func init() { ServerCmd.Flags().String("certfile", "", "Cert file to serve thru https") ServerCmd.Flags().String("keyfile", "", "Key file to serve thru https") + + ServerCmd.Flags().Bool("clientauth", false, "Activate client mutual tls authentication") + ServerCmd.Flags().String("clientcafile", "", "Cert file to validate client certificates") } func validateServerCommandArguments(cmd *cobra.Command, args []string) error { cert, _ := cmd.Flags().GetString("certfile") key, _ := cmd.Flags().GetString("keyfile") + cliAuth, _ := cmd.Flags().GetBool("clientauth") + if (cert == "") != (key == "") { return errors.New("expected both or neither (certfile and keyfile)") } + + if cert == "" { + // If we don't serve thru https client authentication can't be enabled + if cliAuth { + return errors.New("Client authentication can't be active in a non https server") + } + } + return nil } diff --git a/internal/server/server.go b/internal/server/server.go index c09857ab..1e28ebcd 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -27,14 +27,17 @@ type ServerConfig struct { DataBindAddr, UserBindAddr, KeyFile, - CertFile string + CertFile, + ClientCaFile string + + ClientAuth bool } // StartServer Starts one instance of each server in a goroutine and remains listening on a channel for trace events generated by them func StartServer(config ServerConfig) { go control.Run(config.ControlBindAddr) go data.Run(config.DataBindAddr) - go user.Run(config.UserBindAddr, config.CertFile, config.KeyFile) + go user.Run(config.UserBindAddr, config.CertFile, config.KeyFile, config.ClientCaFile, config.ClientAuth) // Wait for ever select {} diff --git a/internal/server/user/server.go b/internal/server/user/server.go index 67b82092..ca3a7cad 100644 --- a/internal/server/user/server.go +++ b/internal/server/user/server.go @@ -17,6 +17,9 @@ package user import ( + "crypto/tls" + "crypto/x509" + "io/ioutil" "log" "net/http" @@ -29,13 +32,32 @@ var Server = http.Server{ } // Run finishes configuring Server and runs ListenAndServe on it -func Run(bindAddr, certFile, keyFile string) { +func Run(bindAddr, certFile, keyFile, cliCaFile string, cliAuth bool) { Server = http.Server{ Addr: bindAddr, Handler: mux.New(), } if (certFile != "") && (keyFile != "") { + if cliAuth { + if Server.TLSConfig == nil { + Server.TLSConfig = &tls.Config{} + } + + var err error + Server.TLSConfig.ClientCAs, err = loadCertificatesFromFile(cliCaFile) + if err != nil { + log.Printf("UserServer failed to load CA certs: %s\nDefault to system CA store.", err) + } else { + CAStore := "System store" + if Server.TLSConfig.ClientCAs != nil { + CAStore = cliCaFile + } + log.Printf("UserServer using CA certs from %s\n", CAStore) + Server.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert + } + } + if err := Server.ListenAndServeTLS(certFile, keyFile); err != http.ErrServerClosed { log.Fatalf("UserServer failed: %s", err) } @@ -45,3 +67,15 @@ func Run(bindAddr, certFile, keyFile string) { } } } + +func loadCertificatesFromFile(certFile string) (pool *x509.CertPool, err error) { + if certFile != "" { + caCerts, err := ioutil.ReadFile(certFile) + if err == nil { + pool = x509.NewCertPool() + pool.AppendCertsFromPEM(caCerts) + } + } + + return +}