2015-03-15 21:26:48 +00:00
|
|
|
package vault
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2015-03-16 00:07:54 +00:00
|
|
|
"strings"
|
2015-03-15 21:26:48 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/hashicorp/vault/logical"
|
2015-03-16 00:07:54 +00:00
|
|
|
"github.com/hashicorp/vault/logical/framework"
|
2015-03-15 21:26:48 +00:00
|
|
|
)
|
|
|
|
|
2015-03-15 21:53:41 +00:00
|
|
|
// logical.Factory
|
2015-07-01 00:30:43 +00:00
|
|
|
func PassthroughBackendFactory(*logical.BackendConfig) (logical.Backend, error) {
|
2015-03-16 00:07:54 +00:00
|
|
|
var b PassthroughBackend
|
2015-03-19 22:11:42 +00:00
|
|
|
b.Backend = &framework.Backend{
|
2015-04-04 03:45:00 +00:00
|
|
|
Help: strings.TrimSpace(passthroughHelp),
|
|
|
|
|
2015-03-16 00:07:54 +00:00
|
|
|
Paths: []*framework.Path{
|
|
|
|
&framework.Path{
|
|
|
|
Pattern: ".*",
|
|
|
|
Fields: map[string]*framework.FieldSchema{
|
2015-08-20 23:41:25 +00:00
|
|
|
//TODO: Deprecated in 0.3; remove in 0.4
|
2015-03-16 00:07:54 +00:00
|
|
|
"lease": &framework.FieldSchema{
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: "Lease time for this key when read. Ex: 1h",
|
|
|
|
},
|
2015-08-20 23:41:25 +00:00
|
|
|
"ttl": &framework.FieldSchema{
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: "TTL time for this key when read. Ex: 1h",
|
|
|
|
},
|
2015-03-16 00:07:54 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
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),
|
|
|
|
},
|
|
|
|
},
|
2015-03-19 19:20:25 +00:00
|
|
|
|
|
|
|
Secrets: []*framework.Secret{
|
|
|
|
&framework.Secret{
|
|
|
|
Type: "generic",
|
|
|
|
|
|
|
|
Renew: b.handleRead,
|
|
|
|
Revoke: b.handleRevoke,
|
|
|
|
},
|
|
|
|
},
|
2015-03-19 22:11:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return b, nil
|
2015-03-15 21:53:41 +00:00
|
|
|
}
|
|
|
|
|
2015-03-15 21:26:48 +00:00
|
|
|
// PassthroughBackend is used storing secrets directly into the physical
|
2015-08-20 23:41:25 +00:00
|
|
|
// backend. The secrest are encrypted in the durable storage and custom TTL
|
2015-03-15 21:26:48 +00:00
|
|
|
// information can be specified, but otherwise this backend doesn't do anything
|
|
|
|
// fancy.
|
2015-03-19 22:11:42 +00:00
|
|
|
type PassthroughBackend struct {
|
|
|
|
*framework.Backend
|
|
|
|
}
|
2015-03-15 21:26:48 +00:00
|
|
|
|
2015-03-16 23:26:34 +00:00
|
|
|
func (b *PassthroughBackend) handleRevoke(
|
2015-03-19 22:11:42 +00:00
|
|
|
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
2015-03-16 23:26:34 +00:00
|
|
|
// This is a no-op
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2015-03-16 00:07:54 +00:00
|
|
|
func (b *PassthroughBackend) handleRead(
|
2015-03-19 22:11:42 +00:00
|
|
|
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
2015-03-15 21:26:48 +00:00
|
|
|
// 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
|
2015-03-19 14:05:22 +00:00
|
|
|
var rawData map[string]interface{}
|
|
|
|
if err := json.Unmarshal(out.Value, &rawData); err != nil {
|
2015-03-15 21:26:48 +00:00
|
|
|
return nil, fmt.Errorf("json decoding failed: %v", err)
|
|
|
|
}
|
|
|
|
|
2015-03-19 19:20:25 +00:00
|
|
|
// Generate the response
|
2015-03-20 16:59:48 +00:00
|
|
|
resp := b.Secret("generic").Response(rawData, nil)
|
2015-03-19 22:28:49 +00:00
|
|
|
resp.Secret.Renewable = false
|
2015-03-19 19:20:25 +00:00
|
|
|
|
2015-03-15 21:26:48 +00:00
|
|
|
// Check if there is a lease key
|
2015-03-19 14:05:22 +00:00
|
|
|
leaseVal, ok := rawData["lease"].(string)
|
2015-03-15 21:26:48 +00:00
|
|
|
if ok {
|
|
|
|
leaseDuration, err := time.ParseDuration(leaseVal)
|
|
|
|
if err == nil {
|
2015-04-14 00:21:31 +00:00
|
|
|
resp.Secret.Renewable = true
|
2015-03-19 22:11:42 +00:00
|
|
|
resp.Secret.Lease = leaseDuration
|
2015-08-20 23:41:25 +00:00
|
|
|
resp.Secret.TTL = leaseDuration
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if there is a ttl key
|
|
|
|
ttlVal, ok := rawData["ttl"].(string)
|
|
|
|
if ok {
|
|
|
|
ttlDuration, err := time.ParseDuration(ttlVal)
|
|
|
|
if err == nil {
|
|
|
|
resp.Secret.Renewable = true
|
|
|
|
resp.Secret.TTL = ttlDuration
|
2015-03-15 21:26:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
2015-03-16 00:07:54 +00:00
|
|
|
func (b *PassthroughBackend) handleWrite(
|
2015-03-19 22:11:42 +00:00
|
|
|
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
2015-03-15 21:26:48 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2015-03-16 00:07:54 +00:00
|
|
|
func (b *PassthroughBackend) handleDelete(
|
2015-03-19 22:11:42 +00:00
|
|
|
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
2015-03-15 21:26:48 +00:00
|
|
|
// Delete the key at the request path
|
|
|
|
if err := req.Storage.Delete(req.Path); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2015-03-16 00:07:54 +00:00
|
|
|
func (b *PassthroughBackend) handleList(
|
2015-03-19 22:11:42 +00:00
|
|
|
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
2015-03-15 21:26:48 +00:00
|
|
|
// 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
|
|
|
|
}
|
2015-03-16 00:07:54 +00:00
|
|
|
|
2015-04-04 03:45:00 +00:00
|
|
|
const passthroughHelp = `
|
|
|
|
The generic backend reads and writes arbitrary secrets to the backend.
|
|
|
|
The secrets are encrypted/decrypted by Vault: they are never stored
|
|
|
|
unencrypted in the backend and the backend never has an opportunity to
|
|
|
|
see the unencrypted value.
|
|
|
|
|
2015-08-20 23:41:25 +00:00
|
|
|
TTLs can be set on a per-secret basis. These TTLs will be sent down
|
2015-04-04 03:45:00 +00:00
|
|
|
when that secret is read, and it is assumed that some outside process will
|
|
|
|
revoke and/or replace the secret at that path.
|
|
|
|
`
|
|
|
|
|
2015-03-16 00:07:54 +00:00
|
|
|
const passthroughHelpSynopsis = `
|
2015-05-27 21:33:58 +00:00
|
|
|
Pass-through secret storage to the storage backend, allowing you to
|
2015-03-16 00:07:54 +00:00
|
|
|
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.
|
|
|
|
|
2015-08-20 23:41:25 +00:00
|
|
|
A TTL can be specified when writing with the "ttl" field. If given, then
|
|
|
|
when the secret is read, the returned lease's duration will be set to
|
|
|
|
that value. 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.
|
2015-03-16 00:07:54 +00:00
|
|
|
`
|