mirror of
https://github.com/hashicorp/vault.git
synced 2026-04-01 07:08:05 -04:00
Merge remote-tracking branch 'oss/master' into f-nomad
* oss/master: Add support for encrypted TLS key files (#3685)
This commit is contained in:
commit
6c19fa3b78
8 changed files with 155 additions and 28 deletions
|
|
@ -475,7 +475,7 @@ CLUSTER_SYNTHESIS_COMPLETE:
|
|||
c.reloadFuncsLock.Lock()
|
||||
lns := make([]net.Listener, 0, len(config.Listeners))
|
||||
for i, lnConfig := range config.Listeners {
|
||||
ln, props, reloadFunc, err := server.NewListener(lnConfig.Type, lnConfig.Config, c.logGate)
|
||||
ln, props, reloadFunc, err := server.NewListener(lnConfig.Type, lnConfig.Config, c.logGate, c.Ui)
|
||||
if err != nil {
|
||||
c.Ui.Output(fmt.Sprintf(
|
||||
"Error initializing listener of type %s: %s",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/errwrap"
|
||||
// We must import sha512 so that it registers with the runtime so that
|
||||
// certificates that use it can be parsed.
|
||||
_ "crypto/sha512"
|
||||
|
|
@ -15,10 +16,11 @@ import (
|
|||
"github.com/hashicorp/vault/helper/proxyutil"
|
||||
"github.com/hashicorp/vault/helper/reload"
|
||||
"github.com/hashicorp/vault/helper/tlsutil"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
// ListenerFactory is the factory function to create a listener.
|
||||
type ListenerFactory func(map[string]interface{}, io.Writer) (net.Listener, map[string]string, reload.ReloadFunc, error)
|
||||
type ListenerFactory func(map[string]interface{}, io.Writer, cli.Ui) (net.Listener, map[string]string, reload.ReloadFunc, error)
|
||||
|
||||
// BuiltinListeners is the list of built-in listener types.
|
||||
var BuiltinListeners = map[string]ListenerFactory{
|
||||
|
|
@ -27,13 +29,13 @@ var BuiltinListeners = map[string]ListenerFactory{
|
|||
|
||||
// NewListener creates a new listener of the given type with the given
|
||||
// configuration. The type is looked up in the BuiltinListeners map.
|
||||
func NewListener(t string, config map[string]interface{}, logger io.Writer) (net.Listener, map[string]string, reload.ReloadFunc, error) {
|
||||
func NewListener(t string, config map[string]interface{}, logger io.Writer, ui cli.Ui) (net.Listener, map[string]string, reload.ReloadFunc, error) {
|
||||
f, ok := BuiltinListeners[t]
|
||||
if !ok {
|
||||
return nil, nil, nil, fmt.Errorf("unknown listener type: %s", t)
|
||||
}
|
||||
|
||||
return f(config, logger)
|
||||
return f(config, logger, ui)
|
||||
}
|
||||
|
||||
func listenerWrapProxy(ln net.Listener, config map[string]interface{}) (net.Listener, error) {
|
||||
|
|
@ -70,7 +72,8 @@ func listenerWrapProxy(ln net.Listener, config map[string]interface{}) (net.List
|
|||
func listenerWrapTLS(
|
||||
ln net.Listener,
|
||||
props map[string]string,
|
||||
config map[string]interface{}) (net.Listener, map[string]string, reload.ReloadFunc, error) {
|
||||
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 {
|
||||
|
|
@ -83,22 +86,35 @@ func listenerWrapTLS(
|
|||
}
|
||||
}
|
||||
|
||||
_, ok := config["tls_cert_file"]
|
||||
certFileRaw, ok := config["tls_cert_file"]
|
||||
if !ok {
|
||||
return nil, nil, nil, fmt.Errorf("'tls_cert_file' must be set")
|
||||
}
|
||||
|
||||
_, ok = config["tls_key_file"]
|
||||
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(config["tls_cert_file"].(string), config["tls_key_file"].(string))
|
||||
|
||||
cg := reload.NewCertificateGetter(certFile, keyFile, "")
|
||||
if err := cg.Reload(config); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("error loading TLS cert: %s", err)
|
||||
// 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 {
|
||||
|
|
|
|||
|
|
@ -7,10 +7,11 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/helper/reload"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func tcpListenerFactory(config map[string]interface{}, _ io.Writer) (net.Listener, map[string]string, reload.ReloadFunc, error) {
|
||||
bind_proto := "tcp"
|
||||
func tcpListenerFactory(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 {
|
||||
|
|
@ -22,10 +23,10 @@ func tcpListenerFactory(config map[string]interface{}, _ io.Writer) (net.Listene
|
|||
// 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:") {
|
||||
bind_proto = "tcp4"
|
||||
bindProto = "tcp4"
|
||||
}
|
||||
|
||||
ln, err := net.Listen(bind_proto, addr)
|
||||
ln, err := net.Listen(bindProto, addr)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
|
@ -38,7 +39,7 @@ func tcpListenerFactory(config map[string]interface{}, _ io.Writer) (net.Listene
|
|||
}
|
||||
|
||||
props := map[string]string{"addr": addr}
|
||||
return listenerWrapTLS(ln, props, config)
|
||||
return listenerWrapTLS(ln, props, config, ui)
|
||||
}
|
||||
|
||||
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
|
||||
|
|
|
|||
|
|
@ -10,13 +10,15 @@ import (
|
|||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func TestTCPListener(t *testing.T) {
|
||||
ln, _, _, err := tcpListenerFactory(map[string]interface{}{
|
||||
"address": "127.0.0.1:0",
|
||||
"tls_disable": "1",
|
||||
}, nil)
|
||||
}, nil, cli.NewMockUi())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
|
@ -54,7 +56,7 @@ func TestTCPListener_tls(t *testing.T) {
|
|||
"tls_key_file": wd + "reload_foo.key",
|
||||
"tls_require_and_verify_client_cert": "true",
|
||||
"tls_client_ca_file": wd + "reload_ca.pem",
|
||||
}, nil)
|
||||
}, nil, cli.NewMockUi())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
|
@ -93,7 +95,7 @@ func TestTCPListener_tls(t *testing.T) {
|
|||
"tls_require_and_verify_client_cert": "true",
|
||||
"tls_disable_client_certs": "true",
|
||||
"tls_client_ca_file": wd + "reload_ca.pem",
|
||||
}, nil)
|
||||
}, nil, cli.NewMockUi())
|
||||
if err == nil {
|
||||
t.Fatal("expected error due to mutually exclusive client cert options")
|
||||
}
|
||||
|
|
@ -104,7 +106,7 @@ func TestTCPListener_tls(t *testing.T) {
|
|||
"tls_key_file": wd + "reload_foo.key",
|
||||
"tls_disable_client_certs": "true",
|
||||
"tls_client_ca_file": wd + "reload_ca.pem",
|
||||
}, nil)
|
||||
}, nil, cli.NewMockUi())
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,14 @@ package reload
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
)
|
||||
|
||||
// ReloadFunc are functions that are called when a reload is requested
|
||||
|
|
@ -17,19 +23,44 @@ type CertificateGetter struct {
|
|||
|
||||
cert *tls.Certificate
|
||||
|
||||
certFile string
|
||||
keyFile string
|
||||
certFile string
|
||||
keyFile string
|
||||
passphrase string
|
||||
}
|
||||
|
||||
func NewCertificateGetter(certFile, keyFile string) *CertificateGetter {
|
||||
func NewCertificateGetter(certFile, keyFile, passphrase string) *CertificateGetter {
|
||||
return &CertificateGetter{
|
||||
certFile: certFile,
|
||||
keyFile: keyFile,
|
||||
certFile: certFile,
|
||||
keyFile: keyFile,
|
||||
passphrase: passphrase,
|
||||
}
|
||||
}
|
||||
|
||||
func (cg *CertificateGetter) Reload(_ map[string]interface{}) error {
|
||||
cert, err := tls.LoadX509KeyPair(cg.certFile, cg.keyFile)
|
||||
certPEMBlock, err := ioutil.ReadFile(cg.certFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keyPEMBlock, err := ioutil.ReadFile(cg.keyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check for encrypted pem block
|
||||
keyBlock, _ := pem.Decode(keyPEMBlock)
|
||||
if keyBlock == nil {
|
||||
return errors.New("Decoded PEM is blank")
|
||||
}
|
||||
|
||||
if x509.IsEncryptedPEMBlock(keyBlock) {
|
||||
keyBlock.Bytes, err = x509.DecryptPEMBlock(keyBlock, []byte(cg.passphrase))
|
||||
if err != nil {
|
||||
return errwrap.Wrapf("Decrypting PEM block failed {{err}}", err)
|
||||
}
|
||||
keyPEMBlock = pem.EncodeToMemory(keyBlock)
|
||||
}
|
||||
|
||||
cert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
74
helper/reload/reload_test.go
Normal file
74
helper/reload/reload_test.go
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
package reload
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
)
|
||||
|
||||
func TestReload_KeyWithPassphrase(t *testing.T) {
|
||||
password := "password"
|
||||
cert := []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIICLzCCAZgCCQCq27CeP4WhlDANBgkqhkiG9w0BAQUFADBcMQswCQYDVQQGEwJV
|
||||
UzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEjAQBgNVBAoM
|
||||
CUhhc2hpQ29ycDEUMBIGA1UEAwwLbXl2YXVsdC5jb20wHhcNMTcxMjEzMjEzNTM3
|
||||
WhcNMTgxMjEzMjEzNTM3WjBcMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExFjAU
|
||||
BgNVBAcMDVNhbiBGcmFuY2lzY28xEjAQBgNVBAoMCUhhc2hpQ29ycDEUMBIGA1UE
|
||||
AwwLbXl2YXVsdC5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMvsz/9l
|
||||
EJIlRG6DOw4fXdB/aJgJk2rR8cU0D8+vECIzb+MdDK0cBHtLiVpZC/RnZMdMzjGn
|
||||
Z++Fp3dEnT6CD0IjKdJcD+qSyZSjHIuYpHjnjrVlM/Le0xST7egoG+fXkSt4myzG
|
||||
ec2WK1jcZefRRGPycvMqx1yUWU76jDdFZSL5AgMBAAEwDQYJKoZIhvcNAQEFBQAD
|
||||
gYEAQfYE26FLZ9SPPU8bHNDxoxDmGrn8yJ78C490Qpix/w6gdLaBtILenrZbhpnB
|
||||
3L3okraM8mplaN2KdAcpnsr4wPv9hbYkam0coxCQEKs8ltHSBaXT6uKRWb00nkGu
|
||||
yAXDRpuPdFRqbXW3ZFC5broUrz4ujxTDKfVeIn0zpPZkv24=
|
||||
-----END CERTIFICATE-----`)
|
||||
key := []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: DES-EDE3-CBC,64B032D83BD6A6DC
|
||||
|
||||
qVJ+mXEBKMkUPrQ8odHunMpPgChQUny4CX73/dAcm7O9iXIv9eXQSxj2qfgCOloj
|
||||
vthg7jYNwtRb0ydzCEnEud35zWw38K/l19/pe4ULfNXlOddlsk4XIHarBiz+KUaX
|
||||
WTbNk0H+DwdcEwhprPgpTk8gp88lZBiHCnTG/s8v/JNt+wkdqjfAp0Xbm9m+OZ7s
|
||||
hlNxZin1OuBdprBqfKWBltUALZYiIBhspMTmh+jGQSyEKNTAIBejIiRH5+xYWuOy
|
||||
xKencq8UpQMOMPR2ZiSw42dU9j8HHMgldI7KszU2FDIEFXG7aSjcxNyyybeBT+Uz
|
||||
YPoxGxSdUYWqaz50UszvHg/QWR8NlPlQc3nFAUVpGKUF9MEQCIAK8HjcpMP+IAVO
|
||||
ertp4cTa2Rpm9YeoFrY6tabvmXApXlQPw6rBn6o5KpceWG3ceOsDOsT+e3edHu9g
|
||||
SGO4hjggbRpO+dBOuwfw4rMn9X1BbqXKJcREAmrgVVSf9/s942E4YOQ+IGJPdtmY
|
||||
WHAFk8hiJepsVCA2NpwVlAD+QbPPaR2RtvYOtq3IKlWRuVQ+6dpxDsz5FlJhs2L+
|
||||
HsX6XqtwuQM8kk1hO8Gm3VeV7+b64r9kfbO8jCM18GexCYiCtig51mJW6IO42d1K
|
||||
bS1axMx/KeDc/sy7LKEbHnjnYanpGz2Wa2EWhnWAeNXD1nUfUNFPp2SsIGbCMnat
|
||||
mC4O4cO7YRl3+iJg3kHtTPGtgtCjrZcjlyBtxT2VC7SsTcTXZBWovczMIstyr4Ka
|
||||
opM24uvQT3Bc0UM0WNh3tdRFuboxDeBDh7PX/2RIoiaMuCCiRZ3O0A==
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
tempDir, err := ioutil.TempDir("", "vault-test")
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating temporary directory: %s", err)
|
||||
}
|
||||
keyFile := tempDir + "/server.key"
|
||||
certFile := tempDir + "/server.crt"
|
||||
|
||||
err = ioutil.WriteFile(certFile, cert, 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("Error writing to temp file: %s", err)
|
||||
}
|
||||
err = ioutil.WriteFile(keyFile, key, 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("Error writing to temp file: %s", err)
|
||||
}
|
||||
|
||||
cg := NewCertificateGetter(certFile, keyFile, "")
|
||||
err = cg.Reload(nil)
|
||||
if err == nil {
|
||||
t.Fatal("error expected")
|
||||
}
|
||||
if !errwrap.Contains(err, x509.IncorrectPasswordError.Error()) {
|
||||
t.Fatalf("expected incorrect password error, got %v", err)
|
||||
}
|
||||
|
||||
cg = NewCertificateGetter(certFile, keyFile, password)
|
||||
if err := cg.Reload(nil); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -1090,7 +1090,7 @@ func NewTestCluster(t testing.T, base *CoreConfig, opts *TestClusterOptions) *Te
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
certGetter := reload.NewCertificateGetter(certFile, keyFile)
|
||||
certGetter := reload.NewCertificateGetter(certFile, keyFile, "")
|
||||
certGetters = append(certGetters, certGetter)
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{tlsCert},
|
||||
|
|
|
|||
|
|
@ -53,7 +53,10 @@ listener "tcp" {
|
|||
combined file.
|
||||
|
||||
- `tls_key_file` `(string: <required-if-enabled>, reloads-on-SIGHUP)` –
|
||||
Specifies the path to the private key for the certificate.
|
||||
Specifies the path to the private key for the certificate. If the key file
|
||||
is encrypted, you will be prompted to enter the passphrase on server startup.
|
||||
The passphrase must stay the same between key files when reloading your
|
||||
configuration using SIGHUP.
|
||||
|
||||
- `tls_min_version` `(string: "tls12")` – Specifies the minimum supported
|
||||
version of TLS. Accepted values are "tls10", "tls11" or "tls12".
|
||||
|
|
|
|||
Loading…
Reference in a new issue