mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-02 03:27:54 +00:00
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:
committed by
Brian Kassouf
parent
c695f93852
commit
3c7c593bca
@@ -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
|
||||
|
||||
134
command/agent/cache/listener.go
vendored
134
command/agent/cache/listener.go
vendored
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -27,6 +27,9 @@ cache {
|
||||
type = "unix"
|
||||
address = "/path/to/socket"
|
||||
tls_disable = true
|
||||
socket_mode = "configmode"
|
||||
socket_user = "configuser"
|
||||
socket_group = "configgroup"
|
||||
}
|
||||
|
||||
listener {
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
271
helper/listenerutil/listener.go
Normal file
271
helper/listenerutil/listener.go
Normal 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
|
||||
}
|
||||
88
helper/listenerutil/listener_test.go
Normal file
88
helper/listenerutil/listener_test.go
Normal 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")
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user