Add test for listener reloading, and update website docs.

This commit is contained in:
Jeff Mitchell 2016-03-14 14:05:47 -04:00
parent b3218d26d6
commit 0e3764832a
5 changed files with 177 additions and 67 deletions

View File

@ -2,8 +2,6 @@ package cli
import ( import (
"os" "os"
"os/signal"
"syscall"
auditFile "github.com/hashicorp/vault/builtin/audit/file" auditFile "github.com/hashicorp/vault/builtin/audit/file"
auditSyslog "github.com/hashicorp/vault/builtin/audit/syslog" auditSyslog "github.com/hashicorp/vault/builtin/audit/syslog"
@ -79,8 +77,8 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory {
"mysql": mysql.Factory, "mysql": mysql.Factory,
"ssh": ssh.Factory, "ssh": ssh.Factory,
}, },
ShutdownCh: makeShutdownCh(), ShutdownCh: command.MakeShutdownCh(),
SighupCh: makeSighupCh(), SighupCh: command.MakeSighupCh(),
ReloadFuncs: map[string][]server.ReloadFunc{}, ReloadFuncs: map[string][]server.ReloadFunc{},
}, nil }, nil
}, },
@ -311,37 +309,3 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory {
}, },
} }
} }
// makeShutdownCh returns a channel that can be used for shutdown
// notifications for commands. This channel will send a message for every
// interrupt or SIGTERM received.
func makeShutdownCh() <-chan struct{} {
resultCh := make(chan struct{})
signalCh := make(chan os.Signal, 4)
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)
go func() {
for {
<-signalCh
resultCh <- struct{}{}
}
}()
return resultCh
}
// makeSighupCh returns a channel that can be used for SIGHUP
// reloading. This channel will send a message for every
// SIGHUP received.
func makeSighupCh() <-chan struct{} {
resultCh := make(chan struct{})
signalCh := make(chan os.Signal, 4)
signal.Notify(signalCh, os.Interrupt, syscall.SIGHUP)
go func() {
for {
<-signalCh
resultCh <- struct{}{}
}
}()
return resultCh
}

View File

@ -8,10 +8,12 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"os/signal"
"runtime" "runtime"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"syscall"
"time" "time"
"github.com/armon/go-metrics" "github.com/armon/go-metrics"
@ -35,8 +37,8 @@ type ServerCommand struct {
CredentialBackends map[string]logical.Factory CredentialBackends map[string]logical.Factory
LogicalBackends map[string]logical.Factory LogicalBackends map[string]logical.Factory
ShutdownCh <-chan struct{} ShutdownCh chan struct{}
SighupCh <-chan struct{} SighupCh chan struct{}
Meta Meta
@ -637,3 +639,37 @@ General Options:
` `
return strings.TrimSpace(helpText) return strings.TrimSpace(helpText)
} }
// MakeShutdownCh returns a channel that can be used for shutdown
// notifications for commands. This channel will send a message for every
// interrupt or SIGTERM received.
func MakeShutdownCh() chan struct{} {
resultCh := make(chan struct{})
signalCh := make(chan os.Signal, 4)
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)
go func() {
for {
<-signalCh
resultCh <- struct{}{}
}
}()
return resultCh
}
// MakeSighupCh returns a channel that can be used for SIGHUP
// reloading. This channel will send a message for every
// SIGHUP received.
func MakeSighupCh() chan struct{} {
resultCh := make(chan struct{})
signalCh := make(chan os.Signal, 4)
signal.Notify(signalCh, os.Interrupt, syscall.SIGHUP)
go func() {
for {
<-signalCh
resultCh <- struct{}{}
}
}()
return resultCh
}

View File

