From 389434c0ac06141d8fb7c61a441c8c1e0eed5067 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Mon, 10 Oct 2016 17:58:57 -0400 Subject: [PATCH 01/89] Added function to add CORS headers and handle preflight requests. --- http/logical.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/http/logical.go b/http/logical.go index 2a84aac90417..425155399b73 100644 --- a/http/logical.go +++ b/http/logical.go @@ -26,6 +26,8 @@ func buildLogicalRequest(w http.ResponseWriter, r *http.Request) (*logical.Reque return nil, http.StatusNotFound, nil } + w = handleCORS(w, r.Method) + // Determine the operation var op logical.Operation switch r.Method { @@ -49,6 +51,8 @@ func buildLogicalRequest(w http.ResponseWriter, r *http.Request) (*logical.Reque op = logical.UpdateOperation case "LIST": op = logical.ListOperation + case "OPTIONS": + return nil, http.StatusOK, nil default: return nil, http.StatusMethodNotAllowed, nil } @@ -147,6 +151,24 @@ func handleLogical(core *vault.Core, dataOnly bool, prepareRequestCallback Prepa }) } +// handleCORS adds required headers to properly response to Cross Origin Resource Sharing (CORS) requests. +func handleCORS(w http.ResponseWriter, method string) http.ResponseWriter { + + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Credentials", "true") + + if method == "OPTIONS" { + w.Header().Set("Access-Control-Allow-Methods", "OPTIONS,GET,PUT,POST,DELETE,LIST") + w.Header().Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Accept,Origin,Authorization") + w.Header().Set("Access-Control-Max-Age", "1800") + + w.Header().Del("Content-Type") + w.Header().Set("Content-Type", "text/plain") + } + + return w +} + func respondLogical(w http.ResponseWriter, r *http.Request, req *logical.Request, dataOnly bool, resp *logical.Response) { var httpResp *logical.HTTPResponse var ret interface{} From cdd2ca87e8cbfdb06012f1fe657501c1483f924b Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Wed, 19 Oct 2016 15:00:01 -0400 Subject: [PATCH 02/89] Added missing required headers to the Access-Control-Allow-Headers header. --- http/logical.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/http/logical.go b/http/logical.go index 425155399b73..2cb43ff7584f 100644 --- a/http/logical.go +++ b/http/logical.go @@ -26,7 +26,7 @@ func buildLogicalRequest(w http.ResponseWriter, r *http.Request) (*logical.Reque return nil, http.StatusNotFound, nil } - w = handleCORS(w, r.Method) + handleCORS(w, r.Method) // Determine the operation var op logical.Operation @@ -152,21 +152,20 @@ func handleLogical(core *vault.Core, dataOnly bool, prepareRequestCallback Prepa } // handleCORS adds required headers to properly response to Cross Origin Resource Sharing (CORS) requests. -func handleCORS(w http.ResponseWriter, method string) http.ResponseWriter { +func handleCORS(w http.ResponseWriter, method string) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Credentials", "true") if method == "OPTIONS" { w.Header().Set("Access-Control-Allow-Methods", "OPTIONS,GET,PUT,POST,DELETE,LIST") - w.Header().Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Accept,Origin,Authorization") + w.Header().Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Accept,Origin,Authorization,X-Vault-Token") w.Header().Set("Access-Control-Max-Age", "1800") w.Header().Del("Content-Type") w.Header().Set("Content-Type", "text/plain") } - return w } func respondLogical(w http.ResponseWriter, r *http.Request, req *logical.Request, dataOnly bool, resp *logical.Response) { From 237a9afdcb298c0cfb448f418896a7563eeb2581 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Wed, 19 Oct 2016 17:50:56 -0400 Subject: [PATCH 03/89] Added config options to enable CORS and set a regexp for origins that are allowed to make cross-origin requests. --- command/server.go | 1 + command/server/config.go | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/command/server.go b/command/server.go index 18040bd68acd..5004ef301cf5 100644 --- a/command/server.go +++ b/command/server.go @@ -219,6 +219,7 @@ func (c *ServerCommand) Run(args []string) int { Seal: seal, AuditBackends: c.AuditBackends, CredentialBackends: c.CredentialBackends, + EnableCORS: config.EnableCORS, LogicalBackends: c.LogicalBackends, Logger: c.logger, DisableCache: config.DisableCache, diff --git a/command/server/config.go b/command/server/config.go index 6e5cd12ff17b..d8ecb5ff793e 100644 --- a/command/server/config.go +++ b/command/server/config.go @@ -27,6 +27,9 @@ type Config struct { DisableCache bool `hcl:"disable_cache"` DisableMlock bool `hcl:"disable_mlock"` + EnableCORS bool `hcl:"enable_cors"` + AllowedDomains string `hcl:"allowed_domains"` + Telemetry *Telemetry `hcl:"telemetry"` MaxLeaseTTL time.Duration `hcl:"-"` @@ -288,6 +291,7 @@ func ParseConfig(d string, logger log.Logger) (*Config, error) { } valid := []string{ + "allowed_domains", "atlas", "backend", "ha_backend", @@ -295,6 +299,7 @@ func ParseConfig(d string, logger log.Logger) (*Config, error) { "cache_size", "disable_cache", "disable_mlock", + "enable_cors", "telemetry", "default_lease_ttl", "max_lease_ttl", From f368d09db5bb1c3132b3dbb5383c955e384f5b5d Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Wed, 19 Oct 2016 17:54:06 -0400 Subject: [PATCH 04/89] Reverted to master revision after moving things into more appropriate places. --- http/logical.go | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/http/logical.go b/http/logical.go index 2cb43ff7584f..2a84aac90417 100644 --- a/http/logical.go +++ b/http/logical.go @@ -26,8 +26,6 @@ func buildLogicalRequest(w http.ResponseWriter, r *http.Request) (*logical.Reque return nil, http.StatusNotFound, nil } - handleCORS(w, r.Method) - // Determine the operation var op logical.Operation switch r.Method { @@ -51,8 +49,6 @@ func buildLogicalRequest(w http.ResponseWriter, r *http.Request) (*logical.Reque op = logical.UpdateOperation case "LIST": op = logical.ListOperation - case "OPTIONS": - return nil, http.StatusOK, nil default: return nil, http.StatusMethodNotAllowed, nil } @@ -151,23 +147,6 @@ func handleLogical(core *vault.Core, dataOnly bool, prepareRequestCallback Prepa }) } -// handleCORS adds required headers to properly response to Cross Origin Resource Sharing (CORS) requests. -func handleCORS(w http.ResponseWriter, method string) { - - w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Access-Control-Allow-Credentials", "true") - - if method == "OPTIONS" { - w.Header().Set("Access-Control-Allow-Methods", "OPTIONS,GET,PUT,POST,DELETE,LIST") - w.Header().Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Accept,Origin,Authorization,X-Vault-Token") - w.Header().Set("Access-Control-Max-Age", "1800") - - w.Header().Del("Content-Type") - w.Header().Set("Content-Type", "text/plain") - } - -} - func respondLogical(w http.ResponseWriter, r *http.Request, req *logical.Request, dataOnly bool, resp *logical.Response) { var httpResp *logical.HTTPResponse var ret interface{} From 889ae2952c7ae0d6a8ee871d263c8026b8022eff Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Wed, 19 Oct 2016 17:55:15 -0400 Subject: [PATCH 05/89] Added wrapper function to apply CORS headers when configured to do so. --- http/handler.go | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/http/handler.go b/http/handler.go index 4ad99216d516..550860693faf 100644 --- a/http/handler.go +++ b/http/handler.go @@ -6,6 +6,7 @@ import ( "io" "net/http" "net/url" + "regexp" "strings" "github.com/hashicorp/errwrap" @@ -58,6 +59,11 @@ func Handler(core *vault.Core) http.Handler { // Wrap the handler in another handler to trigger all help paths. handler := handleHelpHandler(mux, core) + // Wrap the handler in another handler to ensure + // CORS headers are added to the requests if enabled. + if useCORS, allowedDomains := core.AllowCORS(); useCORS { + handler = handleCORS(handler, allowedDomains) + } return handler } @@ -116,6 +122,36 @@ func parseRequest(r *http.Request, out interface{}) error { return err } +// handleCORS adds required headers to properly respond to request that +// require Cross Origin Resource Sharing (CORS) headers. +func handleCORS(handler http.Handler, allowedDomains *regexp.Regexp) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var allowedOrigin string + + origin := r.Header.Get("Origin") + + if allowedDomains.MatchString(origin) && len(origin) > 0 { + allowedOrigin = origin + } + + w.Header().Set("Access-Control-Allow-Origin", allowedOrigin) + w.Header().Set("Access-Control-Allow-Credentials", "true") + + // Handle preflight requests + if r.Method == "OPTIONS" { + w.Header().Set("Access-Control-Allow-Methods", "OPTIONS,GET,PUT,POST,DELETE,LIST") + w.Header().Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Accept,Origin,Authorization,X-Vault-Token") + w.Header().Set("Access-Control-Max-Age", "1800") + w.Header().Set("Content-Type", "text/plain") + + w.WriteHeader(http.StatusOK) + return + } + handler.ServeHTTP(w, r) + return + }) +} + // handleRequestForwarding determines whether to forward a request or not, // falling back on the older behavior of redirecting the client func handleRequestForwarding(core *vault.Core, handler http.Handler) http.Handler { From 8c268e13f7d8c1edff114b94967cfe9b6a1d348c Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Wed, 19 Oct 2016 17:57:40 -0400 Subject: [PATCH 06/89] Added fields to Core and CoreConfig structs to enable CORS if desired. Added supporting code and funcs. --- vault/core.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/vault/core.go b/vault/core.go index 21c152a3e268..3188bf9d82ec 100644 --- a/vault/core.go +++ b/vault/core.go @@ -10,6 +10,7 @@ import ( "net" "net/http" "net/url" + "regexp" "sync" "time" @@ -128,6 +129,12 @@ type activeAdvertisement struct { // interface for API handlers and is responsible for managing the logical and physical // backends, router, security barrier, and audit trails. type Core struct { + // enableCORS determines if CORS headers should be returned on the HTTP reponses. + enableCORS bool + + // allowedDomains is a regex that determines which domains are allowed to make cross-domain requests + allowedDomains *regexp.Regexp + // HABackend may be available depending on the physical backend ha physical.HABackend @@ -296,6 +303,10 @@ type Core struct { // CoreConfig is used to parameterize a core type CoreConfig struct { + AllowedDomains string `json:"allowed_domains" structs:"allowed_domains" mapstructure:"allowed_domains"` + + EnableCORS bool `json:"enable_cors" structs:"enable_cors" mapstructure:"enable_cors"` + LogicalBackends map[string]logical.Factory `json:"logical_backends" structs:"logical_backends" mapstructure:"logical_backends"` CredentialBackends map[string]logical.Factory `json:"credential_backends" structs:"credential_backends" mapstructure:"credential_backends"` @@ -405,6 +416,7 @@ func NewCore(conf *CoreConfig) (*Core, error) { // Setup the core c := &Core{ + enableCORS: conf.EnableCORS, redirectAddr: conf.RedirectAddr, clusterAddr: conf.ClusterAddr, physical: conf.Physical, @@ -423,6 +435,17 @@ func NewCore(conf *CoreConfig) (*Core, error) { clusterListenerShutdownSuccessCh: make(chan struct{}), } + if c.enableCORS { + if conf.AllowedDomains == "" { + c.allowedDomains, _ = regexp.Compile(".*") + } else { + c.allowedDomains, err = regexp.Compile(conf.AllowedDomains) + if err != nil { + return nil, fmt.Errorf("invalid regexp specified: %v", err) + } + } + } + if conf.HAPhysical != nil && conf.HAPhysical.HAEnabled() { c.ha = conf.HAPhysical } @@ -609,6 +632,15 @@ func (c *Core) Standby() (bool, error) { return c.standby, nil } +// AllowCORS checks if Vault should insert CORS headers on responses. It returns a bool +// and a *regexp.Regexp that is used to validate if the origin of the request is allowed. +func (c *Core) AllowCORS() (bool, *regexp.Regexp) { + if c.enableCORS { + return c.enableCORS, c.allowedDomains + } + return false, nil +} + // Leader is used to get the current active leader func (c *Core) Leader() (isLeader bool, leaderAddr string, err error) { c.stateLock.RLock() From 5852bb3cea5605c09fd868edfdb2b6474019e978 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Thu, 20 Oct 2016 10:53:36 -0400 Subject: [PATCH 07/89] Standardized on allowed origins instead of domains. --- command/server/config.go | 4 ++-- http/handler.go | 8 ++++---- vault/core.go | 14 +++++++------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/command/server/config.go b/command/server/config.go index d8ecb5ff793e..9da31c14d48d 100644 --- a/command/server/config.go +++ b/command/server/config.go @@ -28,7 +28,7 @@ type Config struct { DisableMlock bool `hcl:"disable_mlock"` EnableCORS bool `hcl:"enable_cors"` - AllowedDomains string `hcl:"allowed_domains"` + AllowedOrigins string `hcl:"allowed_origins"` Telemetry *Telemetry `hcl:"telemetry"` @@ -291,7 +291,7 @@ func ParseConfig(d string, logger log.Logger) (*Config, error) { } valid := []string{ - "allowed_domains", + "allowed_origins", "atlas", "backend", "ha_backend", diff --git a/http/handler.go b/http/handler.go index 550860693faf..d0b37f5204b0 100644 --- a/http/handler.go +++ b/http/handler.go @@ -61,8 +61,8 @@ func Handler(core *vault.Core) http.Handler { // Wrap the handler in another handler to ensure // CORS headers are added to the requests if enabled. - if useCORS, allowedDomains := core.AllowCORS(); useCORS { - handler = handleCORS(handler, allowedDomains) + if useCORS, allowedOrigins := core.AllowCORS(); useCORS { + handler = handleCORS(handler, allowedOrigins) } return handler } @@ -124,13 +124,13 @@ func parseRequest(r *http.Request, out interface{}) error { // handleCORS adds required headers to properly respond to request that // require Cross Origin Resource Sharing (CORS) headers. -func handleCORS(handler http.Handler, allowedDomains *regexp.Regexp) http.Handler { +func handleCORS(handler http.Handler, allowedOrigins *regexp.Regexp) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var allowedOrigin string origin := r.Header.Get("Origin") - if allowedDomains.MatchString(origin) && len(origin) > 0 { + if allowedOrigins.MatchString(origin) && len(origin) > 0 { allowedOrigin = origin } diff --git a/vault/core.go b/vault/core.go index 3188bf9d82ec..2419ffd5a350 100644 --- a/vault/core.go +++ b/vault/core.go @@ -132,8 +132,8 @@ type Core struct { // enableCORS determines if CORS headers should be returned on the HTTP reponses. enableCORS bool - // allowedDomains is a regex that determines which domains are allowed to make cross-domain requests - allowedDomains *regexp.Regexp + // allowedOrigins is a regex that determines which domains are allowed to make cross-domain requests + allowedOrigins *regexp.Regexp // HABackend may be available depending on the physical backend ha physical.HABackend @@ -303,7 +303,7 @@ type Core struct { // CoreConfig is used to parameterize a core type CoreConfig struct { - AllowedDomains string `json:"allowed_domains" structs:"allowed_domains" mapstructure:"allowed_domains"` + AllowedOrigins string `json:"allowed_origins" structs:"allowed_origins" mapstructure:"allowed_origins"` EnableCORS bool `json:"enable_cors" structs:"enable_cors" mapstructure:"enable_cors"` @@ -436,10 +436,10 @@ func NewCore(conf *CoreConfig) (*Core, error) { } if c.enableCORS { - if conf.AllowedDomains == "" { - c.allowedDomains, _ = regexp.Compile(".*") + if conf.AllowedOrigins == "" { + c.allowedOrigins, _ = regexp.Compile(".*") } else { - c.allowedDomains, err = regexp.Compile(conf.AllowedDomains) + c.allowedOrigins, err = regexp.Compile(conf.AllowedOrigins) if err != nil { return nil, fmt.Errorf("invalid regexp specified: %v", err) } @@ -636,7 +636,7 @@ func (c *Core) Standby() (bool, error) { // and a *regexp.Regexp that is used to validate if the origin of the request is allowed. func (c *Core) AllowCORS() (bool, *regexp.Regexp) { if c.enableCORS { - return c.enableCORS, c.allowedDomains + return c.enableCORS, c.allowedOrigins } return false, nil } From 336ba0604a62ffaca0bd836eed306478c598c5fc Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Thu, 20 Oct 2016 10:54:20 -0400 Subject: [PATCH 08/89] Added CORS configuration options to the tests and associated files. --- command/server/config_test.go | 4 ++++ command/server/test-fixtures/config-dir/bar.json | 4 +++- command/server/test-fixtures/config-dir/baz.hcl | 2 ++ command/server/test-fixtures/config-dir/foo.hcl | 2 ++ command/server/test-fixtures/config.hcl | 6 ++++-- command/server/test-fixtures/config.hcl.json | 4 +++- command/server/test-fixtures/config2.hcl.json | 2 ++ 7 files changed, 20 insertions(+), 4 deletions(-) diff --git a/command/server/config_test.go b/command/server/config_test.go index fcaae7189ce4..326aacf50d3a 100644 --- a/command/server/config_test.go +++ b/command/server/config_test.go @@ -19,6 +19,8 @@ func TestLoadConfigFile(t *testing.T) { } expected := &Config{ + EnableCORS: true, + AllowedOrigins: "http://localhost:8[0-9]{3}", Listeners: []*Listener{ &Listener{ Type: "atlas", @@ -83,6 +85,8 @@ func TestLoadConfigFile_json(t *testing.T) { } expected := &Config{ + EnableCORS: true, + AllowedOrigins: "http://localhost:8[0-9]{3}", Listeners: []*Listener{ &Listener{ Type: "tcp", diff --git a/command/server/test-fixtures/config-dir/bar.json b/command/server/test-fixtures/config-dir/bar.json index 677e81aae152..171f91cc2940 100644 --- a/command/server/test-fixtures/config-dir/bar.json +++ b/command/server/test-fixtures/config-dir/bar.json @@ -5,5 +5,7 @@ } }, - "max_lease_ttl": "10h" + "max_lease_ttl": "10h", + "enable_cors": true, + "allowed_origins": "http://localhost:8[0-9]{3}" } diff --git a/command/server/test-fixtures/config-dir/baz.hcl b/command/server/test-fixtures/config-dir/baz.hcl index 9b650104b2ea..9cbaf5b14501 100644 --- a/command/server/test-fixtures/config-dir/baz.hcl +++ b/command/server/test-fixtures/config-dir/baz.hcl @@ -6,3 +6,5 @@ telemetry { default_lease_ttl = "10h" cluster_name = "testcluster" +enable_cors = true +allowed_origins = "http://localhost:8[0-9]{3}" diff --git a/command/server/test-fixtures/config-dir/foo.hcl b/command/server/test-fixtures/config-dir/foo.hcl index 815a21624fbd..4fbd7e293323 100644 --- a/command/server/test-fixtures/config-dir/foo.hcl +++ b/command/server/test-fixtures/config-dir/foo.hcl @@ -1,5 +1,7 @@ disable_cache = true disable_mlock = true +enable_cors = true +allowed_origins = "http://localhost:8[0-9]{3}" backend "consul" { foo = "bar" diff --git a/command/server/test-fixtures/config.hcl b/command/server/test-fixtures/config.hcl index 9ad47900c0fc..eff3d910b4fe 100644 --- a/command/server/test-fixtures/config.hcl +++ b/command/server/test-fixtures/config.hcl @@ -1,5 +1,7 @@ -disable_cache = true -disable_mlock = true +disable_cache = true +disable_mlock = true +enable_cors = true +allowed_origins = "http://localhost:8[0-9]{3}" listener "atlas" { token = "foobar" diff --git a/command/server/test-fixtures/config.hcl.json b/command/server/test-fixtures/config.hcl.json index 67480965c2f5..56760ef96649 100644 --- a/command/server/test-fixtures/config.hcl.json +++ b/command/server/test-fixtures/config.hcl.json @@ -22,5 +22,7 @@ }, "max_lease_ttl": "10h", "default_lease_ttl": "10h", - "cluster_name":"testcluster" + "cluster_name":"testcluster", + "enable_cors": true, + "allowed_origins": "http://localhost:8[0-9]{3}" } diff --git a/command/server/test-fixtures/config2.hcl.json b/command/server/test-fixtures/config2.hcl.json index 7e4c9b9de351..0001088b1dcd 100644 --- a/command/server/test-fixtures/config2.hcl.json +++ b/command/server/test-fixtures/config2.hcl.json @@ -1,4 +1,6 @@ { + "enable_cors": true, + "allowed_origins": "http://localhost:8[0-9]{3}" "listener":[ { "tcp":{ From 88da2da6d853f890ce1def9b3403764a83dcc14f Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Thu, 20 Oct 2016 12:14:04 -0400 Subject: [PATCH 09/89] Exported the HandleCORS function and moved the invokation into the server command. --- command/server.go | 10 +++++++++- http/handler.go | 14 +++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/command/server.go b/command/server.go index 5004ef301cf5..ab7c2dc854d0 100644 --- a/command/server.go +++ b/command/server.go @@ -8,6 +8,7 @@ import ( "net/url" "os" "os/signal" + "regexp" "runtime" "sort" "strconv" @@ -219,7 +220,6 @@ func (c *ServerCommand) Run(args []string) int { Seal: seal, AuditBackends: c.AuditBackends, CredentialBackends: c.CredentialBackends, - EnableCORS: config.EnableCORS, LogicalBackends: c.LogicalBackends, Logger: c.logger, DisableCache: config.DisableCache, @@ -228,6 +228,7 @@ func (c *ServerCommand) Run(args []string) int { DefaultLeaseTTL: config.DefaultLeaseTTL, ClusterName: config.ClusterName, CacheSize: config.CacheSize, + EnableCORS: config.EnableCORS, } var disableClustering bool @@ -518,6 +519,13 @@ func (c *ServerCommand) Run(args []string) int { handler := vaulthttp.Handler(core) + // Wrap the handler in another handler to ensure + // CORS headers are added to the requests if enabled. + if coreConfig.EnableCORS { + allowedOrigins, _ := regexp.Compile(coreConfig.AllowedOrigins) + handler = vaulthttp.HandleCORS(handler, allowedOrigins) + } + // This needs to happen before we first unseal, so before we trigger dev // mode if it's set core.SetClusterListenerAddrs(clusterAddrs) diff --git a/http/handler.go b/http/handler.go index d0b37f5204b0..a5af4f6378a2 100644 --- a/http/handler.go +++ b/http/handler.go @@ -59,11 +59,11 @@ func Handler(core *vault.Core) http.Handler { // Wrap the handler in another handler to trigger all help paths. handler := handleHelpHandler(mux, core) - // Wrap the handler in another handler to ensure - // CORS headers are added to the requests if enabled. - if useCORS, allowedOrigins := core.AllowCORS(); useCORS { - handler = handleCORS(handler, allowedOrigins) - } + // // Wrap the handler in another handler to ensure + // // CORS headers are added to the requests if enabled. + // if useCORS, allowedOrigins := core.AllowCORS(); useCORS { + // handler = handleCORS(handler, allowedOrigins) + // } return handler } @@ -122,9 +122,9 @@ func parseRequest(r *http.Request, out interface{}) error { return err } -// handleCORS adds required headers to properly respond to request that +// HandleCORS adds required headers to properly respond to request that // require Cross Origin Resource Sharing (CORS) headers. -func handleCORS(handler http.Handler, allowedOrigins *regexp.Regexp) http.Handler { +func HandleCORS(handler http.Handler, allowedOrigins *regexp.Regexp) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var allowedOrigin string From d18ed1879ccff0b868d4a4d553bb324413a4838e Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Thu, 20 Oct 2016 13:31:26 -0400 Subject: [PATCH 10/89] This was not the right place to apply the CORS handler. Removed. --- command/server.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/command/server.go b/command/server.go index ab7c2dc854d0..3e323e1a2237 100644 --- a/command/server.go +++ b/command/server.go @@ -8,7 +8,6 @@ import ( "net/url" "os" "os/signal" - "regexp" "runtime" "sort" "strconv" @@ -519,13 +518,6 @@ func (c *ServerCommand) Run(args []string) int { handler := vaulthttp.Handler(core) - // Wrap the handler in another handler to ensure - // CORS headers are added to the requests if enabled. - if coreConfig.EnableCORS { - allowedOrigins, _ := regexp.Compile(coreConfig.AllowedOrigins) - handler = vaulthttp.HandleCORS(handler, allowedOrigins) - } - // This needs to happen before we first unseal, so before we trigger dev // mode if it's set core.SetClusterListenerAddrs(clusterAddrs) From 0f5a7fa37e18a5924cdf2eee59d85d08cddd8b4c Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Thu, 20 Oct 2016 14:10:25 -0400 Subject: [PATCH 11/89] Added tests for requests that require CORS. --- http/handler_test.go | 37 +++++++++++++++++++++++++++++++++++++ http/logical_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/http/handler_test.go b/http/handler_test.go index 835d58fba7f2..f5983b6d4a57 100644 --- a/http/handler_test.go +++ b/http/handler_test.go @@ -13,6 +13,43 @@ import ( "github.com/hashicorp/vault/vault" ) +func TestHandler_cors(t *testing.T) { + core, _, token := vault.TestCoreUnsealed(t) + ln, addr := TestServer(t, core) + defer ln.Close() + + // Test preflight requests + req, err := http.NewRequest("OPTIONS", addr+"/v1/sys/seal-status", nil) + if err != nil { + t.Fatalf("err: %s", err) + } + req.Header.Set(AuthHeaderName, token) + req.Header.Set("Origin", addr) + + client := cleanhttp.DefaultClient() + resp, err := client.Do(req) + if err != nil { + t.Fatalf("err: %s", err) + } + + expHeaders := map[string]string{ + "Access-Control-Allow-Origin": addr, + "Access-Control-Allow-Headers": "X-Requested-With,Content-Type,Accept,Origin,Authorization,X-Vault-Token", + "Access-Control-Allow-Credentials": "true", + } + + for expHeader, expected := range expHeaders { + actual := resp.Header.Get(expHeader) + if actual == "" { + t.Fatalf("bad:\nHeader: %#v was not on response.", expHeader) + } + + if actual != expected { + t.Fatalf("bad:\nExpected: %#v\nActual: %#v\n", expected, actual) + } + } +} + // We use this test to verify header auth func TestSysMounts_headerAuth(t *testing.T) { core, _, token := vault.TestCoreUnsealed(t) diff --git a/http/logical_test.go b/http/logical_test.go index 161261686d2f..279a29060922 100644 --- a/http/logical_test.go +++ b/http/logical_test.go @@ -16,6 +16,36 @@ import ( "github.com/hashicorp/vault/vault" ) +func TestLogical_cors(t *testing.T) { + core, _, token := vault.TestCoreUnsealed(t) + ln, addr := TestServer(t, core) + defer ln.Close() + TestServerAuth(t, addr, token) + + expHeaders := map[string]string{ + "Access-Control-Allow-Origin": addr, + "Access-Control-Allow-Credentials": "true", + } + + // WRITE + resp := testHttpPut(t, token, addr+"/v1/secret/foo", map[string]interface{}{ + "data": "bar", + }) + testResponseStatus(t, resp, 204) + + for expHeader, expected := range expHeaders { + actual := resp.Header.Get(expHeader) + if actual == "" { + t.Fatalf("bad:\nHeader: %#v was not on response.", expHeader) + } + + if actual != expected { + t.Fatalf("bad:\nExpected: %#v\nActual: %#v\n", expected, actual) + } + } + +} + func TestLogical(t *testing.T) { core, _, token := vault.TestCoreUnsealed(t) ln, addr := TestServer(t, core) From fd5494ba490eaf1afd289fef4dc904446eb1c820 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Thu, 20 Oct 2016 14:13:07 -0400 Subject: [PATCH 12/89] Finalized how and where to apply the CORS headers. --- http/handler.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/http/handler.go b/http/handler.go index a5af4f6378a2..e5e818ecd862 100644 --- a/http/handler.go +++ b/http/handler.go @@ -59,11 +59,11 @@ func Handler(core *vault.Core) http.Handler { // Wrap the handler in another handler to trigger all help paths. handler := handleHelpHandler(mux, core) - // // Wrap the handler in another handler to ensure - // // CORS headers are added to the requests if enabled. - // if useCORS, allowedOrigins := core.AllowCORS(); useCORS { - // handler = handleCORS(handler, allowedOrigins) - // } + // Wrap the handler in another handler to ensure + // CORS headers are added to the requests if enabled. + if useCORS, allowedOrigins := core.CORSInfo(); useCORS { + handler = handleCORS(handler, allowedOrigins) + } return handler } @@ -124,7 +124,7 @@ func parseRequest(r *http.Request, out interface{}) error { // HandleCORS adds required headers to properly respond to request that // require Cross Origin Resource Sharing (CORS) headers. -func HandleCORS(handler http.Handler, allowedOrigins *regexp.Regexp) http.Handler { +func handleCORS(handler http.Handler, allowedOrigins *regexp.Regexp) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var allowedOrigin string From 6c54ded6bd542425308db51dfd0cd67e92a649da Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Thu, 20 Oct 2016 14:13:53 -0400 Subject: [PATCH 13/89] Setting the Origin header in order to test CORS handling. --- http/http_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/http/http_test.go b/http/http_test.go index 86ddde4d95f2..d6e04ee6e8b2 100644 --- a/http/http_test.go +++ b/http/http_test.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net/http" + "regexp" "testing" "time" @@ -44,6 +45,11 @@ func testHttpData(t *testing.T, method string, token string, addr string, body i t.Fatalf("err: %s", err) } + // Get the address of the local listener in order to attach it to an Origin header. + // This will allow for the testing of requests the require CORS, without using a browser. + hostURLRegexp, _ := regexp.Compile("http[s]?://.+:[0-9]+") + req.Header.Set("Origin", hostURLRegexp.FindString(addr)) + req.Header.Set("Content-Type", "application/json") if len(token) != 0 { From 9b2f66ca1b662162a830f6f4c6bc892266c52a26 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Thu, 20 Oct 2016 14:15:03 -0400 Subject: [PATCH 14/89] Removed AllowCORS() func and replaced it with CORSInfo() func. This makes more sense. --- vault/core.go | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/vault/core.go b/vault/core.go index 2419ffd5a350..903dda392998 100644 --- a/vault/core.go +++ b/vault/core.go @@ -435,13 +435,13 @@ func NewCore(conf *CoreConfig) (*Core, error) { clusterListenerShutdownSuccessCh: make(chan struct{}), } - if c.enableCORS { + if conf.EnableCORS { if conf.AllowedOrigins == "" { c.allowedOrigins, _ = regexp.Compile(".*") } else { - c.allowedOrigins, err = regexp.Compile(conf.AllowedOrigins) + c.allowedOrigins, err = regexp.Compile(".*") if err != nil { - return nil, fmt.Errorf("invalid regexp specified: %v", err) + return nil, fmt.Errorf("invalid regexp: %v", err) } } } @@ -500,6 +500,10 @@ func NewCore(conf *CoreConfig) (*Core, error) { return c, storedKeyErr } +func (c *Core) CORSInfo() (bool, *regexp.Regexp) { + return c.enableCORS, c.allowedOrigins +} + // Shutdown is invoked when the Vault instance is about to be terminated. It // should not be accessible as part of an API call as it will cause an availability // problem. It is only used to gracefully quit in the case of HA so that failover @@ -632,15 +636,6 @@ func (c *Core) Standby() (bool, error) { return c.standby, nil } -// AllowCORS checks if Vault should insert CORS headers on responses. It returns a bool -// and a *regexp.Regexp that is used to validate if the origin of the request is allowed. -func (c *Core) AllowCORS() (bool, *regexp.Regexp) { - if c.enableCORS { - return c.enableCORS, c.allowedOrigins - } - return false, nil -} - // Leader is used to get the current active leader func (c *Core) Leader() (isLeader bool, leaderAddr string, err error) { c.stateLock.RLock() From 6747af084ff24eb814c0ec25fa63ff8abe014c8b Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Thu, 20 Oct 2016 14:16:18 -0400 Subject: [PATCH 15/89] Added configuration for CORS where required. --- vault/testing.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vault/testing.go b/vault/testing.go index f721da0e8a50..6d975d2982dd 100644 --- a/vault/testing.go +++ b/vault/testing.go @@ -121,6 +121,8 @@ func TestCoreWithSeal(t *testing.T, testSeal Seal) *Core { CredentialBackends: noopBackends, DisableMlock: true, Logger: logger, + EnableCORS: true, + AllowedOrigins: ".*", } if testSeal != nil { conf.Seal = testSeal From a8cca06c6fdd8884a354de0028fc9ecba2de4686 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Wed, 9 Nov 2016 14:11:59 -0500 Subject: [PATCH 16/89] Reverting changes. CORS config to be done via a /sys endpoint. --- command/server/config.go | 5 ----- command/server/config_test.go | 4 ---- command/server/test-fixtures/config-dir/bar.json | 4 +--- command/server/test-fixtures/config-dir/baz.hcl | 2 -- command/server/test-fixtures/config-dir/foo.hcl | 2 -- command/server/test-fixtures/config.hcl | 6 ++---- command/server/test-fixtures/config.hcl.json | 4 +--- command/server/test-fixtures/config2.hcl.json | 2 -- 8 files changed, 4 insertions(+), 25 deletions(-) diff --git a/command/server/config.go b/command/server/config.go index 9da31c14d48d..6e5cd12ff17b 100644 --- a/command/server/config.go +++ b/command/server/config.go @@ -27,9 +27,6 @@ type Config struct { DisableCache bool `hcl:"disable_cache"` DisableMlock bool `hcl:"disable_mlock"` - EnableCORS bool `hcl:"enable_cors"` - AllowedOrigins string `hcl:"allowed_origins"` - Telemetry *Telemetry `hcl:"telemetry"` MaxLeaseTTL time.Duration `hcl:"-"` @@ -291,7 +288,6 @@ func ParseConfig(d string, logger log.Logger) (*Config, error) { } valid := []string{ - "allowed_origins", "atlas", "backend", "ha_backend", @@ -299,7 +295,6 @@ func ParseConfig(d string, logger log.Logger) (*Config, error) { "cache_size", "disable_cache", "disable_mlock", - "enable_cors", "telemetry", "default_lease_ttl", "max_lease_ttl", diff --git a/command/server/config_test.go b/command/server/config_test.go index 326aacf50d3a..fcaae7189ce4 100644 --- a/command/server/config_test.go +++ b/command/server/config_test.go @@ -19,8 +19,6 @@ func TestLoadConfigFile(t *testing.T) { } expected := &Config{ - EnableCORS: true, - AllowedOrigins: "http://localhost:8[0-9]{3}", Listeners: []*Listener{ &Listener{ Type: "atlas", @@ -85,8 +83,6 @@ func TestLoadConfigFile_json(t *testing.T) { } expected := &Config{ - EnableCORS: true, - AllowedOrigins: "http://localhost:8[0-9]{3}", Listeners: []*Listener{ &Listener{ Type: "tcp", diff --git a/command/server/test-fixtures/config-dir/bar.json b/command/server/test-fixtures/config-dir/bar.json index 171f91cc2940..677e81aae152 100644 --- a/command/server/test-fixtures/config-dir/bar.json +++ b/command/server/test-fixtures/config-dir/bar.json @@ -5,7 +5,5 @@ } }, - "max_lease_ttl": "10h", - "enable_cors": true, - "allowed_origins": "http://localhost:8[0-9]{3}" + "max_lease_ttl": "10h" } diff --git a/command/server/test-fixtures/config-dir/baz.hcl b/command/server/test-fixtures/config-dir/baz.hcl index 9cbaf5b14501..9b650104b2ea 100644 --- a/command/server/test-fixtures/config-dir/baz.hcl +++ b/command/server/test-fixtures/config-dir/baz.hcl @@ -6,5 +6,3 @@ telemetry { default_lease_ttl = "10h" cluster_name = "testcluster" -enable_cors = true -allowed_origins = "http://localhost:8[0-9]{3}" diff --git a/command/server/test-fixtures/config-dir/foo.hcl b/command/server/test-fixtures/config-dir/foo.hcl index 4fbd7e293323..815a21624fbd 100644 --- a/command/server/test-fixtures/config-dir/foo.hcl +++ b/command/server/test-fixtures/config-dir/foo.hcl @@ -1,7 +1,5 @@ disable_cache = true disable_mlock = true -enable_cors = true -allowed_origins = "http://localhost:8[0-9]{3}" backend "consul" { foo = "bar" diff --git a/command/server/test-fixtures/config.hcl b/command/server/test-fixtures/config.hcl index eff3d910b4fe..9ad47900c0fc 100644 --- a/command/server/test-fixtures/config.hcl +++ b/command/server/test-fixtures/config.hcl @@ -1,7 +1,5 @@ -disable_cache = true -disable_mlock = true -enable_cors = true -allowed_origins = "http://localhost:8[0-9]{3}" +disable_cache = true +disable_mlock = true listener "atlas" { token = "foobar" diff --git a/command/server/test-fixtures/config.hcl.json b/command/server/test-fixtures/config.hcl.json index 56760ef96649..67480965c2f5 100644 --- a/command/server/test-fixtures/config.hcl.json +++ b/command/server/test-fixtures/config.hcl.json @@ -22,7 +22,5 @@ }, "max_lease_ttl": "10h", "default_lease_ttl": "10h", - "cluster_name":"testcluster", - "enable_cors": true, - "allowed_origins": "http://localhost:8[0-9]{3}" + "cluster_name":"testcluster" } diff --git a/command/server/test-fixtures/config2.hcl.json b/command/server/test-fixtures/config2.hcl.json index 0001088b1dcd..7e4c9b9de351 100644 --- a/command/server/test-fixtures/config2.hcl.json +++ b/command/server/test-fixtures/config2.hcl.json @@ -1,6 +1,4 @@ { - "enable_cors": true, - "allowed_origins": "http://localhost:8[0-9]{3}" "listener":[ { "tcp":{ From 6d80c03986e1188ad2027c2e66c0169aee628212 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Wed, 9 Nov 2016 14:11:59 -0500 Subject: [PATCH 17/89] Reverting changes. CORS config to be done via a /sys endpoint. --- command/server.go | 1 - command/server/config.go | 5 ----- command/server/config_test.go | 4 ---- command/server/test-fixtures/config-dir/bar.json | 4 +--- command/server/test-fixtures/config-dir/baz.hcl | 2 -- command/server/test-fixtures/config-dir/foo.hcl | 2 -- command/server/test-fixtures/config.hcl | 6 ++---- command/server/test-fixtures/config.hcl.json | 4 +--- command/server/test-fixtures/config2.hcl.json | 2 -- 9 files changed, 4 insertions(+), 26 deletions(-) diff --git a/command/server.go b/command/server.go index 3e323e1a2237..18040bd68acd 100644 --- a/command/server.go +++ b/command/server.go @@ -227,7 +227,6 @@ func (c *ServerCommand) Run(args []string) int { DefaultLeaseTTL: config.DefaultLeaseTTL, ClusterName: config.ClusterName, CacheSize: config.CacheSize, - EnableCORS: config.EnableCORS, } var disableClustering bool diff --git a/command/server/config.go b/command/server/config.go index 9da31c14d48d..6e5cd12ff17b 100644 --- a/command/server/config.go +++ b/command/server/config.go @@ -27,9 +27,6 @@ type Config struct { DisableCache bool `hcl:"disable_cache"` DisableMlock bool `hcl:"disable_mlock"` - EnableCORS bool `hcl:"enable_cors"` - AllowedOrigins string `hcl:"allowed_origins"` - Telemetry *Telemetry `hcl:"telemetry"` MaxLeaseTTL time.Duration `hcl:"-"` @@ -291,7 +288,6 @@ func ParseConfig(d string, logger log.Logger) (*Config, error) { } valid := []string{ - "allowed_origins", "atlas", "backend", "ha_backend", @@ -299,7 +295,6 @@ func ParseConfig(d string, logger log.Logger) (*Config, error) { "cache_size", "disable_cache", "disable_mlock", - "enable_cors", "telemetry", "default_lease_ttl", "max_lease_ttl", diff --git a/command/server/config_test.go b/command/server/config_test.go index 326aacf50d3a..fcaae7189ce4 100644 --- a/command/server/config_test.go +++ b/command/server/config_test.go @@ -19,8 +19,6 @@ func TestLoadConfigFile(t *testing.T) { } expected := &Config{ - EnableCORS: true, - AllowedOrigins: "http://localhost:8[0-9]{3}", Listeners: []*Listener{ &Listener{ Type: "atlas", @@ -85,8 +83,6 @@ func TestLoadConfigFile_json(t *testing.T) { } expected := &Config{ - EnableCORS: true, - AllowedOrigins: "http://localhost:8[0-9]{3}", Listeners: []*Listener{ &Listener{ Type: "tcp", diff --git a/command/server/test-fixtures/config-dir/bar.json b/command/server/test-fixtures/config-dir/bar.json index 171f91cc2940..677e81aae152 100644 --- a/command/server/test-fixtures/config-dir/bar.json +++ b/command/server/test-fixtures/config-dir/bar.json @@ -5,7 +5,5 @@ } }, - "max_lease_ttl": "10h", - "enable_cors": true, - "allowed_origins": "http://localhost:8[0-9]{3}" + "max_lease_ttl": "10h" } diff --git a/command/server/test-fixtures/config-dir/baz.hcl b/command/server/test-fixtures/config-dir/baz.hcl index 9cbaf5b14501..9b650104b2ea 100644 --- a/command/server/test-fixtures/config-dir/baz.hcl +++ b/command/server/test-fixtures/config-dir/baz.hcl @@ -6,5 +6,3 @@ telemetry { default_lease_ttl = "10h" cluster_name = "testcluster" -enable_cors = true -allowed_origins = "http://localhost:8[0-9]{3}" diff --git a/command/server/test-fixtures/config-dir/foo.hcl b/command/server/test-fixtures/config-dir/foo.hcl index 4fbd7e293323..815a21624fbd 100644 --- a/command/server/test-fixtures/config-dir/foo.hcl +++ b/command/server/test-fixtures/config-dir/foo.hcl @@ -1,7 +1,5 @@ disable_cache = true disable_mlock = true -enable_cors = true -allowed_origins = "http://localhost:8[0-9]{3}" backend "consul" { foo = "bar" diff --git a/command/server/test-fixtures/config.hcl b/command/server/test-fixtures/config.hcl index eff3d910b4fe..9ad47900c0fc 100644 --- a/command/server/test-fixtures/config.hcl +++ b/command/server/test-fixtures/config.hcl @@ -1,7 +1,5 @@ -disable_cache = true -disable_mlock = true -enable_cors = true -allowed_origins = "http://localhost:8[0-9]{3}" +disable_cache = true +disable_mlock = true listener "atlas" { token = "foobar" diff --git a/command/server/test-fixtures/config.hcl.json b/command/server/test-fixtures/config.hcl.json index 56760ef96649..67480965c2f5 100644 --- a/command/server/test-fixtures/config.hcl.json +++ b/command/server/test-fixtures/config.hcl.json @@ -22,7 +22,5 @@ }, "max_lease_ttl": "10h", "default_lease_ttl": "10h", - "cluster_name":"testcluster", - "enable_cors": true, - "allowed_origins": "http://localhost:8[0-9]{3}" + "cluster_name":"testcluster" } diff --git a/command/server/test-fixtures/config2.hcl.json b/command/server/test-fixtures/config2.hcl.json index 0001088b1dcd..7e4c9b9de351 100644 --- a/command/server/test-fixtures/config2.hcl.json +++ b/command/server/test-fixtures/config2.hcl.json @@ -1,6 +1,4 @@ { - "enable_cors": true, - "allowed_origins": "http://localhost:8[0-9]{3}" "listener":[ { "tcp":{ From 7ca440aab3ba8373794da9d814cbbe35007a75a5 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Fri, 18 Nov 2016 10:54:06 -0500 Subject: [PATCH 18/89] Refactored to align with the way things are going to be handled. --- vault/testing.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/vault/testing.go b/vault/testing.go index 6d975d2982dd..f721da0e8a50 100644 --- a/vault/testing.go +++ b/vault/testing.go @@ -121,8 +121,6 @@ func TestCoreWithSeal(t *testing.T, testSeal Seal) *Core { CredentialBackends: noopBackends, DisableMlock: true, Logger: logger, - EnableCORS: true, - AllowedOrigins: ".*", } if testSeal != nil { conf.Seal = testSeal From 71048cfa2382fd167145468837368556b640de03 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Fri, 18 Nov 2016 10:57:43 -0500 Subject: [PATCH 19/89] Refactored to place access to the CORS settings behind the barrier in the system backend, but accessible from the core. --- vault/core.go | 39 ++++--------- vault/cors.go | 121 ++++++++++++++++++++++++++++++++++++++++ vault/logical_system.go | 75 ++++++++++++++++++++++++- 3 files changed, 205 insertions(+), 30 deletions(-) create mode 100644 vault/cors.go diff --git a/vault/core.go b/vault/core.go index 794992fb188b..cabd9d4169f4 100644 --- a/vault/core.go +++ b/vault/core.go @@ -10,7 +10,6 @@ import ( "net" "net/http" "net/url" - "regexp" "sync" "time" @@ -129,12 +128,6 @@ type activeAdvertisement struct { // interface for API handlers and is responsible for managing the logical and physical // backends, router, security barrier, and audit trails. type Core struct { - // enableCORS determines if CORS headers should be returned on the HTTP reponses. - enableCORS bool - - // allowedOrigins is a regex that determines which domains are allowed to make cross-domain requests - allowedOrigins *regexp.Regexp - // HABackend may be available depending on the physical backend ha physical.HABackend @@ -299,14 +292,13 @@ type Core struct { rpcClientConn *grpc.ClientConn // The grpc forwarding client rpcForwardingClient RequestForwardingClient + + // CORS Information + corsConfig *CORSConfig } // CoreConfig is used to parameterize a core type CoreConfig struct { - AllowedOrigins string `json:"allowed_origins" structs:"allowed_origins" mapstructure:"allowed_origins"` - - EnableCORS bool `json:"enable_cors" structs:"enable_cors" mapstructure:"enable_cors"` - LogicalBackends map[string]logical.Factory `json:"logical_backends" structs:"logical_backends" mapstructure:"logical_backends"` CredentialBackends map[string]logical.Factory `json:"credential_backends" structs:"credential_backends" mapstructure:"credential_backends"` @@ -416,7 +408,6 @@ func NewCore(conf *CoreConfig) (*Core, error) { // Setup the core c := &Core{ - enableCORS: conf.EnableCORS, redirectAddr: conf.RedirectAddr, clusterAddr: conf.ClusterAddr, physical: conf.Physical, @@ -435,21 +426,12 @@ func NewCore(conf *CoreConfig) (*Core, error) { clusterListenerShutdownSuccessCh: make(chan struct{}), } - if conf.EnableCORS { - if conf.AllowedOrigins == "" { - c.allowedOrigins, _ = regexp.Compile(".*") - } else { - c.allowedOrigins, err = regexp.Compile(".*") - if err != nil { - return nil, fmt.Errorf("invalid regexp: %v", err) - } - } - } - if conf.HAPhysical != nil && conf.HAPhysical.HAEnabled() { c.ha = conf.HAPhysical } + // c.corsConfig = newCORSConfig() + // We create the funcs here, then populate the given config with it so that // the caller can share state conf.ReloadFuncsLock = &c.reloadFuncsLock @@ -500,10 +482,6 @@ func NewCore(conf *CoreConfig) (*Core, error) { return c, storedKeyErr } -func (c *Core) CORSInfo() (bool, *regexp.Regexp) { - return c.enableCORS, c.allowedOrigins -} - // Shutdown is invoked when the Vault instance is about to be terminated. It // should not be accessible as part of an API call as it will cause an availability // problem. It is only used to gracefully quit in the case of HA so that failover @@ -519,6 +497,13 @@ func (c *Core) Shutdown() error { return c.sealInternal() } +func (c *Core) CORSConfig() (*CORSConfig, error) { + if c.corsConfig == nil { + return nil, errors.New("CORS is not configured") + } + return c.corsConfig, nil +} + // LookupToken returns the properties of the token from the token store. This // is particularly useful to fetch the accessor of the client token and get it // populated in the logical request along with the client token. The accessor diff --git a/vault/cors.go b/vault/cors.go new file mode 100644 index 000000000000..85172f20ac64 --- /dev/null +++ b/vault/cors.go @@ -0,0 +1,121 @@ +package vault + +import ( + "errors" + "net/http" + "regexp" + "strings" +) + +type CORSConfig struct { + enabled bool + allowedOrigins *regexp.Regexp + allowedHeaders *map[string]string + allowedMethods *[]string + allowCredentials bool +} + +func newCORSConfig() *CORSConfig { + return &CORSConfig{ + enabled: false, + allowedOrigins: ®exp.Regexp{}, + allowedHeaders: &map[string]string{ + "Access-Control-Allow-Headers": "X-Requested-With,Content-Type,Accept,Origin,Authorization,X-Vault-Token", + "Access-Control-Max-Age": "1800", + "Content-Type": "text/plain", + }, + allowCredentials: true, + allowedMethods: &[]string{http.MethodOptions}, + } +} + +func (c *CORSConfig) Enabled() bool { + return c.enabled +} + +func (c *CORSConfig) AddMethod(newMethod string) *[]string { + // Do not duplicate methods. + for _, method := range *c.allowedMethods { + if method == newMethod { + return c.allowedMethods + } + } + + *c.allowedMethods = append(*c.allowedMethods, newMethod) + return c.allowedMethods +} + +func (c *CORSConfig) AllowedMethods() []string { + return *c.allowedMethods +} + +func (c *CORSConfig) ApplyHeaders(w http.ResponseWriter, r *http.Request) error { + // check that the origin is valid & set the header. + origin := r.Header.Get("Origin") + validOrigin, err := c.validOrigin(origin) + if err != nil { + return err + } + if !validOrigin { + return nil + } + + methods := strings.Join(c.AllowedMethods(), ",") + w.Header().Set("Allow", methods) + + // add the credentials header if allowed. + if c.allowCredentials { + w.Header().Set("Access-Control-Allow-Credentials", "true") + } + + // apply headers for preflight requests + if r.Method == http.MethodOptions { + for k, v := range *c.allowedHeaders { + w.Header().Set(k, v) + } + } + + return nil +} + +// Disable sets CORS to disabled and clears the allowed origins +func (c *CORSConfig) Disable() { + c.enabled = false + c.allowedOrigins = ®exp.Regexp{} +} + +func (c *CORSConfig) AllowedOrigins() *regexp.Regexp { + return c.allowedOrigins +} + +func (c *CORSConfig) EnableCORS(s string) (*regexp.Regexp, error) { + if s == "" { + return nil, errors.New("regexp cannot be an empty string") + } + + allowedOrigins, err := regexp.Compile(s) + if err != nil { + return nil, err + } + + c.allowedOrigins = allowedOrigins + c.enabled = true + + return c.allowedOrigins, nil +} + +func (c *CORSConfig) validOrigin(origin string) (bool, error) { + if len(origin) == 0 { + return false, errors.New("origin is empty") + } + + if c.allowedOrigins == nil { + return false, errors.New("no origins are allowed") + } + + if c.allowedOrigins.MatchString(origin) { + return true, nil + } + + return false, nil +} diff --git a/vault/logical_system.go b/vault/logical_system.go index c64f168622ba..12edf327822f 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -35,6 +35,7 @@ func NewSystemBackend(core *Core, config *logical.BackendConfig) (logical.Backen Root: []string{ "auth/*", "remount", + "config/*", "revoke-prefix/*", "audit", "audit/*", @@ -66,6 +67,30 @@ func NewSystemBackend(core *Core, config *logical.BackendConfig) (logical.Backen HelpDescription: strings.TrimSpace(sysHelp["capabilities_accessor"][1]), }, + &framework.Path{ + Pattern: "config/cors$", + + Fields: map[string]*framework.FieldSchema{ + "enable": &framework.FieldSchema{ + Type: framework.TypeBool, + Description: "Enables or disables CORS headers on requests.", + }, + "allowed_origins": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "A regular expression describing origins that may make cross-origin requests.", + }, + }, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.ReadOperation: b.handleCORSStatus, + logical.UpdateOperation: b.handleCORSEnable, + logical.DeleteOperation: b.handleCORSDisable, + }, + + HelpDescription: "CORS does stuff.", + HelpSynopsis: "CORS is CORS", + }, + &framework.Path{ Pattern: "capabilities$", @@ -617,7 +642,51 @@ type SystemBackend struct { Backend *framework.Backend } -// handleCapabilitiesreturns the ACL capabilities of the token for a given path +// corsStatus returns the current CORS configuration as a logical.Response +func (b *SystemBackend) corsStatusResponse(corsConf *CORSConfig) *logical.Response { + return &logical.Response{ + Data: map[string]interface{}{ + "enabled": corsConf.Enabled(), + "allowed_origins": corsConf.AllowedOrigins().String(), + }, + } +} + +// handleCORSEnable sets the regexp that describes origins that are allowed +// to make cross-origin requests and sets the CORS enabled flag to true +func (b *SystemBackend) handleCORSEnable(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { + corsConf, err := b.Core.CORSConfig() + if err != nil { + corsConf = newCORSConfig() + } + + s := d.Get("allowed_origins") + + corsConf.EnableCORS(s.(string)) + + return b.corsStatusResponse(corsConf), nil +} + +// handleCORSDisable clears the allowed origins regexp and sets the CORS enabled flag to false +func (b *SystemBackend) handleCORSDisable(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { + corsConf, _ := b.Core.CORSConfig() + + corsConf.Disable() + + return b.corsStatusResponse(corsConf), nil +} + +// handleCORSStatus returns the current CORS configuration +func (b *SystemBackend) handleCORSStatus(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { + corsConf, err := b.Core.CORSConfig() + if err != nil { + return nil, err + } + + return b.corsStatusResponse(corsConf), nil +} + +// handleCapabilities returns the ACL capabilities of the token for a given path func (b *SystemBackend) handleCapabilities(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { token := d.Get("token").(string) if token == "" { @@ -635,8 +704,8 @@ func (b *SystemBackend) handleCapabilities(req *logical.Request, d *framework.Fi }, nil } -// handleCapabilitiesAccessor returns the ACL capabilities of the token associted -// with the given accessor for a given path. +// handleCapabilitiesAccessor returns the ACL capabilities of the +// token associted with the given accessor for a given path. func (b *SystemBackend) handleCapabilitiesAccessor(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { accessor := d.Get("accessor").(string) if accessor == "" { From 9a4c9f1fe9d438b027f796d70ff68bf6cf4b3573 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Fri, 18 Nov 2016 10:59:08 -0500 Subject: [PATCH 20/89] Move the logic to add the headers for CORS onto to the cors configuration struct. --- http/handler.go | 34 +++++++++------------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/http/handler.go b/http/handler.go index 424a9c26e543..381c2f2b8d8e 100644 --- a/http/handler.go +++ b/http/handler.go @@ -6,7 +6,6 @@ import ( "io" "net/http" "net/url" - "regexp" "strings" "github.com/hashicorp/errwrap" @@ -54,16 +53,11 @@ func Handler(core *vault.Core) http.Handler { mux.Handle("/v1/sys/wrapping/unwrap", handleRequestForwarding(core, handleLogical(core, false, wrappingVerificationFunc))) mux.Handle("/v1/sys/capabilities-self", handleRequestForwarding(core, handleLogical(core, true, nil))) mux.Handle("/v1/sys/", handleRequestForwarding(core, handleLogical(core, true, nil))) - mux.Handle("/v1/", handleRequestForwarding(core, handleLogical(core, false, nil))) + mux.Handle("/v1/", handleCORS(core, handleRequestForwarding(core, handleLogical(core, false, nil)))) // Wrap the handler in another handler to trigger all help paths. handler := handleHelpHandler(mux, core) - // Wrap the handler in another handler to ensure - // CORS headers are added to the requests if enabled. - if useCORS, allowedOrigins := core.CORSInfo(); useCORS { - handler = handleCORS(handler, allowedOrigins) - } return handler } @@ -124,29 +118,19 @@ func parseRequest(r *http.Request, out interface{}) error { // HandleCORS adds required headers to properly respond to request that // require Cross Origin Resource Sharing (CORS) headers. -func handleCORS(handler http.Handler, allowedOrigins *regexp.Regexp) http.Handler { +func handleCORS(core *vault.Core, handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var allowedOrigin string + var corsConf *vault.CORSConfig - origin := r.Header.Get("Origin") + corsConf, _ = core.CORSConfig() - if allowedOrigins.MatchString(origin) && len(origin) > 0 { - allowedOrigin = origin + if corsConf.Enabled() { + err := corsConf.ApplyHeaders(w, r) + if err != nil { + respondError(w, http.StatusInternalServerError, err) + } } - w.Header().Set("Access-Control-Allow-Origin", allowedOrigin) - w.Header().Set("Access-Control-Allow-Credentials", "true") - - // Handle preflight requests - if r.Method == "OPTIONS" { - w.Header().Set("Access-Control-Allow-Methods", "OPTIONS,GET,PUT,POST,DELETE,LIST") - w.Header().Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Accept,Origin,Authorization,X-Vault-Token") - w.Header().Set("Access-Control-Max-Age", "1800") - w.Header().Set("Content-Type", "text/plain") - - w.WriteHeader(http.StatusOK) - return - } handler.ServeHTTP(w, r) return }) From 71ec8982216aec1c92679745de0ebc18f79e8863 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Wed, 23 Nov 2016 13:45:51 -0500 Subject: [PATCH 21/89] Added cors command. --- cli/commands.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cli/commands.go b/cli/commands.go index 5297ea43bfbe..60146532382e 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -57,6 +57,12 @@ func Commands(metaPtr *meta.Meta) map[string]cli.CommandFactory { }, nil }, + "cors": func() (cli.Command, error) { + return &command.CorsCommand{ + Meta: *metaPtr, + }, nil + }, + "server": func() (cli.Command, error) { return &command.ServerCommand{ Meta: *metaPtr, From 4584a87850ed3fc730e24dfaf6493a0f11feb8be Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Wed, 23 Nov 2016 13:47:21 -0500 Subject: [PATCH 22/89] Moved to logic determine when to the apply the CORS headers to the ApplyHeaders() func. --- http/handler.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/http/handler.go b/http/handler.go index 9edfcfea8449..d54fd776daeb 100644 --- a/http/handler.go +++ b/http/handler.go @@ -128,17 +128,12 @@ func parseRequest(r *http.Request, out interface{}) error { // require Cross Origin Resource Sharing (CORS) headers. func handleCORS(core *vault.Core, handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var corsConf *vault.CORSConfig - - corsConf, _ = core.CORSConfig() - - if corsConf.Enabled() { - err := corsConf.ApplyHeaders(w, r) - if err != nil { - respondError(w, http.StatusInternalServerError, err) + corsConf, err := core.CORSConfig() + if err == nil { + if corsConf.Enabled() { + corsConf.ApplyHeaders(w, r) } } - handler.ServeHTTP(w, r) return }) From 367a04f9e099aa73038c78dadc69d8ff9f7a582a Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Wed, 23 Nov 2016 13:48:07 -0500 Subject: [PATCH 23/89] Made the error returned by CORSConfig() a const. --- vault/core.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vault/core.go b/vault/core.go index 96f6ace6158f..ae015f28d31a 100644 --- a/vault/core.go +++ b/vault/core.go @@ -433,8 +433,6 @@ func NewCore(conf *CoreConfig) (*Core, error) { c.ha = conf.HAPhysical } - // c.corsConfig = newCORSConfig() - // We create the funcs here, then populate the given config with it so that // the caller can share state conf.ReloadFuncsLock = &c.reloadFuncsLock @@ -500,9 +498,10 @@ func (c *Core) Shutdown() error { return c.sealInternal() } +// CORSConfig returns the current CORS configuration func (c *Core) CORSConfig() (*CORSConfig, error) { if c.corsConfig == nil { - return nil, errors.New("CORS is not configured") + return nil, errCORSNotConfigured } return c.corsConfig, nil } From ca2487f57a11a66933b4defe15c6e583e4caf1e1 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Wed, 23 Nov 2016 13:48:47 -0500 Subject: [PATCH 24/89] Lots of refactoring and clean up. --- vault/cors.go | 94 +++++++++++++++++++++------------------------------ 1 file changed, 39 insertions(+), 55 deletions(-) diff --git a/vault/cors.go b/vault/cors.go index 85172f20ac64..ccf8b48c3699 100644 --- a/vault/cors.go +++ b/vault/cors.go @@ -7,6 +7,8 @@ import ( "strings" ) +var errCORSNotConfigured = errors.New("CORS is not configured") + type CORSConfig struct { enabled bool allowedOrigins *regexp.Regexp @@ -33,89 +35,71 @@ func (c *CORSConfig) Enabled() bool { return c.enabled } -func (c *CORSConfig) AddMethod(newMethod string) *[]string { - // Do not duplicate methods. - for _, method := range *c.allowedMethods { - if method == newMethod { - return c.allowedMethods - } +func (c *CORSConfig) Enable(s string) error { + if s == "" { + return errors.New("regexp cannot be an empty string") } - *c.allowedMethods = append(*c.allowedMethods, newMethod) - return c.allowedMethods -} - -func (c *CORSConfig) AllowedMethods() []string { - return *c.allowedMethods -} - -func (c *CORSConfig) ApplyHeaders(w http.ResponseWriter, r *http.Request) error { - // check that the origin is valid & set the header. - origin := r.Header.Get("Origin") - validOrigin, err := c.validOrigin(origin) + allowedOrigins, err := regexp.Compile(s) if err != nil { return err } - if !validOrigin { - return nil - } - - methods := strings.Join(c.AllowedMethods(), ",") - w.Header().Set("Allow", methods) - - // add the credentials header if allowed. - if c.allowCredentials { - w.Header().Set("Access-Control-Allow-Credentials", "true") - } - // apply headers for preflight requests - if r.Method == http.MethodOptions { - for k, v := range *c.allowedHeaders { - w.Header().Set(k, v) - } - } + c.allowedOrigins = allowedOrigins + c.enabled = true return nil } // Disable sets CORS to disabled and clears the allowed origins func (c *CORSConfig) Disable() { - c.enabled = false - c.allowedOrigins = ®exp.Regexp{} + c = nil + // c.enabled = false + // c.allowedOrigins = ®exp.Regexp{} } -func (c *CORSConfig) AllowedOrigins() *regexp.Regexp { - return c.allowedOrigins +func (c *CORSConfig) AllowedMethods() []string { + return *c.allowedMethods } -func (c *CORSConfig) EnableCORS(s string) (*regexp.Regexp, error) { - if s == "" { - return nil, errors.New("regexp cannot be an empty string") - } +func (c *CORSConfig) ApplyHeaders(w http.ResponseWriter, r *http.Request) { + // check that the origin is valid & set the header. + origin := r.Header.Get("Origin") + if c.validOrigin(origin) { - allowedOrigins, err := regexp.Compile(s) - if err != nil { - return nil, err - } + methods := strings.Join(c.AllowedMethods(), ",") + w.Header().Set("Allow", methods) - c.allowedOrigins = allowedOrigins - c.enabled = true + // add the credentials header if allowed. + if c.allowCredentials { + w.Header().Set("Access-Control-Allow-Credentials", "true") + } + + // apply headers for preflight requests + if r.Method == http.MethodOptions { + for k, v := range *c.allowedHeaders { + w.Header().Set(k, v) + } + } + } +} - return c.allowedOrigins, nil +func (c *CORSConfig) AllowedOrigins() *regexp.Regexp { + return c.allowedOrigins } -func (c *CORSConfig) validOrigin(origin string) (bool, error) { +func (c *CORSConfig) validOrigin(origin string) bool { if len(origin) == 0 { - return false, errors.New("origin is empty") + return false } if c.allowedOrigins == nil { - return false, errors.New("no origins are allowed") + return false } if c.allowedOrigins.MatchString(origin) { - return true, nil + return true } - return false, nil + return false } From 4f6b25d7f2cca7240a640b375ddd3d22bbdb197d Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Wed, 23 Nov 2016 13:50:19 -0500 Subject: [PATCH 25/89] Refactoring and DRYing up. --- vault/logical_system.go | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/vault/logical_system.go b/vault/logical_system.go index 95e8713fb683..42d682e00d85 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -643,47 +643,46 @@ type SystemBackend struct { } // corsStatus returns the current CORS configuration as a logical.Response -func (b *SystemBackend) corsStatusResponse(corsConf *CORSConfig) *logical.Response { +func (b *SystemBackend) corsStatusResponse() (*logical.Response, error) { + corsConf := b.Core.corsConfig + if corsConf == nil { + return nil, errCORSNotConfigured + } return &logical.Response{ Data: map[string]interface{}{ "enabled": corsConf.Enabled(), "allowed_origins": corsConf.AllowedOrigins().String(), }, - } + }, nil } // handleCORSEnable sets the regexp that describes origins that are allowed // to make cross-origin requests and sets the CORS enabled flag to true func (b *SystemBackend) handleCORSEnable(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { - corsConf, err := b.Core.CORSConfig() - if err != nil { - corsConf = newCORSConfig() + if b.Core.corsConfig == nil { + b.Core.corsConfig = newCORSConfig() } - s := d.Get("allowed_origins") - corsConf.EnableCORS(s.(string)) + b.Core.corsConfig.Enable(s.(string)) - return b.corsStatusResponse(corsConf), nil + return b.corsStatusResponse() } // handleCORSDisable clears the allowed origins regexp and sets the CORS enabled flag to false func (b *SystemBackend) handleCORSDisable(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { - corsConf, _ := b.Core.CORSConfig() - - corsConf.Disable() + b.Core.corsConfig = nil - return b.corsStatusResponse(corsConf), nil + return &logical.Response{ + Data: map[string]interface{}{ + "enabled": false, + }, + }, nil } // handleCORSStatus returns the current CORS configuration func (b *SystemBackend) handleCORSStatus(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { - corsConf, err := b.Core.CORSConfig() - if err != nil { - return nil, err - } - - return b.corsStatusResponse(corsConf), nil + return b.corsStatusResponse() } // handleCapabilities returns the ACL capabilities of the token for a given path From 3643a10f582dcff312399864381e41cffce822cf Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Wed, 23 Nov 2016 13:52:37 -0500 Subject: [PATCH 26/89] Added tests for the CORS functionality. --- vault/logical_system_test.go | 52 ++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go index d6bca56f656a..aa5767bdb6ad 100644 --- a/vault/logical_system_test.go +++ b/vault/logical_system_test.go @@ -17,6 +17,7 @@ func TestSystemBackend_RootPaths(t *testing.T) { expected := []string{ "auth/*", "remount", + "config/*", "revoke-prefix/*", "audit", "audit/*", @@ -31,6 +32,57 @@ func TestSystemBackend_RootPaths(t *testing.T) { } } +func TestSystemConfigCORS(t *testing.T) { + b := testSystemBackend(t) + req := logical.TestRequest(t, logical.ReadOperation, "config/cors") + _, err := b.HandleRequest(req) + if err != errCORSNotConfigured { + t.Fatalf("expected: %v\nactual: %v", errCORSNotConfigured, err) + } + + req = logical.TestRequest(t, logical.UpdateOperation, "config/cors") + req.Data["allowed_origins"] = "http://.+:[0-9]{4}" + actual, err := b.HandleRequest(req) + + expected := &logical.Response{ + Data: map[string]interface{}{ + "allowed_origins": "http://.+:[0-9]{4}", + "enabled": true, + }, + } + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("UPDATE FAILED -- bad: %#v", actual) + } + + req = logical.TestRequest(t, logical.ReadOperation, "config/cors") + actual, err = b.HandleRequest(req) + if err != nil { + t.Fatalf("err: %v", err) + } + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("READ FAILED -- bad: %#v", actual) + } + + expected = &logical.Response{ + Data: map[string]interface{}{ + "enabled": false, + }, + } + + req = logical.TestRequest(t, logical.DeleteOperation, "config/cors") + actual, err = b.HandleRequest(req) + if err != nil { + t.Fatalf("err: %v", err) + } + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("DELETE FAILED -- bad: %#v", actual) + } + +} + func TestSystemBackend_mounts(t *testing.T) { b := testSystemBackend(t) req := logical.TestRequest(t, logical.ReadOperation, "mounts") From a98bba2becc866d231b14b5a2526311a977899cc Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Wed, 23 Nov 2016 13:53:02 -0500 Subject: [PATCH 27/89] Initial commit. --- api/sys_config_cors.go | 59 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 api/sys_config_cors.go diff --git a/api/sys_config_cors.go b/api/sys_config_cors.go new file mode 100644 index 000000000000..ff9c0739128a --- /dev/null +++ b/api/sys_config_cors.go @@ -0,0 +1,59 @@ +package api + +import "fmt" + +func (c *Sys) CORSStatus() (*CORSResponse, error) { + r := c.c.NewRequest("GET", "/v1/sys/config/cors") + resp, err := c.c.RawRequest(r) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var result CORSResponse + err = resp.DecodeJSON(&result) + return &result, err +} + +func (c *Sys) ConfigureCORS(req *CORSRequest) (*CORSResponse, error) { + r := c.c.NewRequest("PUT", "/v1/sys/config/cors") + if err := r.SetJSONBody(req); err != nil { + return nil, err + } + + resp, err := c.c.RawRequest(r) + if err != nil { + fmt.Printf("r = %#v\n", r) + return nil, err + } + defer resp.Body.Close() + + var result CORSResponse + err = resp.DecodeJSON(&result) + return &result, err +} + +func (c *Sys) DisableCORS() (*CORSResponse, error) { + r := c.c.NewRequest("DELETE", "/v1/sys/config/cors") + + resp, err := c.c.RawRequest(r) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var result CORSResponse + err = resp.DecodeJSON(&result) + return &result, err + +} + +type CORSRequest struct { + AllowedOrigins string `json:"allowed_origins"` + Enabled bool `json:"enabled"` +} + +type CORSResponse struct { + AllowedOrigins string `json:"allowed_origins"` + Enabled bool `json:"enabled"` +} From e57ac5e2ba85789e8b71db38a22bd4611c29462a Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Wed, 23 Nov 2016 13:53:23 -0500 Subject: [PATCH 28/89] Initial commit. --- command/cors.go | 117 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 command/cors.go diff --git a/command/cors.go b/command/cors.go new file mode 100644 index 000000000000..068503059c3f --- /dev/null +++ b/command/cors.go @@ -0,0 +1,117 @@ +package command + +import ( + "fmt" + "regexp" + "strings" + + "github.com/hashicorp/vault/api" + "github.com/hashicorp/vault/meta" +) + +type CorsCommand struct { + meta.Meta +} + +func (c *CorsCommand) Run(args []string) int { + var allowedStr string + var disable, status bool + + flags := c.Meta.FlagSet("cors", meta.FlagSetDefault) + flags.Usage = func() { c.Ui.Error(c.Help()) } + flags.StringVar(&allowedStr, "allowed-origins", "", "") + flags.BoolVar(&disable, "disable", false, "") + flags.BoolVar(&status, "status", false, "") + if err := flags.Parse(args); err != nil { + return 1 + } + + allowedOrigins, err := regexp.Compile(allowedStr) + if err != nil { + return 1 + } + + corsRequest := &api.CORSRequest{} + if !disable { + corsRequest.AllowedOrigins = allowedOrigins.String() + } else { + corsRequest.Enabled = false + } + + return c.runCORS(status, corsRequest) +} + +func (c *CorsCommand) runCORS(status bool, corsRequest *api.CORSRequest) int { + client, err := c.Client() + if err != nil { + c.Ui.Error(fmt.Sprintf( + "Error initializing client: %s", err)) + return 1 + } + + if status { + resp, err := client.Sys().CORSStatus() + if err != nil { + c.Ui.Error(fmt.Sprintf( + "Error getting CORS configuration: %s", err)) + return 1 + } + + if corsRequest.Enabled == false { + _, err := client.Sys().DisableCORS() + if err != nil { + c.Ui.Error(fmt.Sprintf( + "Error disabling CORS: %s", err)) + return 1 + } + } + + c.Ui.Output( + fmt.Sprintf("CORS Configuration\nEnabled: %t\nAllowed Origins: %s\n", + resp.Enabled, + resp.AllowedOrigins, + )) + return 0 + } + + _, err = client.Sys().ConfigureCORS(corsRequest) + if err != nil { + c.Ui.Error(fmt.Sprintf( + "Error configuring CORS: %s", err)) + return 1 + } + + return 0 +} + +func (c *CorsCommand) Help() string { + helpText := ` +Usage: vault cors [options] + + Configures the HTTP server to return CORS headers. + + This command connects to a Vault server and can enable CORS, disable CORS, or change + the regular expression for origins that are allowed to make cross-origin requests. + +General Options: +` + meta.GeneralOptionsUsage() + ` +Cors Options: + + -status Returns the current CORS configuration. + + -allowed-origins="" A regular expression that describes the origins + that should be allowed to make cross-origin + requests and be served CORS headers. A return code + of 0 means the regular expressions is valid, and + Vault will now serve CORS headers to clients from + matching origins; a return code of 1 means an error + was encountered. + + -disable Stop serving CORS headers for all origins. +` + return strings.TrimSpace(helpText) +} + +func (c *CorsCommand) Synopsis() string { + return "Configure CORS settings" +} From ceb493e592ff300c770df8676f68df4085e8c1d6 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Thu, 24 Nov 2016 13:18:05 -0500 Subject: [PATCH 29/89] Added help text. --- vault/logical_system.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/vault/logical_system.go b/vault/logical_system.go index 42d682e00d85..b82b970fb1d2 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -87,8 +87,8 @@ func NewSystemBackend(core *Core, config *logical.BackendConfig) (logical.Backen logical.DeleteOperation: b.handleCORSDisable, }, - HelpDescription: "CORS does stuff.", - HelpSynopsis: "CORS is CORS", + HelpDescription: strings.TrimSpace(sysHelp["config/cors"][0]), + HelpSynopsis: strings.TrimSpace(sysHelp["config/cors"][1]), }, &framework.Path{ @@ -1777,6 +1777,21 @@ as well as perform core operations. // sysHelp is all the help text for the sys backend. var sysHelp = map[string][2]string{ + "config/cors": { + "Configures or returns the current configuration of CORS settings.", + ` +This path responds to the following HTTP methods. + + GET / + Returns the configuration of the CORS setting. + + POST / + Sets the regular expression which describes origins that can make cross-origin requests. + + DELETE / + Clears the CORS configuration and disables acceptance of CORS requests. + `, + }, "init": { "Initializes or returns the initialization status of the Vault.", ` From 7aa839c655c22565828832782357543f92debecd Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Mon, 28 Nov 2016 17:07:04 -0500 Subject: [PATCH 30/89] Fixed a bunch of logic bugs that were causing the server to panic after checking the status of the CORS configuration. --- command/cors.go | 47 +++++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/command/cors.go b/command/cors.go index 068503059c3f..cca56db1fa63 100644 --- a/command/cors.go +++ b/command/cors.go @@ -26,16 +26,20 @@ func (c *CorsCommand) Run(args []string) int { return 1 } - allowedOrigins, err := regexp.Compile(allowedStr) - if err != nil { - return 1 - } - corsRequest := &api.CORSRequest{} - if !disable { - corsRequest.AllowedOrigins = allowedOrigins.String() - } else { - corsRequest.Enabled = false + + if !status { + allowedOrigins, err := regexp.Compile(allowedStr) + if err != nil { + return 1 + } + + if !disable { + corsRequest.AllowedOrigins = allowedOrigins.String() + corsRequest.Enabled = true + } else { + corsRequest.Enabled = false + } } return c.runCORS(status, corsRequest) @@ -49,6 +53,7 @@ func (c *CorsCommand) runCORS(status bool, corsRequest *api.CORSRequest) int { return 1 } + // Get the current CORS configuration. if status { resp, err := client.Sys().CORSStatus() if err != nil { @@ -56,24 +61,26 @@ func (c *CorsCommand) runCORS(status bool, corsRequest *api.CORSRequest) int { "Error getting CORS configuration: %s", err)) return 1 } - - if corsRequest.Enabled == false { - _, err := client.Sys().DisableCORS() - if err != nil { - c.Ui.Error(fmt.Sprintf( - "Error disabling CORS: %s", err)) - return 1 - } - } - c.Ui.Output( - fmt.Sprintf("CORS Configuration\nEnabled: %t\nAllowed Origins: %s\n", + fmt.Sprintf("Enabled: %t\nAllowed Origins: %s", resp.Enabled, resp.AllowedOrigins, )) return 0 } + // Disable (i.e. clear) the CORS configuration. + if corsRequest.Enabled == false { + _, err := client.Sys().DisableCORS() + if err != nil { + c.Ui.Error(fmt.Sprintf( + "Error disabling CORS: %s", err)) + return 1 + } + return 0 + } + + // Update the CORS configuration. _, err = client.Sys().ConfigureCORS(corsRequest) if err != nil { c.Ui.Error(fmt.Sprintf( From a974a2b3c54336672975d372802ad5df437053da Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 29 Nov 2016 12:01:37 -0500 Subject: [PATCH 31/89] Removed debug code. --- api/sys_config_cors.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/api/sys_config_cors.go b/api/sys_config_cors.go index ff9c0739128a..e7f2a59453c7 100644 --- a/api/sys_config_cors.go +++ b/api/sys_config_cors.go @@ -1,7 +1,5 @@ package api -import "fmt" - func (c *Sys) CORSStatus() (*CORSResponse, error) { r := c.c.NewRequest("GET", "/v1/sys/config/cors") resp, err := c.c.RawRequest(r) @@ -23,7 +21,6 @@ func (c *Sys) ConfigureCORS(req *CORSRequest) (*CORSResponse, error) { resp, err := c.c.RawRequest(r) if err != nil { - fmt.Printf("r = %#v\n", r) return nil, err } defer resp.Body.Close() From ef2a522dcb9c75302e9f599bb9e21c5f291e2ecb Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 29 Nov 2016 12:02:13 -0500 Subject: [PATCH 32/89] Fixed logic bug. --- command/cors.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/command/cors.go b/command/cors.go index cca56db1fa63..a49ba417967f 100644 --- a/command/cors.go +++ b/command/cors.go @@ -34,11 +34,11 @@ func (c *CorsCommand) Run(args []string) int { return 1 } - if !disable { + if disable { + corsRequest.Enabled = false + } else { corsRequest.AllowedOrigins = allowedOrigins.String() corsRequest.Enabled = true - } else { - corsRequest.Enabled = false } } From b22172e0d8746a7ecb89ba2c3efc2570c0e94976 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 29 Nov 2016 16:52:50 -0500 Subject: [PATCH 33/89] Added code to ensure the server responds correctly when the CORS request fails. --- http/handler.go | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/http/handler.go b/http/handler.go index d54fd776daeb..e3113c049858 100644 --- a/http/handler.go +++ b/http/handler.go @@ -124,16 +124,23 @@ func parseRequest(r *http.Request, out interface{}) error { return err } -// HandleCORS adds required headers to properly respond to request that -// require Cross Origin Resource Sharing (CORS) headers. +// HandleCORS adds required headers to properly respond to +// requests that require Cross Origin Resource Sharing (CORS) headers. func handleCORS(core *vault.Core, handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - corsConf, err := core.CORSConfig() - if err == nil { - if corsConf.Enabled() { - corsConf.ApplyHeaders(w, r) - } + corsConf := core.CORSConfig() + statusCode := corsConf.ApplyHeaders(w, r) + + if statusCode != http.StatusOK { + respondRaw(w, r, &logical.Response{ + Data: map[string]interface{}{ + logical.HTTPStatusCode: statusCode, + logical.HTTPRawBody: []byte(""), + }, + }) + return } + handler.ServeHTTP(w, r) return }) From dd0668626f618bedf4799f443c6b025f4df9d108 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 29 Nov 2016 16:53:22 -0500 Subject: [PATCH 34/89] Skip handling the logical request for preflights. --- http/logical.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/http/logical.go b/http/logical.go index 6e644789781f..1d5c67153b2f 100644 --- a/http/logical.go +++ b/http/logical.go @@ -54,6 +54,7 @@ func buildLogicalRequest(core *vault.Core, w http.ResponseWriter, r *http.Reques op = logical.UpdateOperation case "LIST": op = logical.ListOperation + case "OPTIONS": default: return nil, http.StatusMethodNotAllowed, nil } @@ -95,6 +96,10 @@ func buildLogicalRequest(core *vault.Core, w http.ResponseWriter, r *http.Reques func handleLogical(core *vault.Core, dataOnly bool, prepareRequestCallback PrepareRequestFunc) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Bubble up CORS preflight requests to the CORS handler. + if r.Method == "OPTIONS" { + return + } req, statusCode, err := buildLogicalRequest(core, w, r) if err != nil || statusCode != 0 { respondError(w, statusCode, err) From 0ad8ac3072ef8829ca5363fcc3dcf0f1daddc088 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 29 Nov 2016 16:57:48 -0500 Subject: [PATCH 35/89] Flesh out tests for CORS handler --- http/handler_test.go | 60 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/http/handler_test.go b/http/handler_test.go index f5983b6d4a57..9573a484aff6 100644 --- a/http/handler_test.go +++ b/http/handler_test.go @@ -14,27 +14,75 @@ import ( ) func TestHandler_cors(t *testing.T) { - core, _, token := vault.TestCoreUnsealed(t) + core, _, _ := vault.TestCoreUnsealed(t) ln, addr := TestServer(t, core) defer ln.Close() - // Test preflight requests - req, err := http.NewRequest("OPTIONS", addr+"/v1/sys/seal-status", nil) + // Enable CORS and allow from any origin for testing. + corsConfig := core.CORSConfig() + err := corsConfig.Enable(addr) + if err != nil { + t.Fatalf("Error enabling CORS: %s", err) + } + + req, err := http.NewRequest(http.MethodOptions, addr+"/v1/", nil) if err != nil { t.Fatalf("err: %s", err) } - req.Header.Set(AuthHeaderName, token) - req.Header.Set("Origin", addr) + req.Header.Set("Origin", "BAD ORIGIN") + // Requests from unacceptable origins will be rejected with a 403. client := cleanhttp.DefaultClient() resp, err := client.Do(req) if err != nil { t.Fatalf("err: %s", err) } + if resp.StatusCode != http.StatusForbidden { + t.Fatalf("Bad status:\nexpected: 403 Forbidden\nactual: %s", resp.Status) + } + + // + // Test preflight requests + // + + // Set a valid origin + req.Header.Set("Origin", addr) + + // Server should NOT accept arbitrary methods. + req.Header.Set("Access-Control-Request-Method", "FOO") + + client = cleanhttp.DefaultClient() + resp, err = client.Do(req) + if err != nil { + t.Fatalf("err: %s", err) + } + + // Fail if an arbitrary method is accepted. + if resp.StatusCode != http.StatusMethodNotAllowed { + t.Fatalf("Bad status:\nexpected: 405 Method Not Allowed\nactual: %s", resp.Status) + } + + // Server SHOULD accept acceptable methods. + req.Header.Set("Access-Control-Request-Method", http.MethodPost) + + client = cleanhttp.DefaultClient() + resp, err = client.Do(req) + if err != nil { + t.Fatalf("err: %s", err) + } + + // Fail if an acceptable method is NOT accepted. + if resp.StatusCode != http.StatusOK { + t.Fatalf("Bad status:\nexpected: 200 OK\nactual: %s", resp.Status) + } + + // + // Test that the CORS headers are applied correctly. + // expHeaders := map[string]string{ "Access-Control-Allow-Origin": addr, - "Access-Control-Allow-Headers": "X-Requested-With,Content-Type,Accept,Origin,Authorization,X-Vault-Token", + "Access-Control-Allow-Headers": "origin,content-type,cache-control,accept,options,authorization,x-requested-with,x-vault-token", "Access-Control-Allow-Credentials": "true", } From 796f0ef10d00a338d749fc418192702144c92a23 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 29 Nov 2016 16:59:42 -0500 Subject: [PATCH 36/89] Refactored so that the core is created with an empty CORSConfig vs. having that field set to nil. Much cleaner this way and doesn't require returning an error from the CORSConfig() func. --- vault/core.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/vault/core.go b/vault/core.go index ae015f28d31a..5fa1c6e3caad 100644 --- a/vault/core.go +++ b/vault/core.go @@ -409,6 +409,9 @@ func NewCore(conf *CoreConfig) (*Core, error) { return nil, fmt.Errorf("barrier setup failed: %v", err) } + // Get a default CORS config. + corsConfig := newCORSConfig() + // Setup the core c := &Core{ redirectAddr: conf.RedirectAddr, @@ -427,6 +430,7 @@ func NewCore(conf *CoreConfig) (*Core, error) { localClusterCertPool: x509.NewCertPool(), clusterListenerShutdownCh: make(chan struct{}), clusterListenerShutdownSuccessCh: make(chan struct{}), + corsConfig: corsConfig, } if conf.HAPhysical != nil && conf.HAPhysical.HAEnabled() { @@ -499,11 +503,8 @@ func (c *Core) Shutdown() error { } // CORSConfig returns the current CORS configuration -func (c *Core) CORSConfig() (*CORSConfig, error) { - if c.corsConfig == nil { - return nil, errCORSNotConfigured - } - return c.corsConfig, nil +func (c *Core) CORSConfig() *CORSConfig { + return c.corsConfig } // LookupToken returns the properties of the token from the token store. This From f1a92253272045fa8f55ee600a7e2a65aa045a91 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 29 Nov 2016 17:02:56 -0500 Subject: [PATCH 37/89] Refactored ApplyHeaders() to return a 403 if the origin is not valid and a 405 if the requested method is not allowed. validOrigin will now properly handle nil origins. --- vault/cors.go | 64 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/vault/cors.go b/vault/cors.go index ccf8b48c3699..c11789522417 100644 --- a/vault/cors.go +++ b/vault/cors.go @@ -18,16 +18,24 @@ type CORSConfig struct { } func newCORSConfig() *CORSConfig { + // defaultOrigins, err := regexp.Compile(expr) + defaultOrigins := ®exp.Regexp{} return &CORSConfig{ enabled: false, - allowedOrigins: ®exp.Regexp{}, + allowedOrigins: defaultOrigins, allowedHeaders: &map[string]string{ - "Access-Control-Allow-Headers": "X-Requested-With,Content-Type,Accept,Origin,Authorization,X-Vault-Token", + "Access-Control-Allow-Headers": "origin,content-type,cache-control,accept,options,authorization,x-requested-with,x-vault-token", "Access-Control-Max-Age": "1800", "Content-Type": "text/plain", }, allowCredentials: true, - allowedMethods: &[]string{http.MethodOptions}, + allowedMethods: &[]string{ + http.MethodDelete, + http.MethodGet, + http.MethodOptions, + http.MethodPost, + http.MethodPut, + }, } } @@ -54,34 +62,58 @@ func (c *CORSConfig) Enable(s string) error { // Disable sets CORS to disabled and clears the allowed origins func (c *CORSConfig) Disable() { c = nil - // c.enabled = false - // c.allowedOrigins = ®exp.Regexp{} } func (c *CORSConfig) AllowedMethods() []string { return *c.allowedMethods } -func (c *CORSConfig) ApplyHeaders(w http.ResponseWriter, r *http.Request) { - // check that the origin is valid & set the header. +// ApplyHeaders examines the CORS configuration and the request to determine +// if the CORS headers should be returned with the response. +func (c *CORSConfig) ApplyHeaders(w http.ResponseWriter, r *http.Request) int { + // If CORS is not enabled, just return a 200 + if !c.enabled { + return http.StatusOK + } + + // Return a 403 if the origin is not + // allowed to make cross-origin requests. origin := r.Header.Get("Origin") - if c.validOrigin(origin) { + if !c.validOrigin(origin) { + return http.StatusForbidden + } + + w.Header().Set("Access-Control-Allow-Origin", origin) + + // apply headers for preflight requests + if r.Method == http.MethodOptions { + methodAllowed := false + requestedMethod := r.Header.Get("Access-Control-Request-Method") + for _, method := range c.AllowedMethods() { + if method == requestedMethod { + methodAllowed = true + continue + } + } + + if !methodAllowed { + return http.StatusMethodNotAllowed + } methods := strings.Join(c.AllowedMethods(), ",") - w.Header().Set("Allow", methods) + w.Header().Set("Access-Control-Allow-Methods", methods) // add the credentials header if allowed. if c.allowCredentials { w.Header().Set("Access-Control-Allow-Credentials", "true") } - // apply headers for preflight requests - if r.Method == http.MethodOptions { - for k, v := range *c.allowedHeaders { - w.Header().Set(k, v) - } + for k, v := range *c.allowedHeaders { + w.Header().Set(k, v) } } + + return http.StatusOK } func (c *CORSConfig) AllowedOrigins() *regexp.Regexp { @@ -89,10 +121,6 @@ func (c *CORSConfig) AllowedOrigins() *regexp.Regexp { } func (c *CORSConfig) validOrigin(origin string) bool { - if len(origin) == 0 { - return false - } - if c.allowedOrigins == nil { return false } From ec20fbcd1193faea228b212d5b34c9d993b48293 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 29 Nov 2016 17:33:49 -0500 Subject: [PATCH 38/89] Fixed typo in comments. --- http/http_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http/http_test.go b/http/http_test.go index d6e04ee6e8b2..1cfb7ea82556 100644 --- a/http/http_test.go +++ b/http/http_test.go @@ -46,7 +46,7 @@ func testHttpData(t *testing.T, method string, token string, addr string, body i } // Get the address of the local listener in order to attach it to an Origin header. - // This will allow for the testing of requests the require CORS, without using a browser. + // This will allow for the testing of requests that require CORS, without using a browser. hostURLRegexp, _ := regexp.Compile("http[s]?://.+:[0-9]+") req.Header.Set("Origin", hostURLRegexp.FindString(addr)) From 582189d1b2f67bd02686aa8cea1cfe306c919c47 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Wed, 30 Nov 2016 11:32:46 -0500 Subject: [PATCH 39/89] Removed (presently) unnecessary test. --- http/logical_test.go | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/http/logical_test.go b/http/logical_test.go index c7bad2d7ba05..8149a5c4f9e0 100644 --- a/http/logical_test.go +++ b/http/logical_test.go @@ -16,36 +16,6 @@ import ( "github.com/hashicorp/vault/vault" ) -func TestLogical_cors(t *testing.T) { - core, _, token := vault.TestCoreUnsealed(t) - ln, addr := TestServer(t, core) - defer ln.Close() - TestServerAuth(t, addr, token) - - expHeaders := map[string]string{ - "Access-Control-Allow-Origin": addr, - "Access-Control-Allow-Credentials": "true", - } - - // WRITE - resp := testHttpPut(t, token, addr+"/v1/secret/foo", map[string]interface{}{ - "data": "bar", - }) - testResponseStatus(t, resp, 204) - - for expHeader, expected := range expHeaders { - actual := resp.Header.Get(expHeader) - if actual == "" { - t.Fatalf("bad:\nHeader: %#v was not on response.", expHeader) - } - - if actual != expected { - t.Fatalf("bad:\nExpected: %#v\nActual: %#v\n", expected, actual) - } - } - -} - func TestLogical(t *testing.T) { core, _, token := vault.TestCoreUnsealed(t) ln, addr := TestServer(t, core) From 121e0edf051d443d197711c8e2b854a214aeebde Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Wed, 30 Nov 2016 15:11:28 -0500 Subject: [PATCH 40/89] Added documentation for CORS functionality. --- website/source/docs/http/index.html.md | 4 +- .../source/docs/http/sys-config-cors.html.md | 127 ++++++++++++++++++ website/source/layouts/http.erb | 10 ++ 3 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 website/source/docs/http/sys-config-cors.html.md diff --git a/website/source/docs/http/index.html.md b/website/source/docs/http/index.html.md index d4238939adf9..2b3c264c7e99 100644 --- a/website/source/docs/http/index.html.md +++ b/website/source/docs/http/index.html.md @@ -157,7 +157,9 @@ The following HTTP status codes are used throughout the API. - `400` - Invalid request, missing or invalid data. See the "validation" section for more details on the error response. - `403` - Forbidden, your authentication details are either - incorrect or you don't have access to this feature. + incorrect, you don't have access to this feature, or - if CORS is + enabled - you made a cross-origin request from an origin that is + not allowed to make such requests. - `404` - Invalid path. This can both mean that the path truly doesn't exist or that you don't have permission to view a specific path. We use 404 in some cases to avoid state leakage. diff --git a/website/source/docs/http/sys-config-cors.html.md b/website/source/docs/http/sys-config-cors.html.md new file mode 100644 index 000000000000..c81f68d63082 --- /dev/null +++ b/website/source/docs/http/sys-config-cors.html.md @@ -0,0 +1,127 @@ +--- +layout: "http" +page_title: "HTTP API: /sys/config/cors" +sidebar_current: "docs-http-config-cors" +description: |- + The '/sys/config/cors' endpoint configures how the Vault server responds to cross-origin requests. +--- + +# /sys/config/cors + +This is a protected path, therefore all requests require a token with `root` +policy or `sudo` capability on the path. + +## GET + +
+
Description
+
+ Returns the current CORS configuration. +
+ +
Method
+
GET
+ +
URL
+
`/sys/config/cors`
+ +
Parameters
+
+ None +
+ +
Returns
+
+ The "allowed_origins" parameter is a regular expression describing origins + that are permitted to make cross-origin requests. + + ```javascript + { + "enabled": true, + "allowed_origins": "http://.+:[0-9]{4}" + } + ``` + + Sample response when CORS is disabled. + + ```javascript + { + "enabled": false, + "allowed_origins": "" + } + ``` +
+
+ +## PUT + +
+
Description
+
+ Configures the Vault server to return CORS headers for origins which match + the `allowed_origins` regular expression. +
+ +
Method
+
PUT
+ +
URL
+
`/sys/config/cors`
+ +
Parameters
+
+
    +
  • + allowed_origins + required + A valid regular expression describing origins that are permitted to + make cross-origin requests. +
  • +
