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. diff --git a/cmd/rqlite/main.go b/cmd/rqlite/main.go index a00b04b3..94ec7093 100644 --- a/cmd/rqlite/main.go +++ b/cmd/rqlite/main.go @@ -3,12 +3,10 @@ package main import ( "crypto/tls" - "crypto/x509" "encoding/json" "errors" "fmt" "io" - "io/ioutil" "net/http" "net/url" "os" @@ -22,6 +20,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 +41,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"` @@ -365,7 +366,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 } @@ -380,25 +381,15 @@ 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 } + tlsConfig.NextProtos = nil // CLI refuses to connect otherwise. client := http.Client{ Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: argv.Insecure, RootCAs: rootCAs}, + TLSClientConfig: tlsConfig, Proxy: http.ProxyFromEnvironment, }, Timeout: argv.HTTPTimeout.Duration, @@ -446,24 +437,13 @@ 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 := 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 } - + tlsConfig.NextProtos = nil // CLI refuses to connect otherwise. client := http.Client{Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: argv.Insecure, RootCAs: rootCAs}, + TLSClientConfig: tlsConfig, Proxy: http.ProxyFromEnvironment, }} @@ -491,7 +471,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 } @@ -577,7 +557,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 } @@ -634,7 +614,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 } diff --git a/http/service.go b/http/service.go index f0b72623..a224f9cc 100644 --- a/http/service.go +++ b/http/service.go @@ -378,7 +378,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())