open-vault/vault/logical_system.go

1127 lines
30 KiB
Go
Raw Normal View History

package vault
import (
"fmt"
"strings"
2015-03-16 23:11:55 +00:00
"time"
"github.com/fatih/structs"
"github.com/hashicorp/vault/logical"
2015-03-16 00:35:59 +00:00
"github.com/hashicorp/vault/logical/framework"
"github.com/mitchellh/mapstructure"
)
var (
// protectedPaths cannot be accessed via the raw APIs.
// This is both for security and to prevent disrupting Vault.
protectedPaths = []string{
barrierInitPath,
keyringPath,
}
)
2015-03-16 00:35:59 +00:00
func NewSystemBackend(core *Core) logical.Backend {
b := &SystemBackend{
Core: core,
}
2015-05-16 00:19:32 +00:00
b.Backend = &framework.Backend{
Help: strings.TrimSpace(sysHelpRoot),
2015-03-31 00:46:18 +00:00
PathsSpecial: &logical.Paths{
Root: []string{
"mounts/*",
"auth/*",
"remount",
"revoke-prefix/*",
"policy",
"policy/*",
"audit",
"audit/*",
2015-04-01 21:03:17 +00:00
"seal", // Must be set for Core.Seal() logic
"raw/*",
"rotate",
2015-03-31 00:46:18 +00:00
},
2015-03-16 00:35:59 +00:00
},
Paths: []*framework.Path{
&framework.Path{
Pattern: "mounts/(?P<path>.+?)/tune$",
2015-03-16 00:35:59 +00:00
Fields: map[string]*framework.FieldSchema{
"path": &framework.FieldSchema{
Type: framework.TypeString,
Description: strings.TrimSpace(sysHelp["mount_path"][0]),
},
"config": &framework.FieldSchema{
Type: framework.TypeMap,
Description: strings.TrimSpace(sysHelp["mount_config"][0]),
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.handleMountConfig,
logical.WriteOperation: b.handleMountTune,
},
HelpSynopsis: strings.TrimSpace(sysHelp["mount_tune"][0]),
HelpDescription: strings.TrimSpace(sysHelp["mount_tune"][1]),
},
&framework.Path{
Pattern: "mounts/(?P<path>.+?)",
2015-03-16 00:35:59 +00:00
Fields: map[string]*framework.FieldSchema{
"path": &framework.FieldSchema{
Type: framework.TypeString,
Description: strings.TrimSpace(sysHelp["mount_path"][0]),
},
2015-03-16 00:35:59 +00:00
"type": &framework.FieldSchema{
Type: framework.TypeString,
Description: strings.TrimSpace(sysHelp["mount_type"][0]),
},
"description": &framework.FieldSchema{
Type: framework.TypeString,
Description: strings.TrimSpace(sysHelp["mount_desc"][0]),
},
"config": &framework.FieldSchema{
Type: framework.TypeMap,
Description: strings.TrimSpace(sysHelp["mount_config"][0]),
},
2015-03-16 00:35:59 +00:00
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.handleMount,
logical.DeleteOperation: b.handleUnmount,
},
HelpSynopsis: strings.TrimSpace(sysHelp["mount"][0]),
HelpDescription: strings.TrimSpace(sysHelp["mount"][1]),
},
&framework.Path{
Pattern: "mounts$",
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.handleMountTable,
},
HelpSynopsis: strings.TrimSpace(sysHelp["mounts"][0]),
HelpDescription: strings.TrimSpace(sysHelp["mounts"][1]),
},
2015-03-16 00:35:59 +00:00
&framework.Path{
Pattern: "remount",
2015-03-19 14:05:22 +00:00
Fields: map[string]*framework.FieldSchema{
"from": &framework.FieldSchema{
Type: framework.TypeString,
Description: strings.TrimSpace(sysHelp["remount_from"][0]),
},
"to": &framework.FieldSchema{
Type: framework.TypeString,
Description: strings.TrimSpace(sysHelp["remount_to"][0]),
},
},
2015-03-16 00:35:59 +00:00
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.handleRemount,
},
HelpSynopsis: strings.TrimSpace(sysHelp["remount"][0]),
HelpDescription: strings.TrimSpace(sysHelp["remount"][1]),
},
2015-03-16 23:11:55 +00:00
&framework.Path{
Pattern: "renew/(?P<lease_id>.+)",
2015-03-16 23:11:55 +00:00
Fields: map[string]*framework.FieldSchema{
"lease_id": &framework.FieldSchema{
2015-03-16 23:11:55 +00:00
Type: framework.TypeString,
Description: strings.TrimSpace(sysHelp["lease_id"][0]),
2015-03-16 23:11:55 +00:00
},
"increment": &framework.FieldSchema{
Type: framework.TypeDurationSecond,
2015-03-16 23:11:55 +00:00
Description: strings.TrimSpace(sysHelp["increment"][0]),
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.handleRenew,
},
HelpSynopsis: strings.TrimSpace(sysHelp["renew"][0]),
HelpDescription: strings.TrimSpace(sysHelp["renew"][1]),
},
2015-03-16 23:26:34 +00:00
&framework.Path{
Pattern: "revoke/(?P<lease_id>.+)",
2015-03-16 23:26:34 +00:00
Fields: map[string]*framework.FieldSchema{
"lease_id": &framework.FieldSchema{
2015-03-16 23:26:34 +00:00
Type: framework.TypeString,
Description: strings.TrimSpace(sysHelp["lease_id"][0]),
2015-03-16 23:26:34 +00:00
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.handleRevoke,
},
HelpSynopsis: strings.TrimSpace(sysHelp["revoke"][0]),
HelpDescription: strings.TrimSpace(sysHelp["revoke"][1]),
},
2015-03-16 23:33:48 +00:00
&framework.Path{
Pattern: "revoke-prefix/(?P<prefix>.+)",
Fields: map[string]*framework.FieldSchema{
"prefix": &framework.FieldSchema{
Type: framework.TypeString,
Description: strings.TrimSpace(sysHelp["revoke-prefix-path"][0]),
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.handleRevokePrefix,
},
HelpSynopsis: strings.TrimSpace(sysHelp["revoke-prefix"][0]),
HelpDescription: strings.TrimSpace(sysHelp["revoke-prefix"][1]),
},
&framework.Path{
Pattern: "auth$",
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.handleAuthTable,
},
HelpSynopsis: strings.TrimSpace(sysHelp["auth-table"][0]),
HelpDescription: strings.TrimSpace(sysHelp["auth-table"][1]),
},
&framework.Path{
Pattern: "auth/(?P<path>.+)",
Fields: map[string]*framework.FieldSchema{
"path": &framework.FieldSchema{
Type: framework.TypeString,
Description: strings.TrimSpace(sysHelp["auth_path"][0]),
},
"type": &framework.FieldSchema{
Type: framework.TypeString,
Description: strings.TrimSpace(sysHelp["auth_type"][0]),
},
"description": &framework.FieldSchema{
Type: framework.TypeString,
Description: strings.TrimSpace(sysHelp["auth_desc"][0]),
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.handleEnableAuth,
logical.DeleteOperation: b.handleDisableAuth,
},
HelpSynopsis: strings.TrimSpace(sysHelp["auth"][0]),
HelpDescription: strings.TrimSpace(sysHelp["auth"][1]),
},
2015-03-23 21:43:31 +00:00
&framework.Path{
Pattern: "policy$",
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.handlePolicyList,
},
HelpSynopsis: strings.TrimSpace(sysHelp["policy-list"][0]),
HelpDescription: strings.TrimSpace(sysHelp["policy-list"][1]),
},
&framework.Path{
Pattern: "policy/(?P<name>.+)",
Fields: map[string]*framework.FieldSchema{
"name": &framework.FieldSchema{
Type: framework.TypeString,
Description: strings.TrimSpace(sysHelp["policy-name"][0]),
},
"rules": &framework.FieldSchema{
Type: framework.TypeString,
Description: strings.TrimSpace(sysHelp["policy-rules"][0]),
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.handlePolicyRead,
logical.WriteOperation: b.handlePolicySet,
logical.DeleteOperation: b.handlePolicyDelete,
},
HelpSynopsis: strings.TrimSpace(sysHelp["policy"][0]),
HelpDescription: strings.TrimSpace(sysHelp["policy"][1]),
},
&framework.Path{
Pattern: "audit$",
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.handleAuditTable,
},
HelpSynopsis: strings.TrimSpace(sysHelp["audit-table"][0]),
HelpDescription: strings.TrimSpace(sysHelp["audit-table"][1]),
},
&framework.Path{
Pattern: "audit/(?P<path>.+)",
Fields: map[string]*framework.FieldSchema{
"path": &framework.FieldSchema{
Type: framework.TypeString,
Description: strings.TrimSpace(sysHelp["audit_path"][0]),
},
"type": &framework.FieldSchema{
Type: framework.TypeString,
Description: strings.TrimSpace(sysHelp["audit_type"][0]),
},
"description": &framework.FieldSchema{
Type: framework.TypeString,
Description: strings.TrimSpace(sysHelp["audit_desc"][0]),
},
"options": &framework.FieldSchema{
Type: framework.TypeMap,
Description: strings.TrimSpace(sysHelp["audit_opts"][0]),
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.handleEnableAudit,
logical.DeleteOperation: b.handleDisableAudit,
},
HelpSynopsis: strings.TrimSpace(sysHelp["audit"][0]),
HelpDescription: strings.TrimSpace(sysHelp["audit"][1]),
},
&framework.Path{
Pattern: "raw/(?P<path>.+)",
Fields: map[string]*framework.FieldSchema{
"path": &framework.FieldSchema{
Type: framework.TypeString,
},
"value": &framework.FieldSchema{
Type: framework.TypeString,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.handleRawRead,
logical.WriteOperation: b.handleRawWrite,
logical.DeleteOperation: b.handleRawDelete,
},
},
&framework.Path{
Pattern: "key-status$",
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.handleKeyStatus,
},
HelpSynopsis: strings.TrimSpace(sysHelp["key-status"][0]),
HelpDescription: strings.TrimSpace(sysHelp["key-status"][1]),
},
&framework.Path{
Pattern: "rotate$",
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.handleRotate,
},
HelpSynopsis: strings.TrimSpace(sysHelp["rotate"][0]),
HelpDescription: strings.TrimSpace(sysHelp["rotate"][1]),
},
2015-03-16 00:35:59 +00:00
},
}
2015-05-16 00:19:32 +00:00
return b.Backend
2015-03-16 00:35:59 +00:00
}
// SystemBackend implements logical.Backend and is used to interact with
// the core of the system. This backend is hardcoded to exist at the "sys"
// prefix. Conceptually it is similar to procfs on Linux.
type SystemBackend struct {
2015-05-16 00:19:32 +00:00
Core *Core
Backend *framework.Backend
}
// handleMountTable handles the "mounts" endpoint to provide the mount table
2015-03-16 00:35:59 +00:00
func (b *SystemBackend) handleMountTable(
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
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
b.Core.mounts.Lock()
defer b.Core.mounts.Unlock()
resp := &logical.Response{
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
Data: make(map[string]interface{}),
}
for _, entry := range b.Core.mounts.Entries {
info := map[string]interface{}{
"type": entry.Type,
"description": entry.Description,
"config": structs.Map(entry.Config),
}
resp.Data[entry.Path] = info
}
return resp, nil
}
// handleMount is used to mount a new path
2015-03-16 00:35:59 +00:00
func (b *SystemBackend) handleMount(
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
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
2015-03-16 00:35:59 +00:00
// Get all the options
path := data.Get("path").(string)
logicalType := data.Get("type").(string)
description := data.Get("description").(string)
var config MountConfig
configMap := data.Get("config").(map[string]interface{})
if configMap != nil && len(configMap) != 0 {
err := mapstructure.Decode(configMap, &config)
if err != nil {
return logical.ErrorResponse(
"unable to convert given mount config information"),
logical.ErrInvalidRequest
}
}
if logicalType == "" {
2015-03-16 00:35:59 +00:00
return logical.ErrorResponse(
"backend type must be specified as a string"),
logical.ErrInvalidRequest
}
// Create the mount entry
me := &MountEntry{
2015-03-16 00:35:59 +00:00
Path: path,
Type: logicalType,
Description: description,
Config: config,
}
if me.Config.DefaultLeaseTTL == nil {
me.Config.DefaultLeaseTTL = new(time.Duration)
}
if me.Config.MaxLeaseTTL == nil {
me.Config.MaxLeaseTTL = new(time.Duration)
}
// Attempt mount
if err := b.Core.mount(me); err != nil {
2015-05-16 00:19:32 +00:00
b.Backend.Logger().Printf("[ERR] sys: mount %#v failed: %v", me, err)
return handleError(err)
}
return nil, nil
}
// used to intercept an HTTPCodedError so it goes back to callee
func handleError(
err error) (*logical.Response, error) {
switch err.(type) {
case logical.HTTPCodedError:
return logical.ErrorResponse(err.Error()), err
default:
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
}
}
// handleUnmount is used to unmount a path
2015-03-16 00:35:59 +00:00
func (b *SystemBackend) handleUnmount(
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
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
suffix := strings.TrimPrefix(req.Path, "mounts/")
if len(suffix) == 0 {
return logical.ErrorResponse("path cannot be blank"), logical.ErrInvalidRequest
}
// Attempt unmount
if err := b.Core.unmount(suffix); err != nil {
2015-05-16 00:19:32 +00:00
b.Backend.Logger().Printf("[ERR] sys: unmount '%s' failed: %v", suffix, err)
return handleError(err)
}
return nil, nil
}
// handleRemount is used to remount a path
2015-03-16 00:35:59 +00:00
func (b *SystemBackend) handleRemount(
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
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
// Get the paths
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
fromPath := data.Get("from").(string)
toPath := data.Get("to").(string)
if fromPath == "" || toPath == "" {
return logical.ErrorResponse(
"both 'from' and 'to' path must be specified as a string"),
logical.ErrInvalidRequest
}
// Attempt remount
if err := b.Core.remount(fromPath, toPath); err != nil {
b.Backend.Logger().Printf("[ERR] sys: remount '%s' to '%s' failed: %v", fromPath, toPath, err)
return handleError(err)
}
return nil, nil
}
// handleMountConfig is used to get config settings on a backend
func (b *SystemBackend) handleMountConfig(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
path := data.Get("path").(string)
if path == "" {
return logical.ErrorResponse(
"path must be specified as a string"),
logical.ErrInvalidRequest
}
def, max, err := b.Core.TTLsByPath(path)
if err != nil {
b.Backend.Logger().Printf("[ERR] sys: fetching config of path '%s' failed: %v", path, err)
return handleError(err)
}
config := MountConfig{
DefaultLeaseTTL: &def,
MaxLeaseTTL: &max,
}
resp := &logical.Response{
Data: map[string]interface{}{
"config": config,
},
}
return resp, nil
}
// handleMountTune is used to set config settings on a backend
func (b *SystemBackend) handleMountTune(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
path := data.Get("path").(string)
if path == "" {
return logical.ErrorResponse(
"path must be specified as a string"),
logical.ErrInvalidRequest
}
var config MountConfig
configMap := data.Get("config").(map[string]interface{})
if configMap == nil || len(configMap) == 0 {
return logical.ErrorResponse(
"invalid parameters; 'config' empty or not supplied"),
logical.ErrInvalidRequest
}
err := mapstructure.Decode(configMap, &config)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf(
"unable to convert given mount config information: %s", err)),
logical.ErrInvalidRequest
}
// Attempt tune
if err := b.Core.tuneMount(path, config); err != nil {
b.Backend.Logger().Printf("[ERR] sys: tune of path '%s' failed: %v", path, err)
return handleError(err)
}
return nil, nil
}
2015-03-16 00:35:59 +00:00
// handleRenew is used to renew a lease with a given LeaseID
2015-03-16 23:11:55 +00:00
func (b *SystemBackend) handleRenew(
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
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
2015-03-16 23:11:55 +00:00
// Get all the options
leaseID := data.Get("lease_id").(string)
2015-03-16 23:11:55 +00:00
incrementRaw := data.Get("increment").(int)
// Convert the increment
increment := time.Duration(incrementRaw) * time.Second
// Invoke the expiration manager directly
resp, err := b.Core.expiration.Renew(leaseID, increment)
2015-03-16 23:14:53 +00:00
if err != nil {
2015-05-16 00:19:32 +00:00
b.Backend.Logger().Printf("[ERR] sys: renew '%s' failed: %v", leaseID, err)
return handleError(err)
2015-03-16 23:14:53 +00:00
}
return resp, err
2015-03-16 23:11:55 +00:00
}
// handleRevoke is used to revoke a given LeaseID
2015-03-16 23:26:34 +00:00
func (b *SystemBackend) handleRevoke(
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
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
2015-03-16 23:26:34 +00:00
// Get all the options
leaseID := data.Get("lease_id").(string)
2015-03-16 23:26:34 +00:00
// Invoke the expiration manager directly
if err := b.Core.expiration.Revoke(leaseID); err != nil {
2015-05-16 00:19:32 +00:00
b.Backend.Logger().Printf("[ERR] sys: revoke '%s' failed: %v", leaseID, err)
return handleError(err)
2015-03-16 23:26:34 +00:00
}
return nil, nil
}
// handleRevokePrefix is used to revoke a prefix with many LeaseIDs
2015-03-16 23:33:48 +00:00
func (b *SystemBackend) handleRevokePrefix(
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
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
2015-03-16 23:33:48 +00:00
// Get all the options
prefix := data.Get("prefix").(string)
// Invoke the expiration manager directly
if err := b.Core.expiration.RevokePrefix(prefix); err != nil {
2015-05-16 00:19:32 +00:00
b.Backend.Logger().Printf("[ERR] sys: revoke prefix '%s' failed: %v", prefix, err)
return handleError(err)
2015-03-16 23:33:48 +00:00
}
return nil, nil
}
// handleAuthTable handles the "auth" endpoint to provide the auth table
func (b *SystemBackend) handleAuthTable(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
b.Core.auth.Lock()
defer b.Core.auth.Unlock()
resp := &logical.Response{
Data: make(map[string]interface{}),
}
for _, entry := range b.Core.auth.Entries {
info := map[string]string{
"type": entry.Type,
"description": entry.Description,
}
resp.Data[entry.Path] = info
}
return resp, nil
}
// handleEnableAuth is used to enable a new credential backend
func (b *SystemBackend) handleEnableAuth(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
// Get all the options
path := data.Get("path").(string)
logicalType := data.Get("type").(string)
description := data.Get("description").(string)
if logicalType == "" {
return logical.ErrorResponse(
"backend type must be specified as a string"),
logical.ErrInvalidRequest
}
// Create the mount entry
me := &MountEntry{
Path: path,
Type: logicalType,
Description: description,
}
// Attempt enabling
if err := b.Core.enableCredential(me); err != nil {
2015-05-16 00:19:32 +00:00
b.Backend.Logger().Printf("[ERR] sys: enable auth %#v failed: %v", me, err)
return handleError(err)
}
return nil, nil
}
// handleDisableAuth is used to disable a credential backend
func (b *SystemBackend) handleDisableAuth(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
suffix := strings.TrimPrefix(req.Path, "auth/")
if len(suffix) == 0 {
return logical.ErrorResponse("path cannot be blank"), logical.ErrInvalidRequest
}
// Attempt disable
if err := b.Core.disableCredential(suffix); err != nil {
2015-05-16 00:19:32 +00:00
b.Backend.Logger().Printf("[ERR] sys: disable auth '%s' failed: %v", suffix, err)
return handleError(err)
}
return nil, nil
}
2015-03-23 21:43:31 +00:00
// handlePolicyList handles the "policy" endpoint to provide the enabled policies
func (b *SystemBackend) handlePolicyList(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
// Get all the configured policies
policies, err := b.Core.policy.ListPolicies()
// Add the special "root" policy
policies = append(policies, "root")
return logical.ListResponse(policies), err
}
// handlePolicyRead handles the "policy/<name>" endpoint to read a policy
func (b *SystemBackend) handlePolicyRead(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
name := data.Get("name").(string)
policy, err := b.Core.policy.GetPolicy(name)
if err != nil {
return handleError(err)
2015-03-23 21:43:31 +00:00
}
if policy == nil {
return nil, nil
}
return &logical.Response{
Data: map[string]interface{}{
"name": name,
"rules": policy.Raw,
},
}, nil
}
// handlePolicySet handles the "policy/<name>" endpoint to set a policy
func (b *SystemBackend) handlePolicySet(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
name := data.Get("name").(string)
rules := data.Get("rules").(string)
// Validate the rules parse
parse, err := Parse(rules)
if err != nil {
return handleError(err)
2015-03-23 21:43:31 +00:00
}
// Override the name
parse.Name = name
// Update the policy
if err := b.Core.policy.SetPolicy(parse); err != nil {
return handleError(err)
2015-03-23 21:43:31 +00:00
}
return nil, nil
}
// handlePolicyDelete handles the "policy/<name>" endpoint to delete a policy
func (b *SystemBackend) handlePolicyDelete(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
name := data.Get("name").(string)
if err := b.Core.policy.DeletePolicy(name); err != nil {
return handleError(err)
2015-03-23 21:43:31 +00:00
}
return nil, nil
}
// handleAuditTable handles the "audit" endpoint to provide the audit table
func (b *SystemBackend) handleAuditTable(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
b.Core.audit.Lock()
defer b.Core.audit.Unlock()
resp := &logical.Response{
Data: make(map[string]interface{}),
}
for _, entry := range b.Core.audit.Entries {
info := map[string]interface{}{
"type": entry.Type,
"description": entry.Description,
"options": entry.Options,
}
resp.Data[entry.Path] = info
}
return resp, nil
}
// handleEnableAudit is used to enable a new audit backend
func (b *SystemBackend) handleEnableAudit(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
// Get all the options
path := data.Get("path").(string)
backendType := data.Get("type").(string)
description := data.Get("description").(string)
options := data.Get("options").(map[string]interface{})
optionMap := make(map[string]string)
for k, v := range options {
vStr, ok := v.(string)
if !ok {
return logical.ErrorResponse("options must be string valued"),
logical.ErrInvalidRequest
}
optionMap[k] = vStr
}
// Create the mount entry
me := &MountEntry{
Path: path,
Type: backendType,
Description: description,
Options: optionMap,
}
// Attempt enabling
if err := b.Core.enableAudit(me); err != nil {
2015-05-16 00:19:32 +00:00
b.Backend.Logger().Printf("[ERR] sys: enable audit %#v failed: %v", me, err)
return handleError(err)
}
return nil, nil
}
// handleDisableAudit is used to disable an audit backend
func (b *SystemBackend) handleDisableAudit(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
path := data.Get("path").(string)
// Attempt disable
if err := b.Core.disableAudit(path); err != nil {
2015-05-16 00:19:32 +00:00
b.Backend.Logger().Printf("[ERR] sys: disable audit '%s' failed: %v", path, err)
return handleError(err)
}
return nil, nil
}
// handleRawRead is used to read directly from the barrier
func (b *SystemBackend) handleRawRead(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
path := data.Get("path").(string)
// Prevent access of protected paths
for _, p := range protectedPaths {
if strings.HasPrefix(path, p) {
err := fmt.Sprintf("cannot read '%s'", path)
return logical.ErrorResponse(err), logical.ErrInvalidRequest
}
}
entry, err := b.Core.barrier.Get(path)
if err != nil {
return handleError(err)
}
if entry == nil {
return nil, nil
}
resp := &logical.Response{
Data: map[string]interface{}{
"value": string(entry.Value),
},
}
return resp, nil
}
// handleRawWrite is used to write directly to the barrier
func (b *SystemBackend) handleRawWrite(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
path := data.Get("path").(string)
// Prevent access of protected paths
for _, p := range protectedPaths {
if strings.HasPrefix(path, p) {
err := fmt.Sprintf("cannot write '%s'", path)
return logical.ErrorResponse(err), logical.ErrInvalidRequest
}
}
value := data.Get("value").(string)
entry := &Entry{
Key: path,
Value: []byte(value),
}
if err := b.Core.barrier.Put(entry); err != nil {
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
}
return nil, nil
}
// handleRawDelete is used to delete directly from the barrier
func (b *SystemBackend) handleRawDelete(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
path := data.Get("path").(string)
// Prevent access of protected paths
for _, p := range protectedPaths {
if strings.HasPrefix(path, p) {
err := fmt.Sprintf("cannot delete '%s'", path)
return logical.ErrorResponse(err), logical.ErrInvalidRequest
}
}
if err := b.Core.barrier.Delete(path); err != nil {
return handleError(err)
}
return nil, nil
}
// handleKeyStatus returns status information about the backend key
func (b *SystemBackend) handleKeyStatus(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
// Get the key info
info, err := b.Core.barrier.ActiveKeyInfo()
if err != nil {
return nil, err
}
resp := &logical.Response{
Data: map[string]interface{}{
"term": info.Term,
"install_time": info.InstallTime.Format(time.RFC3339),
},
}
return resp, nil
}
// handleRotate is used to trigger a key rotation
func (b *SystemBackend) handleRotate(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
2015-05-28 23:43:15 +00:00
// Rotate to the new term
newTerm, err := b.Core.barrier.Rotate()
if err != nil {
2015-05-28 00:56:55 +00:00
b.Backend.Logger().Printf("[ERR] sys: failed to create new encryption key: %v", err)
return handleError(err)
}
2015-05-28 00:56:55 +00:00
b.Backend.Logger().Printf("[INFO] sys: installed new encryption key")
2015-05-28 23:43:15 +00:00
// In HA mode, we need to an upgrade path for the standby instances
2015-05-28 23:43:15 +00:00
if b.Core.ha != nil {
// Create the upgrade path to the new term
if err := b.Core.barrier.CreateUpgrade(newTerm); err != nil {
b.Backend.Logger().Printf("[ERR] sys: failed to create new upgrade for key term %d: %v", newTerm, err)
}
// Schedule the destroy of the upgrade path
time.AfterFunc(keyRotateGracePeriod, func() {
if err := b.Core.barrier.DestroyUpgrade(newTerm); err != nil {
b.Backend.Logger().Printf("[ERR] sys: failed to destroy upgrade for key term %d: %v", newTerm, err)
}
})
}
return nil, nil
}
const sysHelpRoot = `
The system backend is built-in to Vault and cannot be remounted or
unmounted. It contains the paths that are used to configure Vault itself
as well as perform core operations.
`
2015-03-16 00:35:59 +00:00
// sysHelp is all the help text for the sys backend.
var sysHelp = map[string][2]string{
"mounts": {
"List the currently mounted backends.",
`
List the currently mounted backends: the mount path, the type of the backend,
and a user friendly description of the purpose for the mount.
`,
},
"mount": {
`Mount a new backend at a new path.`,
`
Mount a backend at a new path. A backend can be mounted multiple times at
multiple paths in order to configure multiple separately configured backends.
Example: you might have an AWS backend for the east coast, and one for the
west coast.
`,
},
"mount_path": {
`The path to mount to. Example: "aws/east"`,
"",
},
"mount_type": {
`The type of the backend. Example: "passthrough"`,
"",
},
"mount_desc": {
`User-friendly description for this mount.`,
"",
},
"mount_config": {
`Configuration for this mount, such as default_lease_ttl
and max_lease_ttl.`,
},
2015-03-16 00:35:59 +00:00
"remount": {
"Move the mount point of an already-mounted backend.",
`
Change the mount point of an already-mounted backend.
`,
},
2015-03-16 23:11:55 +00:00
2015-03-19 14:05:22 +00:00
"remount_from": {
"",
"",
},
"remount_to": {
"",
"",
},
"mount_tune": {
"Tune backend configuration parameters for this mount.",
},
2015-03-16 23:11:55 +00:00
"renew": {
"Renew a lease on a secret",
`
When a secret is read, it may optionally include a lease interval
and a boolean indicating if renew is possible. For secrets that support
lease renewal, this endpoint is used to extend the validity of the
lease and to prevent an automatic revocation.
`,
},
"lease_id": {
"The lease identifier to renew. This is included with a lease.",
2015-03-16 23:11:55 +00:00
"",
},
"increment": {
"The desired increment in seconds to the lease",
"",
},
2015-03-16 23:26:34 +00:00
"revoke": {
"Revoke a leased secret immediately",
`
When a secret is generated with a lease, it is automatically revoked
at the end of the lease period if not renewed. However, in some cases
you may want to force an immediate revocation. This endpoint can be
used to revoke the secret with the given Lease ID.
2015-03-16 23:26:34 +00:00
`,
},
2015-03-16 23:33:48 +00:00
"revoke-prefix": {
"Revoke all secrets generated in a given prefix",
`
Revokes all the secrets generated under a given mount prefix. As
an example, "prod/aws/" might be the AWS logical backend, and due to
a change in the "ops" policy, we may want to invalidate all the secrets
generated. We can do a revoke prefix at "prod/aws/ops" to revoke all
the ops secrets. This does a prefix match on the Lease IDs and revokes
2015-03-16 23:33:48 +00:00
all matching leases.
`,
},
"revoke-prefix-path": {
`The path to revoke keys under. Example: "prod/aws/ops"`,
"",
},
"auth-table": {
"List the currently enabled credential backends.",
`
List the currently enabled credential backends: the name, the type of the backend,
and a user friendly description of the purpose for the credential backend.
`,
},
"auth": {
`Enable a new credential backend with a name.`,
`
Enable a credential mechanism at a new path. A backend can be mounted multiple times at
multiple paths in order to configure multiple separately configured backends.
Example: you might have an OAuth backend for GitHub, and one for Google Apps.
`,
},
"auth_path": {
`The path to mount to. Cannot be delimited. Example: "user"`,
"",
},
"auth_type": {
`The type of the backend. Example: "userpass"`,
"",
},
"auth_desc": {
`User-friendly description for this crential backend.`,
"",
},
2015-03-23 21:43:31 +00:00
"policy-list": {
`List the configured access control policies.`,
`
List the names of the configured access control policies. Policies are associated
with client tokens to limit access to keys in the Vault.
`,
},
"policy": {
`Read, Modify, or Delete an access control policy.`,
`
Read the rules of an existing policy, create or update the rules of a policy,
or delete a policy.
`,
},
"policy-name": {
`The name of the policy. Example: "ops"`,
"",
},
"policy-rules": {
`The rules of the policy. Either given in HCL or JSON format.`,
"",
},
"audit-table": {
"List the currently enabled audit backends.",
`
List the currently enabled audit backends: the name, the type of the backend,
a user friendly description of the audit backend, and it's configuration options.
`,
},
"audit_path": {
`The name of the backend. Cannot be delimited. Example: "mysql"`,
"",
},
"audit_type": {
`The type of the backend. Example: "mysql"`,
"",
},
"audit_desc": {
`User-friendly description for this audit backend.`,
"",
},
"audit_opts": {
`Configuration options for the audit backend.`,
"",
},
"audit": {
`Enable or disable audit backends.`,
`
Enable a new audit backend or disable an existing backend.
`,
},
"key-status": {
"Provides information about the backend encryption key.",
`
Provides the current backend encryption key term and installation time.
`,
},
"rotate": {
"Rotates the backend encryption key used to persist data.",
`
Rotate generates a new encryption key which is used to encrypt all
data going to the storage backend. The old encryption keys are kept so
that data encrypted using those keys can still be decrypted.
`,
},
2015-03-16 00:35:59 +00:00
}