open-vault/logical/framework/backend.go

288 lines
7.0 KiB
Go
Raw Normal View History

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"
"regexp"
"sync"
"time"
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 {
// 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-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
// 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
// back. It is called with the data from the entry.
//
// 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
RollbackMinAge time.Duration
2015-03-18 00:15:23 +00:00
once sync.Once
pathsRe []*regexp.Regexp
2015-03-14 04:11:19 +00:00
}
// OperationFunc is the callback called for an operation on a path.
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 22:11:42 +00:00
type OperationFunc func(*logical.Request, *FieldData) (*logical.Response, error)
// RollbackFunc is the callback for rollbacks.
type RollbackFunc func(*logical.Request, string, interface{}) error
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
}
// Look up the callback for this operation
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 != "" {
callback = path.helpCallback
ok = true
}
}
if !ok {
2015-03-15 21:57:19 +00:00
return nil, logical.ErrUnsupportedOperation
}
2015-03-14 06:58:20 +00:00
// Call the callback with the request and the data
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 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
}
// Route looks up the path that would be used for a given path string.
func (b *Backend) Route(path string) *Path {
result, _ := b.route(path)
return result
}
// 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
}
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)
}
}
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) {
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 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
}
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 22:11:42 +00:00
rawSecretType, ok := req.Secret.InternalData["secret_type"]
2015-03-19 18:41:41 +00:00
if !ok {
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 22:11:42 +00:00
return nil, fmt.Errorf("secret is unsupported by this backend")
2015-03-19 18:41:41 +00:00
}
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 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:
return secret.HandleRenew(req)
2015-03-19 19:20:25 +00:00
case logical.RevokeOperation:
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 {
return logical.ErrorResponse(err.Error()), nil
}
if len(keys) == 0 {
return nil, nil
2015-03-18 00:15:23 +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)
if _, ok := req.Data["immediate"]; ok {
minAge = time.Now().UTC().Add(1000 * time.Hour)
}
2015-03-18 00:15:23 +00:00
for _, k := range keys {
entry, err := GetWAL(req.Storage, k)
2015-03-18 00:15:23 +00:00
if err != nil {
merr = multierror.Append(merr, err)
continue
}
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
// Attempt a rollback
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 {
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
case TypeMap:
return map[string]interface{}{}
2015-03-14 04:11:19 +00:00
default:
panic("unknown type: " + t.String())
}
}