+
+ +
Returns
+
+ The "allowed_origins" parameter is a valid regular expression describing + origins that are permitted to make cross-origin requests. + + ```javascript + { + "enabled": true, + "allowed_origins": "http[s]?://.+:[0-9]{4}" + } + ``` + +
+
+ +## DELETE + +
+
Description
+
+ Disables the CORS functionality of the Vault server. +
+ +
Method
+
DELETE
+ +
URL
+
`/sys/config/cors`
+ +
Parameters
+
+ None +
+ +
Returns
+
+ + ```javascript + { + "enabled": false + } + ``` + +
+
diff --git a/website/source/layouts/http.erb b/website/source/layouts/http.erb index 93f7ef83dd45..16ddce67867a 100644 --- a/website/source/layouts/http.erb +++ b/website/source/layouts/http.erb @@ -46,6 +46,16 @@ + > + Configuration + + + + > Secret Mounts
Returns
- The "allowed_origins" parameter is a valid regular expression describing - origins that are permitted to make cross-origin requests. ```javascript { "enabled": true, - "allowed_origins": "http[s]?://.+:[0-9]{4}" + "allowed_origins": "*" } ``` From 13bf4aeead36c67912eea9502f68647afae11d7f Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 24 Jan 2017 10:40:14 -0500 Subject: [PATCH 49/89] Removed unnecessary header. --- api/request.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/api/request.go b/api/request.go index db49388d7d4f..8f22dd572565 100644 --- a/api/request.go +++ b/api/request.go @@ -59,9 +59,6 @@ func (r *Request) ToHTTP() (*http.Request, error) { req.URL.Host = r.URL.Host req.Host = r.URL.Host - // The Vault client is not a browser so sending it CORS headers is unnecessary. - req.Header.Set("X-Vault-No-CORS", "Men have become the tools of their tools. Henry David Thoreau") - if len(r.ClientToken) != 0 { req.Header.Set("X-Vault-Token", r.ClientToken) } From 543cebd759d5fb043ae61fa93930896b4136ad48 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 24 Jan 2017 10:40:42 -0500 Subject: [PATCH 50/89] Remove cors CLI command. --- cli/commands.go | 7 --- command/cors.go | 119 ------------------------------------------------ 2 files changed, 126 deletions(-) delete mode 100644 command/cors.go diff --git a/cli/commands.go b/cli/commands.go index 60146532382e..852cdd8d06a0 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -56,13 +56,6 @@ func Commands(metaPtr *meta.Meta) map[string]cli.CommandFactory { Meta: *metaPtr, }, nil }, - - "cors": func() (cli.Command, error) { - return &command.CorsCommand{ - Meta: *metaPtr, - }, nil - }, - "server": func() (cli.Command, error) { return &command.ServerCommand{ Meta: *metaPtr, diff --git a/command/cors.go b/command/cors.go deleted file mode 100644 index 2b561d9fe79b..000000000000 --- a/command/cors.go +++ /dev/null @@ -1,119 +0,0 @@ -package command - -import ( - "fmt" - "strings" - - "github.com/hashicorp/vault/api" - "github.com/hashicorp/vault/meta" -) - -type CorsCommand struct { - meta.Meta -} - -func (c *CorsCommand) Run(args []string) int { - var allowedStr string - var disable, status bool - - flags := c.Meta.FlagSet("cors", meta.FlagSetDefault) - flags.Usage = func() { c.Ui.Error(c.Help()) } - flags.StringVar(&allowedStr, "allowed-origins", "", "") - flags.BoolVar(&disable, "disable", false, "") - flags.BoolVar(&status, "status", false, "") - if err := flags.Parse(args); err != nil { - return 1 - } - - corsRequest := &api.CORSRequest{} - - if !status { - if disable { - corsRequest.Enabled = false - } else { - corsRequest.AllowedOrigins = allowedStr - corsRequest.Enabled = true - } - } - - return c.runCORS(status, corsRequest) -} - -func (c *CorsCommand) runCORS(status bool, corsRequest *api.CORSRequest) int { - client, err := c.Client() - if err != nil { - c.Ui.Error(fmt.Sprintf( - "Error initializing client: %s", err)) - return 1 - } - - // Get the current CORS configuration. - if status { - resp, err := client.Sys().CORSStatus() - if err != nil { - c.Ui.Error(fmt.Sprintf( - "Error getting CORS configuration: %s", err)) - return 1 - } - c.Ui.Output( - fmt.Sprintf("Enabled: %t\nAllowed Origins: %s", - resp.Enabled, - resp.AllowedOrigins, - )) - return 0 - } - - // Disable (i.e. clear) the CORS configuration. - if corsRequest.Enabled == false { - _, err := client.Sys().DisableCORS() - if err != nil { - c.Ui.Error(fmt.Sprintf( - "Error disabling CORS: %s", err)) - return 1 - } - return 0 - } - - // Update the CORS configuration. - _, err = client.Sys().ConfigureCORS(corsRequest) - if err != nil { - c.Ui.Error(fmt.Sprintf( - "Error configuring CORS: %s", err)) - return 1 - } - - return 0 -} - -func (c *CorsCommand) Help() string { - helpText := ` -Usage: vault cors [options] - - Configures the HTTP server to return CORS headers. - - This command connects to a Vault server and can enable CORS, disable CORS, or - change the list of origins that are allowed to make cross-origin requests. - -General Options: -` + meta.GeneralOptionsUsage() + ` -Cors Options: - - -status Returns the current CORS configuration. - - -allowed-origins="" Setting allowed-origins enables the CORS functionality - Values for allowed-origins can either be "*" or s - space-separated list of origins that should be - allowed to make cross-origin requests and be served - CORS headers. A return code of 0 means the regular - expressions is valid, and Vault will now serve CORS - headers to clients from matching origins; a return - code of 1 means an error was encountered. - - -disable Stop serving CORS headers for all origins. -` - return strings.TrimSpace(helpText) -} - -func (c *CorsCommand) Synopsis() string { - return "Configure CORS settings" -} From 027e5466f34fbfccff273ea0177e7e38571bb6fc Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 24 Jan 2017 10:41:35 -0500 Subject: [PATCH 51/89] Merged the handleCORS() func into wrapCORSHandler(). Was not necessary to have two functions. --- http/cors.go | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/http/cors.go b/http/cors.go index 5ddf57bcb158..c7a288531675 100644 --- a/http/cors.go +++ b/http/cors.go @@ -9,30 +9,24 @@ import ( func wrapCORSHandler(h http.Handler, core *vault.Core) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - // If the help parameter is not blank, then show the help - if req.Header.Get(NoCORS) == "" { - statusCode := handleCORS(core, w, req) - if statusCode != http.StatusOK { - respondRaw(w, req, &logical.Response{ - Data: map[string]interface{}{ - logical.HTTPStatusCode: statusCode, - logical.HTTPRawBody: []byte(""), - }, - }) - return - } + corsConf := core.CORSConfig() + statusCode := corsConf.ApplyHeaders(w, req) + if statusCode != http.StatusOK { + respondRaw(w, req, &logical.Response{ + Data: map[string]interface{}{ + logical.HTTPStatusCode: statusCode, + logical.HTTPRawBody: []byte(""), + }, + }) + return + } + + // For pre-flight requests just send back the headers and return. + if req.Method == http.MethodOptions { + return } h.ServeHTTP(w, req) return }) } - -// HandleCORS adds required headers to properly respond to -// requests that require Cross Origin Resource Sharing (CORS) headers. -func handleCORS(core *vault.Core, w http.ResponseWriter, req *http.Request) int { - corsConf := core.CORSConfig() - statusCode := corsConf.ApplyHeaders(w, req) - - return statusCode -} From d838b8e387a4a9de4040ead05b56dadd8451ab58 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 24 Jan 2017 10:42:12 -0500 Subject: [PATCH 52/89] Removed unnecessary header. --- http/handler.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/http/handler.go b/http/handler.go index 843afcc28794..74b25e5ebc2e 100644 --- a/http/handler.go +++ b/http/handler.go @@ -31,9 +31,6 @@ const ( // not to use request forwarding NoRequestForwardingHeaderName = "X-Vault-No-Request-Forwarding" - // NoCORS is the name of the header telling Vault not to use CORS handler. - NoCORS = "X-Vault-No-CORS" - // MaxRequestSize is the maximum accepted request size. This is to prevent // a denial of service attack where no Content-Length is provided and the server // is fed ever more data until it exhausts memory. From 9d5fb2ac6013e4810ee67de1612b37bbdb9cc19a Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 24 Jan 2017 10:43:35 -0500 Subject: [PATCH 53/89] Move check for preflight requests up to the CORS handler where it belonged. --- http/logical.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/http/logical.go b/http/logical.go index 7020d5749671..59ac2eaaca09 100644 --- a/http/logical.go +++ b/http/logical.go @@ -91,10 +91,6 @@ func buildLogicalRequest(core *vault.Core, w http.ResponseWriter, r *http.Reques func handleLogical(core *vault.Core, dataOnly bool, prepareRequestCallback PrepareRequestFunc) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Bubble up CORS preflight requests to the CORS handler. - if r.Method == "OPTIONS" { - return - } req, statusCode, err := buildLogicalRequest(core, w, r) if err != nil || statusCode != 0 { respondError(w, statusCode, err) From 75955d58d3c3520acc3cff55fae138b063cd9500 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 24 Jan 2017 10:48:46 -0500 Subject: [PATCH 54/89] Removed unneeded struct fields. Changed pointers to slices to just slices. No longer using a regular expression to check that the wildcard is the sole origin when used. --- vault/cors.go | 78 ++++++++++++++++++++++----------------------------- 1 file changed, 34 insertions(+), 44 deletions(-) diff --git a/vault/cors.go b/vault/cors.go index e0c548d091c2..49dcfef0682c 100644 --- a/vault/cors.go +++ b/vault/cors.go @@ -3,38 +3,32 @@ package vault import ( "errors" "net/http" - "regexp" "strings" ) var errCORSNotConfigured = errors.New("CORS is not configured") +var allowedHeaders = map[string]string{ + "Access-Control-Allow-Headers": "origin,content-type,cache-control,accept,options,authorization,x-requested-with,x-vault-token", + "Access-Control-Max-Age": "1800", +} + +var allowedMethods = []string{ + http.MethodDelete, + http.MethodGet, + http.MethodOptions, + http.MethodPost, + http.MethodPut, +} + type CORSConfig struct { - enabled bool - allowedOrigins *[]string - allowedHeaders *map[string]string - allowedMethods *[]string - allowCredentials bool + enabled bool + allowedOrigins []string } func newCORSConfig() *CORSConfig { - defaultOrigins := &[]string{} return &CORSConfig{ - enabled: false, - allowedOrigins: defaultOrigins, - allowedHeaders: &map[string]string{ - "Access-Control-Allow-Headers": "origin,content-type,cache-control,accept,options,authorization,x-requested-with,x-vault-token", - "Access-Control-Max-Age": "1800", - // "Content-Type": "text/plain", - }, - allowCredentials: true, - allowedMethods: &[]string{ - http.MethodDelete, - http.MethodGet, - http.MethodOptions, - http.MethodPost, - http.MethodPut, - }, + enabled: false, } } @@ -43,13 +37,13 @@ func (c *CORSConfig) Enabled() bool { } func (c *CORSConfig) Enable(s string) error { - if matched, _ := regexp.MatchString(`\*`, s); matched && len(s) > 1 { - return errors.New("wildcard must be only value") + if strings.Contains("*", s) && len(s) > 1 { + return errors.New("wildcard must be the only value") } allowedOrigins := strings.Split(s, " ") - c.allowedOrigins = &allowedOrigins + c.allowedOrigins = allowedOrigins c.enabled = true return nil @@ -58,24 +52,23 @@ func (c *CORSConfig) Enable(s string) error { // Disable sets CORS to disabled and clears the allowed origins func (c *CORSConfig) Disable() { c.enabled = false - c.allowedOrigins = &[]string{} -} - -func (c *CORSConfig) AllowedMethods() []string { - return *c.allowedMethods + c.allowedOrigins = []string{} } // ApplyHeaders examines the CORS configuration and the request to determine // if the CORS headers should be returned with the response. func (c *CORSConfig) ApplyHeaders(w http.ResponseWriter, r *http.Request) int { - // If CORS is not enabled, just return a 200 - if !c.enabled { + origin := r.Header.Get("Origin") + + // If CORS is not enabled or if no Origin header is present (i.e. the request + // is from the Vault CLI. A browser will always send an Origin header), then + // just return a 200. + if !c.enabled || origin == "" { return http.StatusOK } // Return a 403 if the origin is not // allowed to make cross-origin requests. - origin := r.Header.Get("Origin") if !c.validOrigin(origin) { return http.StatusForbidden } @@ -87,7 +80,7 @@ func (c *CORSConfig) ApplyHeaders(w http.ResponseWriter, r *http.Request) int { if r.Method == http.MethodOptions { methodAllowed := false requestedMethod := r.Header.Get("Access-Control-Request-Method") - for _, method := range c.AllowedMethods() { + for _, method := range allowedMethods { if method == requestedMethod { methodAllowed = true continue @@ -98,15 +91,10 @@ func (c *CORSConfig) ApplyHeaders(w http.ResponseWriter, r *http.Request) int { return http.StatusMethodNotAllowed } - methods := strings.Join(c.AllowedMethods(), ",") + methods := strings.Join(allowedMethods, ",") w.Header().Set("Access-Control-Allow-Methods", methods) - // add the credentials header if allowed. - if c.allowCredentials { - w.Header().Set("Access-Control-Allow-Credentials", "true") - } - - for k, v := range *c.allowedHeaders { + for k, v := range allowedHeaders { w.Header().Set(k, v) } } @@ -114,8 +102,10 @@ func (c *CORSConfig) ApplyHeaders(w http.ResponseWriter, r *http.Request) int { return http.StatusOK } +// AllowedOrigins returns a space-separated list of origins which can make +// cross-origin requests. func (c *CORSConfig) AllowedOrigins() string { - return strings.Join(*c.allowedOrigins, " ") + return strings.Join(c.allowedOrigins, " ") } func (c *CORSConfig) validOrigin(origin string) bool { @@ -123,11 +113,11 @@ func (c *CORSConfig) validOrigin(origin string) bool { return false } - if len(*c.allowedOrigins) == 1 && (*c.allowedOrigins)[0] == "*" { + if len(c.allowedOrigins) == 1 && (c.allowedOrigins)[0] == "*" { return true } - for _, allowedOrigin := range *c.allowedOrigins { + for _, allowedOrigin := range c.allowedOrigins { if origin == allowedOrigin { return true } From e27b9922167d1f8e92083a58444c7552e0c8c14a Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 24 Jan 2017 10:50:19 -0500 Subject: [PATCH 55/89] Refactored help text and code to reflect the fact that CORSConfig.allowedOrigins is no longer a regular expression. --- vault/logical_system.go | 13 +++++++------ vault/logical_system_test.go | 4 ++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/vault/logical_system.go b/vault/logical_system.go index f498ad92814f..ebe2f91cb8bc 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -81,7 +81,7 @@ func NewSystemBackend(core *Core, config *logical.BackendConfig) (logical.Backen }, "allowed_origins": &framework.FieldSchema{ Type: framework.TypeString, - Description: "A regular expression describing origins that may make cross-origin requests.", + Description: "A space-separated list of origins that may make cross-origin requests.", }, }, @@ -666,6 +666,7 @@ func (b *SystemBackend) corsStatusResponse() (*logical.Response, error) { if corsConf == nil { return nil, errCORSNotConfigured } + return &logical.Response{ Data: map[string]interface{}{ "enabled": corsConf.Enabled(), @@ -674,15 +675,15 @@ func (b *SystemBackend) corsStatusResponse() (*logical.Response, error) { }, nil } -// handleCORSEnable sets the regexp that describes origins that are allowed +// handleCORSEnable sets the list of origins that are allowed // to make cross-origin requests and sets the CORS enabled flag to true func (b *SystemBackend) handleCORSEnable(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { if b.Core.corsConfig == nil { b.Core.corsConfig = newCORSConfig() } - s := d.Get("allowed_origins") + origins := d.Get("allowed_origins").(string) - err := b.Core.corsConfig.Enable(s.(string)) + err := b.Core.corsConfig.Enable(origins) if err != nil { return nil, err } @@ -690,7 +691,7 @@ func (b *SystemBackend) handleCORSEnable(req *logical.Request, d *framework.Fiel return b.corsStatusResponse() } -// handleCORSDisable clears the allowed origins regexp and sets the CORS enabled flag to false +// handleCORSDisable clears the allowed origins and sets the CORS enabled flag to false func (b *SystemBackend) handleCORSDisable(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { b.Core.CORSConfig().Disable() @@ -1816,7 +1817,7 @@ This path responds to the following HTTP methods. Returns the configuration of the CORS setting. POST / - Sets the regular expression which describes origins that can make cross-origin requests. + Sets the space-separate list of origins that can make cross-origin requests. DELETE / Clears the CORS configuration and disables acceptance of CORS requests. diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go index daa35de5f87d..0a930c5d9851 100644 --- a/vault/logical_system_test.go +++ b/vault/logical_system_test.go @@ -36,12 +36,12 @@ func TestSystemConfigCORS(t *testing.T) { b := testSystemBackend(t) req := logical.TestRequest(t, logical.UpdateOperation, "config/cors") - req.Data["allowed_origins"] = "http://.+:[0-9]{4}" + req.Data["allowed_origins"] = "http://www.example.com" actual, err := b.HandleRequest(req) expected := &logical.Response{ Data: map[string]interface{}{ - "allowed_origins": "http://.+:[0-9]{4}", + "allowed_origins": "http:/www.example.com", "enabled": true, }, } From 8c0a7c9e7ce91f46b10699b8c255997b6d3b056b Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Wed, 25 Jan 2017 11:10:26 -0500 Subject: [PATCH 56/89] New config store for api-managable configurations. --- vault/config_store.go | 156 +++++++++++++++++++++++++++++++++++++ vault/config_store_test.go | 103 ++++++++++++++++++++++++ 2 files changed, 259 insertions(+) create mode 100644 vault/config_store.go create mode 100644 vault/config_store_test.go diff --git a/vault/config_store.go b/vault/config_store.go new file mode 100644 index 000000000000..fcf7163bafc6 --- /dev/null +++ b/vault/config_store.go @@ -0,0 +1,156 @@ +package vault + +import ( + "fmt" + "time" + + "github.com/armon/go-metrics" + "github.com/hashicorp/golang-lru" + "github.com/hashicorp/vault/logical" +) + +const ( + // policySubPath is the sub-path used for the policy store + // view. This is nested under the system view. + configSubPath = "config/" + + // policyCacheSize is the number of policies that are kept cached + configCacheSize = 1024 +) + +type Config struct { + Name string + CORS *CORSConfig +} + +// ConfigStore is used to provide durable storage of configuration items +type ConfigStore struct { + view *BarrierView + lru *lru.TwoQueueCache +} + +// ConfigEntry is used to store a configuration by name +type ConfigEntry struct { + Version int + Raw string +} + +// NewConfigStore creates a new ConfigStore that is backed using a given view. +// It used used to durably store and manage named configurations. +func NewConfigStore(view *BarrierView, system logical.SystemView) *ConfigStore { + c := &ConfigStore{ + view: view, + } + if !system.CachingDisabled() { + cache, _ := lru.New2Q(configCacheSize) + c.lru = cache + } + + return c +} + +// setupConfigStore is used to initialize the config store +// when the vault is being unsealed. +func (c *Core) setupConfigStore() error { + // Create a sub-view + view := c.systemBarrierView.SubView(configSubPath) + + // Create the config store + c.configStore = NewConfigStore(view, &dynamicSystemView{core: c}) + + return nil +} + +// teardownConfigStore is used to reverse setupConfigStore +// when the vault is being sealed. +func (c *Core) teardownConfigStore() error { + c.configStore = nil + return nil +} + +// SetConfig is used to create or update the given config +func (cs *ConfigStore) SetConfig(c *Config) error { + defer metrics.MeasureSince([]string{"config", "set_config"}, time.Now()) + if c.Name == "" { + return fmt.Errorf("config name missing") + } + + return cs.setConfigInternal(c) +} + +func (cs *ConfigStore) setConfigInternal(c *Config) error { + var entry *logical.StorageEntry + var err error + + entry, err = logical.StorageEntryJSON(c.Name, c) + if cs.lru != nil { + // Update the LRU cache + cs.lru.Add(c.Name, c) + } + + if err != nil { + return fmt.Errorf("failed to create entry: %v", err) + } + if err := cs.view.Put(entry); err != nil { + return fmt.Errorf("failed to persist config: %v", err) + } + + return nil +} + +// GetConfig is used to fetch the named config +func (cs *ConfigStore) GetConfig(name string) (*Config, error) { + defer metrics.MeasureSince([]string{"config", "get_config"}, time.Now()) + if cs.lru != nil { + // Check for cached config + if raw, ok := cs.lru.Get(name); ok { + return raw.(*Config), nil + } + } + + // Load the config in + out, err := cs.view.Get(name) + if err != nil { + return nil, fmt.Errorf("failed to read config: %v", err) + } + if out == nil { + return nil, nil + } + + config := new(Config) + err = out.DecodeJSON(config) + if err != nil { + return nil, err + } + + if cs.lru != nil { + // Update the LRU cache + cs.lru.Add(name, config) + } + + return config, nil +} + +// ListConfigs is used to list the available configs +func (cs *ConfigStore) ListConfigs() ([]string, error) { + defer metrics.MeasureSince([]string{"config", "list_configs"}, time.Now()) + // Scan the view, since the config names are the same as the + // key names. + keys, err := logical.CollectKeys(cs.view) + + return keys, err +} + +// DeleteConfig is used to delete the named config +func (cs *ConfigStore) DeleteConfig(name string) error { + defer metrics.MeasureSince([]string{"config", "delete_config"}, time.Now()) + if err := cs.view.Delete(name); err != nil { + return fmt.Errorf("failed to delete config: %v", err) + } + + if cs.lru != nil { + // Clear the cache + cs.lru.Remove(name) + } + return nil +} diff --git a/vault/config_store_test.go b/vault/config_store_test.go new file mode 100644 index 000000000000..d08d7b3e984c --- /dev/null +++ b/vault/config_store_test.go @@ -0,0 +1,103 @@ +package vault + +import ( + "reflect" + "testing" + + "github.com/hashicorp/vault/logical" +) + +func mockConfigStore(t *testing.T) *ConfigStore { + _, barrier, _ := mockBarrier(t) + view := NewBarrierView(barrier, "foo/") + c := NewConfigStore(view, logical.TestSystemView()) + return c +} + +func mockConfigStoreNoCache(t *testing.T) *ConfigStore { + sysView := logical.TestSystemView() + sysView.CachingDisabledVal = true + _, barrier, _ := mockBarrier(t) + view := NewBarrierView(barrier, "foo/") + c := NewConfigStore(view, sysView) + return c +} + +func TestConfigStore_CRUD(t *testing.T) { + cs := mockConfigStore(t) + testConfigStore_CRUD(t, cs) + + cs = mockConfigStoreNoCache(t) + testConfigStore_CRUD(t, cs) +} + +func testConfigStore_CRUD(t *testing.T, cs *ConfigStore) { + // Get should return nothing + c, err := cs.GetConfig("dev") + if err != nil { + t.Fatalf("err: %v", err) + } + if c != nil { + t.Fatalf("bad: %v", c) + } + + // Delete should be no-op + err = cs.DeleteConfig("dev") + if err != nil { + t.Fatalf("err: %v", err) + } + + // List should be blank + out, err := cs.ListConfigs() + if err != nil { + t.Fatalf("err: %v", err) + } + if len(out) != 0 { + t.Fatalf("bad: %v", out) + } + + config := &Config{ + Name: "cors", + CORS: &CORSConfig{ + AllowedOrigins: []string{"http://www.example.com"}, + Enabled: true, + }, + } + + err = cs.SetConfig(config) + if err != nil { + t.Fatalf("err: %v", err) + } + // Get should work + c, err = cs.GetConfig("cors") + if err != nil { + t.Fatalf("err: %v", err) + } + if !reflect.DeepEqual(c, config) { + t.Fatalf("bad: %v", c) + } + + // List should be one element + out, err = cs.ListConfigs() + if err != nil { + t.Fatalf("err: %v", err) + } + if len(out) != 1 || out[0] != "cors" { + t.Fatalf("bad: %v", out) + } + + // Delete should be clear the entry + err = cs.DeleteConfig("cors") + if err != nil { + t.Fatalf("err: %v", err) + } + + // Get should fail + c, err = cs.GetConfig("cors") + if err != nil { + t.Fatalf("err: %v", err) + } + if c != nil { + t.Fatalf("bad: %v", c) + } +} From 8ed7fe8dc40e2ef2b5c9649c8ea7e6e4f24d9c45 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Thu, 26 Jan 2017 10:02:49 -0500 Subject: [PATCH 57/89] Added Settings field to Config struct to allow for many types of configurations to be stored. setupConfigStore will now allow for loading existing configs at unseal time. --- vault/config_store.go | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/vault/config_store.go b/vault/config_store.go index fcf7163bafc6..3bd5b757005f 100644 --- a/vault/config_store.go +++ b/vault/config_store.go @@ -2,6 +2,7 @@ package vault import ( "fmt" + "strconv" "time" "github.com/armon/go-metrics" @@ -10,17 +11,21 @@ import ( ) const ( - // policySubPath is the sub-path used for the policy store + // configSubPath is the sub-path used for the config store // view. This is nested under the system view. configSubPath = "config/" - // policyCacheSize is the number of policies that are kept cached + // configCacheSize is the number of configs that are kept cached configCacheSize = 1024 ) +var defaultConfigs = []string{ + "cors", +} + type Config struct { - Name string - CORS *CORSConfig + Name string + Settings map[string]string } // ConfigStore is used to provide durable storage of configuration items @@ -58,6 +63,25 @@ func (c *Core) setupConfigStore() error { // Create the config store c.configStore = NewConfigStore(view, &dynamicSystemView{core: c}) + for _, name := range defaultConfigs { + config, err := c.configStore.GetConfig(name) + if err != nil { + return err + } + + if config != nil { + switch config.Name { + case "cors": + enabled, err := strconv.ParseBool(config.Settings["enabled"]) + if err != nil { + enabled = false + } + c.corsConfig.Enable(config.Settings["allowed_origins"]) + c.corsConfig.Enabled = enabled + } + } + } + return nil } From 580ca52b724b645f4f3c281859504f3a3483b4f3 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Thu, 26 Jan 2017 10:03:20 -0500 Subject: [PATCH 58/89] Refactored to use new Config structure. --- vault/config_store_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vault/config_store_test.go b/vault/config_store_test.go index d08d7b3e984c..5f7c4868f98a 100644 --- a/vault/config_store_test.go +++ b/vault/config_store_test.go @@ -58,9 +58,9 @@ func testConfigStore_CRUD(t *testing.T, cs *ConfigStore) { config := &Config{ Name: "cors", - CORS: &CORSConfig{ - AllowedOrigins: []string{"http://www.example.com"}, - Enabled: true, + Settings: map[string]string{ + "allowed_origins": "http://www.example.com http://localhost", + "enabled": "true", }, } @@ -73,6 +73,7 @@ func testConfigStore_CRUD(t *testing.T, cs *ConfigStore) { if err != nil { t.Fatalf("err: %v", err) } + if !reflect.DeepEqual(c, config) { t.Fatalf("bad: %v", c) } From 0ab733dcd7bf94c795f1b4db7e531098ed8932b2 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Thu, 26 Jan 2017 10:04:15 -0500 Subject: [PATCH 59/89] Added configStore to Core. Wired up unseal and seal tasks. --- vault/core.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/vault/core.go b/vault/core.go index 25213cac56d6..f438632ed032 100644 --- a/vault/core.go +++ b/vault/core.go @@ -231,6 +231,9 @@ type Core struct { // policy store is used to manage named ACL policies policyStore *PolicyStore + // config store is used to manage API-managable configs + configStore *ConfigStore + // token store is used to manage authentication tokens tokenStore *TokenStore @@ -1212,6 +1215,9 @@ func (c *Core) postUnseal() (retErr error) { if err := c.setupPolicyStore(); err != nil { return err } + if err := c.setupConfigStore(); err != nil { + return err + } if err := c.loadCredentials(); err != nil { return err } @@ -1271,6 +1277,9 @@ func (c *Core) preSeal() error { if err := c.teardownPolicyStore(); err != nil { result = multierror.Append(result, errwrap.Wrapf("error tearing down policy store: {{err}}", err)) } + if err := c.teardownConfigStore(); err != nil { + result = multierror.Append(result, errwrap.Wrapf("error tearing down config store: {{err}}", err)) + } if err := c.stopRollback(); err != nil { result = multierror.Append(result, errwrap.Wrapf("error stopping rollback: {{err}}", err)) } From 7645af47005f8e6d8cf81d9c689edebf40190315 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Thu, 26 Jan 2017 10:31:35 -0500 Subject: [PATCH 60/89] Simplified things a bit. --- vault/cors.go | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/vault/cors.go b/vault/cors.go index 49dcfef0682c..1461e4e489d7 100644 --- a/vault/cors.go +++ b/vault/cors.go @@ -22,20 +22,16 @@ var allowedMethods = []string{ } type CORSConfig struct { - enabled bool - allowedOrigins []string + Enabled bool + AllowedOrigins []string } func newCORSConfig() *CORSConfig { return &CORSConfig{ - enabled: false, + Enabled: false, } } -func (c *CORSConfig) Enabled() bool { - return c.enabled -} - func (c *CORSConfig) Enable(s string) error { if strings.Contains("*", s) && len(s) > 1 { return errors.New("wildcard must be the only value") @@ -43,16 +39,16 @@ func (c *CORSConfig) Enable(s string) error { allowedOrigins := strings.Split(s, " ") - c.allowedOrigins = allowedOrigins - c.enabled = true + c.AllowedOrigins = allowedOrigins + c.Enabled = true return nil } // Disable sets CORS to disabled and clears the allowed origins func (c *CORSConfig) Disable() { - c.enabled = false - c.allowedOrigins = []string{} + c.Enabled = false + c.AllowedOrigins = []string{} } // ApplyHeaders examines the CORS configuration and the request to determine @@ -63,7 +59,7 @@ func (c *CORSConfig) ApplyHeaders(w http.ResponseWriter, r *http.Request) int { // If CORS is not enabled or if no Origin header is present (i.e. the request // is from the Vault CLI. A browser will always send an Origin header), then // just return a 200. - if !c.enabled || origin == "" { + if !c.Enabled || origin == "" { return http.StatusOK } @@ -102,22 +98,16 @@ func (c *CORSConfig) ApplyHeaders(w http.ResponseWriter, r *http.Request) int { return http.StatusOK } -// AllowedOrigins returns a space-separated list of origins which can make -// cross-origin requests. -func (c *CORSConfig) AllowedOrigins() string { - return strings.Join(c.allowedOrigins, " ") -} - func (c *CORSConfig) validOrigin(origin string) bool { - if c.allowedOrigins == nil { + if c.AllowedOrigins == nil { return false } - if len(c.allowedOrigins) == 1 && (c.allowedOrigins)[0] == "*" { + if len(c.AllowedOrigins) == 1 && (c.AllowedOrigins)[0] == "*" { return true } - for _, allowedOrigin := range c.allowedOrigins { + for _, allowedOrigin := range c.AllowedOrigins { if origin == allowedOrigin { return true } From 1b483774babf6902ebc7f9785f6dea01b538f7fc Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Thu, 26 Jan 2017 10:32:38 -0500 Subject: [PATCH 61/89] Added code to update the ConfigStore. --- vault/logical_system.go | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/vault/logical_system.go b/vault/logical_system.go index ebe2f91cb8bc..22782e92b45e 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -669,8 +669,8 @@ func (b *SystemBackend) corsStatusResponse() (*logical.Response, error) { return &logical.Response{ Data: map[string]interface{}{ - "enabled": corsConf.Enabled(), - "allowed_origins": corsConf.AllowedOrigins(), + "enabled": corsConf.Enabled, + "allowed_origins": strings.Join(corsConf.AllowedOrigins, " "), }, }, nil } @@ -688,6 +688,19 @@ func (b *SystemBackend) handleCORSEnable(req *logical.Request, d *framework.Fiel return nil, err } + config := &Config{ + Name: "cors", + Settings: map[string]string{ + "allowed_origins": origins, + "enabled": "true", + }, + } + + // Update the config + if err := b.Core.configStore.SetConfig(config); err != nil { + return handleError(err) + } + return b.corsStatusResponse() } @@ -695,6 +708,10 @@ func (b *SystemBackend) handleCORSEnable(req *logical.Request, d *framework.Fiel func (b *SystemBackend) handleCORSDisable(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { b.Core.CORSConfig().Disable() + if err := b.Core.configStore.DeleteConfig("cors"); err != nil { + return handleError(err) + } + return b.corsStatusResponse() } From 5f16cf7fc197400874cf494c9146e7f360dcb25e Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Thu, 26 Jan 2017 13:25:27 -0500 Subject: [PATCH 62/89] Fixed a typo that was making the test fail. --- vault/logical_system_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go index 0a930c5d9851..d86589007290 100644 --- a/vault/logical_system_test.go +++ b/vault/logical_system_test.go @@ -41,13 +41,13 @@ func TestSystemConfigCORS(t *testing.T) { expected := &logical.Response{ Data: map[string]interface{}{ - "allowed_origins": "http:/www.example.com", "enabled": true, + "allowed_origins": "http://www.example.com", }, } if !reflect.DeepEqual(actual, expected) { - t.Fatalf("UPDATE FAILED -- bad: %#v", actual) + t.Fatalf("UPDATE FAILED\nexpected:\n%#v\nactual:\n%#v", expected, actual) } req = logical.TestRequest(t, logical.ReadOperation, "config/cors") From 70b9a9bac70200513328bce3cae354ca3961c0eb Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Thu, 26 Jan 2017 14:06:42 -0500 Subject: [PATCH 63/89] Added missing response headers. Renamed allowedHeaders to responseHeaders to avoid confusion with the 'Access-Control-Allow-Headers' header. Added 'Vary' header to the responseHeaders slice instead of setting it in a separate step in the ApplyHeaders func. --- vault/cors.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/vault/cors.go b/vault/cors.go index 1461e4e489d7..b3e904e3cbe5 100644 --- a/vault/cors.go +++ b/vault/cors.go @@ -8,9 +8,11 @@ import ( var errCORSNotConfigured = errors.New("CORS is not configured") -var allowedHeaders = map[string]string{ - "Access-Control-Allow-Headers": "origin,content-type,cache-control,accept,options,authorization,x-requested-with,x-vault-token", - "Access-Control-Max-Age": "1800", +var responseHeaders = map[string]string{ + "Access-Control-Allow-Headers": "origin,content-type,cache-control,accept,options,authorization,x-requested-with,x-vault-token", + "Access-Control-Max-Age": "1800", + "Access-Control-Allow-Credentials": "true", + "Vary": "Origin", } var allowedMethods = []string{ @@ -70,7 +72,6 @@ func (c *CORSConfig) ApplyHeaders(w http.ResponseWriter, r *http.Request) int { } w.Header().Set("Access-Control-Allow-Origin", origin) - w.Header().Set("Vary", "Origin") // apply headers for preflight requests if r.Method == http.MethodOptions { @@ -90,7 +91,7 @@ func (c *CORSConfig) ApplyHeaders(w http.ResponseWriter, r *http.Request) int { methods := strings.Join(allowedMethods, ",") w.Header().Set("Access-Control-Allow-Methods", methods) - for k, v := range allowedHeaders { + for k, v := range responseHeaders { w.Header().Set(k, v) } } From f70c6dc2fa6f433fcc4c4ec430b5c92af79dab25 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Thu, 26 Jan 2017 14:11:39 -0500 Subject: [PATCH 64/89] Refactored newCORSConfig() out. --- vault/core.go | 5 +---- vault/cors.go | 6 ------ vault/logical_system.go | 3 --- 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/vault/core.go b/vault/core.go index f438632ed032..2110e1f4d452 100644 --- a/vault/core.go +++ b/vault/core.go @@ -414,9 +414,6 @@ func NewCore(conf *CoreConfig) (*Core, error) { return nil, fmt.Errorf("barrier setup failed: %v", err) } - // Get a default CORS config. - corsConfig := newCORSConfig() - // Setup the core c := &Core{ redirectAddr: conf.RedirectAddr, @@ -435,7 +432,7 @@ func NewCore(conf *CoreConfig) (*Core, error) { clusterCertPool: x509.NewCertPool(), clusterListenerShutdownCh: make(chan struct{}), clusterListenerShutdownSuccessCh: make(chan struct{}), - corsConfig: corsConfig, + corsConfig: &CORSConfig{}, } // Wrap the backend in a cache unless disabled diff --git a/vault/cors.go b/vault/cors.go index b3e904e3cbe5..b72a3e9cc266 100644 --- a/vault/cors.go +++ b/vault/cors.go @@ -28,12 +28,6 @@ type CORSConfig struct { AllowedOrigins []string } -func newCORSConfig() *CORSConfig { - return &CORSConfig{ - Enabled: false, - } -} - func (c *CORSConfig) Enable(s string) error { if strings.Contains("*", s) && len(s) > 1 { return errors.New("wildcard must be the only value") diff --git a/vault/logical_system.go b/vault/logical_system.go index 22782e92b45e..099b360fecfe 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -678,9 +678,6 @@ func (b *SystemBackend) corsStatusResponse() (*logical.Response, error) { // handleCORSEnable sets the list of origins that are allowed // to make cross-origin requests and sets the CORS enabled flag to true func (b *SystemBackend) handleCORSEnable(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { - if b.Core.corsConfig == nil { - b.Core.corsConfig = newCORSConfig() - } origins := d.Get("allowed_origins").(string) err := b.Core.corsConfig.Enable(origins) From 0859a858525867ae050ba74bd44d6cfea9b8c0a3 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 31 Jan 2017 10:48:31 -0500 Subject: [PATCH 65/89] Moved logic of when to apply the CORS headers here. This makes the logic easier to follow. --- http/cors.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/http/cors.go b/http/cors.go index c7a288531675..54d81c931bd1 100644 --- a/http/cors.go +++ b/http/cors.go @@ -3,21 +3,24 @@ package http import ( "net/http" - "github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/vault" ) func wrapCORSHandler(h http.Handler, core *vault.Core) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { corsConf := core.CORSConfig() + + // If CORS is not enabled or if no Origin header is present (i.e. the request + // is from the Vault CLI. A browser will always send an Origin header), then + // just return a 204. + if !corsConf.Enabled() || req.Header.Get("Origin") == "" { + h.ServeHTTP(w, req) + return + } + statusCode := corsConf.ApplyHeaders(w, req) - if statusCode != http.StatusOK { - respondRaw(w, req, &logical.Response{ - Data: map[string]interface{}{ - logical.HTTPStatusCode: statusCode, - logical.HTTPRawBody: []byte(""), - }, - }) + if statusCode != http.StatusNoContent { + h.ServeHTTP(w, req) return } From 096868681fbd25f13cb691138a1ed746f1e8b622 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 31 Jan 2017 10:49:27 -0500 Subject: [PATCH 66/89] Refactored CORS handler test to reflect the current state of things. --- http/handler_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/http/handler_test.go b/http/handler_test.go index 578663928c44..805d40f9a3c9 100644 --- a/http/handler_test.go +++ b/http/handler_test.go @@ -81,9 +81,8 @@ func TestHandler_cors(t *testing.T) { // Test that the CORS headers are applied correctly. // expHeaders := map[string]string{ - "Access-Control-Allow-Origin": addr, - "Access-Control-Allow-Headers": "origin,content-type,cache-control,accept,options,authorization,x-requested-with,x-vault-token", - "Access-Control-Allow-Credentials": "true", + "Access-Control-Allow-Origin": addr, + "Access-Control-Allow-Headers": "*", "Vary": "Origin", } From 26af710a59cfa8a76cbecff1ffc4ff9d9158f406 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 31 Jan 2017 10:50:45 -0500 Subject: [PATCH 67/89] It is not necessary to set the isEnabled flag on the CORSConfig. This is done by the CORSConfig.Enable() func. --- vault/config_store.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/vault/config_store.go b/vault/config_store.go index 3bd5b757005f..c78a260790d5 100644 --- a/vault/config_store.go +++ b/vault/config_store.go @@ -2,7 +2,6 @@ package vault import ( "fmt" - "strconv" "time" "github.com/armon/go-metrics" @@ -72,12 +71,7 @@ func (c *Core) setupConfigStore() error { if config != nil { switch config.Name { case "cors": - enabled, err := strconv.ParseBool(config.Settings["enabled"]) - if err != nil { - enabled = false - } c.corsConfig.Enable(config.Settings["allowed_origins"]) - c.corsConfig.Enabled = enabled } } } From 4ac6a0b6d3085933f389ee22d298febe5eea4167 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 31 Jan 2017 10:52:02 -0500 Subject: [PATCH 68/89] Creating an instance of a sync.RWMutex for the CORSConfig when the core is created. --- vault/core.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vault/core.go b/vault/core.go index 2110e1f4d452..ede07002f116 100644 --- a/vault/core.go +++ b/vault/core.go @@ -432,7 +432,7 @@ func NewCore(conf *CoreConfig) (*Core, error) { clusterCertPool: x509.NewCertPool(), clusterListenerShutdownCh: make(chan struct{}), clusterListenerShutdownSuccessCh: make(chan struct{}), - corsConfig: &CORSConfig{}, + corsConfig: &CORSConfig{mutex: &sync.RWMutex{}}, } // Wrap the backend in a cache unless disabled @@ -511,7 +511,7 @@ func (c *Core) Shutdown() error { // CORSConfig returns the current CORS configuration func (c *Core) CORSConfig() *CORSConfig { - return c.corsConfig + return c.corsConfig.Get() } // LookupToken returns the properties of the token from the token store. This From e1afa97b0c3fb0ed26e00d7e01846735c0dc0719 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 31 Jan 2017 10:57:15 -0500 Subject: [PATCH 69/89] Renamed responseHeaders to preflightHeaders. Added 'LIST' method to list of allowed methods. Refactored the export fields in CORSConfig struct to be unexported and added a RWMutex to the struct. Added guards to CORConfig's funcs. Created new funcs to expose the unexported fields of CORSConfig. Using strutil.StrListContains() where appropriate. --- vault/cors.go | 88 ++++++++++++++++++++++++++++----------------------- 1 file changed, 49 insertions(+), 39 deletions(-) diff --git a/vault/cors.go b/vault/cors.go index b72a3e9cc266..8fd55af65151 100644 --- a/vault/cors.go +++ b/vault/cors.go @@ -4,15 +4,17 @@ import ( "errors" "net/http" "strings" + "sync" + + "github.com/hashicorp/vault/helper/strutil" ) var errCORSNotConfigured = errors.New("CORS is not configured") -var responseHeaders = map[string]string{ - "Access-Control-Allow-Headers": "origin,content-type,cache-control,accept,options,authorization,x-requested-with,x-vault-token", +var preflightHeaders = map[string]string{ + "Access-Control-Allow-Headers": "*", "Access-Control-Max-Age": "1800", "Access-Control-Allow-Credentials": "true", - "Vary": "Origin", } var allowedMethods = []string{ @@ -21,43 +23,64 @@ var allowedMethods = []string{ http.MethodOptions, http.MethodPost, http.MethodPut, + "LIST", // LIST is not an official HTTP method, but Vault supports it. } +// CORSConfig stores the state of the CORS configuration. type CORSConfig struct { - Enabled bool - AllowedOrigins []string + isEnabled bool + allowedOrigins []string + mutex *sync.RWMutex } +// Enable takes either a '*' or a comma-seprated list of URLs that can make +// cross-origin requests to Vault. func (c *CORSConfig) Enable(s string) error { + c.mutex.Lock() + defer c.mutex.Unlock() + if strings.Contains("*", s) && len(s) > 1 { return errors.New("wildcard must be the only value") } - allowedOrigins := strings.Split(s, " ") - - c.AllowedOrigins = allowedOrigins - c.Enabled = true + c.allowedOrigins = strings.Split(s, ",") + c.isEnabled = true return nil } +// Get returns the state of the CORS configuration. +func (c *CORSConfig) Get() *CORSConfig { + c.mutex.Lock() + defer c.mutex.Unlock() + + return c +} + +// Enabled returns the value of CORSConfig.isEnabled +func (c *CORSConfig) Enabled() bool { + c.mutex.Lock() + defer c.mutex.Unlock() + + return c.isEnabled +} + // Disable sets CORS to disabled and clears the allowed origins func (c *CORSConfig) Disable() { - c.Enabled = false - c.AllowedOrigins = []string{} + c.mutex.Lock() + defer c.mutex.Unlock() + + c.isEnabled = false + c.allowedOrigins = []string{} } // ApplyHeaders examines the CORS configuration and the request to determine // if the CORS headers should be returned with the response. func (c *CORSConfig) ApplyHeaders(w http.ResponseWriter, r *http.Request) int { - origin := r.Header.Get("Origin") + c.mutex.Lock() + defer c.mutex.Unlock() - // If CORS is not enabled or if no Origin header is present (i.e. the request - // is from the Vault CLI. A browser will always send an Origin header), then - // just return a 200. - if !c.Enabled || origin == "" { - return http.StatusOK - } + origin := r.Header.Get("Origin") // Return a 403 if the origin is not // allowed to make cross-origin requests. @@ -66,47 +89,34 @@ func (c *CORSConfig) ApplyHeaders(w http.ResponseWriter, r *http.Request) int { } w.Header().Set("Access-Control-Allow-Origin", origin) + w.Header().Set("Vary", "Origin") // apply headers for preflight requests if r.Method == http.MethodOptions { - methodAllowed := false requestedMethod := r.Header.Get("Access-Control-Request-Method") - for _, method := range allowedMethods { - if method == requestedMethod { - methodAllowed = true - continue - } - } - if !methodAllowed { + if !strutil.StrListContains(allowedMethods, requestedMethod) { return http.StatusMethodNotAllowed } - methods := strings.Join(allowedMethods, ",") - w.Header().Set("Access-Control-Allow-Methods", methods) + w.Header().Set("Access-Control-Allow-Methods", strings.Join(allowedMethods, ",")) - for k, v := range responseHeaders { + for k, v := range preflightHeaders { w.Header().Set(k, v) } } - return http.StatusOK + return http.StatusNoContent } func (c *CORSConfig) validOrigin(origin string) bool { - if c.AllowedOrigins == nil { + if c.allowedOrigins == nil { return false } - if len(c.AllowedOrigins) == 1 && (c.AllowedOrigins)[0] == "*" { + if len(c.allowedOrigins) == 1 && (c.allowedOrigins)[0] == "*" { return true } - for _, allowedOrigin := range c.AllowedOrigins { - if origin == allowedOrigin { - return true - } - } - - return false + return strutil.StrListContains(c.allowedOrigins, origin) } From 4b5a32a9e505dc783b99a5dee0d2be99dbec0534 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 31 Jan 2017 10:58:25 -0500 Subject: [PATCH 70/89] Renamed handleCORSEnable, Disable, and Status to handleCORSUpdate, Delete, and Read. Also no longer returning values from the handleCORSUpdate and handleCORSDelete funcs. They will simply return a 204. --- vault/logical_system.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/vault/logical_system.go b/vault/logical_system.go index 099b360fecfe..135f5e846d08 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -86,9 +86,9 @@ func NewSystemBackend(core *Core, config *logical.BackendConfig) (logical.Backen }, Callbacks: map[logical.Operation]framework.OperationFunc{ - logical.ReadOperation: b.handleCORSStatus, - logical.UpdateOperation: b.handleCORSEnable, - logical.DeleteOperation: b.handleCORSDisable, + logical.ReadOperation: b.handleCORSRead, + logical.UpdateOperation: b.handleCORSUpdate, + logical.DeleteOperation: b.handleCORSDelete, }, HelpDescription: strings.TrimSpace(sysHelp["config/cors"][0]), @@ -662,22 +662,22 @@ type SystemBackend struct { // corsStatus returns the current CORS configuration as a logical.Response func (b *SystemBackend) corsStatusResponse() (*logical.Response, error) { - corsConf := b.Core.corsConfig + corsConf := b.Core.corsConfig.Get() if corsConf == nil { return nil, errCORSNotConfigured } return &logical.Response{ Data: map[string]interface{}{ - "enabled": corsConf.Enabled, - "allowed_origins": strings.Join(corsConf.AllowedOrigins, " "), + "enabled": corsConf.isEnabled, + "allowed_origins": strings.Join(corsConf.allowedOrigins, ","), }, }, nil } -// handleCORSEnable sets the list of origins that are allowed +// handleCORSUpdate sets the list of origins that are allowed // to make cross-origin requests and sets the CORS enabled flag to true -func (b *SystemBackend) handleCORSEnable(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { +func (b *SystemBackend) handleCORSUpdate(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { origins := d.Get("allowed_origins").(string) err := b.Core.corsConfig.Enable(origins) @@ -698,22 +698,22 @@ func (b *SystemBackend) handleCORSEnable(req *logical.Request, d *framework.Fiel return handleError(err) } - return b.corsStatusResponse() + return nil, nil } -// handleCORSDisable clears the allowed origins and sets the CORS enabled flag to false -func (b *SystemBackend) handleCORSDisable(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { +// handleCORSDelete clears the allowed origins and sets the CORS enabled flag to false +func (b *SystemBackend) handleCORSDelete(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { b.Core.CORSConfig().Disable() if err := b.Core.configStore.DeleteConfig("cors"); err != nil { return handleError(err) } - return b.corsStatusResponse() + return nil, nil } -// handleCORSStatus returns the current CORS configuration -func (b *SystemBackend) handleCORSStatus(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { +// handleCORSRead returns the current CORS configuration +func (b *SystemBackend) handleCORSRead(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { return b.corsStatusResponse() } From 31279331e999040f75399cc84a42c8b98f39b74d Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 31 Jan 2017 11:00:33 -0500 Subject: [PATCH 71/89] Changed docs to reflect the fact that the CORS Update and Delete funcs simply return a 204. --- .../source/docs/http/sys-config-cors.html.md | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/website/source/docs/http/sys-config-cors.html.md b/website/source/docs/http/sys-config-cors.html.md index 045d51c55349..05755f996b4f 100644 --- a/website/source/docs/http/sys-config-cors.html.md +++ b/website/source/docs/http/sys-config-cors.html.md @@ -73,22 +73,14 @@ policy or `sudo` capability on the path.
  • allowed_origins required - Valid values are either a wildcard (*) or a space-separated list of + Valid values are either a wildcard (*) or a comma-separated list of exact origins that are permitted to make cross-origin requests.
  • Returns
    -
    - - ```javascript - { - "enabled": true, - "allowed_origins": "*" - } - ``` - +
    `204` response code.
    @@ -112,13 +104,6 @@ policy or `sudo` capability on the path.
    Returns
    -
    - - ```javascript - { - "enabled": false - } - ``` - +
    `204` response code.
    From c3447f4e584dc5ed52db82e7b43f81abcca3b739 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 31 Jan 2017 12:23:23 -0500 Subject: [PATCH 72/89] Refactor test to reflect the fact that the CORS Update and Delete methods return no data. --- vault/logical_system_test.go | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go index d86589007290..896d2b5be965 100644 --- a/vault/logical_system_test.go +++ b/vault/logical_system_test.go @@ -37,7 +37,10 @@ func TestSystemConfigCORS(t *testing.T) { req := logical.TestRequest(t, logical.UpdateOperation, "config/cors") req.Data["allowed_origins"] = "http://www.example.com" - actual, err := b.HandleRequest(req) + _, err := b.HandleRequest(req) + if err != nil { + t.Fatal(err) + } expected := &logical.Response{ Data: map[string]interface{}{ @@ -46,18 +49,26 @@ func TestSystemConfigCORS(t *testing.T) { }, } + req = logical.TestRequest(t, logical.ReadOperation, "config/cors") + actual, err := b.HandleRequest(req) + if err != nil { + t.Fatalf("err: %v", err) + } + if !reflect.DeepEqual(actual, expected) { - t.Fatalf("UPDATE FAILED\nexpected:\n%#v\nactual:\n%#v", expected, actual) + t.Fatalf("UPDATE FAILED -- bad: %#v", actual) } - req = logical.TestRequest(t, logical.ReadOperation, "config/cors") - actual, err = b.HandleRequest(req) + req = logical.TestRequest(t, logical.DeleteOperation, "config/cors") + _, err = b.HandleRequest(req) if err != nil { t.Fatalf("err: %v", err) } - if !reflect.DeepEqual(actual, expected) { - t.Fatalf("READ FAILED -- bad: %#v", actual) + req = logical.TestRequest(t, logical.ReadOperation, "config/cors") + actual, err = b.HandleRequest(req) + if err != nil { + t.Fatalf("err: %v", err) } expected = &logical.Response{ @@ -67,12 +78,6 @@ func TestSystemConfigCORS(t *testing.T) { }, } - req = logical.TestRequest(t, logical.DeleteOperation, "config/cors") - actual, err = b.HandleRequest(req) - if err != nil { - t.Fatalf("err: %v", err) - } - if !reflect.DeepEqual(actual, expected) { t.Fatalf("DELETE FAILED -- bad: %#v", actual) } From 54ce27bc007916a9d62a57a51832bda79baeb201 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Sun, 5 Feb 2017 12:15:15 -0500 Subject: [PATCH 73/89] Merged handleCORSRead() and corsStatusResponse() into a single func. --- vault/logical_system.go | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/vault/logical_system.go b/vault/logical_system.go index 3d51626d86da..a0eee5afd3df 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -693,8 +693,8 @@ type SystemBackend struct { Backend *framework.Backend } -// corsStatus returns the current CORS configuration as a logical.Response -func (b *SystemBackend) corsStatusResponse() (*logical.Response, error) { +// handleCORSRead returns the current CORS configuration +func (b *SystemBackend) handleCORSRead(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { corsConf := b.Core.corsConfig.Get() if corsConf == nil { return nil, errCORSNotConfigured @@ -734,6 +734,17 @@ func (b *SystemBackend) handleCORSUpdate(req *logical.Request, d *framework.Fiel return nil, nil } +// handleCORSDelete clears the allowed origins and sets the CORS enabled flag to false +func (b *SystemBackend) handleCORSDelete(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { + b.Core.CORSConfig().Disable() + + if err := b.Core.configStore.DeleteConfig("cors"); err != nil { + return handleError(err) + } + + return nil, nil +} + // handleAuditedHeaderUpdate creates or overwrites a header entry func (b *SystemBackend) handleAuditedHeaderUpdate(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { header := d.Get("header").(string) @@ -767,22 +778,6 @@ func (b *SystemBackend) handleAuditedHeaderDelete(req *logical.Request, d *frame return nil, nil } -// handleCORSDelete clears the allowed origins and sets the CORS enabled flag to false -func (b *SystemBackend) handleCORSDelete(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { - b.Core.CORSConfig().Disable() - - if err := b.Core.configStore.DeleteConfig("cors"); err != nil { - return handleError(err) - } - - return nil, nil -} - -// handleCORSRead returns the current CORS configuration -func (b *SystemBackend) handleCORSRead(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { - return b.corsStatusResponse() -} - // handleAuditedHeaderRead returns the header configuration for the given header name func (b *SystemBackend) handleAuditedHeaderRead(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { header := d.Get("header").(string) From 6bc49a53f03ab3bff807c17383e7796b801c64ce Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 7 Feb 2017 13:28:44 -0500 Subject: [PATCH 74/89] Moved logic to determine when to apply the CORS headers to the http handler. This was a more logical place to put it. --- http/cors.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/http/cors.go b/http/cors.go index 54d81c931bd1..6c6ab1bcfbd5 100644 --- a/http/cors.go +++ b/http/cors.go @@ -10,25 +10,31 @@ func wrapCORSHandler(h http.Handler, core *vault.Core) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { corsConf := core.CORSConfig() + origin := req.Header.Get("Origin") + requestMethod := req.Header.Get("Access-Control-Request-Method") + // If CORS is not enabled or if no Origin header is present (i.e. the request // is from the Vault CLI. A browser will always send an Origin header), then // just return a 204. - if !corsConf.Enabled() || req.Header.Get("Origin") == "" { + if !corsConf.IsEnabled() || origin == "" { h.ServeHTTP(w, req) return } - statusCode := corsConf.ApplyHeaders(w, req) - if statusCode != http.StatusNoContent { - h.ServeHTTP(w, req) + // Return a 403 if the origin is not + // allowed to make cross-origin requests. + if !corsConf.IsValidOrigin(origin) { + w.WriteHeader(http.StatusForbidden) return } - // For pre-flight requests just send back the headers and return. - if req.Method == http.MethodOptions { + if req.Method == http.MethodOptions && !corsConf.IsValidMethod(requestMethod) { + w.WriteHeader(http.StatusMethodNotAllowed) return } + corsConf.ApplyHeaders(w, req) + h.ServeHTTP(w, req) return }) From bb5d81d2d6018ea21df65155d38af24889da2270 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 7 Feb 2017 13:29:26 -0500 Subject: [PATCH 75/89] Removed the config store. --- vault/config_store.go | 174 ------------------------------------- vault/config_store_test.go | 104 ---------------------- 2 files changed, 278 deletions(-) delete mode 100644 vault/config_store.go delete mode 100644 vault/config_store_test.go diff --git a/vault/config_store.go b/vault/config_store.go deleted file mode 100644 index c78a260790d5..000000000000 --- a/vault/config_store.go +++ /dev/null @@ -1,174 +0,0 @@ -package vault - -import ( - "fmt" - "time" - - "github.com/armon/go-metrics" - "github.com/hashicorp/golang-lru" - "github.com/hashicorp/vault/logical" -) - -const ( - // configSubPath is the sub-path used for the config store - // view. This is nested under the system view. - configSubPath = "config/" - - // configCacheSize is the number of configs that are kept cached - configCacheSize = 1024 -) - -var defaultConfigs = []string{ - "cors", -} - -type Config struct { - Name string - Settings map[string]string -} - -// ConfigStore is used to provide durable storage of configuration items -type ConfigStore struct { - view *BarrierView - lru *lru.TwoQueueCache -} - -// ConfigEntry is used to store a configuration by name -type ConfigEntry struct { - Version int - Raw string -} - -// NewConfigStore creates a new ConfigStore that is backed using a given view. -// It used used to durably store and manage named configurations. -func NewConfigStore(view *BarrierView, system logical.SystemView) *ConfigStore { - c := &ConfigStore{ - view: view, - } - if !system.CachingDisabled() { - cache, _ := lru.New2Q(configCacheSize) - c.lru = cache - } - - return c -} - -// setupConfigStore is used to initialize the config store -// when the vault is being unsealed. -func (c *Core) setupConfigStore() error { - // Create a sub-view - view := c.systemBarrierView.SubView(configSubPath) - - // Create the config store - c.configStore = NewConfigStore(view, &dynamicSystemView{core: c}) - - for _, name := range defaultConfigs { - config, err := c.configStore.GetConfig(name) - if err != nil { - return err - } - - if config != nil { - switch config.Name { - case "cors": - c.corsConfig.Enable(config.Settings["allowed_origins"]) - } - } - } - - return nil -} - -// teardownConfigStore is used to reverse setupConfigStore -// when the vault is being sealed. -func (c *Core) teardownConfigStore() error { - c.configStore = nil - return nil -} - -// SetConfig is used to create or update the given config -func (cs *ConfigStore) SetConfig(c *Config) error { - defer metrics.MeasureSince([]string{"config", "set_config"}, time.Now()) - if c.Name == "" { - return fmt.Errorf("config name missing") - } - - return cs.setConfigInternal(c) -} - -func (cs *ConfigStore) setConfigInternal(c *Config) error { - var entry *logical.StorageEntry - var err error - - entry, err = logical.StorageEntryJSON(c.Name, c) - if cs.lru != nil { - // Update the LRU cache - cs.lru.Add(c.Name, c) - } - - if err != nil { - return fmt.Errorf("failed to create entry: %v", err) - } - if err := cs.view.Put(entry); err != nil { - return fmt.Errorf("failed to persist config: %v", err) - } - - return nil -} - -// GetConfig is used to fetch the named config -func (cs *ConfigStore) GetConfig(name string) (*Config, error) { - defer metrics.MeasureSince([]string{"config", "get_config"}, time.Now()) - if cs.lru != nil { - // Check for cached config - if raw, ok := cs.lru.Get(name); ok { - return raw.(*Config), nil - } - } - - // Load the config in - out, err := cs.view.Get(name) - if err != nil { - return nil, fmt.Errorf("failed to read config: %v", err) - } - if out == nil { - return nil, nil - } - - config := new(Config) - err = out.DecodeJSON(config) - if err != nil { - return nil, err - } - - if cs.lru != nil { - // Update the LRU cache - cs.lru.Add(name, config) - } - - return config, nil -} - -// ListConfigs is used to list the available configs -func (cs *ConfigStore) ListConfigs() ([]string, error) { - defer metrics.MeasureSince([]string{"config", "list_configs"}, time.Now()) - // Scan the view, since the config names are the same as the - // key names. - keys, err := logical.CollectKeys(cs.view) - - return keys, err -} - -// DeleteConfig is used to delete the named config -func (cs *ConfigStore) DeleteConfig(name string) error { - defer metrics.MeasureSince([]string{"config", "delete_config"}, time.Now()) - if err := cs.view.Delete(name); err != nil { - return fmt.Errorf("failed to delete config: %v", err) - } - - if cs.lru != nil { - // Clear the cache - cs.lru.Remove(name) - } - return nil -} diff --git a/vault/config_store_test.go b/vault/config_store_test.go deleted file mode 100644 index 5f7c4868f98a..000000000000 --- a/vault/config_store_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package vault - -import ( - "reflect" - "testing" - - "github.com/hashicorp/vault/logical" -) - -func mockConfigStore(t *testing.T) *ConfigStore { - _, barrier, _ := mockBarrier(t) - view := NewBarrierView(barrier, "foo/") - c := NewConfigStore(view, logical.TestSystemView()) - return c -} - -func mockConfigStoreNoCache(t *testing.T) *ConfigStore { - sysView := logical.TestSystemView() - sysView.CachingDisabledVal = true - _, barrier, _ := mockBarrier(t) - view := NewBarrierView(barrier, "foo/") - c := NewConfigStore(view, sysView) - return c -} - -func TestConfigStore_CRUD(t *testing.T) { - cs := mockConfigStore(t) - testConfigStore_CRUD(t, cs) - - cs = mockConfigStoreNoCache(t) - testConfigStore_CRUD(t, cs) -} - -func testConfigStore_CRUD(t *testing.T, cs *ConfigStore) { - // Get should return nothing - c, err := cs.GetConfig("dev") - if err != nil { - t.Fatalf("err: %v", err) - } - if c != nil { - t.Fatalf("bad: %v", c) - } - - // Delete should be no-op - err = cs.DeleteConfig("dev") - if err != nil { - t.Fatalf("err: %v", err) - } - - // List should be blank - out, err := cs.ListConfigs() - if err != nil { - t.Fatalf("err: %v", err) - } - if len(out) != 0 { - t.Fatalf("bad: %v", out) - } - - config := &Config{ - Name: "cors", - Settings: map[string]string{ - "allowed_origins": "http://www.example.com http://localhost", - "enabled": "true", - }, - } - - err = cs.SetConfig(config) - if err != nil { - t.Fatalf("err: %v", err) - } - // Get should work - c, err = cs.GetConfig("cors") - if err != nil { - t.Fatalf("err: %v", err) - } - - if !reflect.DeepEqual(c, config) { - t.Fatalf("bad: %v", c) - } - - // List should be one element - out, err = cs.ListConfigs() - if err != nil { - t.Fatalf("err: %v", err) - } - if len(out) != 1 || out[0] != "cors" { - t.Fatalf("bad: %v", out) - } - - // Delete should be clear the entry - err = cs.DeleteConfig("cors") - if err != nil { - t.Fatalf("err: %v", err) - } - - // Get should fail - c, err = cs.GetConfig("cors") - if err != nil { - t.Fatalf("err: %v", err) - } - if c != nil { - t.Fatalf("bad: %v", c) - } -} From 9afc27981d0e48702b43de88c2a72ad218cdede0 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 7 Feb 2017 13:30:04 -0500 Subject: [PATCH 76/89] Gave the test a URL that is actually valid. --- http/handler_test.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/http/handler_test.go b/http/handler_test.go index 805d40f9a3c9..ee9700c7d9a9 100644 --- a/http/handler_test.go +++ b/http/handler_test.go @@ -25,7 +25,7 @@ func TestHandler_cors(t *testing.T) { t.Fatalf("Error enabling CORS: %s", err) } - req, err := http.NewRequest(http.MethodOptions, addr+"/v1/", nil) + req, err := http.NewRequest(http.MethodOptions, addr+"/v1/sys/seal-status", nil) if err != nil { t.Fatalf("err: %s", err) } @@ -72,11 +72,6 @@ func TestHandler_cors(t *testing.T) { t.Fatalf("err: %s", err) } - // Fail if an acceptable method is NOT accepted. - if resp.StatusCode != http.StatusOK { - t.Fatalf("Bad status:\nexpected: 200 OK\nactual: %s", resp.Status) - } - // // Test that the CORS headers are applied correctly. // From 3b0a32dda8df5e0f4bb5be45d663dee044e286a9 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 7 Feb 2017 13:30:50 -0500 Subject: [PATCH 77/89] Refactored after removing the ConfigStore. --- vault/core.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/vault/core.go b/vault/core.go index 266053685f11..a038bef49113 100644 --- a/vault/core.go +++ b/vault/core.go @@ -235,9 +235,6 @@ type Core struct { // policy store is used to manage named ACL policies policyStore *PolicyStore - // config store is used to manage API-managable configs - configStore *ConfigStore - // token store is used to manage authentication tokens tokenStore *TokenStore @@ -436,7 +433,7 @@ func NewCore(conf *CoreConfig) (*Core, error) { clusterCertPool: x509.NewCertPool(), clusterListenerShutdownCh: make(chan struct{}), clusterListenerShutdownSuccessCh: make(chan struct{}), - corsConfig: &CORSConfig{mutex: &sync.RWMutex{}}, + corsConfig: &CORSConfig{}, } // Wrap the backend in a cache unless disabled @@ -1216,7 +1213,7 @@ func (c *Core) postUnseal() (retErr error) { if err := c.setupPolicyStore(); err != nil { return err } - if err := c.setupConfigStore(); err != nil { + if err := c.loadCORSConfig(); err != nil { return err } if err := c.loadCredentials(); err != nil { @@ -1281,7 +1278,7 @@ func (c *Core) preSeal() error { if err := c.teardownPolicyStore(); err != nil { result = multierror.Append(result, errwrap.Wrapf("error tearing down policy store: {{err}}", err)) } - if err := c.teardownConfigStore(); err != nil { + if err := c.saveCORSConfig(); err != nil { result = multierror.Append(result, errwrap.Wrapf("error tearing down config store: {{err}}", err)) } if err := c.stopRollback(); err != nil { From d8a689a5ea7250f38880339a86994ac5e44af809 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 7 Feb 2017 13:31:47 -0500 Subject: [PATCH 78/89] Exported the Enabled and AllowedOrigins fields of the CORSConfig struct for JSON encoding. Added funcs to load the existing CORS config at unseal and save the CORS config to teardown. Embeded the RWMutex in the CORSConfig struct, instead of making it a field. Refactored to support this. Renamed validOrigin func to IsValid, because it reads better. Added the IsValidMethod func to check if the verb of the HTTP request is acceptable for cross-origin requests. --- vault/cors.go | 111 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 75 insertions(+), 36 deletions(-) diff --git a/vault/cors.go b/vault/cors.go index 8fd55af65151..4f41cc88c912 100644 --- a/vault/cors.go +++ b/vault/cors.go @@ -2,11 +2,13 @@ package vault import ( "errors" + "fmt" "net/http" "strings" "sync" "github.com/hashicorp/vault/helper/strutil" + "github.com/hashicorp/vault/logical" ) var errCORSNotConfigured = errors.New("CORS is not configured") @@ -28,77 +30,103 @@ var allowedMethods = []string{ // CORSConfig stores the state of the CORS configuration. type CORSConfig struct { - isEnabled bool - allowedOrigins []string - mutex *sync.RWMutex + Enabled bool `json:"enabled"` + AllowedOrigins []string `json:"allowed_origins"` + sync.RWMutex +} + +func (c *Core) saveCORSConfig() error { + view := c.systemBarrierView.SubView("config/") + + entry, err := logical.StorageEntryJSON("cors", c.corsConfig) + if err != nil { + return fmt.Errorf("failed to create CORS confif entry: %v", err) + } + + if err := view.Put(entry); err != nil { + return fmt.Errorf("failed to save CORS config: %v", err) + } + + return nil +} + +func (c *Core) loadCORSConfig() error { + view := c.systemBarrierView.SubView("config/") + + // Load the config in + out, err := view.Get("cors") + if err != nil { + return fmt.Errorf("failed to read CORS config: %v", err) + } + if out == nil { + return nil + } + + config := new(CORSConfig) + err = out.DecodeJSON(config) + if err != nil { + return err + } + + c.corsConfig = config + + return nil } // Enable takes either a '*' or a comma-seprated list of URLs that can make // cross-origin requests to Vault. func (c *CORSConfig) Enable(s string) error { - c.mutex.Lock() - defer c.mutex.Unlock() + c.RWMutex.Lock() + defer c.RWMutex.Unlock() if strings.Contains("*", s) && len(s) > 1 { return errors.New("wildcard must be the only value") } - c.allowedOrigins = strings.Split(s, ",") - c.isEnabled = true + c.AllowedOrigins = strings.Split(s, ",") + c.Enabled = true return nil } // Get returns the state of the CORS configuration. func (c *CORSConfig) Get() *CORSConfig { - c.mutex.Lock() - defer c.mutex.Unlock() + c.RWMutex.Lock() + defer c.RWMutex.Unlock() return c } -// Enabled returns the value of CORSConfig.isEnabled -func (c *CORSConfig) Enabled() bool { - c.mutex.Lock() - defer c.mutex.Unlock() +// IsEnabled returns the value of CORSConfig.isEnabled +func (c *CORSConfig) IsEnabled() bool { + c.RWMutex.Lock() + defer c.RWMutex.Unlock() - return c.isEnabled + return c.Enabled } // Disable sets CORS to disabled and clears the allowed origins func (c *CORSConfig) Disable() { - c.mutex.Lock() - defer c.mutex.Unlock() + c.RWMutex.Lock() + defer c.RWMutex.Unlock() - c.isEnabled = false - c.allowedOrigins = []string{} + c.Enabled = false + c.AllowedOrigins = []string{} } // ApplyHeaders examines the CORS configuration and the request to determine // if the CORS headers should be returned with the response. func (c *CORSConfig) ApplyHeaders(w http.ResponseWriter, r *http.Request) int { - c.mutex.Lock() - defer c.mutex.Unlock() + c.RWMutex.Lock() + defer c.RWMutex.Unlock() origin := r.Header.Get("Origin") - // Return a 403 if the origin is not - // allowed to make cross-origin requests. - if !c.validOrigin(origin) { - return http.StatusForbidden - } - w.Header().Set("Access-Control-Allow-Origin", origin) w.Header().Set("Vary", "Origin") // apply headers for preflight requests if r.Method == http.MethodOptions { - requestedMethod := r.Header.Get("Access-Control-Request-Method") - - if !strutil.StrListContains(allowedMethods, requestedMethod) { - return http.StatusMethodNotAllowed - } - w.Header().Set("Access-Control-Allow-Methods", strings.Join(allowedMethods, ",")) for k, v := range preflightHeaders { @@ -109,14 +137,25 @@ func (c *CORSConfig) ApplyHeaders(w http.ResponseWriter, r *http.Request) int { return http.StatusNoContent } -func (c *CORSConfig) validOrigin(origin string) bool { - if c.allowedOrigins == nil { +// IsValidOrigin determines if the origin of the request is allowed to make +// cross-origin requests based on the CORSConfig. +func (c *CORSConfig) IsValidOrigin(origin string) bool { + if c.AllowedOrigins == nil { return false } - if len(c.allowedOrigins) == 1 && (c.allowedOrigins)[0] == "*" { + if len(c.AllowedOrigins) == 1 && (c.AllowedOrigins)[0] == "*" { return true } - return strutil.StrListContains(c.allowedOrigins, origin) + return strutil.StrListContains(c.AllowedOrigins, origin) +} + +// IsValidMethod determines if the verb of the HTTP request is allowed. +func (c *CORSConfig) IsValidMethod(method string) bool { + if method == "" { + return false + } + + return strutil.StrListContains(allowedMethods, method) } From 077dfad52cfb8d3cc7f329058b6ca649ca7d4928 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 7 Feb 2017 13:39:25 -0500 Subject: [PATCH 79/89] Updated field names. --- vault/logical_system.go | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/vault/logical_system.go b/vault/logical_system.go index a0eee5afd3df..73a5a26e08a8 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -702,8 +702,8 @@ func (b *SystemBackend) handleCORSRead(req *logical.Request, d *framework.FieldD return &logical.Response{ Data: map[string]interface{}{ - "enabled": corsConf.isEnabled, - "allowed_origins": strings.Join(corsConf.allowedOrigins, ","), + "enabled": corsConf.Enabled, + "allowed_origins": strings.Join(corsConf.AllowedOrigins, ","), }, }, nil } @@ -718,19 +718,6 @@ func (b *SystemBackend) handleCORSUpdate(req *logical.Request, d *framework.Fiel return nil, err } - config := &Config{ - Name: "cors", - Settings: map[string]string{ - "allowed_origins": origins, - "enabled": "true", - }, - } - - // Update the config - if err := b.Core.configStore.SetConfig(config); err != nil { - return handleError(err) - } - return nil, nil } @@ -738,10 +725,6 @@ func (b *SystemBackend) handleCORSUpdate(req *logical.Request, d *framework.Fiel func (b *SystemBackend) handleCORSDelete(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { b.Core.CORSConfig().Disable() - if err := b.Core.configStore.DeleteConfig("cors"); err != nil { - return handleError(err) - } - return nil, nil } From fca8f369845b725609853aa2cd1a793097474a3a Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Wed, 1 Mar 2017 17:20:34 -0500 Subject: [PATCH 80/89] Moved the code to put the headers on the response here. Instead of making it part of the CORSConfig struct. --- http/cors.go | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/http/cors.go b/http/cors.go index 6c6ab1bcfbd5..0e11db9cea2f 100644 --- a/http/cors.go +++ b/http/cors.go @@ -2,10 +2,26 @@ package http import ( "net/http" + "strings" + "github.com/hashicorp/vault/helper/strutil" "github.com/hashicorp/vault/vault" ) +var preflightHeaders = map[string]string{ + "Access-Control-Allow-Headers": "*", + "Access-Control-Max-Age": "300", +} + +var allowedMethods = []string{ + http.MethodDelete, + http.MethodGet, + http.MethodOptions, + http.MethodPost, + http.MethodPut, + "LIST", // LIST is not an official HTTP method, but Vault supports it. +} + func wrapCORSHandler(h http.Handler, core *vault.Core) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { corsConf := core.CORSConfig() @@ -28,12 +44,22 @@ func wrapCORSHandler(h http.Handler, core *vault.Core) http.Handler { return } - if req.Method == http.MethodOptions && !corsConf.IsValidMethod(requestMethod) { + if req.Method == http.MethodOptions && !strutil.StrListContains(allowedMethods, requestMethod) { w.WriteHeader(http.StatusMethodNotAllowed) return } - corsConf.ApplyHeaders(w, req) + w.Header().Set("Access-Control-Allow-Origin", origin) + w.Header().Set("Vary", "Origin") + + // apply headers for preflight requests + if req.Method == http.MethodOptions { + w.Header().Set("Access-Control-Allow-Methods", strings.Join(allowedMethods, ",")) + + for k, v := range preflightHeaders { + w.Header().Set(k, v) + } + } h.ServeHTTP(w, req) return From 468fda3983e7cbae7702a699070ad0b09e588a9d Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Wed, 1 Mar 2017 17:22:11 -0500 Subject: [PATCH 81/89] CORSConfig.Get() was removed. Updated to reflect that. Fixed a typo in vault/core.go. --- vault/core.go | 4 ++-- vault/logical_system.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vault/core.go b/vault/core.go index b7e17eb71a45..e0a12c131ef9 100644 --- a/vault/core.go +++ b/vault/core.go @@ -529,7 +529,7 @@ func (c *Core) Shutdown() error { // CORSConfig returns the current CORS configuration func (c *Core) CORSConfig() *CORSConfig { - return c.corsConfig.Get() + return c.corsConfig } // LookupToken returns the properties of the token from the token store. This @@ -1326,7 +1326,7 @@ func (c *Core) preSeal() error { result = multierror.Append(result, errwrap.Wrapf("error tearing down policy store: {{err}}", err)) } if err := c.saveCORSConfig(); err != nil { - result = multierror.Append(result, errwrap.Wrapf("error tearing down config store: {{err}}", err)) + result = multierror.Append(result, errwrap.Wrapf("error tearing down CORS config: {{err}}", err)) } if err := c.stopRollback(); err != nil { result = multierror.Append(result, errwrap.Wrapf("error stopping rollback: {{err}}", err)) diff --git a/vault/logical_system.go b/vault/logical_system.go index 65376e0fa97c..fdc34a9d910d 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -737,7 +737,7 @@ type SystemBackend struct { // handleCORSRead returns the current CORS configuration func (b *SystemBackend) handleCORSRead(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { - corsConf := b.Core.corsConfig.Get() + corsConf := b.Core.corsConfig if corsConf == nil { return nil, errCORSNotConfigured } From 2a5a5ef79bd5908e30a69d3d7fd420b14d12d776 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Wed, 1 Mar 2017 17:25:40 -0500 Subject: [PATCH 82/89] Removed ApplyHeaders and Get(). Applied correct locks where needed. Fixed typos, and put the embedded Mutex struct as the first field in the CORSConfig struct for the sake of readability. --- vault/cors.go | 82 ++++++++++----------------------------------------- 1 file changed, 15 insertions(+), 67 deletions(-) diff --git a/vault/cors.go b/vault/cors.go index 4f41cc88c912..dd93e7db0868 100644 --- a/vault/cors.go +++ b/vault/cors.go @@ -3,7 +3,6 @@ package vault import ( "errors" "fmt" - "net/http" "strings" "sync" @@ -13,26 +12,11 @@ import ( var errCORSNotConfigured = errors.New("CORS is not configured") -var preflightHeaders = map[string]string{ - "Access-Control-Allow-Headers": "*", - "Access-Control-Max-Age": "1800", - "Access-Control-Allow-Credentials": "true", -} - -var allowedMethods = []string{ - http.MethodDelete, - http.MethodGet, - http.MethodOptions, - http.MethodPost, - http.MethodPut, - "LIST", // LIST is not an official HTTP method, but Vault supports it. -} - // CORSConfig stores the state of the CORS configuration. type CORSConfig struct { + sync.RWMutex Enabled bool `json:"enabled"` AllowedOrigins []string `json:"allowed_origins"` - sync.RWMutex } func (c *Core) saveCORSConfig() error { @@ -40,7 +24,7 @@ func (c *Core) saveCORSConfig() error { entry, err := logical.StorageEntryJSON("cors", c.corsConfig) if err != nil { - return fmt.Errorf("failed to create CORS confif entry: %v", err) + return fmt.Errorf("failed to create CORS config entry: %v", err) } if err := view.Put(entry); err != nil { @@ -75,71 +59,44 @@ func (c *Core) loadCORSConfig() error { // Enable takes either a '*' or a comma-seprated list of URLs that can make // cross-origin requests to Vault. -func (c *CORSConfig) Enable(s string) error { - c.RWMutex.Lock() - defer c.RWMutex.Unlock() +func (c *CORSConfig) Enable(urls string) error { - if strings.Contains("*", s) && len(s) > 1 { + if strings.Contains("*", urls) && len(urls) > 1 { return errors.New("wildcard must be the only value") } - c.AllowedOrigins = strings.Split(s, ",") + c.Lock() + defer c.Unlock() + + c.AllowedOrigins = strings.Split(urls, ",") c.Enabled = true return nil } -// Get returns the state of the CORS configuration. -func (c *CORSConfig) Get() *CORSConfig { - c.RWMutex.Lock() - defer c.RWMutex.Unlock() - - return c -} - // IsEnabled returns the value of CORSConfig.isEnabled func (c *CORSConfig) IsEnabled() bool { - c.RWMutex.Lock() - defer c.RWMutex.Unlock() + c.RLock() + defer c.RUnlock() return c.Enabled } // Disable sets CORS to disabled and clears the allowed origins func (c *CORSConfig) Disable() { - c.RWMutex.Lock() - defer c.RWMutex.Unlock() + c.Lock() + defer c.Unlock() c.Enabled = false c.AllowedOrigins = []string{} } -// ApplyHeaders examines the CORS configuration and the request to determine -// if the CORS headers should be returned with the response. -func (c *CORSConfig) ApplyHeaders(w http.ResponseWriter, r *http.Request) int { - c.RWMutex.Lock() - defer c.RWMutex.Unlock() - - origin := r.Header.Get("Origin") - - w.Header().Set("Access-Control-Allow-Origin", origin) - w.Header().Set("Vary", "Origin") - - // apply headers for preflight requests - if r.Method == http.MethodOptions { - w.Header().Set("Access-Control-Allow-Methods", strings.Join(allowedMethods, ",")) - - for k, v := range preflightHeaders { - w.Header().Set(k, v) - } - } - - return http.StatusNoContent -} - // IsValidOrigin determines if the origin of the request is allowed to make // cross-origin requests based on the CORSConfig. func (c *CORSConfig) IsValidOrigin(origin string) bool { + c.RLock() + defer c.RUnlock() + if c.AllowedOrigins == nil { return false } @@ -150,12 +107,3 @@ func (c *CORSConfig) IsValidOrigin(origin string) bool { return strutil.StrListContains(c.AllowedOrigins, origin) } - -// IsValidMethod determines if the verb of the HTTP request is allowed. -func (c *CORSConfig) IsValidMethod(method string) bool { - if method == "" { - return false - } - - return strutil.StrListContains(allowedMethods, method) -} From f6f18b8acad727eaf512b81171371e0e601dc62e Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Fri, 3 Mar 2017 11:53:42 -0500 Subject: [PATCH 83/89] Was missing a return after writing the headers for a preflight request, which was resulting in an erroneous 405. --- http/cors.go | 1 + 1 file changed, 1 insertion(+) diff --git a/http/cors.go b/http/cors.go index 0e11db9cea2f..5bd0a1366bfc 100644 --- a/http/cors.go +++ b/http/cors.go @@ -59,6 +59,7 @@ func wrapCORSHandler(h http.Handler, core *vault.Core) http.Handler { for k, v := range preflightHeaders { w.Header().Set(k, v) } + return } h.ServeHTTP(w, req) From 607d2d52200cf99fe6b61474eeb918b634001145 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 9 May 2017 21:19:22 -0400 Subject: [PATCH 84/89] Updated imports. --- vault/mount.go | 1 + 1 file changed, 1 insertion(+) diff --git a/vault/mount.go b/vault/mount.go index d1024daefe28..bdcadd4dfd01 100644 --- a/vault/mount.go +++ b/vault/mount.go @@ -10,6 +10,7 @@ import ( "time" "github.com/hashicorp/go-uuid" + "github.com/hashicorp/vault/helper/consts" "github.com/hashicorp/vault/helper/jsonutil" "github.com/hashicorp/vault/helper/strutil" "github.com/hashicorp/vault/logical" From ed209aa8cc0b1d49301cf8247cec427858453467 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 9 May 2017 21:21:04 -0400 Subject: [PATCH 85/89] Made parameter to Enable a string slice. --- vault/cors.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/vault/cors.go b/vault/cors.go index dd93e7db0868..f73ce879ba98 100644 --- a/vault/cors.go +++ b/vault/cors.go @@ -3,7 +3,6 @@ package vault import ( "errors" "fmt" - "strings" "sync" "github.com/hashicorp/vault/helper/strutil" @@ -46,29 +45,26 @@ func (c *Core) loadCORSConfig() error { return nil } - config := new(CORSConfig) - err = out.DecodeJSON(config) + err = out.DecodeJSON(c.corsConfig) if err != nil { return err } - c.corsConfig = config - return nil } // Enable takes either a '*' or a comma-seprated list of URLs that can make // cross-origin requests to Vault. -func (c *CORSConfig) Enable(urls string) error { +func (c *CORSConfig) Enable(urls []string) error { - if strings.Contains("*", urls) && len(urls) > 1 { + if strutil.StrListContains(urls, "*") && len(urls) > 1 { return errors.New("wildcard must be the only value") } c.Lock() defer c.Unlock() - c.AllowedOrigins = strings.Split(urls, ",") + c.AllowedOrigins = urls c.Enabled = true return nil From bd484530863e310ca4927999811aa5d9446e0116 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 9 May 2017 21:21:24 -0400 Subject: [PATCH 86/89] Fixed a typo. --- vault/logical_system.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vault/logical_system.go b/vault/logical_system.go index 8d2bcc995db7..6ba2290e71b6 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -2304,7 +2304,7 @@ This path responds to the following HTTP methods. Returns the configuration of the CORS setting. POST / - Sets the space-separate list of origins that can make cross-origin requests. + Sets the comma-separated list of origins that can make cross-origin requests. DELETE / Clears the CORS configuration and disables acceptance of CORS requests. From b5e9b84e5437b0581747c4e383b68f4c6a3c8e25 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Tue, 9 May 2017 21:34:50 -0400 Subject: [PATCH 87/89] Corrected parameter to func Enable. --- http/handler_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http/handler_test.go b/http/handler_test.go index 7d8eb0101b1d..e36fffbc9deb 100644 --- a/http/handler_test.go +++ b/http/handler_test.go @@ -21,7 +21,7 @@ func TestHandler_cors(t *testing.T) { // Enable CORS and allow from any origin for testing. corsConfig := core.CORSConfig() - err := corsConfig.Enable(addr) + err := corsConfig.Enable([]string{addr}) if err != nil { t.Fatalf("Error enabling CORS: %s", err) } From 4fbeadb562f9c3ec19a95ca66372a583b1b4cd32 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Wed, 10 May 2017 09:42:58 -0400 Subject: [PATCH 88/89] Added Access-Control-Max-Age header to list of expected headers. --- http/handler_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/http/handler_test.go b/http/handler_test.go index e36fffbc9deb..8450a8b6be98 100644 --- a/http/handler_test.go +++ b/http/handler_test.go @@ -79,6 +79,7 @@ func TestHandler_cors(t *testing.T) { expHeaders := map[string]string{ "Access-Control-Allow-Origin": addr, "Access-Control-Allow-Headers": "*", + "Access-Control-Max-Age": "300", "Vary": "Origin", } From 8c9b4e2cccf2b56ff0adf74eb8d7b0171a2382a0 Mon Sep 17 00:00:00 2001 From: Aaron Salvo Date: Wed, 10 May 2017 17:01:32 -0400 Subject: [PATCH 89/89] Enable func returns an error if the list of URLs is empty. Reworded error message when trying to use the wildcard and URLs. --- vault/cors.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vault/cors.go b/vault/cors.go index f73ce879ba98..288c57b498f1 100644 --- a/vault/cors.go +++ b/vault/cors.go @@ -56,9 +56,12 @@ func (c *Core) loadCORSConfig() error { // Enable takes either a '*' or a comma-seprated list of URLs that can make // cross-origin requests to Vault. func (c *CORSConfig) Enable(urls []string) error { + if len(urls) == 0 { + return errors.New("the list of allowed origins cannot be empty") + } if strutil.StrListContains(urls, "*") && len(urls) > 1 { - return errors.New("wildcard must be the only value") + return errors.New("to allow all origins the '*' must be the only value for allowed_origins") } c.Lock()