167 lines
3.9 KiB
Go
167 lines
3.9 KiB
Go
|
// Copyright (c) HashiCorp, Inc.
|
||
|
// SPDX-License-Identifier: MPL-2.0
|
||
|
|
||
|
package vault
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"sync"
|
||
|
"sync/atomic"
|
||
|
|
||
|
"github.com/hashicorp/go-secure-stdlib/strutil"
|
||
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
||
|
"github.com/hashicorp/vault/sdk/logical"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
CORSDisabled uint32 = iota
|
||
|
CORSEnabled
|
||
|
)
|
||
|
|
||
|
var StdAllowedHeaders = []string{
|
||
|
"Content-Type",
|
||
|
"X-Requested-With",
|
||
|
"X-Vault-AWS-IAM-Server-ID",
|
||
|
"X-Vault-MFA",
|
||
|
"X-Vault-No-Request-Forwarding",
|
||
|
"X-Vault-Wrap-Format",
|
||
|
"X-Vault-Wrap-TTL",
|
||
|
"X-Vault-Policy-Override",
|
||
|
"Authorization",
|
||
|
consts.AuthHeaderName,
|
||
|
}
|
||
|
|
||
|
// CORSConfig stores the state of the CORS configuration.
|
||
|
type CORSConfig struct {
|
||
|
sync.RWMutex `json:"-"`
|
||
|
core *Core
|
||
|
Enabled *uint32 `json:"enabled"`
|
||
|
AllowedOrigins []string `json:"allowed_origins,omitempty"`
|
||
|
AllowedHeaders []string `json:"allowed_headers,omitempty"`
|
||
|
}
|
||
|
|
||
|
func (c *Core) saveCORSConfig(ctx context.Context) error {
|
||
|
view := c.systemBarrierView.SubView("config/")
|
||
|
|
||
|
enabled := atomic.LoadUint32(c.corsConfig.Enabled)
|
||
|
localConfig := &CORSConfig{
|
||
|
Enabled: &enabled,
|
||
|
}
|
||
|
c.corsConfig.RLock()
|
||
|
localConfig.AllowedOrigins = c.corsConfig.AllowedOrigins
|
||
|
localConfig.AllowedHeaders = c.corsConfig.AllowedHeaders
|
||
|
c.corsConfig.RUnlock()
|
||
|
|
||
|
entry, err := logical.StorageEntryJSON("cors", localConfig)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to create CORS config entry: %w", err)
|
||
|
}
|
||
|
|
||
|
if err := view.Put(ctx, entry); err != nil {
|
||
|
return fmt.Errorf("failed to save CORS config: %w", err)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// This should only be called with the core state lock held for writing
|
||
|
func (c *Core) loadCORSConfig(ctx context.Context) error {
|
||
|
view := c.systemBarrierView.SubView("config/")
|
||
|
|
||
|
// Load the config in
|
||
|
out, err := view.Get(ctx, "cors")
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to read CORS config: %w", err)
|
||
|
}
|
||
|
if out == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
newConfig := new(CORSConfig)
|
||
|
err = out.DecodeJSON(newConfig)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if newConfig.Enabled == nil {
|
||
|
newConfig.Enabled = new(uint32)
|
||
|
}
|
||
|
|
||
|
newConfig.core = c
|
||
|
|
||
|
c.corsConfig = newConfig
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Enable takes either a '*' or a comma-separated list of URLs that can make
|
||
|
// cross-origin requests to Vault.
|
||
|
func (c *CORSConfig) Enable(ctx context.Context, urls []string, headers []string) error {
|
||
|
if len(urls) == 0 {
|
||
|
return errors.New("at least one origin or the wildcard must be provided")
|
||
|
}
|
||
|
|
||
|
if strutil.StrListContains(urls, "*") && len(urls) > 1 {
|
||
|
return errors.New("to allow all origins the '*' must be the only value for allowed_origins")
|
||
|
}
|
||
|
|
||
|
c.Lock()
|
||
|
c.AllowedOrigins = urls
|
||
|
|
||
|
// Start with the standard headers to Vault accepts.
|
||
|
c.AllowedHeaders = append([]string{}, StdAllowedHeaders...)
|
||
|
|
||
|
// Allow the user to add additional headers to the list of
|
||
|
// headers allowed on cross-origin requests.
|
||
|
if len(headers) > 0 {
|
||
|
c.AllowedHeaders = append(c.AllowedHeaders, headers...)
|
||
|
}
|
||
|
c.Unlock()
|
||
|
|
||
|
atomic.StoreUint32(c.Enabled, CORSEnabled)
|
||
|
|
||
|
return c.core.saveCORSConfig(ctx)
|
||
|
}
|
||
|
|
||
|
// IsEnabled returns the value of CORSConfig.isEnabled
|
||
|
func (c *CORSConfig) IsEnabled() bool {
|
||
|
return atomic.LoadUint32(c.Enabled) == CORSEnabled
|
||
|
}
|
||
|
|
||
|
// Disable sets CORS to disabled and clears the allowed origins & headers.
|
||
|
func (c *CORSConfig) Disable(ctx context.Context) error {
|
||
|
atomic.StoreUint32(c.Enabled, CORSDisabled)
|
||
|
c.Lock()
|
||
|
|
||
|
c.AllowedOrigins = nil
|
||
|
c.AllowedHeaders = nil
|
||
|
|
||
|
c.Unlock()
|
||
|
|
||
|
return c.core.saveCORSConfig(ctx)
|
||
|
}
|
||
|
|
||
|
// 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 len(c.AllowedOrigins) == 0 {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
if len(c.AllowedOrigins) == 1 && (c.AllowedOrigins)[0] == "*" {
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
return strutil.StrListContains(c.AllowedOrigins, origin)
|
||
|
}
|