Rejig how dynamic values are represented in system view and location of some functions in various packages; create mount-tune command and API analogues; update documentation

This commit is contained in:
Jeff Mitchell 2015-09-02 15:56:58 -04:00
parent 4239f9d243
commit 488d33c70a
18 changed files with 521 additions and 200 deletions

View File

@ -54,7 +54,7 @@ func (c *Sys) Unmount(path string) error {
return err return err
} }
func (c *Sys) Remount(from, to string, config vault.MountConfig) error { func (c *Sys) Remount(from, to string) error {
if err := c.checkMountPath(from); err != nil { if err := c.checkMountPath(from); err != nil {
return err return err
} }
@ -63,9 +63,8 @@ func (c *Sys) Remount(from, to string, config vault.MountConfig) error {
} }
body := map[string]interface{}{ body := map[string]interface{}{
"from": from, "from": from,
"to": to, "to": to,
"config": config,
} }
r := c.c.NewRequest("POST", "/v1/sys/remount") r := c.c.NewRequest("POST", "/v1/sys/remount")
@ -80,6 +79,41 @@ func (c *Sys) Remount(from, to string, config vault.MountConfig) error {
return err return err
} }
func (c *Sys) TuneMount(path string, config vault.MountConfig) error {
if err := c.checkMountPath(path); err != nil {
return err
}
body := map[string]interface{}{
"config": config,
}
r := c.c.NewRequest("POST", fmt.Sprintf("/v1/sys/mounts/%s/tune", path))
if err := r.SetJSONBody(body); err != nil {
return err
}
resp, err := c.c.RawRequest(r)
if err == nil {
defer resp.Body.Close()
}
return err
}
func (c *Sys) MountConfig(path string) error {
if err := c.checkMountPath(path); err != nil {
return err
}
r := c.c.NewRequest("GET", fmt.Sprintf("/v1/sys/mounts/%s/tune", path))
resp, err := c.c.RawRequest(r)
if err == nil {
defer resp.Body.Close()
}
return err
}
func (c *Sys) checkMountPath(path string) error { func (c *Sys) checkMountPath(path string) error {
if path[0] == '/' { if path[0] == '/' {
return fmt.Errorf("path must not start with /: %s", path) return fmt.Errorf("path must not start with /: %s", path)

View File

@ -114,7 +114,10 @@ func (b *backend) pathIssueCert(
var ttl time.Duration var ttl time.Duration
if len(ttlField) == 0 { if len(ttlField) == 0 {
ttl = b.System.DefaultLeaseTTL() ttl, err = b.System().DefaultLeaseTTL()
if err != nil {
return nil, fmt.Errorf("Error fetching default TTL: %s", err)
}
} else { } else {
ttl, err = time.ParseDuration(ttlField) ttl, err = time.ParseDuration(ttlField)
if err != nil { if err != nil {
@ -125,7 +128,10 @@ func (b *backend) pathIssueCert(
var maxTTL time.Duration var maxTTL time.Duration
if len(role.MaxTTL) == 0 { if len(role.MaxTTL) == 0 {
maxTTL = b.System.MaxLeaseTTL() maxTTL, err = b.System().MaxLeaseTTL()
if err != nil {
return nil, fmt.Errorf("Error fetching max TTL: %s", err)
}
} else { } else {
maxTTL, err = time.ParseDuration(role.MaxTTL) maxTTL, err = time.ParseDuration(role.MaxTTL)
if err != nil { if err != nil {

View File

@ -256,10 +256,13 @@ func (b *backend) pathRoleCreate(
if len(entry.MaxTTL) == 0 { if len(entry.MaxTTL) == 0 {
entry.MaxTTL = data.Get("lease_max").(string) entry.MaxTTL = data.Get("lease_max").(string)
} }
var maxTTL time.Duration var maxTTL time.Duration
maxSystemTTL, err := b.System().MaxLeaseTTL()
if err != nil {
return nil, fmt.Errorf("Error fetching max TTL: %s", err)
}
if len(entry.MaxTTL) == 0 { if len(entry.MaxTTL) == 0 {
maxTTL = b.System.MaxLeaseTTL() maxTTL = maxSystemTTL
} else { } else {
maxTTL, err = time.ParseDuration(entry.MaxTTL) maxTTL, err = time.ParseDuration(entry.MaxTTL)
if err != nil { if err != nil {
@ -267,18 +270,18 @@ func (b *backend) pathRoleCreate(
"Invalid ttl: %s", err)), nil "Invalid ttl: %s", err)), nil
} }
} }
if maxTTL > b.System.MaxLeaseTTL() { if maxTTL > maxSystemTTL {
return logical.ErrorResponse("Requested max TTL is higher than system maximum"), nil return logical.ErrorResponse("Requested max TTL is higher than system maximum"), nil
} }
var ttl time.Duration
if len(entry.TTL) == 0 { if len(entry.TTL) == 0 {
entry.TTL = data.Get("lease").(string) entry.TTL = data.Get("lease").(string)
} }
switch len(entry.TTL) { ttl, err := b.System().DefaultLeaseTTL()
case 0: if err != nil {
ttl = b.System.DefaultLeaseTTL() return nil, fmt.Errorf("Error fetching max TTL: %s", err)
default: }
if len(entry.TTL) != 0 {
ttl, err = time.ParseDuration(entry.TTL) ttl, err = time.ParseDuration(entry.TTL)
if err != nil { if err != nil {
return logical.ErrorResponse(fmt.Sprintf( return logical.ErrorResponse(fmt.Sprintf(

View File

@ -226,6 +226,12 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory {
}, nil }, nil
}, },
"mount-tune": func() (cli.Command, error) {
return &command.MountTuneCommand{
Meta: meta,
}, nil
},
"remount": func() (cli.Command, error) { "remount": func() (cli.Command, error) {
return &command.RemountCommand{ return &command.RemountCommand{
Meta: meta, Meta: meta,

View File

@ -35,17 +35,17 @@ func (c *MountsCommand) Run(args []string) int {
} }
paths := make([]string, 0, len(mounts)) paths := make([]string, 0, len(mounts))
for path, _ := range mounts { for path := range mounts {
paths = append(paths, path) paths = append(paths, path)
} }
sort.Strings(paths) sort.Strings(paths)
columns := []string{"Path | Type | Description"} columns := []string{"Path | Type | Default TTL | Max TTL | Description"}
for _, path := range paths { for _, path := range paths {
mount := mounts[path] mount := mounts[path]
columns = append(columns, fmt.Sprintf( columns = append(columns, fmt.Sprintf(
"%s | %s | %s", path, mount.Type, mount.Description)) "%s | %s | %s | %s | %s", path, mount.Type, mount.Config.DefaultLeaseTTL, mount.Config.MaxLeaseTTL, mount.Description))
} }
c.Ui.Output(columnize.SimpleFormat(columns)) c.Ui.Output(columnize.SimpleFormat(columns))
@ -62,8 +62,9 @@ Usage: vault mounts [options]
Outputs information about the mounted backends. Outputs information about the mounted backends.
This command lists the mounted backends, their mount points, and This command lists the mounted backends, their mount points, the
a human-friendly description of the mount point. configured TTLs, and a human-friendly description of the mount point.
A TTL of '0' indicates that the system default is being used.
General Options: General Options:

106
command/mounttune.go Normal file
View File

@ -0,0 +1,106 @@
package command
import (
"fmt"
"strings"
"time"
"github.com/hashicorp/vault/vault"
)
// RemountCommand is a Command that remounts a mounted secret backend
// to a new endpoint.
type MountTuneCommand struct {
Meta
}
func (c *MountTuneCommand) Run(args []string) int {
var defaultLeaseTTL, maxLeaseTTL string
flags := c.Meta.FlagSet("mount-tune", FlagSetDefault)
flags.StringVar(&defaultLeaseTTL, "default-lease-ttl", "", "")
flags.StringVar(&maxLeaseTTL, "max-lease-ttl", "", "")
flags.Usage = func() { c.Ui.Error(c.Help()) }
if err := flags.Parse(args); err != nil {
return 1
}
args = flags.Args()
if len(args) != 1 {
flags.Usage()
c.Ui.Error(fmt.Sprintf(
"\n'mount-tune' expects one arguments: the mount path"))
return 1
}
path := args[0]
mountConfig := vault.MountConfig{}
if defaultLeaseTTL != "" {
defTTL, err := time.ParseDuration(defaultLeaseTTL)
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error parsing default lease TTL duration: %s", err))
return 2
}
mountConfig.DefaultLeaseTTL = &defTTL
}
if maxLeaseTTL != "" {
maxTTL, err := time.ParseDuration(maxLeaseTTL)
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error parsing max lease TTL duration: %s", err))
return 2
}
mountConfig.MaxLeaseTTL = &maxTTL
}
client, err := c.Client()
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error initializing client: %s", err))
return 2
}
if err := client.Sys().TuneMount(path, mountConfig); err != nil {
c.Ui.Error(fmt.Sprintf(
"Mount tune error: %s", err))
return 2
}
c.Ui.Output(fmt.Sprintf(
"Successfully tuned mount '%s'!", path))
return 0
}
func (c *MountTuneCommand) Synopsis() string {
return "Tune mount configuration parameters"
}
func (c *MountTuneCommand) Help() string {
helpText := `
Usage: vault mount-tune [options] path
Tune configuration options fro a mounted secret backend.
Example: vault tune-mount -default-lease-ttl="24h" secret/
General Options:
` + generalOptionsUsage() + `
Mount Options:
-default-lease-ttl=<duration> Default lease time-to-live for this backend.
If not specified, uses the global default, or
the previously set value. Set to '0' to
explicitly set it to use the global default.
-max-lease-ttl=<duration> Max lease time-to-live for this backend.
If not specified, uses the global default, or
the previously set value. Set to '0' to
explicitly set it to use the global default.
`
return strings.TrimSpace(helpText)
}

View File

@ -3,9 +3,6 @@ package command
import ( import (
"fmt" "fmt"
"strings" "strings"
"time"
"github.com/hashicorp/vault/vault"
) )
// RemountCommand is a Command that remounts a mounted secret backend // RemountCommand is a Command that remounts a mounted secret backend
@ -15,10 +12,7 @@ type RemountCommand struct {
} }
func (c *RemountCommand) Run(args []string) int { func (c *RemountCommand) Run(args []string) int {
var defaultLeaseTTL, maxLeaseTTL string
flags := c.Meta.FlagSet("remount", FlagSetDefault) flags := c.Meta.FlagSet("remount", FlagSetDefault)
flags.StringVar(&defaultLeaseTTL, "default-lease-ttl", "", "")
flags.StringVar(&maxLeaseTTL, "max-lease-ttl", "", "")
flags.Usage = func() { c.Ui.Error(c.Help()) } flags.Usage = func() { c.Ui.Error(c.Help()) }
if err := flags.Parse(args); err != nil { if err := flags.Parse(args); err != nil {
return 1 return 1
@ -35,26 +29,6 @@ func (c *RemountCommand) Run(args []string) int {
from := args[0] from := args[0]
to := args[1] to := args[1]
mountConfig := vault.MountConfig{}
if defaultLeaseTTL != "" {
defTTL, err := time.ParseDuration(defaultLeaseTTL)
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error parsing default lease TTL duration: %s", err))
return 2
}
mountConfig.DefaultLeaseTTL = &defTTL
}
if maxLeaseTTL != "" {
maxTTL, err := time.ParseDuration(maxLeaseTTL)
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error parsing max lease TTL duration: %s", err))
return 2
}
mountConfig.MaxLeaseTTL = &maxTTL
}
client, err := c.Client() client, err := c.Client()
if err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf( c.Ui.Error(fmt.Sprintf(
@ -62,7 +36,7 @@ func (c *RemountCommand) Run(args []string) int {
return 2 return 2
} }
if err := client.Sys().Remount(from, to, mountConfig); err != nil { if err := client.Sys().Remount(from, to); err != nil {
c.Ui.Error(fmt.Sprintf( c.Ui.Error(fmt.Sprintf(
"Unmount error: %s", err)) "Unmount error: %s", err))
return 2 return 2
@ -89,27 +63,11 @@ Usage: vault remount [options] from to
the Vault data associated with the backend will be preserved (such the Vault data associated with the backend will be preserved (such
as configuration data). as configuration data).
If the 'from' and 'to' values of the same, performs an in-place
remount. This allows you to change mount options.
Example: vault remount secret/ generic/ Example: vault remount secret/ generic/
General Options: General Options:
` + generalOptionsUsage() + ` ` + generalOptionsUsage()
Mount Options:
-default-lease-ttl=<duration> Default lease time-to-live for this backend.
If not specified, uses the global default, or
the previously set value. Set to '0' to
explicitly set it to use the global default.
-max-lease-ttl=<duration> Max lease time-to-live for this backend.
If not specified, uses the global default, or
the previously set value. Set to '0' to
explicitly set it to use the global default.
`
return strings.TrimSpace(helpText) return strings.TrimSpace(helpText)
} }

View File

@ -56,11 +56,8 @@ type Backend struct {
// See the built-in AuthRenew helpers in lease.go for common callbacks. // See the built-in AuthRenew helpers in lease.go for common callbacks.
AuthRenew OperationFunc AuthRenew OperationFunc
// System provides an interface to access certain system configuration
// information, such as globally configured default and max lease TTLs.
System logical.SystemView
logger *log.Logger logger *log.Logger
system logical.SystemView
once sync.Once once sync.Once
pathsRe []*regexp.Regexp pathsRe []*regexp.Regexp
} }
@ -146,7 +143,7 @@ func (b *Backend) SpecialPaths() *logical.Paths {
// Setup is used to initialize the backend with the initial backend configuration // Setup is used to initialize the backend with the initial backend configuration
func (b *Backend) Setup(config *logical.BackendConfig) (logical.Backend, error) { func (b *Backend) Setup(config *logical.BackendConfig) (logical.Backend, error) {
b.logger = config.Logger b.logger = config.Logger
b.System = config.System b.system = config.System
return b, nil return b, nil
} }
@ -160,6 +157,10 @@ func (b *Backend) Logger() *log.Logger {
return log.New(ioutil.Discard, "", 0) return log.New(ioutil.Discard, "", 0)
} }
func (b *Backend) System() logical.SystemView {
return b.system
}
// Route looks up the path that would be used for a given path string. // Route looks up the path that would be used for a given path string.
func (b *Backend) Route(path string) *Path { func (b *Backend) Route(path string) *Path {
result, _ := b.route(path) result, _ := b.route(path)

View File

@ -22,6 +22,10 @@ type Backend interface {
// ends in '*' then it is a prefix-based match. The '*' can only appear // ends in '*' then it is a prefix-based match. The '*' can only appear
// at the end. // at the end.
SpecialPaths() *Paths SpecialPaths() *Paths
// System provides an interface to access certain system configuration
// information, such as globally configured default and max lease TTLs.
System() SystemView
} }
// BackendConfig is provided to the factory to initialize the backend // BackendConfig is provided to the factory to initialize the backend

View File

@ -6,12 +6,12 @@ import "time"
// for logical backends to consume // for logical backends to consume
type SystemView interface { type SystemView interface {
// DefaultLeaseTTL returns the default lease TTL set in Vault configuration // DefaultLeaseTTL returns the default lease TTL set in Vault configuration
DefaultLeaseTTL() time.Duration DefaultLeaseTTL() (time.Duration, error)
// MaxLeaseTTL returns the max lease TTL set in Vault configuration; backend // MaxLeaseTTL returns the max lease TTL set in Vault configuration; backend
// authors should take care not to issue credentials that last longer than // authors should take care not to issue credentials that last longer than
// this value, as Vault will revoke them // this value, as Vault will revoke them
MaxLeaseTTL() time.Duration MaxLeaseTTL() (time.Duration, error)
} }
type StaticSystemView struct { type StaticSystemView struct {
@ -19,29 +19,10 @@ type StaticSystemView struct {
MaxLeaseTTLVal time.Duration MaxLeaseTTLVal time.Duration
} }
func (d *StaticSystemView) DefaultLeaseTTL() time.Duration { func (d StaticSystemView) DefaultLeaseTTL() (time.Duration, error) {
return d.DefaultLeaseTTLVal return d.DefaultLeaseTTLVal, nil
} }
func (d *StaticSystemView) MaxLeaseTTL() time.Duration { func (d StaticSystemView) MaxLeaseTTL() (time.Duration, error) {
return d.MaxLeaseTTLVal return d.MaxLeaseTTLVal, nil
}
type DynamicSystemView struct {
DefaultLeaseTTLVal **time.Duration
MaxLeaseTTLVal **time.Duration
}
func (d *DynamicSystemView) DefaultLeaseTTL() time.Duration {
if *d.DefaultLeaseTTLVal == nil {
return 0
}
return **d.DefaultLeaseTTLVal
}
func (d *DynamicSystemView) MaxLeaseTTL() time.Duration {
if *d.MaxLeaseTTLVal == nil {
return 0
}
return **d.MaxLeaseTTLVal
} }

View File

@ -478,7 +478,7 @@ func (c *Core) handleRequest(req *logical.Request) (retResp *logical.Response, r
// We exclude renewal of a lease, since it does not need to be re-registered // We exclude renewal of a lease, since it does not need to be re-registered
if resp != nil && resp.Secret != nil && !strings.HasPrefix(req.Path, "sys/renew/") { if resp != nil && resp.Secret != nil && !strings.HasPrefix(req.Path, "sys/renew/") {
// Get the SystemView for the mount // Get the SystemView for the mount
sysView, err := c.PathSysView(req.Path) sysView, err := c.sysViewByPath(req.Path)
if err != nil { if err != nil {
c.logger.Println(err) c.logger.Println(err)
return nil, auth, ErrInternalError return nil, auth, ErrInternalError
@ -486,12 +486,22 @@ func (c *Core) handleRequest(req *logical.Request) (retResp *logical.Response, r
// Apply the default lease if none given // Apply the default lease if none given
if resp.Secret.TTL == 0 { if resp.Secret.TTL == 0 {
resp.Secret.TTL = sysView.DefaultLeaseTTL() ttl, err := sysView.DefaultLeaseTTL()
if err != nil {
c.logger.Println(err)
return nil, auth, ErrInternalError
}
resp.Secret.TTL = ttl
} }
// Limit the lease duration // Limit the lease duration
if resp.Secret.TTL > sysView.MaxLeaseTTL() { maxTTL, err := sysView.MaxLeaseTTL()
resp.Secret.TTL = sysView.MaxLeaseTTL() if err != nil {
c.logger.Println(err)
return nil, auth, ErrInternalError
}
if resp.Secret.TTL > maxTTL {
resp.Secret.TTL = maxTTL
} }
// Register the lease // Register the lease

View File

@ -56,14 +56,36 @@ func NewSystemBackend(core *Core) logical.Backend {
}, },
&framework.Path{ &framework.Path{
Pattern: "mounts/(?P<path>.+)", Pattern: "mounts/(?P<path>.+?)/tune$",
Fields: map[string]*framework.FieldSchema{ Fields: map[string]*framework.FieldSchema{
"path": &framework.FieldSchema{ "path": &framework.FieldSchema{
Type: framework.TypeString, Type: framework.TypeString,
Description: strings.TrimSpace(sysHelp["mount_path"][0]), 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>.+?)",
Fields: map[string]*framework.FieldSchema{
"path": &framework.FieldSchema{
Type: framework.TypeString,
Description: strings.TrimSpace(sysHelp["mount_path"][0]),
},
"type": &framework.FieldSchema{ "type": &framework.FieldSchema{
Type: framework.TypeString, Type: framework.TypeString,
Description: strings.TrimSpace(sysHelp["mount_type"][0]), Description: strings.TrimSpace(sysHelp["mount_type"][0]),
@ -99,10 +121,6 @@ func NewSystemBackend(core *Core) logical.Backend {
Type: framework.TypeString, Type: framework.TypeString,
Description: strings.TrimSpace(sysHelp["remount_to"][0]), Description: strings.TrimSpace(sysHelp["remount_to"][0]),
}, },
"config": &framework.FieldSchema{
Type: framework.TypeMap,
Description: strings.TrimSpace(sysHelp["mount_config"][0]),
},
}, },
Callbacks: map[logical.Operation]framework.OperationFunc{ Callbacks: map[logical.Operation]framework.OperationFunc{
@ -449,20 +467,73 @@ func (b *SystemBackend) handleRemount(
logical.ErrInvalidRequest logical.ErrInvalidRequest
} }
var config MountConfig // Attempt remount
configMap := data.Get("config").(map[string]interface{}) if err := b.Core.remount(fromPath, toPath); err != nil {
if configMap != nil && len(configMap) != 0 { b.Backend.Logger().Printf("[ERR] sys: remount '%s' to '%s' failed: %v", fromPath, toPath, err)
err := mapstructure.Decode(configMap, &config) return handleError(err)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf(
"unable to convert given mount config information: %s", err)),
logical.ErrInvalidRequest
}
} }
// Attempt remount return nil, nil
if err := b.Core.remount(fromPath, toPath, config); err != nil { }
b.Backend.Logger().Printf("[ERR] sys: remount '%s' to '%s' failed: %v", fromPath, toPath, err)
// 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 handleError(err)
} }
@ -893,6 +964,10 @@ Change the mount point of an already-mounted backend.
"", "",
}, },
"mount_tune": {
"Tune backend configuration parameters for this mount.",
},
"renew": { "renew": {
"Renew a lease on a secret", "Renew a lease on a secret",
` `

View File

@ -40,6 +40,27 @@ var (
} }
) )
type dynamicSystemView struct {
core *Core
path string
}
func (d dynamicSystemView) DefaultLeaseTTL() (time.Duration, error) {
def, _, err := d.core.TTLsByPath(d.path)
if err != nil {
return 0, err
}
return def, nil
}
func (d dynamicSystemView) MaxLeaseTTL() (time.Duration, error) {
def, _, err := d.core.TTLsByPath(d.path)
if err != nil {
return 0, err
}
return def, nil
}
// MountTable is used to represent the internal mount table // MountTable is used to represent the internal mount table
type MountTable struct { type MountTable struct {
// This lock should be held whenever modifying the Entries field. // This lock should be held whenever modifying the Entries field.
@ -118,8 +139,8 @@ type MountEntry struct {
// MountConfig is used to hold settable options // MountConfig is used to hold settable options
type MountConfig struct { type MountConfig struct {
DefaultLeaseTTL *time.Duration `json:"default_lease_ttl" structs:"default_lease_ttl"` // Override for global default DefaultLeaseTTL *time.Duration `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"` // Override for global default
MaxLeaseTTL *time.Duration `json:"max_lease_ttl" structs:"max_lease_ttl"` // Override for global default MaxLeaseTTL *time.Duration `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"` // Override for global default
} }
// Returns a deep copy of the mount entry // Returns a deep copy of the mount entry
@ -165,7 +186,7 @@ func (c *Core) mount(me *MountEntry) error {
view := NewBarrierView(c.barrier, backendBarrierPrefix+me.UUID+"/") view := NewBarrierView(c.barrier, backendBarrierPrefix+me.UUID+"/")
// Create the new backend // Create the new backend
sysView, err := c.MountEntrySysView(me) sysView, err := c.mountEntrySysView(me)
if err != nil { if err != nil {
return err return err
} }
@ -283,7 +304,7 @@ func (c *Core) taintMountEntry(path string) error {
} }
// Remount is used to remount a path at a new mount point. // Remount is used to remount a path at a new mount point.
func (c *Core) remount(src, dst string, config MountConfig) error { func (c *Core) remount(src, dst string) error {
c.mounts.Lock() c.mounts.Lock()
defer c.mounts.Unlock() defer c.mounts.Unlock()
@ -302,48 +323,12 @@ func (c *Core) remount(src, dst string, config MountConfig) error {
} }
} }
inPlace := false
if src == dst {
inPlace = true
}
// Verify exact match of the route // Verify exact match of the route
match := c.router.MatchingMount(src) match := c.router.MatchingMount(src)
if match == "" || src != match { if match == "" || src != match {
return fmt.Errorf("no matching mount at '%s'", src) return fmt.Errorf("no matching mount at '%s'", src)
} }
// Verify there is no conflicting mount
if inPlace {
for _, ent := range c.mounts.Entries {
if ent.Path == src {
if config.DefaultLeaseTTL != nil {
if *config.DefaultLeaseTTL == 0 {
ent.Config.DefaultLeaseTTL = &c.defaultLeaseTTL
} else {
ent.Config.DefaultLeaseTTL = config.DefaultLeaseTTL
}
}
if config.MaxLeaseTTL != nil {
if *config.MaxLeaseTTL == 0 {
ent.Config.MaxLeaseTTL = &c.maxLeaseTTL
} else {
ent.Config.MaxLeaseTTL = config.MaxLeaseTTL
}
}
break
}
}
// Update the mount table
if err := c.persistMounts(c.mounts); err != nil {
return errors.New("failed to update mount table")
}
c.logger.Printf("[INFO] core: remounted '%s' in-place", src)
return nil
}
if match := c.router.MatchingMount(dst); match != "" { if match := c.router.MatchingMount(dst); match != "" {
return fmt.Errorf("existing mount at '%s'", match) return fmt.Errorf("existing mount at '%s'", match)
} }
@ -371,23 +356,10 @@ func (c *Core) remount(src, dst string, config MountConfig) error {
// Update the entry in the mount table // Update the entry in the mount table
newTable := c.mounts.Clone() newTable := c.mounts.Clone()
for _, ent := range newTable.Entries { for _, ent := range newTable.Entries {
//FIXME: Update systemview in each logical backend
if ent.Path == src { if ent.Path == src {
ent.Path = dst ent.Path = dst
ent.Tainted = false ent.Tainted = false
if config.DefaultLeaseTTL != nil {
if *config.DefaultLeaseTTL == 0 {
ent.Config.DefaultLeaseTTL = &c.defaultLeaseTTL
} else {
ent.Config.DefaultLeaseTTL = config.DefaultLeaseTTL
}
}
if config.MaxLeaseTTL != nil {
if *config.MaxLeaseTTL == 0 {
ent.Config.MaxLeaseTTL = &c.maxLeaseTTL
} else {
ent.Config.MaxLeaseTTL = config.MaxLeaseTTL
}
}
break break
} }
} }
@ -412,6 +384,67 @@ func (c *Core) remount(src, dst string, config MountConfig) error {
return nil return nil
} }
// tuneMount is used to set config on a mount point
func (c *Core) tuneMount(path string, config MountConfig) error {
// Ensure we end the path in a slash
if !strings.HasSuffix(path, "/") {
path += "/"
}
// Prevent protected paths from being changed
for _, p := range protectedMounts {
if strings.HasPrefix(path, p) {
return fmt.Errorf("cannot tune '%s'", path)
}
}
// Verify exact match of the route
match := c.router.MatchingMount(path)
if match == "" || path != match {
return fmt.Errorf("no matching mount at '%s'", path)
}
// Find and modify mount
for _, ent := range c.mounts.Entries {
if ent.Path == path {
if config.MaxLeaseTTL != nil {
if *config.MaxLeaseTTL == 0 {
*ent.Config.MaxLeaseTTL = 0
} else {
ent.Config.MaxLeaseTTL = config.MaxLeaseTTL
}
}
if config.DefaultLeaseTTL != nil {
if *ent.Config.MaxLeaseTTL == 0 {
if *config.DefaultLeaseTTL > c.maxLeaseTTL {
return fmt.Errorf("Given default lease TTL of %d greater than system default lease TTL of %d",
*config.DefaultLeaseTTL, c.maxLeaseTTL)
}
} else {
if *ent.Config.MaxLeaseTTL != 0 && *ent.Config.MaxLeaseTTL < *config.DefaultLeaseTTL {
return fmt.Errorf("Given default lease TTL of %d greater than backend max lease TTL of %d",
*config.DefaultLeaseTTL, *ent.Config.MaxLeaseTTL)
}
}
if *config.DefaultLeaseTTL == 0 {
*ent.Config.DefaultLeaseTTL = 0
} else {
ent.Config.DefaultLeaseTTL = config.DefaultLeaseTTL
}
}
break
}
}
// Update the mount table
if err := c.persistMounts(c.mounts); err != nil {
return errors.New("failed to update mount table")
}
c.logger.Printf("[INFO] core: tuned '%s'", path)
return nil
}
// loadMounts is invoked as part of postUnseal to load the mount table // loadMounts is invoked as part of postUnseal to load the mount table
func (c *Core) loadMounts() error { func (c *Core) loadMounts() error {
// Load the existing mount table // Load the existing mount table
@ -481,7 +514,7 @@ func (c *Core) setupMounts() error {
// Initialize the backend // Initialize the backend
// Create the new backend // Create the new backend
sysView, err := c.MountEntrySysView(entry) sysView, err := c.mountEntrySysView(entry)
if err != nil { if err != nil {
return err return err
} }
@ -542,32 +575,32 @@ func (c *Core) newLogicalBackend(t string, sysView logical.SystemView, view logi
return b, nil return b, nil
} }
// MountEntrySysView creates a logical.SystemView from global and // mountEntrySysView creates a logical.SystemView from global and
// mount-specific entries // mount-specific entries
func (c *Core) MountEntrySysView(me *MountEntry) (logical.SystemView, error) { func (c *Core) mountEntrySysView(me *MountEntry) (logical.SystemView, error) {
if me == nil { if me == nil {
return nil, fmt.Errorf("[ERR] core: nil MountEntry when generating SystemView") return nil, fmt.Errorf("[ERR] core: nil MountEntry when generating SystemView")
} }
sysDefaultLeaseTTLPtr := &c.defaultLeaseTTL sysView := dynamicSystemView{
sysMaxLeaseTTLPtr := &c.maxLeaseTTL core: c,
sysView := &logical.DynamicSystemView{ path: me.Path,
DefaultLeaseTTLVal: &sysDefaultLeaseTTLPtr,
MaxLeaseTTLVal: &sysMaxLeaseTTLPtr,
}
if me.Config.DefaultLeaseTTL != nil && *me.Config.DefaultLeaseTTL != 0 {
sysView.DefaultLeaseTTLVal = &me.Config.DefaultLeaseTTL
}
if me.Config.MaxLeaseTTL != nil && *me.Config.MaxLeaseTTL != 0 {
sysView.MaxLeaseTTLVal = &me.Config.MaxLeaseTTL
} }
return sysView, nil return sysView, nil
} }
// PathSysView is a simple helper for MountEntrySysView // sysViewByPath is a simple helper for MountEntrySysView
func (c *Core) PathSysView(path string) (logical.SystemView, error) { func (c *Core) sysViewByPath(path string) (logical.SystemView, error) {
me, err := c.mountEntryByPath(path)
if err != nil {
return nil, err
}
return c.mountEntrySysView(me)
}
// mountEntryByPath searches across all tables to find the MountEntry
func (c *Core) mountEntryByPath(path string) (*MountEntry, error) {
pathSep := strings.IndexRune(path, '/') pathSep := strings.IndexRune(path, '/')
if pathSep == -1 { if pathSep == -1 {
return nil, fmt.Errorf("[ERR] core: failed to find separator for path %s", path) return nil, fmt.Errorf("[ERR] core: failed to find separator for path %s", path)
@ -582,7 +615,28 @@ func (c *Core) PathSysView(path string) (logical.SystemView, error) {
if me == nil { if me == nil {
return nil, fmt.Errorf("[ERR] core: failed to find mount entry for path %s", path) return nil, fmt.Errorf("[ERR] core: failed to find mount entry for path %s", path)
} }
return c.MountEntrySysView(me) return me, nil
}
// TTLsByPath returns the default and max TTLs corresponding to a particular
// mount point, or the system default
func (c *Core) TTLsByPath(path string) (def, max time.Duration, retErr error) {
me, err := c.mountEntryByPath(path)
if err != nil {
return 0, 0, err
}
def = c.defaultLeaseTTL
max = c.maxLeaseTTL
if me.Config.DefaultLeaseTTL != nil && *me.Config.DefaultLeaseTTL != 0 {
def = *me.Config.DefaultLeaseTTL
}
if me.Config.MaxLeaseTTL != nil && *me.Config.MaxLeaseTTL != 0 {
max = *me.Config.MaxLeaseTTL
}
return
} }
// defaultMountTable creates a default mount table // defaultMountTable creates a default mount table

View File

@ -91,6 +91,15 @@ func (r *Router) Remount(src, dst string) error {
// Update the mount point // Update the mount point
r.root.Delete(src) r.root.Delete(src)
mountEntry, ok := raw.(*mountEntry)
if !ok {
return fmt.Errorf("Unable to retrieve mount entry at '%s'", src)
}
sysView := mountEntry.backend.System()
dynSysView, ok := sysView.(dynamicSystemView)
if ok {
dynSysView.path = dst
}
r.root.Insert(dst, raw) r.root.Insert(dst, raw)
return nil return nil
} }

View File

@ -5,6 +5,7 @@ import (
"strings" "strings"
"sync" "sync"
"testing" "testing"
"time"
"github.com/hashicorp/vault/helper/uuid" "github.com/hashicorp/vault/helper/uuid"
"github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical"
@ -41,6 +42,13 @@ func (n *NoopBackend) SpecialPaths() *logical.Paths {
} }
} }
func (n *NoopBackend) System() logical.SystemView {
return logical.StaticSystemView{
DefaultLeaseTTLVal: time.Hour * 24,
MaxLeaseTTLVal: time.Hour * 24 * 30,
}
}
func TestRouter_Mount(t *testing.T) { func TestRouter_Mount(t *testing.T) {
r := NewRouter() r := NewRouter()
_, barrier, _ := mockBarrier(t) _, barrier, _ := mockBarrier(t)

View File

@ -6,6 +6,7 @@ import (
"net" "net"
"os/exec" "os/exec"
"testing" "testing"
"time"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
@ -262,3 +263,10 @@ func (n *rawHTTP) HandleRequest(req *logical.Request) (*logical.Response, error)
func (n *rawHTTP) SpecialPaths() *logical.Paths { func (n *rawHTTP) SpecialPaths() *logical.Paths {
return &logical.Paths{Unauthenticated: []string{"*"}} return &logical.Paths{Unauthenticated: []string{"*"}}
} }
func (n *rawHTTP) System() logical.SystemView {
return logical.StaticSystemView{
DefaultLeaseTTLVal: time.Hour * 24,
MaxLeaseTTLVal: time.Hour * 24 * 30,
}
}

View File

@ -21,6 +21,9 @@ description: |-
<dt>Method</dt> <dt>Method</dt>
<dd>GET</dd> <dd>GET</dd>
<dt>URL</dt>
<dd>`/sys/mounts`</dd>
<dt>Parameters</dt> <dt>Parameters</dt>
<dd> <dd>
None None
@ -54,6 +57,40 @@ description: |-
</dd> </dd>
</dl> </dl>
<dl>
<dt>Description</dt>
<dd>
List the given secret backends configuration. `default_lease_ttl`
or `max_lease_ttl` values of `0` mean that the system
defaults are used by this backend.
</dd>
<dt>Method</dt>
<dd>GET</dd>
<dt>URL</dt>
<dd>`/sys/mounts/<mount point>/tune`</dd>
<dt>Parameters</dt>
<dd>
None
</dd>
<dt>Returns</dt>
<dd>
```javascript
{
"config": {
"default_lease_ttl": 0,
"max_lease_ttl": 0
}
}
```
</dd>
</dl>
## POST ## POST
<dl> <dl>
@ -99,6 +136,39 @@ description: |-
</dd> </dd>
</dl> </dl>
<dl>
<dt>Description</dt>
<dd>
Tune configuration parameters for a given mount point.
</dd>
<dt>Method</dt>
<dd>POST</dd>
<dt>URL</dt>
<dd>`/sys/mounts/<mount point>/tune`</dd>
<dt>Parameters</dt>
<dd>
<ul>
<li>
<span class="param">config</span>
<span class="param-flags">required</span>
Config options for this mount. This is an object with
two possible values: `default_lease_ttl` and
`max_lease_ttl`. These control the default and
maximum lease time-to-live, respectively. If set
on a specific mount, this overrides the global
defaults.
</li>
</ul>
</dd>
<dt>Returns</dt>
<dd>`204` response code.
</dd>
</dl>
## DELETE ## DELETE
<dl> <dl>

View File

@ -28,21 +28,8 @@ description: |-
<li> <li>
<span class="param">to</span> <span class="param">to</span>
<span class="param-flags">required</span> <span class="param-flags">required</span>
The new mount point. This can be the same The new mount point.
as `from` if you simply want to change
backend configuration with the `config`
parameter.
</li> </li>
<li>
<span class="param">config</span>
<span class="param-flags">optional</span>
Config options for this mount. This is an object with
two possible values: `default_lease_ttl` and
`max_lease_ttl`. These control the default and
maximum lease time-to-live, respectively. If set
on a specific mount, this overrides the global
defaults.
</li>
</ul> </ul>
</dd> </dd>