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
This commit is contained in:
Vishal Nayak
2019-03-14 14:53:14 -04:00
committed by Brian Kassouf
parent c695f93852
commit 3c7c593bca
10 changed files with 440 additions and 252 deletions

View File

@@ -419,35 +419,37 @@ func (c *AgentCommand) Run(args []string) int {
var listeners []net.Listener var listeners []net.Listener
for i, lnConfig := range config.Cache.Listeners { 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 { 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 return 1
} }
listeners = append(listeners, listener) listeners = append(listeners, ln)
scheme := "https://" scheme := "https://"
if props["tls"] == "disabled" { if tlsConf == nil {
scheme = "http://" scheme = "http://"
} }
if lnConfig.Type == "unix" { if ln.Addr().Network() == "unix" {
scheme = "unix://" scheme = "unix://"
} }
infoKey := fmt.Sprintf("api address %d", i+1) infoKey := fmt.Sprintf("api address %d", i+1)
info[infoKey] = scheme + listener.Addr().String() info[infoKey] = scheme + ln.Addr().String()
infoKeys = append(infoKeys, infoKey) infoKeys = append(infoKeys, infoKey)
cacheLogger.Info("starting listener", "addr", listener.Addr().String())
server := &http.Server{ server := &http.Server{
Addr: ln.Addr().String(),
TLSConfig: tlsConf,
Handler: mux, Handler: mux,
ReadHeaderTimeout: 10 * time.Second, ReadHeaderTimeout: 10 * time.Second,
ReadTimeout: 30 * time.Second, ReadTimeout: 30 * time.Second,
IdleTimeout: 5 * time.Minute, IdleTimeout: 5 * time.Minute,
ErrorLog: cacheLogger.StandardLogger(nil), ErrorLog: cacheLogger.StandardLogger(nil),
} }
go server.Serve(listener)
go server.Serve(ln)
} }
// Ensure that listeners are closed at all the exits // Ensure that listeners are closed at all the exits

View File

@@ -1,105 +1,69 @@
package cache package cache
import ( import (
"crypto/tls"
"fmt" "fmt"
"io"
"net" "net"
"os"
"strings" "strings"
"github.com/hashicorp/vault/command/agent/config" "github.com/hashicorp/vault/command/agent/config"
"github.com/hashicorp/vault/command/server" "github.com/hashicorp/vault/command/server"
"github.com/hashicorp/vault/helper/reload" "github.com/hashicorp/vault/helper/listenerutil"
"github.com/mitchellh/cli"
) )
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 { switch lnConfig.Type {
case "unix":
return unixSocketListener(lnConfig.Config, logger, ui)
case "tcp": case "tcp":
return tcpListener(lnConfig.Config, logger, ui)
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"])
}
if addr == "" { if addr == "" {
return nil, nil, nil, fmt.Errorf("address field should point to socket file path") addr = "127.0.0.1:8200"
} }
// 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)
if err != nil {
return nil, 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" 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 // If they've passed 0.0.0.0, we only want to bind on IPv4
// rather than golang's dual stack default // rather than golang's dual stack default
if strings.HasPrefix(addr, "0.0.0.0:") { if strings.HasPrefix(addr, "0.0.0.0:") {
bindProto = "tcp4" bindProto = "tcp4"
} }
ln, err := net.Listen(bindProto, addr) ln, err = net.Listen(bindProto, addr)
if err != nil { if err != nil {
return nil, nil, nil, err 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
} }
ln = server.TCPKeepAliveListener{ln.(*net.TCPListener)} default:
return nil, nil, fmt.Errorf("invalid listener type: %q", lnConfig.Type)
props := map[string]string{"addr": addr}
return server.ListenerWrapTLS(ln, props, config, ui)
} }
// rmListener is an implementation of net.Listener that forwards most props := map[string]string{"addr": ln.Addr().String()}
// calls to the listener but also removes a file as part of the close. We ln, props, _, tlsConf, err := listenerutil.WrapTLS(ln, props, lnConfig.Config, nil)
// use this to cleanup the unix domain socket on close. if err != nil {
type rmListener struct { return nil, nil, err
net.Listener
Path string
} }
func (l *rmListener) Close() error { return ln, tlsConf, nil
// Close the listener itself
if err := l.Listener.Close(); err != nil {
return err
}
// Remove the file
return os.Remove(l.Path)
} }

View File

@@ -48,6 +48,9 @@ func TestLoadConfigFile_AgentCache(t *testing.T) {
Config: map[string]interface{}{ Config: map[string]interface{}{
"address": "/path/to/socket", "address": "/path/to/socket",
"tls_disable": true, "tls_disable": true,
"socket_mode": "configmode",
"socket_user": "configuser",
"socket_group": "configgroup",
}, },
}, },
&Listener{ &Listener{

View File

@@ -27,6 +27,9 @@ cache {
type = "unix" type = "unix"
address = "/path/to/socket" address = "/path/to/socket"
tls_disable = true tls_disable = true
socket_mode = "configmode"
socket_user = "configuser"
socket_group = "configgroup"
} }
listener { listener {

View File

@@ -26,6 +26,9 @@ cache {
listener "unix" { listener "unix" {
address = "/path/to/socket" address = "/path/to/socket"
tls_disable = true tls_disable = true
socket_mode = "configmode"
socket_user = "configuser"
socket_group = "configgroup"
} }
listener "tcp" { listener "tcp" {

View File

@@ -5,18 +5,12 @@ import (
// We must import sha512 so that it registers with the runtime so that // We must import sha512 so that it registers with the runtime so that
// certificates that use it can be parsed. // certificates that use it can be parsed.
_ "crypto/sha512" _ "crypto/sha512"
"crypto/tls"
"crypto/x509"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net" "net"
"github.com/hashicorp/vault/helper/parseutil"
"github.com/hashicorp/vault/helper/proxyutil" "github.com/hashicorp/vault/helper/proxyutil"
"github.com/hashicorp/vault/helper/reload" "github.com/hashicorp/vault/helper/reload"
"github.com/hashicorp/vault/helper/tlsutil"
"github.com/jefferai/isbadcipher"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
@@ -72,149 +66,3 @@ func listenerWrapProxy(ln net.Listener, config map[string]interface{}) (net.List
return newLn, nil 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
}

View File

@@ -9,6 +9,7 @@ import (
"time" "time"
"github.com/hashicorp/errwrap" "github.com/hashicorp/errwrap"
"github.com/hashicorp/vault/helper/listenerutil"
"github.com/hashicorp/vault/helper/parseutil" "github.com/hashicorp/vault/helper/parseutil"
"github.com/hashicorp/vault/helper/reload" "github.com/hashicorp/vault/helper/reload"
"github.com/mitchellh/cli" "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 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 // TCPKeepAliveListener sets TCP keep-alive timeouts on accepted

View File

@@ -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
}

View File

@@ -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")
}
})
}

View File

@@ -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 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 `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 ## 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. - `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 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 `unix`. For example, `127.0.0.1:8200` or `/path/to/socket`. Defaults to
`127.0.0.1:8007`. `127.0.0.1:8200`.
- `tls_disable` `(bool: false)` - Specifies if TLS will be disabled. - `tls_disable` `(bool: false)` - Specifies if TLS will be disabled.
@@ -250,7 +250,7 @@ cache {
} }
listener "tcp" { listener "tcp" {
address = "127.0.0.1:8007" address = "127.0.0.1:8200"
tls_disable = true tls_disable = true
} }
} }