2015-03-15 23:39:49 +00:00
|
|
|
package framework
|
2015-03-14 04:11:19 +00:00
|
|
|
|
|
|
|
import (
|
2015-03-19 18:41:41 +00:00
|
|
|
"fmt"
|
2015-03-14 06:17:25 +00:00
|
|
|
"regexp"
|
|
|
|
"sync"
|
2015-03-18 00:58:05 +00:00
|
|
|
"time"
|
2015-03-14 06:17:25 +00:00
|
|
|
|
2015-03-18 00:15:23 +00:00
|
|
|
"github.com/hashicorp/go-multierror"
|
2015-03-15 21:57:19 +00:00
|
|
|
"github.com/hashicorp/vault/logical"
|
2015-03-14 04:11:19 +00:00
|
|
|
)
|
|
|
|
|
2015-03-15 21:57:19 +00:00
|
|
|
// Backend is an implementation of logical.Backend that allows
|
2015-03-14 04:11:19 +00:00
|
|
|
// the implementer to code a backend using a much more programmer-friendly
|
|
|
|
// framework that handles a lot of the routing and validation for you.
|
|
|
|
//
|
2015-03-15 21:57:19 +00:00
|
|
|
// This is recommended over implementing logical.Backend directly.
|
2015-03-14 04:11:19 +00:00
|
|
|
type Backend struct {
|
2015-03-14 06:17:25 +00:00
|
|
|
// Paths are the various routes that the backend responds to.
|
|
|
|
// This cannot be modified after construction (i.e. dynamically changing
|
|
|
|
// paths, including adding or removing, is not allowed once the
|
|
|
|
// backend is in use).
|
2015-03-19 13:39:25 +00:00
|
|
|
//
|
2015-03-31 00:46:18 +00:00
|
|
|
// PathsSpecial is the list of path patterns that denote the
|
|
|
|
// paths above that require special privileges. These can't be
|
2015-03-16 00:35:59 +00:00
|
|
|
// regular expressions, it is either exact match or prefix match.
|
|
|
|
// For prefix match, append '*' as a suffix.
|
2015-03-31 00:46:18 +00:00
|
|
|
Paths []*Path
|
|
|
|
PathsSpecial *logical.Paths
|
2015-03-16 00:35:59 +00:00
|
|
|
|
2015-03-19 14:07:45 +00:00
|
|
|
// Secrets is the list of secret types that this backend can
|
|
|
|
// return. It is used to automatically generate proper responses,
|
|
|
|
// and ease specifying callbacks for revocation, renewal, etc.
|
|
|
|
Secrets []*Secret
|
|
|
|
|
2015-03-18 00:15:23 +00:00
|
|
|
// Rollback is called when a WAL entry (see wal.go) has to be rolled
|
2015-03-21 10:08:13 +00:00
|
|
|
// back. It is called with the data from the entry.
|
2015-03-21 10:03:59 +00:00
|
|
|
//
|
|
|
|
// RollbackMinAge is the minimum age of a WAL entry before it is attempted
|
|
|
|
// to be rolled back. This should be longer than the maximum time it takes
|
|
|
|
// to successfully create a secret.
|
|
|
|
Rollback RollbackFunc
|
2015-03-18 00:58:05 +00:00
|
|
|
RollbackMinAge time.Duration
|
2015-03-18 00:15:23 +00:00
|
|
|
|
2015-03-14 06:25:17 +00:00
|
|
|
once sync.Once
|
|
|
|
pathsRe []*regexp.Regexp
|
2015-03-14 04:11:19 +00:00
|
|
|
}
|
|
|
|
|
2015-03-14 07:19:25 +00:00
|
|
|
// OperationFunc is the callback called for an operation on a path.
|
2015-03-19 22:11:42 +00:00
|
|
|
type OperationFunc func(*logical.Request, *FieldData) (*logical.Response, error)
|
2015-03-14 07:19:25 +00:00
|
|
|
|
2015-03-21 10:03:59 +00:00
|
|
|
// RollbackFunc is the callback for rollbacks.
|
2015-03-21 10:08:13 +00:00
|
|
|
type RollbackFunc func(*logical.Request, string, interface{}) error
|
2015-03-21 10:03:59 +00:00
|
|
|
|
2015-03-15 21:57:19 +00:00
|
|
|
// logical.Backend impl.
|
|
|
|
func (b *Backend) HandleRequest(req *logical.Request) (*logical.Response, error) {
|
2015-03-19 18:41:41 +00:00
|
|
|
// Check for special cased global operations. These don't route
|
|
|
|
// to a specific Path.
|
|
|
|
switch req.Operation {
|
2015-03-19 19:20:25 +00:00
|
|
|
case logical.RenewOperation:
|
|
|
|
fallthrough
|
2015-03-19 18:41:41 +00:00
|
|
|
case logical.RevokeOperation:
|
2015-03-19 19:20:25 +00:00
|
|
|
return b.handleRevokeRenew(req)
|
2015-03-19 18:41:41 +00:00
|
|
|
case logical.RollbackOperation:
|
2015-03-18 00:15:23 +00:00
|
|
|
return b.handleRollback(req)
|
|
|
|
}
|
|
|
|
|
2015-03-14 06:58:20 +00:00
|
|
|
// Find the matching route
|
|
|
|
path, captures := b.route(req.Path)
|
|
|
|
if path == nil {
|
2015-03-15 21:57:19 +00:00
|
|
|
return nil, logical.ErrUnsupportedPath
|
2015-03-14 06:58:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Build up the data for the route, with the URL taking priority
|
|
|
|
// for the fields over the PUT data.
|
|
|
|
raw := make(map[string]interface{}, len(path.Fields))
|
|
|
|
for k, v := range req.Data {
|
|
|
|
raw[k] = v
|
|
|
|
}
|
|
|
|
for k, v := range captures {
|
|
|
|
raw[k] = v
|
|
|
|
}
|
|
|
|
|
2015-03-14 07:19:25 +00:00
|
|
|
// Look up the callback for this operation
|
2015-03-14 17:12:50 +00:00
|
|
|
var callback OperationFunc
|
|
|
|
var ok bool
|
|
|
|
if path.Callbacks != nil {
|
|
|
|
callback, ok = path.Callbacks[req.Operation]
|
|
|
|
}
|
|
|
|
if !ok {
|
2015-03-15 21:57:19 +00:00
|
|
|
if req.Operation == logical.HelpOperation && path.HelpSynopsis != "" {
|
2015-03-14 17:12:50 +00:00
|
|
|
callback = path.helpCallback
|
|
|
|
ok = true
|
|
|
|
}
|
2015-03-14 07:19:25 +00:00
|
|
|
}
|
|
|
|
if !ok {
|
2015-03-15 21:57:19 +00:00
|
|
|
return nil, logical.ErrUnsupportedOperation
|
2015-03-14 07:19:25 +00:00
|
|
|
}
|
|
|
|
|
2015-03-14 06:58:20 +00:00
|
|
|
// Call the callback with the request and the data
|
2015-03-19 22:11:42 +00:00
|
|
|
return callback(req, &FieldData{
|
|
|
|
Raw: raw,
|
|
|
|
Schema: path.Fields,
|
2015-03-14 06:58:20 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2015-03-15 21:57:19 +00:00
|
|
|
// logical.Backend impl.
|
2015-03-31 00:46:18 +00:00
|
|
|
func (b *Backend) SpecialPaths() *logical.Paths {
|
|
|
|
return b.PathsSpecial
|
2015-03-14 06:58:20 +00:00
|
|
|
}
|
|
|
|
|
2015-03-14 06:48:49 +00:00
|
|
|
// Route looks up the path that would be used for a given path string.
|
2015-03-14 06:17:25 +00:00
|
|
|
func (b *Backend) Route(path string) *Path {
|
2015-03-14 06:48:49 +00:00
|
|
|
result, _ := b.route(path)
|
|
|
|
return result
|
2015-03-14 06:17:25 +00:00
|
|
|
}
|
|
|
|
|
2015-03-19 13:59:01 +00:00
|
|
|
// Secret is used to look up the secret with the given type.
|
|
|
|
func (b *Backend) Secret(k string) *Secret {
|
|
|
|
for _, s := range b.Secrets {
|
|
|
|
if s.Type == k {
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-03-14 06:25:17 +00:00
|
|
|
func (b *Backend) init() {
|
|
|
|
b.pathsRe = make([]*regexp.Regexp, len(b.Paths))
|
|
|
|
for i, p := range b.Paths {
|
|
|
|
b.pathsRe[i] = regexp.MustCompile(p.Pattern)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-14 06:48:49 +00:00
|
|
|
func (b *Backend) route(path string) (*Path, map[string]string) {
|
|
|
|
b.once.Do(b.init)
|
|
|
|
|
|
|
|
for i, re := range b.pathsRe {
|
|
|
|
matches := re.FindStringSubmatch(path)
|
|
|
|
if matches == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// We have a match, determine the mapping of the captures and
|
|
|
|
// store that for returning.
|
|
|
|
var captures map[string]string
|
|
|
|
path := b.Paths[i]
|
|
|
|
if captureNames := re.SubexpNames(); len(captureNames) > 1 {
|
|
|
|
captures = make(map[string]string, len(captureNames))
|
|
|
|
for i, name := range captureNames {
|
|
|
|
if name != "" {
|
|
|
|
captures[name] = matches[i]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return path, captures
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2015-03-19 19:20:25 +00:00
|
|
|
func (b *Backend) handleRevokeRenew(
|
2015-03-19 18:41:41 +00:00
|
|
|
req *logical.Request) (*logical.Response, error) {
|
2015-03-19 22:11:42 +00:00
|
|
|
if req.Secret == nil {
|
|
|
|
return nil, fmt.Errorf("request has no secret")
|
2015-03-19 18:41:41 +00:00
|
|
|
}
|
|
|
|
|
2015-03-19 22:11:42 +00:00
|
|
|
rawSecretType, ok := req.Secret.InternalData["secret_type"]
|
2015-03-19 18:41:41 +00:00
|
|
|
if !ok {
|
2015-03-19 22:11:42 +00:00
|
|
|
return nil, fmt.Errorf("secret is unsupported by this backend")
|
2015-03-19 18:41:41 +00:00
|
|
|
}
|
2015-03-19 22:11:42 +00:00
|
|
|
secretType, ok := rawSecretType.(string)
|
|
|
|
if !ok {
|
2015-03-19 18:41:41 +00:00
|
|
|
return nil, fmt.Errorf("secret is unsupported by this backend")
|
|
|
|
}
|
|
|
|
|
|
|
|
secret := b.Secret(secretType)
|
|
|
|
if secret == nil {
|
|
|
|
return nil, fmt.Errorf("secret is unsupported by this backend")
|
|
|
|
}
|
|
|
|
|
2015-03-19 19:20:25 +00:00
|
|
|
switch req.Operation {
|
|
|
|
case logical.RenewOperation:
|
2015-03-21 15:20:30 +00:00
|
|
|
return secret.HandleRenew(req)
|
2015-03-19 19:20:25 +00:00
|
|
|
case logical.RevokeOperation:
|
2015-03-21 15:20:30 +00:00
|
|
|
return secret.HandleRevoke(req)
|
2015-03-19 19:20:25 +00:00
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf(
|
|
|
|
"invalid operation for revoke/renew: %s", req.Operation)
|
|
|
|
}
|
2015-03-19 18:41:41 +00:00
|
|
|
}
|
|
|
|
|
2015-03-18 00:15:23 +00:00
|
|
|
func (b *Backend) handleRollback(
|
|
|
|
req *logical.Request) (*logical.Response, error) {
|
|
|
|
if b.Rollback == nil {
|
|
|
|
return nil, logical.ErrUnsupportedOperation
|
|
|
|
}
|
|
|
|
|
|
|
|
var merr error
|
|
|
|
keys, err := ListWAL(req.Storage)
|
|
|
|
if err != nil {
|
2015-03-18 00:58:05 +00:00
|
|
|
return logical.ErrorResponse(err.Error()), nil
|
|
|
|
}
|
|
|
|
if len(keys) == 0 {
|
|
|
|
return nil, nil
|
2015-03-18 00:15:23 +00:00
|
|
|
}
|
|
|
|
|
2015-03-18 00:58:05 +00:00
|
|
|
// Calculate the minimum time that the WAL entries could be
|
|
|
|
// created in order to be rolled back.
|
|
|
|
age := b.RollbackMinAge
|
|
|
|
if age == 0 {
|
|
|
|
age = 10 * time.Minute
|
|
|
|
}
|
|
|
|
minAge := time.Now().UTC().Add(-1 * age)
|
2015-03-21 10:18:33 +00:00
|
|
|
if _, ok := req.Data["immediate"]; ok {
|
|
|
|
minAge = time.Now().UTC().Add(1000 * time.Hour)
|
|
|
|
}
|
2015-03-18 00:58:05 +00:00
|
|
|
|
2015-03-18 00:15:23 +00:00
|
|
|
for _, k := range keys {
|
2015-03-18 00:58:05 +00:00
|
|
|
entry, err := GetWAL(req.Storage, k)
|
2015-03-18 00:15:23 +00:00
|
|
|
if err != nil {
|
|
|
|
merr = multierror.Append(merr, err)
|
|
|
|
continue
|
|
|
|
}
|
2015-03-18 00:58:05 +00:00
|
|
|
if entry == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the entry isn't old enough, then don't roll it back
|
|
|
|
if !time.Unix(entry.CreatedAt, 0).Before(minAge) {
|
|
|
|
continue
|
|
|
|
}
|
2015-03-18 00:15:23 +00:00
|
|
|
|
2015-03-18 00:58:05 +00:00
|
|
|
// Attempt a rollback
|
2015-03-21 10:08:13 +00:00
|
|
|
err = b.Rollback(req, entry.Kind, entry.Data)
|
|
|
|
if err != nil {
|
|
|
|
err = fmt.Errorf(
|
|
|
|
"Error rolling back '%s' entry: %s", entry.Kind, err)
|
|
|
|
}
|
|
|
|
if err == nil {
|
|
|
|
err = DeleteWAL(req.Storage, k)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
merr = multierror.Append(merr, err)
|
2015-03-18 00:15:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if merr == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return logical.ErrorResponse(merr.Error()), nil
|
|
|
|
}
|
|
|
|
|
2015-03-14 04:11:19 +00:00
|
|
|
// FieldSchema is a basic schema to describe the format of a path field.
|
|
|
|
type FieldSchema struct {
|
2015-03-14 17:12:50 +00:00
|
|
|
Type FieldType
|
|
|
|
Default interface{}
|
|
|
|
Description string
|
2015-03-14 04:15:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// DefaultOrZero returns the default value if it is set, or otherwise
|
|
|
|
// the zero value of the type.
|
|
|
|
func (s *FieldSchema) DefaultOrZero() interface{} {
|
|
|
|
if s.Default != nil {
|
|
|
|
return s.Default
|
|
|
|
}
|
|
|
|
|
|
|
|
return s.Type.Zero()
|
2015-03-14 04:11:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t FieldType) Zero() interface{} {
|
|
|
|
switch t {
|
|
|
|
case TypeString:
|
|
|
|
return ""
|
|
|
|
case TypeInt:
|
|
|
|
return 0
|
|
|
|
case TypeBool:
|
|
|
|
return false
|
2015-03-31 23:43:37 +00:00
|
|
|
case TypeMap:
|
|
|
|
return map[string]interface{}{}
|
2015-03-14 04:11:19 +00:00
|
|
|
default:
|
|
|
|
panic("unknown type: " + t.String())
|
|
|
|
}
|
|
|
|
}
|