diff --git a/command/server.go b/command/server.go index 29ebf0c5f5..3bab492ebd 100644 --- a/command/server.go +++ b/command/server.go @@ -3,12 +3,16 @@ package command import ( "encoding/hex" "fmt" + "log" "net" "net/http" + "os" + "sort" "strings" "github.com/hashicorp/vault/command/server" "github.com/hashicorp/vault/helper/flag-slice" + "github.com/hashicorp/vault/helper/gated-writer" vaulthttp "github.com/hashicorp/vault/http" "github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/physical" @@ -61,6 +65,11 @@ func (c *ServerCommand) Run(args []string) int { } } + // Create a logger. We wrap it in a gated writer so that it doesn't + // start logging too early. + logGate := &gatedwriter.Writer{Writer: os.Stderr} + logger := log.New(logGate, "", log.LstdFlags) + // Initialize the backend backend, err := physical.NewBackend( config.Backend.Type, config.Backend.Config) @@ -76,6 +85,7 @@ func (c *ServerCommand) Run(args []string) int { Physical: backend, CredentialBackends: c.CredentialBackends, LogicalBackends: c.LogicalBackends, + Logger: logger, }) // If we're in dev mode, then initialize the core @@ -95,16 +105,22 @@ func (c *ServerCommand) Run(args []string) int { "immediately begin using the Vault CLI.\n\n"+ "The unseal key and root token are reproduced below in case you\n"+ "want to seal/unseal the Vault or play with authentication.\n\n"+ - "Unseal Key: %s\nRoot Token: %s\n\n", + "Unseal Key: %s\nRoot Token: %s\n", hex.EncodeToString(init.SecretShares[0]), init.RootToken, )) } + // Compile server information for output later + infoKeys := make([]string, 0, 10) + info := make(map[string]string) + info["backend"] = config.Backend.Type + infoKeys = append(infoKeys, "backend") + // Initialize the listeners lns := make([]net.Listener, 0, len(config.Listeners)) - for _, lnConfig := range config.Listeners { - ln, err := server.NewListener(lnConfig.Type, lnConfig.Config) + for i, lnConfig := range config.Listeners { + ln, props, err := server.NewListener(lnConfig.Type, lnConfig.Config) if err != nil { c.Ui.Error(fmt.Sprintf( "Error initializing listener of type %s: %s", @@ -112,6 +128,18 @@ func (c *ServerCommand) Run(args []string) int { return 1 } + // Store the listener props for output later + key := fmt.Sprintf("listener %d", i+1) + propsList := make([]string, 0, len(props)) + for k, v := range props { + propsList = append(propsList, fmt.Sprintf( + "%s: %q", k, v)) + } + sort.Strings(propsList) + infoKeys = append(infoKeys, key) + info[key] = fmt.Sprintf( + "%s (%s)", lnConfig.Type, strings.Join(propsList, ", ")) + lns = append(lns, ln) } @@ -122,6 +150,24 @@ func (c *ServerCommand) Run(args []string) int { go server.Serve(ln) } + // Server configuration output + padding := 18 + c.Ui.Output("==> Vault server configuration:\n") + for _, k := range infoKeys { + c.Ui.Output(fmt.Sprintf( + "%s%s: %s", + strings.Repeat(" ", padding-len(k)), + strings.Title(k), + info[k])) + } + c.Ui.Output("") + + // Output the header that the server has started + c.Ui.Output("==> Vault server started! Log data will stream in below:\n") + + // Release the log gate. + logGate.Flush() + <-make(chan struct{}) return 0 } diff --git a/command/server/listener.go b/command/server/listener.go index f723bedd0c..7d1de552fd 100644 --- a/command/server/listener.go +++ b/command/server/listener.go @@ -7,7 +7,7 @@ import ( ) // ListenerFactory is the factory function to create a listener. -type ListenerFactory func(map[string]string) (net.Listener, error) +type ListenerFactory func(map[string]string) (net.Listener, map[string]string, error) // BuiltinListeners is the list of built-in listener types. var BuiltinListeners = map[string]ListenerFactory{ @@ -16,34 +16,38 @@ 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]string) (net.Listener, error) { +func NewListener(t string, config map[string]string) (net.Listener, map[string]string, error) { f, ok := BuiltinListeners[t] if !ok { - return nil, fmt.Errorf("unknown listener type: %s", t) + return nil, nil, fmt.Errorf("unknown listener type: %s", t) } return f(config) } func listenerWrapTLS( - ln net.Listener, config map[string]string) (net.Listener, error) { + ln net.Listener, + props map[string]string, + config map[string]string) (net.Listener, map[string]string, error) { + props["tls"] = "disabled" + if v, ok := config["tls_disable"]; ok && v != "" { - return ln, nil + return ln, props, nil } certFile, ok := config["tls_cert_file"] if !ok { - return nil, fmt.Errorf("'tls_cert_file' must be set") + return nil, nil, fmt.Errorf("'tls_cert_file' must be set") } keyFile, ok := config["tls_key_file"] if !ok { - return nil, fmt.Errorf("'tls_key_file' must be set") + return nil, nil, fmt.Errorf("'tls_key_file' must be set") } cert, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { - return nil, fmt.Errorf("error loading TLS cert: %s", err) + return nil, nil, fmt.Errorf("error loading TLS cert: %s", err) } tlsConf := &tls.Config{} @@ -51,5 +55,6 @@ func listenerWrapTLS( tlsConf.NextProtos = []string{"http/1.1"} ln = tls.NewListener(ln, tlsConf) - return ln, nil + props["tls"] = "enabled" + return ln, props, nil } diff --git a/command/server/listener_tcp.go b/command/server/listener_tcp.go index 04565a8c79..f4f833dbcc 100644 --- a/command/server/listener_tcp.go +++ b/command/server/listener_tcp.go @@ -5,7 +5,7 @@ import ( "time" ) -func tcpListenerFactory(config map[string]string) (net.Listener, error) { +func tcpListenerFactory(config map[string]string) (net.Listener, map[string]string, error) { addr, ok := config["address"] if !ok { addr = "127.0.0.1:8200" @@ -13,11 +13,12 @@ func tcpListenerFactory(config map[string]string) (net.Listener, error) { ln, err := net.Listen("tcp", addr) if err != nil { - return nil, err + return nil, nil, err } ln = tcpKeepAliveListener{ln.(*net.TCPListener)} - return listenerWrapTLS(ln, config) + props := map[string]string{"addr": addr} + return listenerWrapTLS(ln, props, config) } // tcpKeepAliveListener sets TCP keep-alive timeouts on accepted diff --git a/helper/gated-writer/writer.go b/helper/gated-writer/writer.go new file mode 100644 index 0000000000..9c5aeba00f --- /dev/null +++ b/helper/gated-writer/writer.go @@ -0,0 +1,43 @@ +package gatedwriter + +import ( + "io" + "sync" +) + +// Writer is an io.Writer implementation that buffers all of its +// data into an internal buffer until it is told to let data through. +type Writer struct { + Writer io.Writer + + buf [][]byte + flush bool + lock sync.RWMutex +} + +// Flush tells the Writer to flush any buffered data and to stop +// buffering. +func (w *Writer) Flush() { + w.lock.Lock() + w.flush = true + w.lock.Unlock() + + for _, p := range w.buf { + w.Write(p) + } + w.buf = nil +} + +func (w *Writer) Write(p []byte) (n int, err error) { + w.lock.RLock() + defer w.lock.RUnlock() + + if w.flush { + return w.Writer.Write(p) + } + + p2 := make([]byte, len(p)) + copy(p2, p) + w.buf = append(w.buf, p2) + return len(p), nil +} diff --git a/helper/gated-writer/writer_test.go b/helper/gated-writer/writer_test.go new file mode 100644 index 0000000000..b007ef1fa8 --- /dev/null +++ b/helper/gated-writer/writer_test.go @@ -0,0 +1,34 @@ +package gatedwriter + +import ( + "bytes" + "io" + "testing" +) + +func TestWriter_impl(t *testing.T) { + var _ io.Writer = new(Writer) +} + +func TestWriter(t *testing.T) { + buf := new(bytes.Buffer) + w := &Writer{Writer: buf} + w.Write([]byte("foo\n")) + w.Write([]byte("bar\n")) + + if buf.String() != "" { + t.Fatalf("bad: %s", buf.String()) + } + + w.Flush() + + if buf.String() != "foo\nbar\n" { + t.Fatalf("bad: %s", buf.String()) + } + + w.Write([]byte("baz\n")) + + if buf.String() != "foo\nbar\nbaz\n" { + t.Fatalf("bad: %s", buf.String()) + } +}