diff --git a/.gitignore b/.gitignore
index 0d5e304039..7dc579c775 100644
--- a/.gitignore
+++ b/.gitignore
@@ -65,6 +65,7 @@ tags
ui/dist
ui/tmp
ui/root
+http/bindata_assetfs.go
# dependencies
ui/node_modules
diff --git a/command/server.go b/command/server.go
index 0241bd6b90..ff9f9fb614 100644
--- a/command/server.go
+++ b/command/server.go
@@ -452,6 +452,7 @@ func (c *ServerCommand) Run(args []string) int {
ClusterName: config.ClusterName,
CacheSize: config.CacheSize,
PluginDirectory: config.PluginDirectory,
+ EnableUI: config.EnableUI,
EnableRaw: config.EnableRawEndpoint,
}
if c.flagDev {
@@ -607,6 +608,16 @@ CLUSTER_SYNTHESIS_COMPLETE:
coreConfig.ClusterAddr = u.String()
}
+ // Override the UI enabling config by the environment variable
+ if enableUI := os.Getenv("VAULT_UI"); enableUI != "" {
+ var err error
+ coreConfig.EnableUI, err = strconv.ParseBool(enableUI)
+ if err != nil {
+ c.UI.Output("Error parsing the environment variable VAULT_UI")
+ return 1
+ }
+ }
+
// Initialize the core
core, newCoreError := vault.NewCore(coreConfig)
if newCoreError != nil {
diff --git a/http/handler.go b/http/handler.go
index 55d1bd458e..2bad9024eb 100644
--- a/http/handler.go
+++ b/http/handler.go
@@ -6,9 +6,11 @@ import (
"io"
"net/http"
"net/url"
+ "os"
"strings"
"time"
+ "github.com/elazarl/go-bindata-assetfs"
"github.com/hashicorp/errwrap"
cleanhttp "github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/vault/helper/consts"
@@ -54,6 +56,9 @@ const (
var (
ReplicationStaleReadTimeout = 2 * time.Second
+
+ // Set to false by stub_asset if the ui build tag isn't enabled
+ uiBuiltIn = true
)
// Handler returns an http.Handler for the API. This can be used on
@@ -82,6 +87,14 @@ func Handler(core *vault.Core) http.Handler {
}
mux.Handle("/v1/sys/", handleRequestForwarding(core, handleLogical(core, false, nil)))
mux.Handle("/v1/", handleRequestForwarding(core, handleLogical(core, false, nil)))
+ if core.UIEnabled() == true {
+ if uiBuiltIn {
+ mux.Handle("/ui/", http.StripPrefix("/ui/", handleUIHeaders(core, handleUI(http.FileServer(&UIAssetWrapper{FileSystem: assetFS()})))))
+ } else {
+ mux.Handle("/ui/", handleUIHeaders(core, handleUIStub()))
+ }
+ mux.Handle("/", handleRootRedirect())
+ }
// Wrap the handler in another handler to trigger all help paths.
helpWrappedHandler := wrapHelpHandler(mux, core)
@@ -145,6 +158,72 @@ func stripPrefix(prefix, path string) (string, bool) {
return path, true
}
+func handleUIHeaders(core *vault.Core, h http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ header := w.Header()
+
+ userHeaders, err := core.UIHeaders()
+ if err != nil {
+ respondError(w, http.StatusInternalServerError, err)
+ return
+ }
+ if userHeaders != nil {
+ for k := range userHeaders {
+ v := userHeaders.Get(k)
+ header.Set(k, v)
+ }
+ }
+ h.ServeHTTP(w, req)
+ })
+}
+
+func handleUI(h http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ h.ServeHTTP(w, req)
+ return
+ })
+}
+
+func handleUIStub() http.Handler {
+ stubHTML := `
+
+
+
Vault UI is not available in this binary. To get Vault UI do one of the following:
+
+ - Download an official release
+ - Run
make release to create your own release binaries.
+ - Run
make dev-ui to create a development binary with the UI.
+
+
+ `
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ w.Write([]byte(stubHTML))
+ })
+}
+
+func handleRootRedirect() http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ http.Redirect(w, req, "/ui/", 307)
+ return
+ })
+}
+
+type UIAssetWrapper struct {
+ FileSystem *assetfs.AssetFS
+}
+
+func (fs *UIAssetWrapper) Open(name string) (http.File, error) {
+ file, err := fs.FileSystem.Open(name)
+ if err == nil {
+ return file, nil
+ }
+ // serve index.html instead of 404ing
+ if err == os.ErrNotExist {
+ return fs.FileSystem.Open("index.html")
+ }
+ return nil, err
+}
+
func parseRequest(r *http.Request, w http.ResponseWriter, out interface{}) error {
// Limit the maximum number of bytes to MaxRequestSize to protect
// against an indefinite amount of data being read.
diff --git a/http/stub_assets.go b/http/stub_assets.go
new file mode 100644
index 0000000000..c64ac582a5
--- /dev/null
+++ b/http/stub_assets.go
@@ -0,0 +1,16 @@
+// +build !ui
+
+package http
+
+import (
+ assetfs "github.com/elazarl/go-bindata-assetfs"
+)
+
+func init() {
+ uiBuiltIn = false
+}
+
+// assetFS is a stub for building Vault without a UI.
+func assetFS() *assetfs.AssetFS {
+ return nil
+}
diff --git a/vault/core.go b/vault/core.go
index fa9dccedd7..bb3cf5843a 100644
--- a/vault/core.go
+++ b/vault/core.go
@@ -363,8 +363,8 @@ type Core struct {
replicationState *uint32
activeNodeReplicationState *uint32
- // uiEnabled indicates whether Vault Web UI is enabled or not
- uiEnabled bool
+ // uiConfig contains UI configuration
+ uiConfig *UIConfig
// rawEnabled indicates whether the Raw endpoint is enabled
rawEnabled bool
@@ -620,6 +620,9 @@ func NewCore(conf *CoreConfig) (*Core, error) {
}
c.auditBackends = auditBackends
+ uiStoragePrefix := systemBarrierPrefix + "ui"
+ c.uiConfig = NewUIConfig(conf.EnableUI, physical.NewView(c.physical, uiStoragePrefix), NewBarrierView(c.barrier, uiStoragePrefix))
+
return c, nil
}
@@ -1510,6 +1513,16 @@ func (c *Core) StepDown(req *logical.Request) (retErr error) {
return retErr
}
+// UIEnabled returns if the UI is enabled
+func (c *Core) UIEnabled() bool {
+ return c.uiConfig.Enabled()
+}
+
+// UIHeaders returns configured UI headers
+func (c *Core) UIHeaders() (http.Header, error) {
+ return c.uiConfig.Headers(context.Background())
+}
+
// sealInternal is an internal method used to seal the vault. It does not do
// any authorization checking. The stateLock must be held prior to calling.
func (c *Core) sealInternal(keepLock bool) error {
diff --git a/vault/logical_system.go b/vault/logical_system.go
index e80d98963e..cd53a70d36 100644
--- a/vault/logical_system.go
+++ b/vault/logical_system.go
@@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"hash"
+ "net/http"
"path/filepath"
"strconv"
"strings"
@@ -77,6 +78,7 @@ func NewSystemBackend(core *Core, logger log.Logger) *SystemBackend {
"rotate",
"config/cors",
"config/auditing/*",
+ "config/ui/headers/*",
"plugins/catalog/*",
"revoke-prefix/*",
"revoke-force/*",
@@ -148,6 +150,41 @@ func NewSystemBackend(core *Core, logger log.Logger) *SystemBackend {
HelpSynopsis: strings.TrimSpace(sysHelp["config/cors"][1]),
},
+ &framework.Path{
+ Pattern: "config/ui/headers/" + framework.GenericNameRegex("header"),
+
+ Fields: map[string]*framework.FieldSchema{
+ "header": &framework.FieldSchema{
+ Type: framework.TypeString,
+ Description: "The name of the header.",
+ },
+ "values": &framework.FieldSchema{
+ Type: framework.TypeStringSlice,
+ Description: "The values to set the header.",
+ },
+ },
+
+ Callbacks: map[logical.Operation]framework.OperationFunc{
+ logical.ReadOperation: b.handleConfigUIHeadersRead,
+ logical.UpdateOperation: b.handleConfigUIHeadersUpdate,
+ logical.DeleteOperation: b.handleConfigUIHeadersDelete,
+ },
+
+ HelpDescription: strings.TrimSpace(sysHelp["config/ui/headers"][0]),
+ HelpSynopsis: strings.TrimSpace(sysHelp["config/ui/headers"][1]),
+ },
+
+ &framework.Path{
+ Pattern: "config/ui/headers/$",
+
+ Callbacks: map[logical.Operation]framework.OperationFunc{
+ logical.ListOperation: b.handleConfigUIHeadersList,
+ },
+
+ HelpDescription: strings.TrimSpace(sysHelp["config/ui/headers"][0]),
+ HelpSynopsis: strings.TrimSpace(sysHelp["config/ui/headers"][1]),
+ },
+
&framework.Path{
Pattern: "capabilities$",
@@ -2699,6 +2736,68 @@ func (b *SystemBackend) handleDisableAudit(ctx context.Context, req *logical.Req
return nil, nil
}
+func (b *SystemBackend) handleConfigUIHeadersRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
+ header := data.Get("header").(string)
+
+ value, err := b.Core.uiConfig.GetHeader(ctx, header)
+ if err != nil {
+ return nil, err
+ }
+ if value == "" {
+ return nil, nil
+ }
+
+ return &logical.Response{
+ Data: map[string]interface{}{
+ "value": value,
+ },
+ }, nil
+}
+
+func (b *SystemBackend) handleConfigUIHeadersList(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
+ headers, err := b.Core.uiConfig.HeaderKeys(ctx)
+ if err != nil {
+ return nil, err
+ }
+ if len(headers) == 0 {
+ return nil, nil
+ }
+
+ return logical.ListResponse(headers), nil
+}
+
+func (b *SystemBackend) handleConfigUIHeadersUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
+ header := data.Get("header").(string)
+ values := data.Get("values").([]string)
+ if header == "" || len(values) == 0 {
+ return logical.ErrorResponse("header and values must be specified"), logical.ErrInvalidRequest
+ }
+
+ if strings.HasPrefix(strings.ToLower(header), "x-vault-") {
+ return logical.ErrorResponse("X-Vault headers cannot be set"), logical.ErrInvalidRequest
+ }
+
+ // Translate the list of values to the valid header string
+ value := http.Header{
+ header: values,
+ }
+ err := b.Core.uiConfig.SetHeader(ctx, header, value.Get(header))
+ if err != nil {
+ return nil, err
+ }
+
+ return nil, nil
+}
+
+func (b *SystemBackend) handleConfigUIHeadersDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
+ header := data.Get("header").(string)
+ err := b.Core.uiConfig.DeleteHeader(ctx, header)
+ if err != nil {
+ return nil, err
+ }
+ return nil, nil
+}
+
// handleRawRead is used to read directly from the barrier
func (b *SystemBackend) handleRawRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
path := data.Get("path").(string)
@@ -3332,6 +3431,21 @@ This path responds to the following HTTP methods.
Clears the CORS configuration and disables acceptance of CORS requests.
`,
},
+ "config/ui/headers": {
+ "Configures response headers that should be returned from the UI.",
+ `
+This path responds to the following HTTP methods.
+ GET /
+ Returns the header value.
+ POST /
+ Sets the header value for the UI.
+ DELETE /
+ Clears the header value for UI.
+
+ LIST /
+ List the headers configured for the UI.
+ `,
+ },
"init": {
"Initializes or returns the initialization status of the Vault.",
`
diff --git a/vault/ui.go b/vault/ui.go
new file mode 100644
index 0000000000..1fe1b528ed
--- /dev/null
+++ b/vault/ui.go
@@ -0,0 +1,226 @@
+package vault
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strings"
+ "sync"
+
+ "github.com/hashicorp/vault/logical"
+ "github.com/hashicorp/vault/physical"
+)
+
+const (
+ uiConfigKey = "config"
+ uiConfigPlaintextKey = "config_plaintext"
+)
+
+var (
+ staticHeaders = http.Header{
+ "Content-Security-Policy": {
+ "default-src 'none'; connect-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'unsafe-inline' 'self'; form-action 'none'; frame-ancestors 'none'",
+ },
+ }
+)
+
+// UIConfig contains UI configuration. This takes both a physical view and a barrier view
+// because it is stored in both plaintext and encrypted to allow for getting the header
+// values before the barrier is unsealed
+type UIConfig struct {
+ l sync.RWMutex
+ physicalStorage physical.Backend
+ barrierStorage logical.Storage
+
+ enabled bool
+}
+
+// NewUIConfig creates a new UI config
+func NewUIConfig(enabled bool, physicalStorage physical.Backend, barrierStorage logical.Storage) *UIConfig {
+ return &UIConfig{
+ physicalStorage: physicalStorage,
+ barrierStorage: barrierStorage,
+ enabled: enabled,
+ }
+}
+
+// Enabled returns if the UI is enabled
+func (c *UIConfig) Enabled() bool {
+ c.l.RLock()
+ defer c.l.RUnlock()
+ return c.enabled
+}
+
+// Headers returns the response headers that should be returned in the UI
+func (c *UIConfig) Headers(ctx context.Context) (http.Header, error) {
+ c.l.RLock()
+ defer c.l.RUnlock()
+
+ config, err := c.get(ctx)
+ if err != nil {
+ return nil, err
+ }
+ headers := make(http.Header)
+ if config != nil {
+ headers = config.Headers
+ }
+
+ for k := range staticHeaders {
+ v := staticHeaders.Get(k)
+ headers.Set(k, v)
+ }
+ return headers, nil
+}
+
+// HeaderKeys returns the list of the configured headers
+func (c *UIConfig) HeaderKeys(ctx context.Context) ([]string, error) {
+ c.l.RLock()
+ defer c.l.RUnlock()
+
+ config, err := c.get(ctx)
+ if err != nil {
+ return nil, err
+ }
+ if config == nil {
+ return nil, nil
+ }
+ var keys []string
+ for k := range config.Headers {
+ keys = append(keys, k)
+ }
+ return keys, nil
+}
+
+// GetHeader retrieves the configured value for the given header
+func (c *UIConfig) GetHeader(ctx context.Context, header string) (string, error) {
+ c.l.RLock()
+ defer c.l.RUnlock()
+
+ config, err := c.get(ctx)
+ if err != nil {
+ return "", err
+ }
+ if config == nil {
+ return "", nil
+ }
+
+ value := config.Headers.Get(header)
+ return value, nil
+}
+
+// SetHeader sets the value for the given header
+func (c *UIConfig) SetHeader(ctx context.Context, header, value string) error {
+ if val := staticHeaders.Get(header); val != "" {
+ return fmt.Errorf("the header %s is not settable", header)
+ }
+
+ c.l.Lock()
+ defer c.l.Unlock()
+
+ config, err := c.get(ctx)
+ if err != nil {
+ return err
+ }
+ if config == nil {
+ config = &uiConfigEntry{
+ Headers: http.Header{
+ header: {value},
+ },
+ }
+ } else {
+ config.Headers.Set(header, value)
+ }
+ return c.save(ctx, config)
+}
+
+// DeleteHeader deletes the header configuration for the given header
+func (c *UIConfig) DeleteHeader(ctx context.Context, header string) error {
+ c.l.Lock()
+ defer c.l.Unlock()
+
+ config, err := c.get(ctx)
+ if err != nil {
+ return err
+ }
+ if config == nil {
+ return nil
+ }
+
+ config.Headers.Del(header)
+ return c.save(ctx, config)
+}
+
+func (c *UIConfig) get(ctx context.Context) (*uiConfigEntry, error) {
+ // Read plaintext always to ensure in sync with barrier value
+ plaintextConfigRaw, err := c.physicalStorage.Get(ctx, uiConfigPlaintextKey)
+ if err != nil {
+ return nil, err
+ }
+
+ configRaw, err := c.barrierStorage.Get(ctx, uiConfigKey)
+ if err == nil {
+ if configRaw == nil {
+ return nil, nil
+ }
+ config := new(uiConfigEntry)
+ if err := json.Unmarshal(configRaw.Value, config); err != nil {
+ return nil, err
+ }
+ // Check that plaintext value matches barrier value, if not sync values
+ if plaintextConfigRaw == nil || bytes.Compare(plaintextConfigRaw.Value, configRaw.Value) != 0 {
+ if err := c.save(ctx, config); err != nil {
+ return nil, err
+ }
+ }
+ return config, nil
+ }
+
+ // Respond with error if not sealed
+ if !strings.Contains(err.Error(), ErrBarrierSealed.Error()) {
+ return nil, err
+ }
+
+ // Respond with plaintext value
+ if configRaw == nil {
+ return nil, nil
+ }
+ config := new(uiConfigEntry)
+ if err := json.Unmarshal(plaintextConfigRaw.Value, config); err != nil {
+ return nil, err
+ }
+ return config, nil
+}
+
+func (c *UIConfig) save(ctx context.Context, config *uiConfigEntry) error {
+ if len(config.Headers) == 0 {
+ if err := c.physicalStorage.Delete(ctx, uiConfigPlaintextKey); err != nil {
+ return err
+ }
+ return c.barrierStorage.Delete(ctx, uiConfigKey)
+ }
+
+ configRaw, err := json.Marshal(config)
+ if err != nil {
+ return err
+ }
+
+ entry := &physical.Entry{
+ Key: uiConfigPlaintextKey,
+ Value: configRaw,
+ }
+ if err := c.physicalStorage.Put(ctx, entry); err != nil {
+ return err
+ }
+
+ barrEntry := &logical.StorageEntry{
+ Key: uiConfigKey,
+ Value: configRaw,
+ }
+ return c.barrierStorage.Put(ctx, barrEntry)
+}
+
+type uiConfigEntry struct {
+ Headers http.Header `json:"headers"`
+}
diff --git a/vault/ui_test.go b/vault/ui_test.go
new file mode 100644
index 0000000000..b1cb2a946c
--- /dev/null
+++ b/vault/ui_test.go
@@ -0,0 +1,125 @@
+package vault
+
+import (
+ "context"
+ "testing"
+
+ "github.com/hashicorp/vault/logical"
+
+ "github.com/hashicorp/vault/helper/logformat"
+ "github.com/hashicorp/vault/physical/inmem"
+ log "github.com/mgutz/logxi/v1"
+)
+
+func TestConfig_Enabled(t *testing.T) {
+ logger := logformat.NewVaultLogger(log.LevelTrace)
+ phys, err := inmem.NewTransactionalInmem(nil, logger)
+ if err != nil {
+ t.Fatal(err)
+ }
+ logl := &logical.InmemStorage{}
+
+ config := NewUIConfig(true, phys, logl)
+ if !config.Enabled() {
+ t.Fatal("ui should be enabled")
+ }
+
+ config = NewUIConfig(false, phys, logl)
+ if config.Enabled() {
+ t.Fatal("ui should not be enabled")
+ }
+}
+
+func TestConfig_Headers(t *testing.T) {
+ logger := logformat.NewVaultLogger(log.LevelTrace)
+ phys, err := inmem.NewTransactionalInmem(nil, logger)
+ if err != nil {
+ t.Fatal(err)
+ }
+ logl := &logical.InmemStorage{}
+
+ config := NewUIConfig(true, phys, logl)
+ headers, err := config.Headers(context.Background())
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+ if len(headers) != len(staticHeaders) {
+ t.Fatalf("expected %d headers, got %d", len(staticHeaders), len(headers))
+ }
+
+ head, err := config.GetHeader(context.Background(), "Test-Header")
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+ if head != "" {
+ t.Fatal("header returned found, should not be found")
+ }
+ err = config.SetHeader(context.Background(), "Test-Header", "123")
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+ head, err = config.GetHeader(context.Background(), "Test-Header")
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+ if head == "" {
+ t.Fatal("header not found when it should be")
+ }
+ if head != "123" {
+ t.Fatalf("expected: %s, got: %s", "123", head)
+ }
+
+ head, err = config.GetHeader(context.Background(), "tEST-hEADER")
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+ if head == "" {
+ t.Fatal("header not found when it should be")
+ }
+ if head != "123" {
+ t.Fatalf("expected: %s, got: %s", "123", head)
+ }
+
+ keys, err := config.HeaderKeys(context.Background())
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+ if len(keys) != 1 {
+ t.Fatalf("expected 1 key, got %d", len(keys))
+ }
+
+ err = config.SetHeader(context.Background(), "Test-Header-2", "321")
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+ keys, err = config.HeaderKeys(context.Background())
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+ if len(keys) != 2 {
+ t.Fatalf("expected 1 key, got %d", len(keys))
+ }
+ err = config.DeleteHeader(context.Background(), "Test-Header-2")
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ err = config.DeleteHeader(context.Background(), "Test-Header")
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+ head, err = config.GetHeader(context.Background(), "Test-Header")
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+ if head != "" {
+ t.Fatal("header returned found, should not be found")
+ }
+ keys, err = config.HeaderKeys(context.Background())
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+ if len(keys) != 0 {
+ t.Fatalf("expected 0 key, got %d", len(keys))
+ }
+}
diff --git a/vendor/github.com/elazarl/go-bindata-assetfs/LICENSE b/vendor/github.com/elazarl/go-bindata-assetfs/LICENSE
new file mode 100644
index 0000000000..5782c72690
--- /dev/null
+++ b/vendor/github.com/elazarl/go-bindata-assetfs/LICENSE
@@ -0,0 +1,23 @@
+Copyright (c) 2014, Elazar Leibovich
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/elazarl/go-bindata-assetfs/README.md b/vendor/github.com/elazarl/go-bindata-assetfs/README.md
new file mode 100644
index 0000000000..27ee48f09d
--- /dev/null
+++ b/vendor/github.com/elazarl/go-bindata-assetfs/README.md
@@ -0,0 +1,46 @@
+# go-bindata-assetfs
+
+Serve embedded files from [jteeuwen/go-bindata](https://github.com/jteeuwen/go-bindata) with `net/http`.
+
+[GoDoc](http://godoc.org/github.com/elazarl/go-bindata-assetfs)
+
+### Installation
+
+Install with
+
+ $ go get github.com/jteeuwen/go-bindata/...
+ $ go get github.com/elazarl/go-bindata-assetfs/...
+
+### Creating embedded data
+
+Usage is identical to [jteeuwen/go-bindata](https://github.com/jteeuwen/go-bindata) usage,
+instead of running `go-bindata` run `go-bindata-assetfs`.
+
+The tool will create a `bindata_assetfs.go` file, which contains the embedded data.
+
+A typical use case is
+
+ $ go-bindata-assetfs data/...
+
+### Using assetFS in your code
+
+The generated file provides an `assetFS()` function that returns a `http.Filesystem`
+wrapping the embedded files. What you usually want to do is:
+
+ http.Handle("/", http.FileServer(assetFS()))
+
+This would run an HTTP server serving the embedded files.
+
+## Without running binary tool
+
+You can always just run the `go-bindata` tool, and then
+
+use
+
+ import "github.com/elazarl/go-bindata-assetfs"
+ ...
+ http.Handle("/",
+ http.FileServer(
+ &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: AssetInfo, Prefix: "data"}))
+
+to serve files embedded from the `data` directory.
diff --git a/vendor/github.com/elazarl/go-bindata-assetfs/assetfs.go b/vendor/github.com/elazarl/go-bindata-assetfs/assetfs.go
new file mode 100644
index 0000000000..04f6d7a39d
--- /dev/null
+++ b/vendor/github.com/elazarl/go-bindata-assetfs/assetfs.go
@@ -0,0 +1,167 @@
+package assetfs
+
+import (
+ "bytes"
+ "errors"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+ "time"
+)
+
+var (
+ defaultFileTimestamp = time.Now()
+)
+
+// FakeFile implements os.FileInfo interface for a given path and size
+type FakeFile struct {
+ // Path is the path of this file
+ Path string
+ // Dir marks of the path is a directory
+ Dir bool
+ // Len is the length of the fake file, zero if it is a directory
+ Len int64
+ // Timestamp is the ModTime of this file
+ Timestamp time.Time
+}
+
+func (f *FakeFile) Name() string {
+ _, name := filepath.Split(f.Path)
+ return name
+}
+
+func (f *FakeFile) Mode() os.FileMode {
+ mode := os.FileMode(0644)
+ if f.Dir {
+ return mode | os.ModeDir
+ }
+ return mode
+}
+
+func (f *FakeFile) ModTime() time.Time {
+ return f.Timestamp
+}
+
+func (f *FakeFile) Size() int64 {
+ return f.Len
+}
+
+func (f *FakeFile) IsDir() bool {
+ return f.Mode().IsDir()
+}
+
+func (f *FakeFile) Sys() interface{} {
+ return nil
+}
+
+// AssetFile implements http.File interface for a no-directory file with content
+type AssetFile struct {
+ *bytes.Reader
+ io.Closer
+ FakeFile
+}
+
+func NewAssetFile(name string, content []byte, timestamp time.Time) *AssetFile {
+ if timestamp.IsZero() {
+ timestamp = defaultFileTimestamp
+ }
+ return &AssetFile{
+ bytes.NewReader(content),
+ ioutil.NopCloser(nil),
+ FakeFile{name, false, int64(len(content)), timestamp}}
+}
+
+func (f *AssetFile) Readdir(count int) ([]os.FileInfo, error) {
+ return nil, errors.New("not a directory")
+}
+
+func (f *AssetFile) Size() int64 {
+ return f.FakeFile.Size()
+}
+
+func (f *AssetFile) Stat() (os.FileInfo, error) {
+ return f, nil
+}
+
+// AssetDirectory implements http.File interface for a directory
+type AssetDirectory struct {
+ AssetFile
+ ChildrenRead int
+ Children []os.FileInfo
+}
+
+func NewAssetDirectory(name string, children []string, fs *AssetFS) *AssetDirectory {
+ fileinfos := make([]os.FileInfo, 0, len(children))
+ for _, child := range children {
+ _, err := fs.AssetDir(filepath.Join(name, child))
+ fileinfos = append(fileinfos, &FakeFile{child, err == nil, 0, time.Time{}})
+ }
+ return &AssetDirectory{
+ AssetFile{
+ bytes.NewReader(nil),
+ ioutil.NopCloser(nil),
+ FakeFile{name, true, 0, time.Time{}},
+ },
+ 0,
+ fileinfos}
+}
+
+func (f *AssetDirectory) Readdir(count int) ([]os.FileInfo, error) {
+ if count <= 0 {
+ return f.Children, nil
+ }
+ if f.ChildrenRead+count > len(f.Children) {
+ count = len(f.Children) - f.ChildrenRead
+ }
+ rv := f.Children[f.ChildrenRead : f.ChildrenRead+count]
+ f.ChildrenRead += count
+ return rv, nil
+}
+
+func (f *AssetDirectory) Stat() (os.FileInfo, error) {
+ return f, nil
+}
+
+// AssetFS implements http.FileSystem, allowing
+// embedded files to be served from net/http package.
+type AssetFS struct {
+ // Asset should return content of file in path if exists
+ Asset func(path string) ([]byte, error)
+ // AssetDir should return list of files in the path
+ AssetDir func(path string) ([]string, error)
+ // AssetInfo should return the info of file in path if exists
+ AssetInfo func(path string) (os.FileInfo, error)
+ // Prefix would be prepended to http requests
+ Prefix string
+}
+
+func (fs *AssetFS) Open(name string) (http.File, error) {
+ name = path.Join(fs.Prefix, name)
+ if len(name) > 0 && name[0] == '/' {
+ name = name[1:]
+ }
+ if b, err := fs.Asset(name); err == nil {
+ timestamp := defaultFileTimestamp
+ if fs.AssetInfo != nil {
+ if info, err := fs.AssetInfo(name); err == nil {
+ timestamp = info.ModTime()
+ }
+ }
+ return NewAssetFile(name, b, timestamp), nil
+ }
+ if children, err := fs.AssetDir(name); err == nil {
+ return NewAssetDirectory(name, children, fs), nil
+ } else {
+ // If the error is not found, return an error that will
+ // result in a 404 error. Otherwise the server returns
+ // a 500 error for files not found.
+ if strings.Contains(err.Error(), "not found") {
+ return nil, os.ErrNotExist
+ }
+ return nil, err
+ }
+}
diff --git a/vendor/github.com/elazarl/go-bindata-assetfs/doc.go b/vendor/github.com/elazarl/go-bindata-assetfs/doc.go
new file mode 100644
index 0000000000..a664249f34
--- /dev/null
+++ b/vendor/github.com/elazarl/go-bindata-assetfs/doc.go
@@ -0,0 +1,13 @@
+// assetfs allows packages to serve static content embedded
+// with the go-bindata tool with the standard net/http package.
+//
+// See https://github.com/jteeuwen/go-bindata for more information
+// about embedding binary data with go-bindata.
+//
+// Usage example, after running
+// $ go-bindata data/...
+// use:
+// http.Handle("/",
+// http.FileServer(
+// &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, Prefix: "data"}))
+package assetfs
diff --git a/vendor/vendor.json b/vendor/vendor.json
index 749c8f3934..af62bde062 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -864,6 +864,12 @@
"revision": "bb3d318650d48840a39aa21a027c6630e198e626",
"revisionTime": "2017-11-10T20:55:13Z"
},
+ {
+ "checksumSHA1": "7DxViusFRJ7UPH0jZqYatwDrOkY=",
+ "path": "github.com/elazarl/go-bindata-assetfs",
+ "revision": "38087fe4dafb822e541b3f7955075cc1c30bd294",
+ "revisionTime": "2018-02-23T16:03:09Z"
+ },
{
"checksumSHA1": "5BP5xofo0GoFi6FtgqFFbmHyUKI=",
"path": "github.com/fatih/structs",