2015-09-10 01:58:09 +00:00
package vault
import (
2018-01-08 18:31:38 +00:00
"context"
2015-09-10 01:58:09 +00:00
"encoding/json"
"fmt"
"strings"
2018-04-05 15:49:21 +00:00
"github.com/hashicorp/errwrap"
2019-04-13 07:44:06 +00:00
"github.com/hashicorp/vault/sdk/framework"
2019-04-12 21:54:35 +00:00
"github.com/hashicorp/vault/sdk/helper/jsonutil"
"github.com/hashicorp/vault/sdk/logical"
2015-09-10 01:58:09 +00:00
)
// CubbyholeBackendFactory constructs a new cubbyhole backend
2018-01-19 06:44:44 +00:00
func CubbyholeBackendFactory ( ctx context . Context , conf * logical . BackendConfig ) ( logical . Backend , error ) {
2018-09-18 03:03:00 +00:00
b := & CubbyholeBackend { }
2015-09-10 01:58:09 +00:00
b . Backend = & framework . Backend {
Help : strings . TrimSpace ( cubbyholeHelp ) ,
}
2018-09-18 03:03:00 +00:00
b . Backend . Paths = append ( b . Backend . Paths , b . paths ( ) ... )
2015-09-10 01:58:09 +00:00
if conf == nil {
2018-04-05 15:49:21 +00:00
return nil , fmt . Errorf ( "configuration passed into backend is nil" )
2015-09-10 01:58:09 +00:00
}
2018-01-19 06:44:44 +00:00
b . Backend . Setup ( ctx , conf )
2015-09-10 01:58:09 +00:00
2018-09-18 03:03:00 +00:00
return b , nil
2015-09-10 01:58:09 +00:00
}
// CubbyholeBackend is used for storing secrets directly into the physical
// backend. The secrets are encrypted in the durable storage.
2017-09-15 13:02:29 +00:00
// This differs from kv in that every token has its own private
2015-09-10 01:58:09 +00:00
// storage view. The view is removed when the token expires.
type CubbyholeBackend struct {
* framework . Backend
2015-09-15 17:49:53 +00:00
saltUUID string
storageView logical . Storage
2015-09-10 01:58:09 +00:00
}
2018-09-18 03:03:00 +00:00
func ( b * CubbyholeBackend ) paths ( ) [ ] * framework . Path {
return [ ] * framework . Path {
{
2018-11-06 18:09:06 +00:00
Pattern : framework . MatchAllRegex ( "path" ) ,
Fields : map [ string ] * framework . FieldSchema {
"path" : {
Type : framework . TypeString ,
Description : "Specifies the path of the secret." ,
} ,
} ,
Operations : map [ logical . Operation ] framework . OperationHandler {
logical . ReadOperation : & framework . PathOperation {
Callback : b . handleRead ,
Summary : "Retrieve the secret at the specified location." ,
} ,
logical . UpdateOperation : & framework . PathOperation {
Callback : b . handleWrite ,
Summary : "Store a secret at the specified location." ,
} ,
logical . CreateOperation : & framework . PathOperation {
Callback : b . handleWrite ,
} ,
logical . DeleteOperation : & framework . PathOperation {
Callback : b . handleDelete ,
Summary : "Deletes the secret at the specified location." ,
} ,
logical . ListOperation : & framework . PathOperation {
Callback : b . handleList ,
Summary : "List secret entries at the specified location." ,
Description : "Folders are suffixed with /. The input must be a folder; list on a file will not return a value. The values themselves are not accessible via this command." ,
} ,
2018-09-18 03:03:00 +00:00
} ,
ExistenceCheck : b . handleExistenceCheck ,
HelpSynopsis : strings . TrimSpace ( cubbyholeHelpSynopsis ) ,
HelpDescription : strings . TrimSpace ( cubbyholeHelpDescription ) ,
} ,
}
}
2018-01-19 06:44:44 +00:00
func ( b * CubbyholeBackend ) revoke ( ctx context . Context , saltedToken string ) error {
2015-09-10 01:58:09 +00:00
if saltedToken == "" {
2018-04-05 15:49:21 +00:00
return fmt . Errorf ( "client token empty during revocation" )
2015-09-10 01:58:09 +00:00
}
2018-01-19 06:44:44 +00:00
if err := logical . ClearView ( ctx , b . storageView . ( * BarrierView ) . SubView ( saltedToken + "/" ) ) ; err != nil {
2015-09-15 15:28:07 +00:00
return err
2015-09-10 01:58:09 +00:00
}
return nil
}
2018-01-08 18:31:38 +00:00
func ( b * CubbyholeBackend ) handleExistenceCheck ( ctx context . Context , req * logical . Request , data * framework . FieldData ) ( bool , error ) {
2018-01-19 06:44:44 +00:00
out , err := req . Storage . Get ( ctx , req . ClientToken + "/" + req . Path )
2016-01-17 00:35:11 +00:00
if err != nil {
2018-04-05 15:49:21 +00:00
return false , errwrap . Wrapf ( "existence check failed: {{err}}" , err )
2016-01-17 00:35:11 +00:00
}
return out != nil , nil
}
2018-01-08 18:31:38 +00:00
func ( b * CubbyholeBackend ) handleRead ( ctx context . Context , req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
2015-09-10 01:58:09 +00:00
if req . ClientToken == "" {
2018-04-05 15:49:21 +00:00
return nil , fmt . Errorf ( "client token empty" )
2015-09-10 01:58:09 +00:00
}
2018-11-06 18:09:06 +00:00
path := data . Get ( "path" ) . ( string )
2020-05-11 19:15:36 +00:00
if path == "" {
return nil , fmt . Errorf ( "missing path" )
}
2015-09-10 01:58:09 +00:00
// Read the path
2018-11-06 18:09:06 +00:00
out , err := req . Storage . Get ( ctx , req . ClientToken + "/" + path )
2015-09-10 01:58:09 +00:00
if err != nil {
2018-04-05 15:49:21 +00:00
return nil , errwrap . Wrapf ( "read failed: {{err}}" , err )
2015-09-10 01:58:09 +00:00
}
// Fast-path the no data case
if out == nil {
return nil , nil
}
// Decode the data
var rawData map [ string ] interface { }
2016-07-06 16:25:40 +00:00
if err := jsonutil . DecodeJSON ( out . Value , & rawData ) ; err != nil {
2018-04-05 15:49:21 +00:00
return nil , errwrap . Wrapf ( "json decoding failed: {{err}}" , err )
2015-09-10 01:58:09 +00:00
}
// Generate the response
resp := & logical . Response {
Data : rawData ,
}
return resp , nil
}
2018-01-08 18:31:38 +00:00
func ( b * CubbyholeBackend ) handleWrite ( ctx context . Context , req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
2015-09-10 01:58:09 +00:00
if req . ClientToken == "" {
2018-04-05 15:49:21 +00:00
return nil , fmt . Errorf ( "client token empty" )
2015-09-10 01:58:09 +00:00
}
// Check that some fields are given
if len ( req . Data ) == 0 {
return nil , fmt . Errorf ( "missing data fields" )
}
2018-11-06 18:09:06 +00:00
path := data . Get ( "path" ) . ( string )
2020-05-11 19:15:36 +00:00
if path == "" {
return nil , fmt . Errorf ( "missing path" )
}
2015-09-10 01:58:09 +00:00
// JSON encode the data
buf , err := json . Marshal ( req . Data )
if err != nil {
2018-04-05 15:49:21 +00:00
return nil , errwrap . Wrapf ( "json encoding failed: {{err}}" , err )
2015-09-10 01:58:09 +00:00
}
// Write out a new key
entry := & logical . StorageEntry {
2018-11-06 18:09:06 +00:00
Key : req . ClientToken + "/" + path ,
2015-09-10 01:58:09 +00:00
Value : buf ,
}
2017-11-09 15:32:49 +00:00
if req . WrapInfo != nil && req . WrapInfo . SealWrap {
entry . SealWrap = true
}
2018-01-19 06:44:44 +00:00
if err := req . Storage . Put ( ctx , entry ) ; err != nil {
2018-04-05 15:49:21 +00:00
return nil , errwrap . Wrapf ( "failed to write: {{err}}" , err )
2015-09-10 01:58:09 +00:00
}
return nil , nil
}
2018-01-08 18:31:38 +00:00
func ( b * CubbyholeBackend ) handleDelete ( ctx context . Context , req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
2015-09-10 01:58:09 +00:00
if req . ClientToken == "" {
2018-04-05 15:49:21 +00:00
return nil , fmt . Errorf ( "client token empty" )
2015-09-10 01:58:09 +00:00
}
2018-11-06 18:09:06 +00:00
path := data . Get ( "path" ) . ( string )
2015-09-10 01:58:09 +00:00
// Delete the key at the request path
2018-11-06 18:09:06 +00:00
if err := req . Storage . Delete ( ctx , req . ClientToken + "/" + path ) ; err != nil {
2015-09-10 01:58:09 +00:00
return nil , err
}
return nil , nil
}
2018-01-08 18:31:38 +00:00
func ( b * CubbyholeBackend ) handleList ( ctx context . Context , req * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
2015-09-10 01:58:09 +00:00
if req . ClientToken == "" {
2018-04-05 15:49:21 +00:00
return nil , fmt . Errorf ( "client token empty" )
2015-09-10 01:58:09 +00:00
}
2016-01-14 19:18:27 +00:00
2016-02-03 19:05:29 +00:00
// Right now we only handle directories, so ensure it ends with / We also
// check if it's empty so we don't end up doing a listing on '<client
// token>//'
2018-11-06 18:09:06 +00:00
path := data . Get ( "path" ) . ( string )
2016-02-03 19:05:29 +00:00
if path != "" && ! strings . HasSuffix ( path , "/" ) {
2016-01-19 22:05:01 +00:00
path = path + "/"
}
2015-09-10 01:58:09 +00:00
// List the keys at the prefix given by the request
2018-01-19 06:44:44 +00:00
keys , err := req . Storage . List ( ctx , req . ClientToken + "/" + path )
2015-09-10 01:58:09 +00:00
if err != nil {
return nil , err
}
2016-01-19 22:05:01 +00:00
// Strip the token
2016-01-14 19:18:27 +00:00
strippedKeys := make ( [ ] string , len ( keys ) )
for i , key := range keys {
2016-01-19 22:05:01 +00:00
strippedKeys [ i ] = strings . TrimPrefix ( key , req . ClientToken + "/" )
2015-09-10 01:58:09 +00:00
}
// Generate the response
return logical . ListResponse ( strippedKeys ) , nil
}
const cubbyholeHelp = `
The cubbyhole 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 .
2017-09-15 13:02:29 +00:00
This backend differs from the ' kv ' backend in that it is namespaced
2015-09-10 01:58:09 +00:00
per - token . Tokens can only read and write their own values , with no
sharing possible ( per - token cubbyholes ) . This can be useful for implementing
certain authentication workflows , as well as "scratch" areas for individual
clients . When the token is revoked , the entire set of stored values for that
token is also removed .
`
const cubbyholeHelpSynopsis = `
Pass - through secret storage to a token - specific cubbyhole in the storage
backend , allowing you to read / write arbitrary data into secret storage .
`
const cubbyholeHelpDescription = `
The cubbyhole backend reads and writes arbitrary data into secret storage ,
encrypting it along the way .
The view into the cubbyhole storage space is different for each token ; it is
a per - token cubbyhole . When the token is revoked all values are removed .
`