Fix up CORS.

Ref #2021
This commit is contained in:
Jeff Mitchell 2017-06-17 01:26:25 -04:00
parent 0303f51b68
commit cf7d56e8f3
8 changed files with 176 additions and 169 deletions

View File

@ -199,7 +199,8 @@ func TestRekey_init_pgp(t *testing.T) {
MaxLeaseTTLVal: time.Hour * 24 * 32,
},
}
sysBackend, err := vault.NewSystemBackend(core, bc)
sysBE := vault.NewSystemBackend(core)
sysBackend, err := sysBE.Backend.Setup(bc)
if err != nil {
t.Fatal(err)
}

View File

@ -450,11 +450,13 @@ func NewCore(conf *CoreConfig) (*Core, error) {
clusterName: conf.ClusterName,
clusterListenerShutdownCh: make(chan struct{}),
clusterListenerShutdownSuccessCh: make(chan struct{}),
corsConfig: &CORSConfig{},
clusterPeerClusterAddrsCache: cache.New(3*heartbeatInterval, time.Second),
enableMlock: !conf.DisableMlock,
}
// Load CORS config and provide core
c.corsConfig = &CORSConfig{core: c}
// Wrap the physical backend in a cache layer if enabled and not already wrapped
if _, isCache := conf.Physical.(*physical.Cache); !conf.DisableCache && !isCache {
c.physical = physical.NewCache(conf.Physical, conf.CacheSize, conf.Logger)
@ -513,7 +515,8 @@ func NewCore(conf *CoreConfig) (*Core, error) {
}
logicalBackends["cubbyhole"] = CubbyholeBackendFactory
logicalBackends["system"] = func(config *logical.BackendConfig) (logical.Backend, error) {
return NewSystemBackend(c, config)
b := NewSystemBackend(c)
return b.Backend.Setup(config)
}
c.logicalBackends = logicalBackends
@ -1368,9 +1371,6 @@ 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.saveCORSConfig(); err != nil {
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))
}

View File

