From 06c0b32108ba13b1365591001c40443d4d49a2fa Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Wed, 15 Nov 2023 14:42:22 -0500 Subject: [PATCH 01/10] Add mTLS support to rqlite CLI --- cmd/rqlite/main.go | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/cmd/rqlite/main.go b/cmd/rqlite/main.go index a00b04b3..442fcc54 100644 --- a/cmd/rqlite/main.go +++ b/cmd/rqlite/main.go @@ -22,6 +22,7 @@ import ( "github.com/rqlite/rqlite/cmd" "github.com/rqlite/rqlite/cmd/rqlite/history" httpcl "github.com/rqlite/rqlite/cmd/rqlite/http" + "github.com/rqlite/rqlite/rtls" ) const maxRedirect = 21 @@ -42,6 +43,8 @@ type argT struct { Prefix string `cli:"P,prefix" usage:"rqlited HTTP URL prefix" dft:"/"` Insecure bool `cli:"i,insecure" usage:"do not verify rqlited HTTPS certificate" dft:"false"` CACert string `cli:"c,ca-cert" usage:"path to trusted X.509 root CA certificate"` + ClientCert string `cli:"d,client-cert" usage:"path to client X.509 certificate for mTLS"` + ClientKey string `cli:"k,client-key" usage:"path to client X.509 key for mTLS"` Credentials string `cli:"u,user" usage:"set basic auth credentials in form username:password"` Version bool `cli:"v,version" usage:"display CLI version"` HTTPTimeout clix.Duration `cli:"t,http-timeout" usage:"set timeout on HTTP requests" dft:"30s"` @@ -380,25 +383,14 @@ func getNodes(client *http.Client, argv *argT) (Nodes, error) { } func getHTTPClient(argv *argT) (*http.Client, error) { - var rootCAs *x509.CertPool - - if argv.CACert != "" { - pemCerts, err := ioutil.ReadFile(argv.CACert) - if err != nil { - return nil, err - } - - rootCAs = x509.NewCertPool() - - ok := rootCAs.AppendCertsFromPEM(pemCerts) - if !ok { - return nil, fmt.Errorf("failed to parse root CA certificate(s)") - } + tlsConfig, err := rtls.CreateClientConfig(argv.ClientCert, argv.ClientKey, argv.CACert, argv.Insecure) + if err != nil { + return nil, err } client := http.Client{ Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: argv.Insecure, RootCAs: rootCAs}, + TLSClientConfig: tlsConfig, Proxy: http.ProxyFromEnvironment, }, Timeout: argv.HTTPTimeout.Duration, From 66eb2c0e5657f6ab0d1e86a1a1357773ab33b290 Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Wed, 15 Nov 2023 14:44:02 -0500 Subject: [PATCH 02/10] CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bca5abd..2ad6f8b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ When officially released 8.0 will support (mostly) seamless upgrades from the 7. - [PR #1362](https://github.com/rqlite/rqlite/pull/1362): Enable SQLite [FTS5](https://www.sqlite.org/fts5.html). Fixes [issue #1361](https://github.com/rqlite/rqlite/issues/1361) - [PR #1405](https://github.com/rqlite/rqlite/pull/1405): Support a configurable HTTP connection timeout in the rqlite CLI. Thanks @jtarchie - [PR #1418](https://github.com/rqlite/rqlite/pull/1418): Add basic CORS support. Fixes [issue #687](https://github.com/rqlite/rqlite/issues/687). Thanks @kkoreilly +- [PR #1422](https://github.com/rqlite/rqlite/pull/1422): Add mTLS support to rqlite CLI. Fixes [issue #1421](https://github.com/rqlite/rqlite/issues/1421) ### Implementation changes and bug fixes - [PR #1368](https://github.com/rqlite/rqlite/pull/1374): Switch to always-on expvar and pprof. From 5e2221609ad02a3073da47f0ca2cec3d664ab307 Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Wed, 15 Nov 2023 14:48:08 -0500 Subject: [PATCH 03/10] Remove deprecated ioutil --- cmd/rqlite/main.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/cmd/rqlite/main.go b/cmd/rqlite/main.go index 442fcc54..27a2d305 100644 --- a/cmd/rqlite/main.go +++ b/cmd/rqlite/main.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net/http" "net/url" "os" @@ -368,7 +367,7 @@ func getNodes(client *http.Client, argv *argT) (Nodes, error) { return nil, err } - response, err := ioutil.ReadAll(resp.Body) + response, err := io.ReadAll(resp.Body) if err != nil { return nil, err } @@ -441,7 +440,7 @@ func sendRequest(ctx *cli.Context, makeNewRequest func(string) (*http.Request, e var rootCAs *x509.CertPool if argv.CACert != "" { - pemCerts, err := ioutil.ReadFile(argv.CACert) + pemCerts, err := os.ReadFile(argv.CACert) if err != nil { return nil, err } @@ -454,8 +453,12 @@ func sendRequest(ctx *cli.Context, makeNewRequest func(string) (*http.Request, e } } + tlsConfig, err := rtls.CreateClientConfig(argv.ClientCert, argv.ClientKey, argv.CACert, argv.Insecure) + if err != nil { + return nil, err + } client := http.Client{Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: argv.Insecure, RootCAs: rootCAs}, + TLSClientConfig: tlsConfig, Proxy: http.ProxyFromEnvironment, }} @@ -483,7 +486,7 @@ func sendRequest(ctx *cli.Context, makeNewRequest func(string) (*http.Request, e if err != nil { return nil, err } - response, err := ioutil.ReadAll(resp.Body) + response, err := io.ReadAll(resp.Body) if err != nil { return nil, err } @@ -569,7 +572,7 @@ func cliJSON(ctx *cli.Context, cmd, line, url string, argv *argT) error { return fmt.Errorf("unauthorized") } - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { return err } @@ -626,7 +629,7 @@ func urlsToWriter(client *http.Client, urls []string, w io.Writer, argv *argT) e return nil } - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { return err } From 447568427175d590da486f46cd20de49982d4216 Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Wed, 15 Nov 2023 14:54:18 -0500 Subject: [PATCH 04/10] Remove unneeded code --- cmd/rqlite/main.go | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/cmd/rqlite/main.go b/cmd/rqlite/main.go index 27a2d305..9972b48c 100644 --- a/cmd/rqlite/main.go +++ b/cmd/rqlite/main.go @@ -3,7 +3,6 @@ package main import ( "crypto/tls" - "crypto/x509" "encoding/json" "errors" "fmt" @@ -437,22 +436,6 @@ func getVersionWithClient(client *http.Client, argv *argT) (string, error) { func sendRequest(ctx *cli.Context, makeNewRequest func(string) (*http.Request, error), urlStr string, argv *argT) (*[]byte, error) { url := urlStr - var rootCAs *x509.CertPool - - if argv.CACert != "" { - pemCerts, err := os.ReadFile(argv.CACert) - if err != nil { - return nil, err - } - - rootCAs = x509.NewCertPool() - - ok := rootCAs.AppendCertsFromPEM(pemCerts) - if !ok { - return nil, fmt.Errorf("failed to parse root CA certificate(s)") - } - } - tlsConfig, err := rtls.CreateClientConfig(argv.ClientCert, argv.ClientKey, argv.CACert, argv.Insecure) if err != nil { return nil, err From 448f9530d7b7e0c1766946606072e752ea55f6e4 Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Wed, 15 Nov 2023 15:01:14 -0500 Subject: [PATCH 05/10] Correct mTLS log message --- http/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http/service.go b/http/service.go index b0695480..90aee076 100644 --- a/http/service.go +++ b/http/service.go @@ -389,7 +389,7 @@ func (s *Service) Start() error { if s.ClientVerify { b.WriteString(", mutual TLS enabled") } else { - b.WriteString(", mutual disabled") + b.WriteString(", mutual TLS disabled") } // print the message s.logger.Println(b.String()) From 5397f8dc0410c6d8d7bdf8db6418542a001cb16c Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Wed, 15 Nov 2023 15:26:17 -0500 Subject: [PATCH 06/10] Disable NextProtos for CLI tool CLI refuses to connect otherwise. Right now I just want to add basic mTLS support, so not digging into it. --- cmd/rqlite/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/rqlite/main.go b/cmd/rqlite/main.go index 9972b48c..710ea498 100644 --- a/cmd/rqlite/main.go +++ b/cmd/rqlite/main.go @@ -385,6 +385,7 @@ func getHTTPClient(argv *argT) (*http.Client, error) { if err != nil { return nil, err } + tlsConfig.NextProtos = nil // CLI too refuses to connect otherwise. client := http.Client{ Transport: &http.Transport{ From 1558811fec397c575d2bed1e73218252bd7d6d5b Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Wed, 15 Nov 2023 15:27:28 -0500 Subject: [PATCH 07/10] Disable NextProtos again --- cmd/rqlite/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/rqlite/main.go b/cmd/rqlite/main.go index 710ea498..eb2b3d17 100644 --- a/cmd/rqlite/main.go +++ b/cmd/rqlite/main.go @@ -441,6 +441,7 @@ func sendRequest(ctx *cli.Context, makeNewRequest func(string) (*http.Request, e if err != nil { return nil, err } + tlsConfig.NextProtos = nil // CLI too refuses to connect otherwise. client := http.Client{Transport: &http.Transport{ TLSClientConfig: tlsConfig, Proxy: http.ProxyFromEnvironment, From 9469cc8c3056c6750ac5835e9856a67c4dccfa10 Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Wed, 15 Nov 2023 15:29:04 -0500 Subject: [PATCH 08/10] Fix comment --- cmd/rqlite/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/rqlite/main.go b/cmd/rqlite/main.go index eb2b3d17..94ec7093 100644 --- a/cmd/rqlite/main.go +++ b/cmd/rqlite/main.go @@ -385,7 +385,7 @@ func getHTTPClient(argv *argT) (*http.Client, error) { if err != nil { return nil, err } - tlsConfig.NextProtos = nil // CLI too refuses to connect otherwise. + tlsConfig.NextProtos = nil // CLI refuses to connect otherwise. client := http.Client{ Transport: &http.Transport{ @@ -441,7 +441,7 @@ func sendRequest(ctx *cli.Context, makeNewRequest func(string) (*http.Request, e if err != nil { return nil, err } - tlsConfig.NextProtos = nil // CLI too refuses to connect otherwise. + tlsConfig.NextProtos = nil // CLI refuses to connect otherwise. client := http.Client{Transport: &http.Transport{ TLSClientConfig: tlsConfig, Proxy: http.ProxyFromEnvironment, From b08cec64a115a932521d24166448ba56392469ad Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Wed, 15 Nov 2023 16:26:54 -0500 Subject: [PATCH 09/10] Move NextProtos to server TLS config only --- cmd/rqlite/main.go | 2 -- rtls/config.go | 6 ++---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/cmd/rqlite/main.go b/cmd/rqlite/main.go index 94ec7093..9972b48c 100644 --- a/cmd/rqlite/main.go +++ b/cmd/rqlite/main.go @@ -385,7 +385,6 @@ func getHTTPClient(argv *argT) (*http.Client, error) { if err != nil { return nil, err } - tlsConfig.NextProtos = nil // CLI refuses to connect otherwise. client := http.Client{ Transport: &http.Transport{ @@ -441,7 +440,6 @@ func sendRequest(ctx *cli.Context, makeNewRequest func(string) (*http.Request, e if err != nil { return nil, err } - tlsConfig.NextProtos = nil // CLI refuses to connect otherwise. client := http.Client{Transport: &http.Transport{ TLSClientConfig: tlsConfig, Proxy: http.ProxyFromEnvironment, diff --git a/rtls/config.go b/rtls/config.go index 5243255d..99c431f9 100644 --- a/rtls/config.go +++ b/rtls/config.go @@ -86,13 +86,12 @@ func CreateClientConfig(certFile, keyFile, caCertFile string, noverify bool) (*t // parameters are the paths to the server's certificate and key files, which will be used to // authenticate the server to the client. The caCertFile parameter is the path to the CA // certificate file, which the server will use to verify any certificate presented by the -// client. If noverify is true, the server will not verify the client's certificate. If -// tls1011 is true, the server will accept TLS 1.0 or 1.1. Otherwise, it will require TLS 1.2 -// or higher. +// client. If noverify is true, the server will not verify the client's certificate. func CreateServerConfig(certFile, keyFile, caCertFile string, noverify bool) (*tls.Config, error) { var err error config := createBaseTLSConfig(false) + config.NextProtos = []string{"h2", "http/1.1"} config.Certificates = make([]tls.Certificate, 1) config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) if err != nil { @@ -118,7 +117,6 @@ func CreateServerConfig(certFile, keyFile, caCertFile string, noverify bool) (*t func createBaseTLSConfig(noverify bool) *tls.Config { return &tls.Config{ InsecureSkipVerify: noverify, - NextProtos: []string{"h2", "http/1.1"}, MinVersion: uint16(tls.VersionTLS12), } } From 9de0fc55a65c9e5e9121d894000c83038ca42514 Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Wed, 15 Nov 2023 16:53:23 -0500 Subject: [PATCH 10/10] Revert "Merge pull request #1423 from rqlite/remove-next-protos" This reverts commit 1b932d48ab231267603404727ae8f63578d32beb, reversing changes made to 60faad15e74cf2fc66bc5c615c9aef7d80f71786. --- cmd/rqlite/main.go | 2 ++ rtls/config.go | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/rqlite/main.go b/cmd/rqlite/main.go index 9972b48c..94ec7093 100644 --- a/cmd/rqlite/main.go +++ b/cmd/rqlite/main.go @@ -385,6 +385,7 @@ func getHTTPClient(argv *argT) (*http.Client, error) { if err != nil { return nil, err } + tlsConfig.NextProtos = nil // CLI refuses to connect otherwise. client := http.Client{ Transport: &http.Transport{ @@ -440,6 +441,7 @@ func sendRequest(ctx *cli.Context, makeNewRequest func(string) (*http.Request, e if err != nil { return nil, err } + tlsConfig.NextProtos = nil // CLI refuses to connect otherwise. client := http.Client{Transport: &http.Transport{ TLSClientConfig: tlsConfig, Proxy: http.ProxyFromEnvironment, diff --git a/rtls/config.go b/rtls/config.go index 99c431f9..5243255d 100644 --- a/rtls/config.go +++ b/rtls/config.go @@ -86,12 +86,13 @@ func CreateClientConfig(certFile, keyFile, caCertFile string, noverify bool) (*t // parameters are the paths to the server's certificate and key files, which will be used to // authenticate the server to the client. The caCertFile parameter is the path to the CA // certificate file, which the server will use to verify any certificate presented by the -// client. If noverify is true, the server will not verify the client's certificate. +// client. If noverify is true, the server will not verify the client's certificate. If +// tls1011 is true, the server will accept TLS 1.0 or 1.1. Otherwise, it will require TLS 1.2 +// or higher. func CreateServerConfig(certFile, keyFile, caCertFile string, noverify bool) (*tls.Config, error) { var err error config := createBaseTLSConfig(false) - config.NextProtos = []string{"h2", "http/1.1"} config.Certificates = make([]tls.Certificate, 1) config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) if err != nil { @@ -117,6 +118,7 @@ func CreateServerConfig(certFile, keyFile, caCertFile string, noverify bool) (*t func createBaseTLSConfig(noverify bool) *tls.Config { return &tls.Config{ InsecureSkipVerify: noverify, + NextProtos: []string{"h2", "http/1.1"}, MinVersion: uint16(tls.VersionTLS12), } }