@ -41,11 +41,7 @@ func TestTCPListener_tls(t *testing.T) {
defer os.RemoveAll(td) defer os.RemoveAll(td)
// Setup initial certs // Setup initial certs
inBytes, _ := ioutil.ReadFile(wd + "reload_foo.pem") inBytes, _ := ioutil.ReadFile(wd + "reload_ca.pem")
ioutil.WriteFile(td+"reload_curr.pem", inBytes, 0777)
inBytes, _ = ioutil.ReadFile(wd + "reload_foo.key")
ioutil.WriteFile(td+"reload_curr.key", inBytes, 0777)
inBytes, _ = ioutil.ReadFile(wd + "reload_ca.pem")
certPool := x509.NewCertPool() certPool := x509.NewCertPool()
ok := certPool.AppendCertsFromPEM(inBytes) ok := certPool.AppendCertsFromPEM(inBytes)
if !ok { if !ok {
@ -54,8 +50,8 @@ func TestTCPListener_tls(t *testing.T) {
ln, _, _, err := tcpListenerFactory(map[string]string{ ln, _, _, err := tcpListenerFactory(map[string]string{
"address": "127.0.0.1:0", "address": "127.0.0.1:0",
"tls_cert_file": td + "reload_curr.pem", "tls_cert_file": wd + "reload_foo.pem",
"tls_key_file": td + "reload_curr.key", "tls_key_file": wd + "reload_foo.key",
}) })
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
@ -75,22 +71,4 @@ func TestTCPListener_tls(t *testing.T) {
} }
testListenerImpl(t, ln, connFn, "foo.example.com") testListenerImpl(t, ln, connFn, "foo.example.com")
/*
inBytes, _ = ioutil.ReadFile(wd + "reload_bar.pem")
ioutil.WriteFile(td+"reload_curr.pem", inBytes, 0777)
inBytes, _ = ioutil.ReadFile(wd + "reload_bar.key")
ioutil.WriteFile(td+"reload_curr.key", inBytes, 0777)
req := logical.TestRequest(t, logical.UpdateOperation, "sys/reload")
req.ClientToken = root
resp, err := core.HandleRequest(req)
if err != nil {
t.Fatalf("err: %s", err)
}
if resp != nil {
t.Fatal("expected nil response")
}
testListenerImpl(t, ln, connFn, "bar.example.com")
*/
} }

View File

@ -3,11 +3,18 @@
package command package command
import ( import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil" "io/ioutil"
"math/rand"
"os" "os"
"strings" "strings"
"sync"
"testing" "testing"
"time"
"github.com/hashicorp/vault/command/server"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
@ -38,6 +45,20 @@ ha_backend "consul" {
ha_backend "file" { ha_backend "file" {
path = "/dev/null" path = "/dev/null"
} }
`
reloadhcl = `
backend "file" {
path = "/dev/null"
}
disable_mlock = true
listener "tcp" {
address = "127.0.0.1:8203"
tls_cert_file = "TMPDIR/reload_FILE.pem"
tls_key_file = "TMPDIR/reload_FILE.key"
}
` `
) )
@ -121,3 +142,111 @@ func TestServer_BadSeparateHA(t *testing.T) {
t.Fatalf("bad: should have gotten an error on a bad HA config") t.Fatalf("bad: should have gotten an error on a bad HA config")
} }
} }
func TestServer_ReloadListener(t *testing.T) {
wd, _ := os.Getwd()
wd += "/server/test-fixtures/reload/"
td, err := ioutil.TempDir("", fmt.Sprintf("vault-test-%d", rand.New(rand.NewSource(time.Now().Unix())).Int63))
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(td)
wg := &sync.WaitGroup{}
// Setup initial certs
inBytes, _ := ioutil.ReadFile(wd + "reload_foo.pem")
ioutil.WriteFile(td+"/reload_foo.pem", inBytes, 0777)
inBytes, _ = ioutil.ReadFile(wd + "reload_foo.key")
ioutil.WriteFile(td+"/reload_foo.key", inBytes, 0777)
inBytes, _ = ioutil.ReadFile(wd + "reload_bar.pem")
ioutil.WriteFile(td+"/reload_bar.pem", inBytes, 0777)
inBytes, _ = ioutil.ReadFile(wd + "reload_bar.key")
ioutil.WriteFile(td+"/reload_bar.key", inBytes, 0777)
relhcl := strings.Replace(strings.Replace(reloadhcl, "TMPDIR", td, -1), "FILE", "foo", -1)
ioutil.WriteFile(td+"/reload.hcl", []byte(relhcl), 0777)
inBytes, _ = ioutil.ReadFile(wd + "reload_ca.pem")
certPool := x509.NewCertPool()
ok := certPool.AppendCertsFromPEM(inBytes)
if !ok {
t.Fatal("not ok when appending CA cert")
}
ui := new(cli.MockUi)
c := &ServerCommand{
Meta: Meta{
Ui: ui,
},
ShutdownCh: MakeShutdownCh(),
SighupCh: MakeSighupCh(),
ReloadFuncs: map[string][]server.ReloadFunc{},
}
finished := false
finishedMutex := sync.Mutex{}
wg.Add(1)
args := []string{"-config", td + "/reload.hcl"}
go func() {
if code := c.Run(args); code != 0 {
t.Error("got a non-zero exit status")
}
finishedMutex.Lock()
finished = true
finishedMutex.Unlock()
wg.Done()
}()
checkFinished := func() {
finishedMutex.Lock()
if finished {
t.Fatal(fmt.Sprintf("finished early; relhcl was\n%s\nstdout was\n%s\nstderr was\n%s\n", relhcl, ui.OutputWriter.String(), ui.ErrorWriter.String()))
}
finishedMutex.Unlock()
}
testCertificateName := func(cn string) error {
conn, err := tls.Dial("tcp", "127.0.0.1:8203", &tls.Config{
RootCAs: certPool,
})
if err != nil {
return err
}
defer conn.Close()
if err = conn.Handshake(); err != nil {
return err
}
servName := conn.ConnectionState().PeerCertificates[0].Subject.CommonName
if servName != cn {
return fmt.Errorf("expected %s, got %s", cn, servName)
}
return nil
}
checkFinished()
time.Sleep(2 * time.Second)
checkFinished()
if err := testCertificateName("foo.example.com"); err != nil {
t.Fatalf("certificate name didn't check out: %s", err)
}
relhcl = strings.Replace(strings.Replace(reloadhcl, "TMPDIR", td, -1), "FILE", "bar", -1)
ioutil.WriteFile(td+"/reload.hcl", []byte(relhcl), 0777)
c.SighupCh <- struct{}{}
checkFinished()
time.Sleep(2 * time.Second)
checkFinished()
if err := testCertificateName("bar.example.com"); err != nil {
t.Fatalf("certificate name didn't check out: %s", err)
}
c.ShutdownCh <- struct{}{}
wg.Wait()
}

