open-vault/vault/logical_passthrough.go
Mitchell Hashimoto c349e97168 vault: clean up VaultID duplications, make secret responses clearer
/cc @armon - This is a reasonably major refactor that I think cleans up
a lot of the logic with secrets in responses. The reason for the
refactor is that while implementing Renew/Revoke in logical/framework I
found the existing API to be really awkward to work with.

Primarily, we needed a way to send down internal data for Vault core to
store since not all the data you need to revoke a key is always sent
down to the user (for example the user than AWS key belongs to).

At first, I was doing this manually in logical/framework with
req.Storage, but this is going to be such a common event that I think
its something core should assist with. Additionally, I think the added
context for secrets will be useful in the future when we have a Vault
API for returning orphaned out keys: we can also return the internal
data that might help an operator.

So this leads me to this refactor. I've removed most of the fields in
`logical.Response` and replaced it with a single `*Secret` pointer. If
this is non-nil, then the response represents a secret. The Secret
struct encapsulates all the lease info and such.

It also has some fields on it that are only populated at _request_ time
for Revoke/Renew operations. There is precedent for this sort of
behavior in the Go stdlib where http.Request/http.Response have fields
that differ based on client/server. I copied this style.

All core unit tests pass. The APIs fail for obvious reasons but I'll fix
that up in the next commit.
2015-03-19 23:11:42 +01:00

163 lines
4.1 KiB
Go

package vault
import (
"encoding/json"
"fmt"
"strings"
"time"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
)
// logical.Factory
func PassthroughBackendFactory(map[string]string) (logical.Backend, error) {
var b PassthroughBackend
b.Backend = &framework.Backend{
Paths: []*framework.Path{
&framework.Path{
Pattern: ".*",
Fields: map[string]*framework.FieldSchema{
"lease": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Lease time for this key when read. Ex: 1h",
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.handleRead,
logical.WriteOperation: b.handleWrite,
logical.DeleteOperation: b.handleDelete,
logical.ListOperation: b.handleList,
},
HelpSynopsis: strings.TrimSpace(passthroughHelpSynopsis),
HelpDescription: strings.TrimSpace(passthroughHelpDescription),
},
},
Secrets: []*framework.Secret{
&framework.Secret{
Type: "generic",
Renew: b.handleRead,
Revoke: b.handleRevoke,
},
},
}
return b, nil
}
// PassthroughBackend is used storing secrets directly into the physical
// backend. The secrest are encrypted in the durable storage and custom lease
// information can be specified, but otherwise this backend doesn't do anything
// fancy.
type PassthroughBackend struct {
*framework.Backend
}
func (b *PassthroughBackend) handleRevoke(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
// This is a no-op
return nil, nil
}
func (b *PassthroughBackend) handleRead(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
// Read the path
out, err := req.Storage.Get(req.Path)
if err != nil {
return nil, fmt.Errorf("read failed: %v", err)
}
// Fast-path the no data case
if out == nil {
return nil, nil
}
// Decode the data
var rawData map[string]interface{}
if err := json.Unmarshal(out.Value, &rawData); err != nil {
return nil, fmt.Errorf("json decoding failed: %v", err)
}
// Generate the response
resp := b.Secret("generic").Response(rawData)
// Check if there is a lease key
leaseVal, ok := rawData["lease"].(string)
if ok {
leaseDuration, err := time.ParseDuration(leaseVal)
if err == nil {
resp.Secret.Renewable = false
resp.Secret.Lease = leaseDuration
}
}
return resp, nil
}
func (b *PassthroughBackend) handleWrite(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
// Check that some fields are given
if len(req.Data) == 0 {
return nil, fmt.Errorf("missing data fields")
}
// JSON encode the data
buf, err := json.Marshal(req.Data)
if err != nil {
return nil, fmt.Errorf("json encoding failed: %v", err)
}
// Write out a new key
entry := &logical.StorageEntry{
Key: req.Path,
Value: buf,
}
if err := req.Storage.Put(entry); err != nil {
return nil, fmt.Errorf("failed to write: %v", err)
}
return nil, nil
}
func (b *PassthroughBackend) handleDelete(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
// Delete the key at the request path
if err := req.Storage.Delete(req.Path); err != nil {
return nil, err
}
return nil, nil
}
func (b *PassthroughBackend) handleList(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
// List the keys at the prefix given by the request
keys, err := req.Storage.List(req.Path)
if err != nil {
return nil, err
}
// Generate the response
return logical.ListResponse(keys), nil
}
const passthroughHelpSynopsis = `
Pass-through secret storage to the physical backend, allowing you to
read/write arbitrary data into secret storage.
`
const passthroughHelpDescription = `
The pass-through backend reads and writes arbitrary data into secret storage,
encrypting it along the way.
A lease can be specified when writing with the "lease" field. If given, then
when the secret is read, Vault will report a lease with that duration. It
is expected that the consumer of this backend properly writes renewed keys
before the lease is up. In addition, revocation must be handled by the
user of this backend.
`