diff --git a/command/server/config.go b/command/server/config.go index 5ef0e3f0fca6..dae8e5353438 100644 --- a/command/server/config.go +++ b/command/server/config.go @@ -677,6 +677,7 @@ func parseListeners(result *Config, list *ast.ObjectList) error { "tls_cipher_suites", "tls_prefer_server_cipher_suites", "tls_require_and_verify_client_cert", + "tls_client_ca_file", "token", } if err := checkHCLKeys(item.Val, valid); err != nil { diff --git a/command/server/config_test.go b/command/server/config_test.go index ab6000064011..c19cecd22eba 100644 --- a/command/server/config_test.go +++ b/command/server/config_test.go @@ -6,6 +6,8 @@ import ( "testing" "time" + "github.com/hashicorp/hcl" + "github.com/hashicorp/hcl/hcl/ast" "github.com/hashicorp/vault/helper/logformat" log "github.com/mgutz/logxi/v1" ) @@ -243,6 +245,56 @@ func TestLoadConfigDir(t *testing.T) { } } +func TestParseListeners(t *testing.T) { + obj, _ := hcl.Parse(strings.TrimSpace(` +listener "tcp" { + address = "127.0.0.1:443" + cluster_address = "127.0.0.1:8201" + tls_disable = false + tls_cert_file = "./certs/server.crt" + tls_key_file = "./certs/server.key" + tls_client_ca_file = "./certs/rootca.crt" + tls_min_version = "tls12" + tls_require_and_verify_client_cert = true +}`)) + + var config Config + list, _ := obj.Node.(*ast.ObjectList) + objList := list.Filter("listener") + parseListeners(&config, objList) + listeners := config.Listeners + if len(listeners) == 0 { + t.Fatalf("expected at least one listener in the config") + } + listener := listeners[0] + if listener.Type != "tcp" { + t.Fatalf("expected tcp listener in the config") + } + + expected := &Config{ + Listeners: []*Listener{ + &Listener{ + Type: "tcp", + Config: map[string]interface{}{ + "address": "127.0.0.1:443", + "cluster_address": "127.0.0.1:8201", + "tls_disable": false, + "tls_cert_file": "./certs/server.crt", + "tls_key_file": "./certs/server.key", + "tls_client_ca_file": "./certs/rootca.crt", + "tls_min_version": "tls12", + "tls_require_and_verify_client_cert": true, + }, + }, + }, + } + + if !reflect.DeepEqual(config, *expected) { + t.Fatalf("expected \n\n%#v\n\n to be \n\n%#v\n\n", config, *expected) + } + +} + func TestParseConfig_badTopLevel(t *testing.T) { logger := logformat.NewVaultLogger(log.LevelTrace) diff --git a/command/server/listener.go b/command/server/listener.go index ed940749584d..9a1f0e1196cd 100644 --- a/command/server/listener.go +++ b/command/server/listener.go @@ -5,8 +5,10 @@ import ( // certificates that use it can be parsed. _ "crypto/sha512" "crypto/tls" + "crypto/x509" "fmt" "io" + "io/ioutil" "net" "github.com/hashicorp/vault/helper/parseutil" @@ -104,6 +106,18 @@ func listenerWrapTLS( if requireClient { tlsConf.ClientAuth = tls.RequireAndVerifyClientCert } + if tlsClientCaFile, ok := config["tls_client_ca_file"]; ok { + caPool := x509.NewCertPool() + data, err := ioutil.ReadFile(tlsClientCaFile.(string)) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to read tls_client_ca_file: %v", err) + } + + if !caPool.AppendCertsFromPEM(data) { + return nil, nil, nil, fmt.Errorf("failed to parse CA certificate in tls_client_ca_file") + } + tlsConf.ClientCAs = caPool + } } ln = tls.NewListener(ln, tlsConf) diff --git a/command/server/listener_tcp_test.go b/command/server/listener_tcp_test.go index c58fbb2e6829..4da12b3711a7 100644 --- a/command/server/listener_tcp_test.go +++ b/command/server/listener_tcp_test.go @@ -49,18 +49,27 @@ func TestTCPListener_tls(t *testing.T) { } ln, _, _, err := tcpListenerFactory(map[string]interface{}{ - "address": "127.0.0.1:0", - "tls_cert_file": wd + "reload_foo.pem", - "tls_key_file": wd + "reload_foo.key", + "address": "127.0.0.1:0", + "tls_cert_file": wd + "reload_foo.pem", + "tls_key_file": wd + "reload_foo.key", + "tls_require_and_verify_client_cert": "true", + "tls_client_ca_file": wd + "reload_ca.pem", }, nil) if err != nil { t.Fatalf("err: %s", err) } + cwd, _ := os.Getwd() + + clientCert, _ := tls.LoadX509KeyPair( + cwd+"/test-fixtures/reload/reload_foo.pem", + cwd+"/test-fixtures/reload/reload_foo.key") connFn := func(lnReal net.Listener) (net.Conn, error) { conn, err := tls.Dial("tcp", ln.Addr().String(), &tls.Config{ - RootCAs: certPool, + RootCAs: certPool, + Certificates: []tls.Certificate{clientCert}, }) + if err != nil { return nil, err } diff --git a/website/source/docs/configuration/listener/tcp.html.md b/website/source/docs/configuration/listener/tcp.html.md index b2b9a173dee2..5c20a04fe868 100644 --- a/website/source/docs/configuration/listener/tcp.html.md +++ b/website/source/docs/configuration/listener/tcp.html.md @@ -71,6 +71,9 @@ listener "tcp" { authentication for this listener; the listener will require a presented client cert that successfully validates against system CAs. +- `tls_client_ca_file` `(string: "")` – PEM-encoded Certificate Authority file + used for checking the authenticity of client. + ## `tcp` Listener Examples ### Configuring TLS