@ -4,24 +4,36 @@ import (
"errors"
"fmt"
"sync"
"sync/atomic"
"github.com/hashicorp/vault/helper/strutil"
"github.com/hashicorp/vault/logical"
)
var errCORSNotConfigured = errors.New("CORS is not configured")
const (
CORSDisabled uint32 = iota
CORSEnabled
)
// CORSConfig stores the state of the CORS configuration.
type CORSConfig struct {
sync.RWMutex
Enabled bool `json:"enabled"`
AllowedOrigins []string `json:"allowed_origins"`
sync.RWMutex `json:"-"`
core *Core
Enabled uint32 `json:"enabled"`
AllowedOrigins []string `json:"allowed_origins,omitempty"`
}
func (c *Core) saveCORSConfig() error {
view := c.systemBarrierView.SubView("config/")
entry, err := logical.StorageEntryJSON("cors", c.corsConfig)
localConfig := &CORSConfig{
Enabled: atomic.LoadUint32(&c.corsConfig.Enabled),
}
c.corsConfig.RLock()
localConfig.AllowedOrigins = c.corsConfig.AllowedOrigins
c.corsConfig.RUnlock()
entry, err := logical.StorageEntryJSON("cors", localConfig)
if err != nil {
return fmt.Errorf("failed to create CORS config entry: %v", err)
}
@ -33,6 +45,7 @@ func (c *Core) saveCORSConfig() error {
return nil
}
// This should only be called with the core state lock held for writing
func (c *Core) loadCORSConfig() error {
view := c.systemBarrierView.SubView("config/")
@ -45,10 +58,14 @@ func (c *Core) loadCORSConfig() error {
return nil
}
err = out.DecodeJSON(c.corsConfig)
newConfig := new(CORSConfig)
err = out.DecodeJSON(newConfig)
if err != nil {
return err
}
newConfig.core = c
c.corsConfig = newConfig
return nil
}
@ -65,38 +82,40 @@ func (c *CORSConfig) Enable(urls []string) error {
}
c.Lock()
defer c.Unlock()
c.AllowedOrigins = urls
c.Enabled = true
c.Unlock()
return nil
atomic.StoreUint32(&c.Enabled, CORSEnabled)
return c.core.saveCORSConfig()
}
// IsEnabled returns the value of CORSConfig.isEnabled
func (c *CORSConfig) IsEnabled() bool {
c.RLock()
defer c.RUnlock()
return c.Enabled
return atomic.LoadUint32(&c.Enabled) == CORSEnabled
}
// Disable sets CORS to disabled and clears the allowed origins
func (c *CORSConfig) Disable() {
func (c *CORSConfig) Disable() error {
atomic.StoreUint32(&c.Enabled, CORSDisabled)
c.Lock()
defer c.Unlock()
c.Enabled = false
c.AllowedOrigins = []string{}
c.AllowedOrigins = []string(nil)
c.Unlock()
return c.core.saveCORSConfig()
}
// 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 we aren't enabling CORS then all origins are valid
if !c.IsEnabled() {
return true
}
c.RLock()
defer c.RUnlock()
if c.AllowedOrigins == nil {
if len(c.AllowedOrigins) == 0 {
return false
}

View File

@ -44,7 +44,7 @@ var (
}
)
func NewSystemBackend(core *Core, config *logical.BackendConfig) (logical.Backend, error) {
func NewSystemBackend(core *Core) *SystemBackend {
b := &SystemBackend{
Core: core,
}
@ -62,7 +62,7 @@ func NewSystemBackend(core *Core, config *logical.BackendConfig) (logical.Backen
"replication/primary/secondary-token",
"replication/reindex",
"rotate",
"config/*",
"config/cors",
"config/auditing/*",
"plugins/catalog/*",
"revoke-prefix/*",
@ -110,7 +110,7 @@ func NewSystemBackend(core *Core, config *logical.BackendConfig) (logical.Backen
},
"allowed_origins": &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: "A comma-separated list of origins that may make cross-origin requests.",
Description: "A comma-separated string or array of strings indicating origins that may make cross-origin requests.",
},
},
@ -823,50 +823,50 @@ func NewSystemBackend(core *Core, config *logical.BackendConfig) (logical.Backen
b.Backend.Invalidate = b.invalidate
return b.Backend.Setup(config)
return b
}
// SystemBackend implements logical.Backend and is used to interact with
// the core of the system. This backend is hardcoded to exist at the "sys"
// prefix. Conceptually it is similar to procfs on Linux.
type SystemBackend struct {
*framework.Backend
Core *Core
Backend *framework.Backend
}
// handleCORSRead returns the current CORS configuration
func (b *SystemBackend) handleCORSRead(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
corsConf := b.Core.corsConfig
if corsConf == nil {
return nil, errCORSNotConfigured
enabled := corsConf.IsEnabled()
resp := &logical.Response{
Data: map[string]interface{}{
"enabled": enabled,
},
}
return &logical.Response{
Data: map[string]interface{}{
"enabled": corsConf.Enabled,
"allowed_origins": strings.Join(corsConf.AllowedOrigins, ","),
},
}, nil
if enabled {
corsConf.RLock()
resp.Data["allowed_origins"] = corsConf.AllowedOrigins
corsConf.RUnlock()
}
return resp, nil
}
// handleCORSUpdate sets the list of origins that are allowed
// to make cross-origin requests and sets the CORS enabled flag to true
// 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) handleCORSUpdate(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
origins := d.Get("allowed_origins").([]string)
err := b.Core.corsConfig.Enable(origins)
if err != nil {
return nil, err
}
return nil, nil
return nil, b.Core.corsConfig.Enable(origins)
}
// handleCORSDelete clears the allowed origins and sets the CORS enabled flag to false
// 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()
return nil, nil
return nil, b.Core.corsConfig.Disable()
}
func (b *SystemBackend) handleTidyLeases(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {

View File

@ -49,6 +49,9 @@ func TestSystemBackend_RootPaths(t *testing.T) {
func TestSystemConfigCORS(t *testing.T) {
b := testSystemBackend(t)
_, barrier, _ := mockBarrier(t)
view := NewBarrierView(barrier, "")
b.(*SystemBackend).Core.systemBarrierView = view
req := logical.TestRequest(t, logical.UpdateOperation, "config/cors")
req.Data["allowed_origins"] = "http://www.example.com"
@ -60,7 +63,7 @@ func TestSystemConfigCORS(t *testing.T) {
expected := &logical.Response{
Data: map[string]interface{}{
"enabled": true,
"allowed_origins": "http://www.example.com",
"allowed_origins": []string{"http://www.example.com"},
},
}
@ -71,7 +74,7 @@ func TestSystemConfigCORS(t *testing.T) {
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("UPDATE FAILED -- bad: %#v", actual)
t.Fatalf("bad: %#v", actual)
}
req = logical.TestRequest(t, logical.DeleteOperation, "config/cors")
@ -89,7 +92,6 @@ func TestSystemConfigCORS(t *testing.T) {
expected = &logical.Response{
Data: map[string]interface{}{
"enabled": false,
"allowed_origins": "",
},
}
@ -980,7 +982,8 @@ func TestSystemBackend_revokePrefixAuth(t *testing.T) {
MaxLeaseTTLVal: time.Hour * 24 * 32,
},
}
b, err := NewSystemBackend(core, bc)
be := NewSystemBackend(core)
b, err := be.Backend.Setup(bc)
if err != nil {
t.Fatal(err)
}
@ -1043,7 +1046,8 @@ func TestSystemBackend_revokePrefixAuth_origUrl(t *testing.T) {
MaxLeaseTTLVal: time.Hour * 24 * 32,
},
}
b, err := NewSystemBackend(core, bc)
be := NewSystemBackend(core)
b, err := be.Backend.Setup(bc)
if err != nil {
t.Fatal(err)
}
@ -1578,7 +1582,8 @@ func testSystemBackend(t *testing.T) logical.Backend {
},
}
b, err := NewSystemBackend(c, bc)
b := NewSystemBackend(c)
_, err := b.Backend.Setup(bc)
if err != nil {
t.Fatal(err)
}
@ -1596,7 +1601,8 @@ func testCoreSystemBackend(t *testing.T) (*Core, logical.Backend, string) {
},
}
b, err := NewSystemBackend(c, bc)
b := NewSystemBackend(c)
_, err := b.Backend.Setup(bc)
if err != nil {
t.Fatal(err)
}

View File

@ -0,0 +1,87 @@
---
layout: "api"
page_title: "/sys/config/cors - HTTP API"
sidebar_current: "docs-http-system-config-cors"
description: |-
The '/sys/config/cors' endpoint configures how the Vault server responds to cross-origin requests.
---
# `/sys/config/cors`
The `/sys/config/cors` endpoint is used to configure CORS settings.
- **`sudo` required**  All CORS endpoints require `sudo` capability in
addition to any path-specific capabilities.
## Read CORS Settings
This endpoint returns the current CORS configuration.
| Method | Path | Produces |
| :------- | :--------------------------- | :--------------------- |
| `GET` | `/sys/config/cors` | `200 application/json` |
### Sample Request
```
$ curl \
--header "X-Vault-Token: ..." \
https://vault.rocks/v1/sys/config/cors
```
### Sample Response
```json
{
"enabled": true,
"allowed_origins": "http://www.example.com"
}
```
## Configure CORS Settings
This endpoint allows configuring the origins that are permitted to make
cross-origin requests.
| Method | Path | Produces |
| :------- | :--------------------------- | :--------------------- |
| `PUT` | `/sys/config/cors` | `204 (empty body)` |
### Parameters
- `allowed_origins` `(string or string array: "" or [])`  A wildcard (`*`), comma-delimited string, or array of strings specifying the origins that are permitted to make cross-origin requests.
### Sample Payload
```json
{
"allowed_origins": "*"
}
```
### Sample Request
```
$ curl \
--header "X-Vault-Token: ..." \
--request PUT \
--data @payload.json \
https://vault.rocks/v1/sys/config/cors
```
## Delete CORS Settings
This endpoint removes any CORS configuration.
| Method | Path | Produces |
| :------- | :--------------------------- | :--------------------- |
| `DELETE` | `/sys/config/cors` | `204 (empty body)` |
### Sample Request
```
$ curl \
--header "X-Vault-Token: ..." \
--request DELETE \
https://vault.rocks/v1/sys/config/cors
```

View File

@ -1,109 +0,0 @@
---
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
<dl>
<dt>Description</dt>
<dd>
Returns the current CORS configuration.
</dd>
<dt>Method</dt>
<dd>GET</dd>
<dt>URL</dt>
<dd>`/sys/config/cors`</dd>
<dt>Parameters</dt>
<dd>
None
</dd>
<dt>Returns</dt>
<dd>
```javascript
{
"enabled": true,
"allowed_origins": "http://www.example.com"
}
```
Sample response when CORS is disabled.
```javascript
{
"enabled": false,
"allowed_origins": ""
}
```
</dd>
</dl>
## PUT
<dl>
<dt>Description</dt>
<dd>
Configures the Vault server to return CORS headers for origins that are
permitted to make cross-origin requests based on the `allowed_origins`
parameter.
</dd>
<dt>Method</dt>
<dd>PUT</dd>
<dt>URL</dt>
<dd>`/sys/config/cors`</dd>
<dt>Parameters</dt>
<dd>
<ul>
<li>
<span class="param">allowed_origins</span>
<span class="param-flags">required</span>
Valid values are either a wildcard (*) or a comma-separated list of
exact origins that are permitted to make cross-origin requests.
</li>
</ul>
</dd>
<dt>Returns</dt>
<dd>`204` response code.
</dd>
</dl>
## DELETE
<dl>
<dt>Description</dt>
<dd>
Disables the CORS functionality of the Vault server.
</dd>
<dt>Method</dt>
<dd>DELETE</dd>
<dt>URL</dt>
<dd>`/sys/config/cors`</dd>
<dt>Parameters</dt>
<dd>
None
</dd>
<dt>Returns</dt>
<dd>`204` response code.
</dd>
</dl>

View File

@ -108,6 +108,9 @@
<li<%= sidebar_current("docs-http-system-config-auditing") %>>
<a href="/api/system/config-auditing.html"><tt>/sys/config/auditing</tt></a>
</li>
<li<%= sidebar_current("docs-http-system-config-cors") %>>
<a href="/api/system/config-cors.html"><tt>/sys/config/cors</tt></a>
</li>
<li<%= sidebar_current("docs-http-system-generate-root") %>>
<a href="/api/system/generate-root.html"><tt>/sys/generate-root</tt></a>
</li>