From 06c0b32108ba13b1365591001c40443d4d49a2fa Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Wed, 15 Nov 2023 14:42:22 -0500 Subject: [PATCH 1/8] 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 2/8] 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 3/8] 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 4/8] 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 5/8] 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 6/8] 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 7/8] 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 8/8] 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,