c349e97168
/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.
163 lines
4.1 KiB
Go
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.
|
|
`
|