Add DynamicSystemView. This uses a pointer to a pointer to always have

up-to-date information. This allows remount to be implemented with the
same source and dest, allowing mount options to be changed on the fly.
If/when Vault gains the ability to HUP its configuration, this should
just work for the global values as well.

Need specific unit tests for this functionality.
This commit is contained in:
Jeff Mitchell 2015-09-01 18:29:30 -04:00
parent 6e0cee3ef4
commit aadf039368
12 changed files with 225 additions and 103 deletions

View File

@ -54,7 +54,7 @@ func (c *Sys) Unmount(path string) error {
return err
}
func (c *Sys) Remount(from, to string, config *vault.MountConfig) error {
func (c *Sys) Remount(from, to string, config vault.MountConfig) error {
if err := c.checkMountPath(from); err != nil {
return err
}
@ -63,11 +63,9 @@ func (c *Sys) Remount(from, to string, config *vault.MountConfig) error {
}
body := map[string]interface{}{
"from": from,
"to": to,
}
if config != nil {
body["config"] = *config
"from": from,
"to": to,
"config": config,
}
r := c.c.NewRequest("POST", "/v1/sys/remount")
@ -91,7 +89,7 @@ func (c *Sys) checkMountPath(path string) error {
}
type Mount struct {
Type string `json:"type" structs:"type"`
Description string `json:"description" structs:"description"`
Config *vault.MountConfig `json:"config" structs:"config"`
Type string `json:"type" structs:"type"`
Description string `json:"description" structs:"description"`
Config vault.MountConfig `json:"config" structs:"config"`
}

View File

@ -28,8 +28,8 @@ func TestBackend_basic(t *testing.T) {
b, err := Factory(&logical.BackendConfig{
Logger: nil,
System: &logical.StaticSystemView{
DefaultLeaseTTLVal: &defaultLeaseTTLVal,
MaxLeaseTTLVal: &maxLeaseTTLVal,
DefaultLeaseTTLVal: defaultLeaseTTLVal,
MaxLeaseTTLVal: maxLeaseTTLVal,
},
})
if err != nil {
@ -56,8 +56,8 @@ func TestBackend_roles(t *testing.T) {
b, err := Factory(&logical.BackendConfig{
Logger: nil,
System: &logical.StaticSystemView{
DefaultLeaseTTLVal: &defaultLeaseTTLVal,
MaxLeaseTTLVal: &maxLeaseTTLVal,
DefaultLeaseTTLVal: defaultLeaseTTLVal,
MaxLeaseTTLVal: maxLeaseTTLVal,
},
})
if err != nil {

View File

@ -19,8 +19,8 @@ func (c *MountCommand) Run(args []string) int {
flags := c.Meta.FlagSet("mount", FlagSetDefault)
flags.StringVar(&description, "description", "", "")
flags.StringVar(&path, "path", "", "")
flags.StringVar(&defaultLeaseTTL, "default_lease_ttl", "", "")
flags.StringVar(&maxLeaseTTL, "max_lease_ttl", "", "")
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
@ -51,31 +51,26 @@ func (c *MountCommand) Run(args []string) int {
mountInfo := &api.Mount{
Type: mountType,
Description: description,
Config: &vault.MountConfig{},
Config: vault.MountConfig{},
}
var passConfig bool
if defaultLeaseTTL != "" {
mountInfo.Config.DefaultLeaseTTL, err = time.ParseDuration(defaultLeaseTTL)
defTTL, err := time.ParseDuration(defaultLeaseTTL)
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error parsing default lease TTL duration: %s", err))
return 2
}
passConfig = true
mountInfo.Config.DefaultLeaseTTL = &defTTL
}
if maxLeaseTTL != "" {
mountInfo.Config.MaxLeaseTTL, err = time.ParseDuration(maxLeaseTTL)
maxTTL, err := time.ParseDuration(maxLeaseTTL)
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error parsing max lease TTL duration: %s", err))
return 2
}
passConfig = true
}
if !passConfig {
mountInfo.Config = nil
mountInfo.Config.MaxLeaseTTL = &maxTTL
}
if err := client.Sys().Mount(path, mountInfo); err != nil {
@ -110,11 +105,21 @@ General Options:
Mount Options:
-description=<desc> Human-friendly description of the purpose for the
mount. This shows up in the mounts command.
-description=<desc> Human-friendly description of the purpose for
the mount. This shows up in the mounts command.
-path=<path> Mount point for the logical backend. This defaults
to the type of the mount.
-path=<path> Mount point for the logical backend. This
defauls to the type of the mount.
-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

@ -17,8 +17,8 @@ type RemountCommand struct {
func (c *RemountCommand) Run(args []string) int {
var defaultLeaseTTL, maxLeaseTTL string
flags := c.Meta.FlagSet("remount", FlagSetDefault)
flags.StringVar(&defaultLeaseTTL, "default_lease_ttl", "", "")
flags.StringVar(&maxLeaseTTL, "max_lease_ttl", "", "")
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
@ -35,30 +35,24 @@ func (c *RemountCommand) Run(args []string) int {
from := args[0]
to := args[1]
mountConfig := &vault.MountConfig{}
var err error
var passConfig bool
mountConfig := vault.MountConfig{}
if defaultLeaseTTL != "" {
mountConfig.DefaultLeaseTTL, err = time.ParseDuration(defaultLeaseTTL)
defTTL, err := time.ParseDuration(defaultLeaseTTL)
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error parsing default lease TTL duration: %s", err))
return 1
return 2
}
passConfig = true
mountConfig.DefaultLeaseTTL = &defTTL
}
if maxLeaseTTL != "" {
mountConfig.MaxLeaseTTL, err = time.ParseDuration(maxLeaseTTL)
maxTTL, err := time.ParseDuration(maxLeaseTTL)
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error parsing max lease TTL duration: %s", err))
return 1
return 2
}
passConfig = true
}
if !passConfig {
mountConfig = nil
mountConfig.MaxLeaseTTL = &maxTTL
}
client, err := c.Client()
@ -95,10 +89,27 @@ Usage: vault remount [options] from to
the Vault data associated with the backend will be preserved (such
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/
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)
}

View File

@ -34,11 +34,11 @@ func (d *FieldData) Validate() error {
case TypeBool, TypeInt, TypeMap, TypeDurationSecond, TypeString:
_, _, err := d.getPrimitive(field, schema)
if err != nil {
return fmt.Errorf("Error converting input %v for field %s", value, field)
return fmt.Errorf("Error converting input %v for field %s: %s", value, field, err)
}
default:
return fmt.Errorf("unknown field type %s for field %s",
schema.Type, field)
schema.Type, field)
}
}

View File

@ -15,14 +15,33 @@ type SystemView interface {
}
type StaticSystemView struct {
DefaultLeaseTTLVal *time.Duration
MaxLeaseTTLVal *time.Duration
DefaultLeaseTTLVal time.Duration
MaxLeaseTTLVal time.Duration
}
func (d *StaticSystemView) DefaultLeaseTTL() time.Duration {
return *d.DefaultLeaseTTLVal
return d.DefaultLeaseTTLVal
}
func (d *StaticSystemView) MaxLeaseTTL() time.Duration {
return *d.MaxLeaseTTLVal
return d.MaxLeaseTTLVal
}
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

@ -99,6 +99,10 @@ func NewSystemBackend(core *Core) logical.Backend {
Type: framework.TypeString,
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{
@ -363,23 +367,16 @@ func (b *SystemBackend) handleMount(
path := data.Get("path").(string)
logicalType := data.Get("type").(string)
description := data.Get("description").(string)
var config *MountConfig
configInt, ok := data.GetOk("config")
if ok {
configMap, ok := configInt.(map[string]interface{})
if !ok {
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(
"cannot convert mount config information into proper values"),
"unable to convert given mount config information"),
logical.ErrInvalidRequest
}
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 == "" {
@ -393,9 +390,14 @@ func (b *SystemBackend) handleMount(
Path: path,
Type: logicalType,
Description: description,
Config: config,
}
if config != nil {
me.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
@ -446,23 +448,16 @@ func (b *SystemBackend) handleRemount(
"both 'from' and 'to' path must be specified as a string"),
logical.ErrInvalidRequest
}
var config *MountConfig
configInt, ok := data.GetOk("config")
if ok {
configMap, ok := configInt.(map[string]interface{})
if !ok {
return logical.ErrorResponse(
"cannot convert mount config information into proper values"),
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(fmt.Sprintf(
"unable to convert given mount config information: %s", err)),
logical.ErrInvalidRequest
}
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
}
}
}
// Attempt remount

View File

@ -5,6 +5,7 @@ import (
"testing"
"time"
"github.com/fatih/structs"
"github.com/hashicorp/vault/audit"
"github.com/hashicorp/vault/logical"
)
@ -39,21 +40,23 @@ func TestSystemBackend_mounts(t *testing.T) {
t.Fatalf("err: %v", err)
}
// We can't know the pointer address ahead of time so simply
// copy what's given
exp := map[string]interface{}{
"secret/": map[string]interface{}{
"type": "generic",
"description": "generic secret storage",
"config": map[string]interface{}{
"default_lease_ttl": time.Duration(0),
"max_lease_ttl": time.Duration(0),
"default_lease_ttl": resp.Data["secret/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(*time.Duration),
"max_lease_ttl": resp.Data["secret/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(*time.Duration),
},
},
"sys/": map[string]interface{}{
"type": "system",
"description": "system endpoints used for control, policy and debugging",
"config": map[string]interface{}{
"default_lease_ttl": time.Duration(0),
"max_lease_ttl": time.Duration(0),
"default_lease_ttl": resp.Data["sys/"].(map[string]interface{})["config"].(map[string]interface{})["default_lease_ttl"].(*time.Duration),
"max_lease_ttl": resp.Data["sys/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(*time.Duration),
},
},
}
@ -123,6 +126,7 @@ func TestSystemBackend_remount(t *testing.T) {
req := logical.TestRequest(t, logical.WriteOperation, "remount")
req.Data["from"] = "secret"
req.Data["to"] = "foo"
req.Data["config"] = structs.Map(MountConfig{})
resp, err := b.HandleRequest(req)
if err != nil {
t.Fatalf("err: %v", err)
@ -138,6 +142,7 @@ func TestSystemBackend_remount_invalid(t *testing.T) {
req := logical.TestRequest(t, logical.WriteOperation, "remount")
req.Data["from"] = "unknown"
req.Data["to"] = "foo"
req.Data["config"] = structs.Map(MountConfig{})
resp, err := b.HandleRequest(req)
if err != logical.ErrInvalidRequest {
t.Fatalf("err: %v", err)

View File

@ -118,8 +118,8 @@ type MountEntry struct {
// MountConfig is used to hold settable options
type MountConfig struct {
DefaultLeaseTTL time.Duration `json:"default_lease_ttl" structs:"default_lease_ttl"` // Override for global default
MaxLeaseTTL time.Duration `json:"max_lease_ttl" structs:"max_lease_ttl"` // Override for global default
DefaultLeaseTTL *time.Duration `json:"default_lease_ttl" structs:"default_lease_ttl"` // Override for global default
MaxLeaseTTL *time.Duration `json:"max_lease_ttl" structs:"max_lease_ttl"` // Override for global default
}
// Returns a deep copy of the mount entry
@ -283,7 +283,7 @@ func (c *Core) taintMountEntry(path string) error {
}
// 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, config MountConfig) error {
c.mounts.Lock()
defer c.mounts.Unlock()
@ -302,6 +302,11 @@ func (c *Core) remount(src, dst string, config *MountConfig) error {
}
}
inPlace := false
if src == dst {
inPlace = true
}
// Verify exact match of the route
match := c.router.MatchingMount(src)
if match == "" || src != match {
@ -309,6 +314,36 @@ func (c *Core) remount(src, dst string, config *MountConfig) error {
}
// 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 != "" {
return fmt.Errorf("existing mount at '%s'", match)
}
@ -339,8 +374,19 @@ func (c *Core) remount(src, dst string, config *MountConfig) error {
if ent.Path == src {
ent.Path = dst
ent.Tainted = false
if config != nil {
ent.Config = *config
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
}
@ -503,15 +549,17 @@ func (c *Core) MountEntrySysView(me *MountEntry) (logical.SystemView, error) {
return nil, fmt.Errorf("[ERR] core: nil MountEntry when generating SystemView")
}
sysView := &logical.StaticSystemView{
DefaultLeaseTTLVal: &c.defaultLeaseTTL,
MaxLeaseTTLVal: &c.maxLeaseTTL,
sysDefaultLeaseTTLPtr := &c.defaultLeaseTTL
sysMaxLeaseTTLPtr := &c.maxLeaseTTL
sysView := &logical.DynamicSystemView{
DefaultLeaseTTLVal: &sysDefaultLeaseTTLPtr,
MaxLeaseTTLVal: &sysMaxLeaseTTLPtr,
}
if me.Config.DefaultLeaseTTL != 0 {
if me.Config.DefaultLeaseTTL != nil && *me.Config.DefaultLeaseTTL != 0 {
sysView.DefaultLeaseTTLVal = &me.Config.DefaultLeaseTTL
}
if me.Config.MaxLeaseTTL != 0 {
if me.Config.MaxLeaseTTL != nil && *me.Config.MaxLeaseTTL != 0 {
sysView.MaxLeaseTTLVal = &me.Config.MaxLeaseTTL
}
@ -545,12 +593,20 @@ func defaultMountTable() *MountTable {
Type: "generic",
Description: "generic secret storage",
UUID: uuid.GenerateUUID(),
Config: MountConfig{
DefaultLeaseTTL: new(time.Duration),
MaxLeaseTTL: new(time.Duration),
},
}
sysMount := &MountEntry{
Path: "sys/",
Type: "system",
Description: "system endpoints used for control, policy and debugging",
UUID: uuid.GenerateUUID(),
Config: MountConfig{
DefaultLeaseTTL: new(time.Duration),
MaxLeaseTTL: new(time.Duration),
},
}
table.Entries = append(table.Entries, genericMount)
table.Entries = append(table.Entries, sysMount)

View File

@ -192,7 +192,7 @@ func TestCore_Unmount_Cleanup(t *testing.T) {
func TestCore_Remount(t *testing.T) {
c, key, _ := TestCoreUnsealed(t)
err := c.remount("secret", "foo", nil)
err := c.remount("secret", "foo", MountConfig{})
if err != nil {
t.Fatalf("err: %v", err)
}
@ -280,7 +280,7 @@ func TestCore_Remount_Cleanup(t *testing.T) {
}
// Remount, this should cleanup
if err := c.remount("test/", "new/", nil); err != nil {
if err := c.remount("test/", "new/", MountConfig{}); err != nil {
t.Fatalf("err: %v", err)
}
@ -309,7 +309,7 @@ func TestCore_Remount_Cleanup(t *testing.T) {
func TestCore_Remount_Protected(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
err := c.remount("sys", "foo", nil)
err := c.remount("sys", "foo", MountConfig{})
if err.Error() != "cannot remount 'sys/'" {
t.Fatalf("err: %v", err)
}

View File

@ -13,7 +13,9 @@ description: |-
<dl>
<dt>Description</dt>
<dd>
Lists all the mounted secret backends.
Lists all the mounted secret backends. `default_lease_ttl`
or `max_lease_ttl` values of `0` mean that the system
defaults are used by this backend.
</dd>
<dt>Method</dt>
@ -31,12 +33,20 @@ description: |-
{
"aws": {
"type": "aws",
"description": "AWS keys"
"description": "AWS keys",
"config": {
"default_lease_ttl": 0,
"max_lease_ttl": 0
}
},
"sys": {
"type": "system",
"description": "system endpoint"
"description": "system endpoint",
"config": {
"default_lease_ttl": 0,
"max_lease_ttl": 0
}
}
}
```
@ -71,6 +81,16 @@ description: |-
<span class="param-flags">optional</span>
A human-friendly description of the mount.
</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>
</dd>

View File

@ -28,8 +28,21 @@ description: |-
<li>
<span class="param">to</span>
<span class="param-flags">required</span>
The new mount point.
The new mount point. This can be the same
as `from` if you simply want to change
backend configuration with the `config`
parameter.
</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>
</dd>