View File

@ -32,6 +32,9 @@ telemetry {
After the configuration is written, use the `-config` flag with `vault server` After the configuration is written, use the `-config` flag with `vault server`
to specify where the configuration is. to specify where the configuration is.
Starting with 0.5.2, limited configuration options can be changed on-the-fly by
sending a SIGHUP to the server process. These are denoted below.
## Reference ## Reference
* `backend` (required) - Configures the storage backend where Vault data * `backend` (required) - Configures the storage backend where Vault data
@ -93,10 +96,10 @@ The supported options are:
by default that TLS will be used. by default that TLS will be used.
* `tls_cert_file` (required unless disabled) - The path to the certificate * `tls_cert_file` (required unless disabled) - The path to the certificate
for TLS. for TLS. This is reloaded via SIGHUP.
* `tls_key_file` (required unless disabled) - The path to the private key * `tls_key_file` (required unless disabled) - The path to the private key
for the certificate. for the certificate. This is reloaded via SIGHUP.
* `tls_min_version` (optional) - **(Vault > 0.2)** If provided, specifies * `tls_min_version` (optional) - **(Vault > 0.2)** If provided, specifies
the minimum supported version of TLS. Accepted values are "tls10", "tls11" the minimum supported version of TLS. Accepted values are "tls10", "tls11"