From 3c7c593bca8e1d8fcb1b389e55dec9d35bd1907d Mon Sep 17 00:00:00 2001 From: Vishal Nayak Date: Thu, 14 Mar 2019 14:53:14 -0400 Subject: [PATCH] Agent: Listener refactoring and socket file system permissions (#6397) * Listener refactoring and file system permissions * added listenerutil and move some common code there * Added test for verifying socket file permissions * Change default port of agent to 8200 * address review feedback * Address review feedback * Read socket options from listener config --- command/agent.go | 18 +- command/agent/cache/listener.go | 134 ++++----- command/agent/config/config_test.go | 7 +- .../config-cache-embedded-type.hcl | 3 + .../config/test-fixtures/config-cache.hcl | 3 + command/server/listener.go | 152 ---------- command/server/listener_tcp.go | 8 +- helper/listenerutil/listener.go | 271 ++++++++++++++++++ helper/listenerutil/listener_test.go | 88 ++++++ .../source/docs/agent/caching/index.html.md | 8 +- 10 files changed, 440 insertions(+), 252 deletions(-) create mode 100644 helper/listenerutil/listener.go create mode 100644 helper/listenerutil/listener_test.go diff --git a/command/agent.go b/command/agent.go index 1bf33cb2ee..d38daf6eb6 100644 --- a/command/agent.go +++ b/command/agent.go @@ -419,35 +419,37 @@ func (c *AgentCommand) Run(args []string) int { var listeners []net.Listener for i, lnConfig := range config.Cache.Listeners { - listener, props, _, err := cache.ServerListener(lnConfig, c.logWriter, c.UI) + ln, tlsConf, err := cache.StartListener(lnConfig) if err != nil { - c.UI.Error(fmt.Sprintf("Error parsing listener configuration: %v", err)) + c.UI.Error(fmt.Sprintf("Error starting listener: %v", err)) return 1 } - listeners = append(listeners, listener) + listeners = append(listeners, ln) scheme := "https://" - if props["tls"] == "disabled" { + if tlsConf == nil { scheme = "http://" } - if lnConfig.Type == "unix" { + if ln.Addr().Network() == "unix" { scheme = "unix://" } infoKey := fmt.Sprintf("api address %d", i+1) - info[infoKey] = scheme + listener.Addr().String() + info[infoKey] = scheme + ln.Addr().String() infoKeys = append(infoKeys, infoKey) - cacheLogger.Info("starting listener", "addr", listener.Addr().String()) server := &http.Server{ + Addr: ln.Addr().String(), + TLSConfig: tlsConf, Handler: mux, ReadHeaderTimeout: 10 * time.Second, ReadTimeout: 30 * time.Second, IdleTimeout: 5 * time.Minute, ErrorLog: cacheLogger.StandardLogger(nil), } - go server.Serve(listener) + + go server.Serve(ln) } // Ensure that listeners are closed at all the exits diff --git a/command/agent/cache/listener.go b/command/agent/cache/listener.go index 11c5bf117b..3ad215a701 100644 --- a/command/agent/cache/listener.go +++ b/command/agent/cache/listener.go @@ -1,105 +1,69 @@ package cache import ( + "crypto/tls" "fmt" - "io" "net" - "os" + "strings" "github.com/hashicorp/vault/command/agent/config" "github.com/hashicorp/vault/command/server" - "github.com/hashicorp/vault/helper/reload" - "github.com/mitchellh/cli" + "github.com/hashicorp/vault/helper/listenerutil" ) -func ServerListener(lnConfig *config.Listener, logger io.Writer, ui cli.Ui) (net.Listener, map[string]string, reload.ReloadFunc, error) { +func StartListener(lnConfig *config.Listener) (net.Listener, *tls.Config, error) { + addr, ok := lnConfig.Config["address"].(string) + if !ok { + return nil, nil, fmt.Errorf("invalid address") + } + + var ln net.Listener + var err error switch lnConfig.Type { - case "unix": - return unixSocketListener(lnConfig.Config, logger, ui) case "tcp": - return tcpListener(lnConfig.Config, logger, ui) + if addr == "" { + addr = "127.0.0.1:8200" + } + + bindProto := "tcp" + // If they've passed 0.0.0.0, we only want to bind on IPv4 + // rather than golang's dual stack default + if strings.HasPrefix(addr, "0.0.0.0:") { + bindProto = "tcp4" + } + + ln, err = net.Listen(bindProto, addr) + if err != nil { + return nil, nil, err + } + ln = &server.TCPKeepAliveListener{ln.(*net.TCPListener)} + + case "unix": + var uConfig *listenerutil.UnixSocketsConfig + if lnConfig.Config["socket_mode"] != nil && + lnConfig.Config["socket_user"] != nil && + lnConfig.Config["socket_group"] != nil { + uConfig = &listenerutil.UnixSocketsConfig{ + Mode: lnConfig.Config["socket_mode"].(string), + User: lnConfig.Config["socket_user"].(string), + Group: lnConfig.Config["socket_group"].(string), + } + } + ln, err = listenerutil.UnixSocketListener(addr, uConfig) + if err != nil { + return nil, nil, err + } + default: - return nil, nil, nil, fmt.Errorf("unsupported listener type: %q", lnConfig.Type) - } -} - -func unixSocketListener(config map[string]interface{}, _ io.Writer, ui cli.Ui) (net.Listener, map[string]string, reload.ReloadFunc, error) { - addr, ok := config["address"].(string) - if !ok { - return nil, nil, nil, fmt.Errorf("invalid address: %v", config["address"]) + return nil, nil, fmt.Errorf("invalid listener type: %q", lnConfig.Type) } - if addr == "" { - return nil, nil, nil, fmt.Errorf("address field should point to socket file path") - } - - // Remove the socket file as it shouldn't exist for the domain socket to - // work - err := os.Remove(addr) - if err != nil && !os.IsNotExist(err) { - return nil, nil, nil, fmt.Errorf("failed to remove the socket file: %v", err) - } - - listener, err := net.Listen("unix", addr) + props := map[string]string{"addr": ln.Addr().String()} + ln, props, _, tlsConf, err := listenerutil.WrapTLS(ln, props, lnConfig.Config, nil) if err != nil { - return nil, nil, nil, err + return nil, nil, err } - // Wrap the listener in rmListener so that the Unix domain socket file is - // removed on close. - listener = &rmListener{ - Listener: listener, - Path: addr, - } - - props := map[string]string{"addr": addr, "tls": "disabled"} - - return server.ListenerWrapTLS(listener, props, config, ui) -} - -func tcpListener(config map[string]interface{}, _ io.Writer, ui cli.Ui) (net.Listener, map[string]string, reload.ReloadFunc, error) { - bindProto := "tcp" - var addr string - addrRaw, ok := config["address"] - if !ok { - addr = "127.0.0.1:8007" - } else { - addr = addrRaw.(string) - } - - // If they've passed 0.0.0.0, we only want to bind on IPv4 - // rather than golang's dual stack default - if strings.HasPrefix(addr, "0.0.0.0:") { - bindProto = "tcp4" - } - - ln, err := net.Listen(bindProto, addr) - if err != nil { - return nil, nil, nil, err - } - - ln = server.TCPKeepAliveListener{ln.(*net.TCPListener)} - - props := map[string]string{"addr": addr} - - return server.ListenerWrapTLS(ln, props, config, ui) -} - -// rmListener is an implementation of net.Listener that forwards most -// calls to the listener but also removes a file as part of the close. We -// use this to cleanup the unix domain socket on close. -type rmListener struct { - net.Listener - Path string -} - -func (l *rmListener) Close() error { - // Close the listener itself - if err := l.Listener.Close(); err != nil { - return err - } - - // Remove the file - return os.Remove(l.Path) + return ln, tlsConf, nil } diff --git a/command/agent/config/config_test.go b/command/agent/config/config_test.go index 97063c713c..4e4511ae4e 100644 --- a/command/agent/config/config_test.go +++ b/command/agent/config/config_test.go @@ -46,8 +46,11 @@ func TestLoadConfigFile_AgentCache(t *testing.T) { &Listener{ Type: "unix", Config: map[string]interface{}{ - "address": "/path/to/socket", - "tls_disable": true, + "address": "/path/to/socket", + "tls_disable": true, + "socket_mode": "configmode", + "socket_user": "configuser", + "socket_group": "configgroup", }, }, &Listener{ diff --git a/command/agent/config/test-fixtures/config-cache-embedded-type.hcl b/command/agent/config/test-fixtures/config-cache-embedded-type.hcl index 7f32a1c262..15bcbe2835 100644 --- a/command/agent/config/test-fixtures/config-cache-embedded-type.hcl +++ b/command/agent/config/test-fixtures/config-cache-embedded-type.hcl @@ -27,6 +27,9 @@ cache { type = "unix" address = "/path/to/socket" tls_disable = true + socket_mode = "configmode" + socket_user = "configuser" + socket_group = "configgroup" } listener { diff --git a/command/agent/config/test-fixtures/config-cache.hcl b/command/agent/config/test-fixtures/config-cache.hcl index 99310d7202..42cc649741 100644 --- a/command/agent/config/test-fixtures/config-cache.hcl +++ b/command/agent/config/test-fixtures/config-cache.hcl @@ -26,6 +26,9 @@ cache { listener "unix" { address = "/path/to/socket" tls_disable = true + socket_mode = "configmode" + socket_user = "configuser" + socket_group = "configgroup" } listener "tcp" { diff --git a/command/server/listener.go b/command/server/listener.go index 803a78fccd..173b76fff1 100644 --- a/command/server/listener.go +++ b/command/server/listener.go @@ -5,18 +5,12 @@ import ( // We must import sha512 so that it registers with the runtime so that // certificates that use it can be parsed. _ "crypto/sha512" - "crypto/tls" - "crypto/x509" "fmt" "io" - "io/ioutil" "net" - "github.com/hashicorp/vault/helper/parseutil" "github.com/hashicorp/vault/helper/proxyutil" "github.com/hashicorp/vault/helper/reload" - "github.com/hashicorp/vault/helper/tlsutil" - "github.com/jefferai/isbadcipher" "github.com/mitchellh/cli" ) @@ -72,149 +66,3 @@ func listenerWrapProxy(ln net.Listener, config map[string]interface{}) (net.List return newLn, nil } - -func ListenerWrapTLS( - ln net.Listener, - props map[string]string, - config map[string]interface{}, - ui cli.Ui) (net.Listener, map[string]string, reload.ReloadFunc, error) { - props["tls"] = "disabled" - - if v, ok := config["tls_disable"]; ok { - disabled, err := parseutil.ParseBool(v) - if err != nil { - return nil, nil, nil, errwrap.Wrapf("invalid value for 'tls_disable': {{err}}", err) - } - if disabled { - return ln, props, nil, nil - } - } - - certFileRaw, ok := config["tls_cert_file"] - if !ok { - return nil, nil, nil, fmt.Errorf("'tls_cert_file' must be set") - } - certFile := certFileRaw.(string) - keyFileRaw, ok := config["tls_key_file"] - if !ok { - return nil, nil, nil, fmt.Errorf("'tls_key_file' must be set") - } - keyFile := keyFileRaw.(string) - - cg := reload.NewCertificateGetter(certFile, keyFile, "") - if err := cg.Reload(config); err != nil { - // We try the key without a passphrase first and if we get an incorrect - // passphrase response, try again after prompting for a passphrase - if errwrap.Contains(err, x509.IncorrectPasswordError.Error()) { - var passphrase string - passphrase, err = ui.AskSecret(fmt.Sprintf("Enter passphrase for %s:", keyFile)) - if err == nil { - cg = reload.NewCertificateGetter(certFile, keyFile, passphrase) - if err = cg.Reload(config); err == nil { - goto PASSPHRASECORRECT - } - } - } - return nil, nil, nil, errwrap.Wrapf("error loading TLS cert: {{err}}", err) - } - -PASSPHRASECORRECT: - var tlsvers string - tlsversRaw, ok := config["tls_min_version"] - if !ok { - tlsvers = "tls12" - } else { - tlsvers = tlsversRaw.(string) - } - - tlsConf := &tls.Config{} - tlsConf.GetCertificate = cg.GetCertificate - tlsConf.NextProtos = []string{"h2", "http/1.1"} - tlsConf.MinVersion, ok = tlsutil.TLSLookup[tlsvers] - if !ok { - return nil, nil, nil, fmt.Errorf("'tls_min_version' value %q not supported, please specify one of [tls10,tls11,tls12]", tlsvers) - } - tlsConf.ClientAuth = tls.RequestClientCert - - if v, ok := config["tls_cipher_suites"]; ok { - ciphers, err := tlsutil.ParseCiphers(v.(string)) - if err != nil { - return nil, nil, nil, errwrap.Wrapf("invalid value for 'tls_cipher_suites': {{err}}", err) - } - - // HTTP/2 with TLS 1.2 blacklists several cipher suites. - // https://tools.ietf.org/html/rfc7540#appendix-A - // - // Since the CLI (net/http) automatically uses HTTP/2 with TLS 1.2, - // we check here if all or some specified cipher suites are blacklisted. - badCiphers := []string{} - for _, cipher := range ciphers { - if isbadcipher.IsBadCipher(cipher) { - // Get the name of the current cipher. - cipherStr, err := tlsutil.GetCipherName(cipher) - if err != nil { - return nil, nil, nil, errwrap.Wrapf("invalid value for 'tls_cipher_suites': {{err}}", err) - } - badCiphers = append(badCiphers, cipherStr) - } - } - if len(badCiphers) == len(ciphers) { - ui.Warn(`WARNING! All cipher suites defined by 'tls_cipher_suites' are blacklisted by the -HTTP/2 specification. HTTP/2 communication with TLS 1.2 will not work as intended -and Vault will be unavailable via the CLI. -Please see https://tools.ietf.org/html/rfc7540#appendix-A for further information.`) - } else if len(badCiphers) > 0 { - ui.Warn(fmt.Sprintf(`WARNING! The following cipher suites defined by 'tls_cipher_suites' are -blacklisted by the HTTP/2 specification: -%v -Please see https://tools.ietf.org/html/rfc7540#appendix-A for further information.`, badCiphers)) - } - tlsConf.CipherSuites = ciphers - } - if v, ok := config["tls_prefer_server_cipher_suites"]; ok { - preferServer, err := parseutil.ParseBool(v) - if err != nil { - return nil, nil, nil, errwrap.Wrapf("invalid value for 'tls_prefer_server_cipher_suites': {{err}}", err) - } - tlsConf.PreferServerCipherSuites = preferServer - } - var requireVerifyCerts bool - var err error - if v, ok := config["tls_require_and_verify_client_cert"]; ok { - requireVerifyCerts, err = parseutil.ParseBool(v) - if err != nil { - return nil, nil, nil, errwrap.Wrapf("invalid value for 'tls_require_and_verify_client_cert': {{err}}", err) - } - if requireVerifyCerts { - 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, errwrap.Wrapf("failed to read tls_client_ca_file: {{err}}", err) - } - - if !caPool.AppendCertsFromPEM(data) { - return nil, nil, nil, fmt.Errorf("failed to parse CA certificate in tls_client_ca_file") - } - tlsConf.ClientCAs = caPool - } - } - if v, ok := config["tls_disable_client_certs"]; ok { - disableClientCerts, err := parseutil.ParseBool(v) - if err != nil { - return nil, nil, nil, errwrap.Wrapf("invalid value for 'tls_disable_client_certs': {{err}}", err) - } - if disableClientCerts && requireVerifyCerts { - return nil, nil, nil, fmt.Errorf("'tls_disable_client_certs' and 'tls_require_and_verify_client_cert' are mutually exclusive") - } - if disableClientCerts { - tlsConf.ClientAuth = tls.NoClientCert - } - } - - ln = tls.NewListener(ln, tlsConf) - props["tls"] = "enabled" - return ln, props, cg.Reload, nil -} diff --git a/command/server/listener_tcp.go b/command/server/listener_tcp.go index 02b7b309fa..ac5938fd53 100644 --- a/command/server/listener_tcp.go +++ b/command/server/listener_tcp.go @@ -9,6 +9,7 @@ import ( "time" "github.com/hashicorp/errwrap" + "github.com/hashicorp/vault/helper/listenerutil" "github.com/hashicorp/vault/helper/parseutil" "github.com/hashicorp/vault/helper/reload" "github.com/mitchellh/cli" @@ -94,7 +95,12 @@ func tcpListenerFactory(config map[string]interface{}, _ io.Writer, ui cli.Ui) ( config["x_forwarded_for_reject_not_authorized"] = true } - return ListenerWrapTLS(ln, props, config, ui) + ln, props, reloadFunc, _, err := listenerutil.WrapTLS(ln, props, config, ui) + if err != nil { + return nil, nil, nil, err + } + + return ln, props, reloadFunc, nil } // TCPKeepAliveListener sets TCP keep-alive timeouts on accepted diff --git a/helper/listenerutil/listener.go b/helper/listenerutil/listener.go new file mode 100644 index 0000000000..755dac0c59 --- /dev/null +++ b/helper/listenerutil/listener.go @@ -0,0 +1,271 @@ +package listenerutil + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" + "net" + "os" + osuser "os/user" + "strconv" + + "github.com/hashicorp/errwrap" + "github.com/hashicorp/vault/helper/parseutil" + "github.com/hashicorp/vault/helper/reload" + "github.com/hashicorp/vault/helper/tlsutil" + "github.com/jefferai/isbadcipher" + "github.com/mitchellh/cli" +) + +type UnixSocketsConfig struct { + User string `hcl:"user"` + Mode string `hcl:"mode"` + Group string `hcl:"group"` +} + +// rmListener is an implementation of net.Listener that forwards most +// calls to the listener but also removes a file as part of the close. We +// use this to cleanup the unix domain socket on close. +type rmListener struct { + net.Listener + Path string +} + +func (l *rmListener) Close() error { + // Close the listener itself + if err := l.Listener.Close(); err != nil { + return err + } + + // Remove the file + return os.Remove(l.Path) +} + +func UnixSocketListener(path string, unixSocketsConfig *UnixSocketsConfig) (net.Listener, error) { + if err := os.Remove(path); err != nil && !os.IsNotExist(err) { + return nil, fmt.Errorf("failed to remove socket file: %v", err) + } + + ln, err := net.Listen("unix", path) + if err != nil { + return nil, err + } + + if unixSocketsConfig != nil { + err = setFilePermissions(path, unixSocketsConfig.User, unixSocketsConfig.Group, unixSocketsConfig.Mode) + if err != nil { + return nil, fmt.Errorf("failed to set file system permissions on the socket file: %s", err) + } + } + + // Wrap the listener in rmListener so that the Unix domain socket file is + // removed on close. + return &rmListener{ + Listener: ln, + Path: path, + }, nil +} + +func WrapTLS( + ln net.Listener, + props map[string]string, + config map[string]interface{}, + ui cli.Ui) (net.Listener, map[string]string, reload.ReloadFunc, *tls.Config, error) { + props["tls"] = "disabled" + + if v, ok := config["tls_disable"]; ok { + disabled, err := parseutil.ParseBool(v) + if err != nil { + return nil, nil, nil, nil, errwrap.Wrapf("invalid value for 'tls_disable': {{err}}", err) + } + if disabled { + return ln, props, nil, nil, nil + } + } + + certFileRaw, ok := config["tls_cert_file"] + if !ok { + return nil, nil, nil, nil, fmt.Errorf("'tls_cert_file' must be set") + } + certFile := certFileRaw.(string) + keyFileRaw, ok := config["tls_key_file"] + if !ok { + return nil, nil, nil, nil, fmt.Errorf("'tls_key_file' must be set") + } + keyFile := keyFileRaw.(string) + + cg := reload.NewCertificateGetter(certFile, keyFile, "") + if err := cg.Reload(config); err != nil { + // We try the key without a passphrase first and if we get an incorrect + // passphrase response, try again after prompting for a passphrase + if errwrap.Contains(err, x509.IncorrectPasswordError.Error()) { + var passphrase string + passphrase, err = ui.AskSecret(fmt.Sprintf("Enter passphrase for %s:", keyFile)) + if err == nil { + cg = reload.NewCertificateGetter(certFile, keyFile, passphrase) + if err = cg.Reload(config); err == nil { + goto PASSPHRASECORRECT + } + } + } + return nil, nil, nil, nil, errwrap.Wrapf("error loading TLS cert: {{err}}", err) + } + +PASSPHRASECORRECT: + var tlsvers string + tlsversRaw, ok := config["tls_min_version"] + if !ok { + tlsvers = "tls12" + } else { + tlsvers = tlsversRaw.(string) + } + + tlsConf := &tls.Config{} + tlsConf.GetCertificate = cg.GetCertificate + tlsConf.NextProtos = []string{"h2", "http/1.1"} + tlsConf.MinVersion, ok = tlsutil.TLSLookup[tlsvers] + if !ok { + return nil, nil, nil, nil, fmt.Errorf("'tls_min_version' value %q not supported, please specify one of [tls10,tls11,tls12]", tlsvers) + } + tlsConf.ClientAuth = tls.RequestClientCert + + if v, ok := config["tls_cipher_suites"]; ok { + ciphers, err := tlsutil.ParseCiphers(v.(string)) + if err != nil { + return nil, nil, nil, nil, errwrap.Wrapf("invalid value for 'tls_cipher_suites': {{err}}", err) + } + + // HTTP/2 with TLS 1.2 blacklists several cipher suites. + // https://tools.ietf.org/html/rfc7540#appendix-A + // + // Since the CLI (net/http) automatically uses HTTP/2 with TLS 1.2, + // we check here if all or some specified cipher suites are blacklisted. + badCiphers := []string{} + for _, cipher := range ciphers { + if isbadcipher.IsBadCipher(cipher) { + // Get the name of the current cipher. + cipherStr, err := tlsutil.GetCipherName(cipher) + if err != nil { + return nil, nil, nil, nil, errwrap.Wrapf("invalid value for 'tls_cipher_suites': {{err}}", err) + } + badCiphers = append(badCiphers, cipherStr) + } + } + if len(badCiphers) == len(ciphers) { + ui.Warn(`WARNING! All cipher suites defined by 'tls_cipher_suites' are blacklisted by the +HTTP/2 specification. HTTP/2 communication with TLS 1.2 will not work as intended +and Vault will be unavailable via the CLI. +Please see https://tools.ietf.org/html/rfc7540#appendix-A for further information.`) + } else if len(badCiphers) > 0 { + ui.Warn(fmt.Sprintf(`WARNING! The following cipher suites defined by 'tls_cipher_suites' are +blacklisted by the HTTP/2 specification: +%v +Please see https://tools.ietf.org/html/rfc7540#appendix-A for further information.`, badCiphers)) + } + tlsConf.CipherSuites = ciphers + } + if v, ok := config["tls_prefer_server_cipher_suites"]; ok { + preferServer, err := parseutil.ParseBool(v) + if err != nil { + return nil, nil, nil, nil, errwrap.Wrapf("invalid value for 'tls_prefer_server_cipher_suites': {{err}}", err) + } + tlsConf.PreferServerCipherSuites = preferServer + } + var requireVerifyCerts bool + var err error + if v, ok := config["tls_require_and_verify_client_cert"]; ok { + requireVerifyCerts, err = parseutil.ParseBool(v) + if err != nil { + return nil, nil, nil, nil, errwrap.Wrapf("invalid value for 'tls_require_and_verify_client_cert': {{err}}", err) + } + if requireVerifyCerts { + 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, nil, errwrap.Wrapf("failed to read tls_client_ca_file: {{err}}", err) + } + + if !caPool.AppendCertsFromPEM(data) { + return nil, nil, nil, nil, fmt.Errorf("failed to parse CA certificate in tls_client_ca_file") + } + tlsConf.ClientCAs = caPool + } + } + if v, ok := config["tls_disable_client_certs"]; ok { + disableClientCerts, err := parseutil.ParseBool(v) + if err != nil { + return nil, nil, nil, nil, errwrap.Wrapf("invalid value for 'tls_disable_client_certs': {{err}}", err) + } + if disableClientCerts && requireVerifyCerts { + return nil, nil, nil, nil, fmt.Errorf("'tls_disable_client_certs' and 'tls_require_and_verify_client_cert' are mutually exclusive") + } + if disableClientCerts { + tlsConf.ClientAuth = tls.NoClientCert + } + } + + ln = tls.NewListener(ln, tlsConf) + props["tls"] = "enabled" + return ln, props, cg.Reload, tlsConf, nil +} + +// setFilePermissions handles configuring ownership and permissions +// settings on a given file. All permission/ownership settings are +// optional. If no user or group is specified, the current user/group +// will be used. Mode is optional, and has no default (the operation is +// not performed if absent). User may be specified by name or ID, but +// group may only be specified by ID. +func setFilePermissions(path string, user, group, mode string) error { + var err error + uid, gid := os.Getuid(), os.Getgid() + + if user != "" { + if uid, err = strconv.Atoi(user); err == nil { + goto GROUP + } + + // Try looking up the user by name + u, err := osuser.Lookup(user) + if err != nil { + return fmt.Errorf("failed to look up user %q: %v", user, err) + } + uid, _ = strconv.Atoi(u.Uid) + } + +GROUP: + if group != "" { + if gid, err = strconv.Atoi(group); err == nil { + goto OWN + } + + // Try looking up the user by name + g, err := osuser.LookupGroup(group) + if err != nil { + return fmt.Errorf("failed to look up group %q: %v", user, err) + } + gid, _ = strconv.Atoi(g.Gid) + } + +OWN: + if err := os.Chown(path, uid, gid); err != nil { + return fmt.Errorf("failed setting ownership to %d:%d on %q: %v", + uid, gid, path, err) + } + + if mode != "" { + mode, err := strconv.ParseUint(mode, 8, 32) + if err != nil { + return fmt.Errorf("invalid mode specified: %v", mode) + } + if err := os.Chmod(path, os.FileMode(mode)); err != nil { + return fmt.Errorf("failed setting permissions to %d on %q: %v", + mode, path, err) + } + } + + return nil +} diff --git a/helper/listenerutil/listener_test.go b/helper/listenerutil/listener_test.go new file mode 100644 index 0000000000..c6bc5afad7 --- /dev/null +++ b/helper/listenerutil/listener_test.go @@ -0,0 +1,88 @@ +package listenerutil + +import ( + "io/ioutil" + "os" + osuser "os/user" + "strconv" + "testing" +) + +func TestUnixSocketListener(t *testing.T) { + t.Run("ids", func(t *testing.T) { + socket, err := ioutil.TempFile("", "socket") + if err != nil { + t.Fatal(err) + } + defer os.Remove(socket.Name()) + + uid, gid := os.Getuid(), os.Getgid() + + u, err := osuser.LookupId(strconv.Itoa(uid)) + if err != nil { + t.Fatal(err) + } + user := u.Username + + g, err := osuser.LookupGroupId(strconv.Itoa(gid)) + if err != nil { + t.Fatal(err) + } + group := g.Name + + l, err := UnixSocketListener(socket.Name(), &UnixSocketsConfig{ + User: user, + Group: group, + Mode: "644", + }) + if err != nil { + t.Fatal(err) + } + defer l.Close() + + fi, err := os.Stat(socket.Name()) + if err != nil { + t.Fatal(err) + } + + mode, err := strconv.ParseUint("644", 8, 32) + if err != nil { + t.Fatal(err) + } + if fi.Mode().Perm() != os.FileMode(mode) { + t.Fatalf("failed to set permissions on the socket file") + } + }) + t.Run("names", func(t *testing.T) { + socket, err := ioutil.TempFile("", "socket") + if err != nil { + t.Fatal(err) + } + defer os.Remove(socket.Name()) + + uid, gid := os.Getuid(), os.Getgid() + l, err := UnixSocketListener(socket.Name(), &UnixSocketsConfig{ + User: strconv.Itoa(uid), + Group: strconv.Itoa(gid), + Mode: "644", + }) + if err != nil { + t.Fatal(err) + } + defer l.Close() + + fi, err := os.Stat(socket.Name()) + if err != nil { + t.Fatal(err) + } + + mode, err := strconv.ParseUint("644", 8, 32) + if err != nil { + t.Fatal(err) + } + if fi.Mode().Perm() != os.FileMode(mode) { + t.Fatalf("failed to set permissions on the socket file") + } + }) + +} diff --git a/website/source/docs/agent/caching/index.html.md b/website/source/docs/agent/caching/index.html.md index 2425fe2b49..cbd16ce599 100644 --- a/website/source/docs/agent/caching/index.html.md +++ b/website/source/docs/agent/caching/index.html.md @@ -114,7 +114,7 @@ secrets are no longer performed by the Vault agent. Agent's listener address will be picked up by the CLI through the `VAULT_AGENT_ADDR` environment variable. This should be a complete URL such as -"http://127.0.0.1:8007". +"http://127.0.0.1:8200". ## API @@ -191,8 +191,8 @@ These configuration values are common to all `listener` blocks. - `address` `(string: required)` - The address for the listener to listen to. This can either be a URL path when using `tcp` or a file path when using - `unix`. For example, `127.0.0.1:8007` or `/path/to/socket`. Defaults to - `127.0.0.1:8007`. + `unix`. For example, `127.0.0.1:8200` or `/path/to/socket`. Defaults to + `127.0.0.1:8200`. - `tls_disable` `(bool: false)` - Specifies if TLS will be disabled. @@ -250,7 +250,7 @@ cache { } listener "tcp" { - address = "127.0.0.1:8007" + address = "127.0.0.1:8200" tls_disable = true } }