open-vault/vault/logical_raw.go

218 lines
5.9 KiB
Go
Raw Normal View History

Recovery Mode (#7559) * Initial work * rework * s/dr/recovery * Add sys/raw support to recovery mode (#7577) * Factor the raw paths out so they can be run with a SystemBackend. # Conflicts: # vault/logical_system.go * Add handleLogicalRecovery which is like handleLogical but is only sufficient for use with the sys-raw endpoint in recovery mode. No authentication is done yet. * Integrate with recovery-mode. We now handle unauthenticated sys/raw requests, albeit on path v1/raw instead v1/sys/raw. * Use sys/raw instead raw during recovery. * Don't bother persisting the recovery token. Authenticate sys/raw requests with it. * RecoveryMode: Support generate-root for autounseals (#7591) * Recovery: Abstract config creation and log settings * Recovery mode integration test. (#7600) * Recovery: Touch up (#7607) * Recovery: Touch up * revert the raw backend creation changes * Added recovery operation token prefix * Move RawBackend to its own file * Update API path and hit it using CLI flag on generate-root * Fix a panic triggered when handling a request that yields a nil response. (#7618) * Improve integ test to actually make changes while in recovery mode and verify they're still there after coming back in regular mode. * Refuse to allow a second recovery token to be generated. * Resize raft cluster to size 1 and start as leader (#7626) * RecoveryMode: Setup raft cluster post unseal (#7635) * Setup raft cluster post unseal in recovery mode * Remove marking as unsealed as its not needed * Address review comments * Accept only one seal config in recovery mode as there is no scope for migration
2019-10-15 04:55:31 +00:00
package vault
import (
"context"
"fmt"
2019-11-07 16:54:34 +00:00
"strings"
Recovery Mode (#7559) * Initial work * rework * s/dr/recovery * Add sys/raw support to recovery mode (#7577) * Factor the raw paths out so they can be run with a SystemBackend. # Conflicts: # vault/logical_system.go * Add handleLogicalRecovery which is like handleLogical but is only sufficient for use with the sys-raw endpoint in recovery mode. No authentication is done yet. * Integrate with recovery-mode. We now handle unauthenticated sys/raw requests, albeit on path v1/raw instead v1/sys/raw. * Use sys/raw instead raw during recovery. * Don't bother persisting the recovery token. Authenticate sys/raw requests with it. * RecoveryMode: Support generate-root for autounseals (#7591) * Recovery: Abstract config creation and log settings * Recovery mode integration test. (#7600) * Recovery: Touch up (#7607) * Recovery: Touch up * revert the raw backend creation changes * Added recovery operation token prefix * Move RawBackend to its own file * Update API path and hit it using CLI flag on generate-root * Fix a panic triggered when handling a request that yields a nil response. (#7618) * Improve integ test to actually make changes while in recovery mode and verify they're still there after coming back in regular mode. * Refuse to allow a second recovery token to be generated. * Resize raft cluster to size 1 and start as leader (#7626) * RecoveryMode: Setup raft cluster post unseal (#7635) * Setup raft cluster post unseal in recovery mode * Remove marking as unsealed as its not needed * Address review comments * Accept only one seal config in recovery mode as there is no scope for migration
2019-10-15 04:55:31 +00:00
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/compressutil"
"github.com/hashicorp/vault/sdk/logical"
)
var (
// protectedPaths cannot be accessed via the raw APIs.
// This is both for security and to prevent disrupting Vault.
protectedPaths = []string{
keyringPath,
// Changing the cluster info path can change the cluster ID which can be disruptive
coreLocalClusterInfoPath,
}
)
type RawBackend struct {
*framework.Backend
barrier SecurityBarrier
logger log.Logger
checkRaw func(path string) error
recoveryMode bool
}
func NewRawBackend(core *Core) *RawBackend {
r := &RawBackend{
barrier: core.barrier,
logger: core.logger.Named("raw"),
checkRaw: func(path string) error {
return nil
},
recoveryMode: core.recoveryMode,
}
r.Backend = &framework.Backend{
Paths: rawPaths("sys/", r),
}
return r
}
// handleRawRead is used to read directly from the barrier
func (b *RawBackend) handleRawRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
path := data.Get("path").(string)
if b.recoveryMode {
b.logger.Info("reading", "path", path)
}
// Prevent access of protected paths
for _, p := range protectedPaths {
if strings.HasPrefix(path, p) {
err := fmt.Sprintf("cannot read '%s'", path)
return logical.ErrorResponse(err), logical.ErrInvalidRequest
}
}
// Run additional checks if needed
if err := b.checkRaw(path); err != nil {
b.logger.Warn(err.Error(), "path", path)
return logical.ErrorResponse("cannot read '%s'", path), logical.ErrInvalidRequest
}
entry, err := b.barrier.Get(ctx, path)
if err != nil {
return handleErrorNoReadOnlyForward(err)
}
if entry == nil {
return nil, nil
}
// Run this through the decompression helper to see if it's been compressed.
// If the input contained the compression canary, `outputBytes` will hold
// the decompressed data. If the input was not compressed, then `outputBytes`
// will be nil.
outputBytes, _, err := compressutil.Decompress(entry.Value)
if err != nil {
return handleErrorNoReadOnlyForward(err)
}
// `outputBytes` is nil if the input is uncompressed. In that case set it to the original input.
if outputBytes == nil {
outputBytes = entry.Value
}
resp := &logical.Response{
Data: map[string]interface{}{
"value": string(outputBytes),
},
}
return resp, nil
}
// handleRawWrite is used to write directly to the barrier
func (b *RawBackend) handleRawWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
path := data.Get("path").(string)
if b.recoveryMode {
b.logger.Info("writing", "path", path)
}
// Prevent access of protected paths
for _, p := range protectedPaths {
if strings.HasPrefix(path, p) {
err := fmt.Sprintf("cannot write '%s'", path)
return logical.ErrorResponse(err), logical.ErrInvalidRequest
}
}
value := data.Get("value").(string)
entry := &logical.StorageEntry{
Key: path,
Value: []byte(value),
}
if err := b.barrier.Put(ctx, entry); err != nil {
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
}
return nil, nil
}
// handleRawDelete is used to delete directly from the barrier
func (b *RawBackend) handleRawDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
path := data.Get("path").(string)
if b.recoveryMode {
b.logger.Info("deleting", "path", path)
}
// Prevent access of protected paths
for _, p := range protectedPaths {
if strings.HasPrefix(path, p) {
err := fmt.Sprintf("cannot delete '%s'", path)
return logical.ErrorResponse(err), logical.ErrInvalidRequest
}
}
if err := b.barrier.Delete(ctx, path); err != nil {
return handleErrorNoReadOnlyForward(err)
}
return nil, nil
}
// handleRawList is used to list directly from the barrier
func (b *RawBackend) handleRawList(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
path := data.Get("path").(string)
if path != "" && !strings.HasSuffix(path, "/") {
path = path + "/"
}
if b.recoveryMode {
b.logger.Info("listing", "path", path)
}
// Prevent access of protected paths
for _, p := range protectedPaths {
if strings.HasPrefix(path, p) {
err := fmt.Sprintf("cannot list '%s'", path)
return logical.ErrorResponse(err), logical.ErrInvalidRequest
}
}
// Run additional checks if needed
if err := b.checkRaw(path); err != nil {
b.logger.Warn(err.Error(), "path", path)
return logical.ErrorResponse("cannot list '%s'", path), logical.ErrInvalidRequest
}
keys, err := b.barrier.List(ctx, path)
if err != nil {
return handleErrorNoReadOnlyForward(err)
}
return logical.ListResponse(keys), nil
}
func rawPaths(prefix string, r *RawBackend) []*framework.Path {
return []*framework.Path{
&framework.Path{
Pattern: prefix + "(raw/?$|raw/(?P<path>.+))",
Fields: map[string]*framework.FieldSchema{
"path": &framework.FieldSchema{
Type: framework.TypeString,
},
"value": &framework.FieldSchema{
Type: framework.TypeString,
},
},
Operations: map[logical.Operation]framework.OperationHandler{
logical.ReadOperation: &framework.PathOperation{
Callback: r.handleRawRead,
Summary: "Read the value of the key at the given path.",
},
logical.UpdateOperation: &framework.PathOperation{
Callback: r.handleRawWrite,
Summary: "Update the value of the key at the given path.",
},
logical.DeleteOperation: &framework.PathOperation{
Callback: r.handleRawDelete,
Summary: "Delete the key with given path.",
},
logical.ListOperation: &framework.PathOperation{
Callback: r.handleRawList,
Summary: "Return a list keys for a given path prefix.",
},
},
HelpSynopsis: strings.TrimSpace(sysHelp["raw"][0]),
HelpDescription: strings.TrimSpace(sysHelp["raw"][1]),
},
}
}