open-vault/vault/ui.go

223 lines
5.2 KiB
Go
Raw Normal View History

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package vault
import (
"bytes"
"context"
"encoding/json"
"net/http"
"strings"
"sync"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/sdk/physical"
)
const (
uiConfigKey = "config"
uiConfigPlaintextKey = "config_plaintext"
)
// UIConfig contains UI configuration. This takes both a physical view and a barrier view
// because it is stored in both plaintext and encrypted to allow for getting the header
// values before the barrier is unsealed
type UIConfig struct {
l sync.RWMutex
physicalStorage physical.Backend
barrierStorage logical.Storage
enabled bool
defaultHeaders http.Header
}
// NewUIConfig creates a new UI config
func NewUIConfig(enabled bool, physicalStorage physical.Backend, barrierStorage logical.Storage) *UIConfig {
defaultHeaders := http.Header{}
UI - raft config and snapshotting (#7410) * add storage route * template out the routes and new raft storage overview * fetch raft config and add new server model * pngcrush the favicon * add view components and binary-file component * add form-save-buttons component * adjust rawRequest so that it can send a request body and returns the response on errors * hook up restore * rename binary-file to file-to-array-buffer * add ember-service-worker * use forked version of ember-service-worker for now * scope the service worker to a single endpoint * show both download buttons for now * add service worker download with a fallback to JS in-mem download * add remove peer functionality * lint go file * add storage-type to the cluster and node models * update edit for to take a cancel action * separate out a css table styles to be used by http-requests-table and on the raft-overview component * add raft-join adapter, model, component and use on the init page * fix styling and gate the menu item on the cluster using raft storage * style tweaks to the raft-join component * fix linting * add form-save-buttons component to storybook * add cancel functionality for backup uploads, and add a success message for successful uploads * add component tests * add filesize.js * add filesize and modified date to file-to-array-buffer * fix linting * fix server section showing in the cluster nav * don't use babel transforms in service worker lib because we don't want 2 copies of babel polyfill * add file-to-array-buffer to storybook * add comments and use removeObjectURL to raft-storage-overview * update alert-banner markdown * messaging change for upload alert banner * Update ui/app/templates/components/raft-storage-restore.hbs Co-Authored-By: Joshua Ogle <joshua@joshuaogle.com> * more comments * actually render the label if passed and update stories with knobs
2019-10-14 18:23:29 +00:00
defaultHeaders.Set("Service-Worker-Allowed", "/")
defaultHeaders.Set("X-Content-Type-Options", "nosniff")
defaultHeaders.Set("Content-Security-Policy", "default-src 'none'; connect-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'unsafe-inline' 'self'; form-action 'none'; frame-ancestors 'none'; font-src 'self'")
return &UIConfig{
physicalStorage: physicalStorage,
barrierStorage: barrierStorage,
enabled: enabled,
defaultHeaders: defaultHeaders,
}
}
// Enabled returns if the UI is enabled
func (c *UIConfig) Enabled() bool {
c.l.RLock()
defer c.l.RUnlock()
return c.enabled
}
// Headers returns the response headers that should be returned in the UI
func (c *UIConfig) Headers(ctx context.Context) (http.Header, error) {
c.l.RLock()
defer c.l.RUnlock()
config, err := c.get(ctx)
if err != nil {
return nil, err
}
headers := make(http.Header)
if config != nil {
headers = config.Headers
}
for k := range c.defaultHeaders {
if headers.Get(k) == "" {
v := c.defaultHeaders.Get(k)
headers.Set(k, v)
}
}
return headers, nil
}
// HeaderKeys returns the list of the configured headers
func (c *UIConfig) HeaderKeys(ctx context.Context) ([]string, error) {
c.l.RLock()
defer c.l.RUnlock()
config, err := c.get(ctx)
if err != nil {
return nil, err
}
if config == nil {
return nil, nil
}
var keys []string
for k := range config.Headers {
keys = append(keys, k)
}
return keys, nil
}
// GetHeader retrieves the configured values for the given header
func (c *UIConfig) GetHeader(ctx context.Context, header string) ([]string, error) {
c.l.RLock()
defer c.l.RUnlock()
config, err := c.get(ctx)
if err != nil {
return nil, err
}
if config == nil {
return nil, nil
}
value := config.Headers.Values(header)
return value, nil
}
// SetHeader sets the values for the given header
func (c *UIConfig) SetHeader(ctx context.Context, header string, values []string) error {
c.l.Lock()
defer c.l.Unlock()
config, err := c.get(ctx)
if err != nil {
return err
}
if config == nil {
config = &uiConfigEntry{
Headers: http.Header{},
}
}
// Clear custom header values before setting new
config.Headers.Del(header)
// Set new values
for _, value := range values {
config.Headers.Add(header, value)
}
return c.save(ctx, config)
}
// DeleteHeader deletes the header configuration for the given header
func (c *UIConfig) DeleteHeader(ctx context.Context, header string) error {
c.l.Lock()
defer c.l.Unlock()
config, err := c.get(ctx)
if err != nil {
return err
}
if config == nil {
return nil
}
config.Headers.Del(header)
return c.save(ctx, config)
}
func (c *UIConfig) get(ctx context.Context) (*uiConfigEntry, error) {
// Read plaintext always to ensure in sync with barrier value
plaintextConfigRaw, err := c.physicalStorage.Get(ctx, uiConfigPlaintextKey)
if err != nil {
return nil, err
}
2022-03-15 20:17:55 +00:00
configRaw, uiConfigGetErr := c.barrierStorage.Get(ctx, uiConfigKey)
2022-03-15 20:17:55 +00:00
// Respond with error only if not sealed, otherwise do not throw the error
if uiConfigGetErr != nil && !strings.Contains(uiConfigGetErr.Error(), ErrBarrierSealed.Error()) {
return nil, uiConfigGetErr
}
if configRaw == nil {
return nil, nil
}
2022-03-15 20:17:55 +00:00
config := new(uiConfigEntry)
2022-03-15 20:17:55 +00:00
if config == nil {
return nil, nil
}
if err := json.Unmarshal(configRaw.Value, config); err != nil {
return nil, err
}
// Check that plaintext value matches barrier value, if not sync values
if uiConfigGetErr == nil && (plaintextConfigRaw == nil ||
!bytes.Equal(plaintextConfigRaw.Value, configRaw.Value)) {
if err := c.save(ctx, config); err != nil {
return nil, err
2022-03-15 20:17:55 +00:00
}
}
return config, nil
}
func (c *UIConfig) save(ctx context.Context, config *uiConfigEntry) error {
if len(config.Headers) == 0 {
if err := c.physicalStorage.Delete(ctx, uiConfigPlaintextKey); err != nil {
return err
}
return c.barrierStorage.Delete(ctx, uiConfigKey)
}
configRaw, err := json.Marshal(config)
if err != nil {
return err
}
entry := &physical.Entry{
Key: uiConfigPlaintextKey,
Value: configRaw,
}
if err := c.physicalStorage.Put(ctx, entry); err != nil {
return err
}
barrEntry := &logical.StorageEntry{
Key: uiConfigKey,
Value: configRaw,
}
return c.barrierStorage.Put(ctx, barrEntry)
}
type uiConfigEntry struct {
Headers http.Header `json:"headers"`
}