diff --git a/api/sys_mounts.go b/api/sys_mounts.go index ee4848b3d..c3dab5d54 100644 --- a/api/sys_mounts.go +++ b/api/sys_mounts.go @@ -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) error { if err := c.checkMountPath(from); err != nil { return err } @@ -63,9 +63,8 @@ func (c *Sys) Remount(from, to string, config vault.MountConfig) error { } body := map[string]interface{}{ - "from": from, - "to": to, - "config": config, + "from": from, + "to": to, } 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 } +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 { if path[0] == '/' { return fmt.Errorf("path must not start with /: %s", path) diff --git a/builtin/logical/pki/path_issue.go b/builtin/logical/pki/path_issue.go index 7429643c0..97c2986f8 100644 --- a/builtin/logical/pki/path_issue.go +++ b/builtin/logical/pki/path_issue.go @@ -114,7 +114,10 @@ func (b *backend) pathIssueCert( var ttl time.Duration 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 { ttl, err = time.ParseDuration(ttlField) if err != nil { @@ -125,7 +128,10 @@ func (b *backend) pathIssueCert( var maxTTL time.Duration 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 { maxTTL, err = time.ParseDuration(role.MaxTTL) if err != nil { diff --git a/builtin/logical/pki/path_roles.go b/builtin/logical/pki/path_roles.go index 21eacfe7a..d5117358f 100644 --- a/builtin/logical/pki/path_roles.go +++ b/builtin/logical/pki/path_roles.go @@ -256,10 +256,13 @@ func (b *backend) pathRoleCreate( if len(entry.MaxTTL) == 0 { entry.MaxTTL = data.Get("lease_max").(string) } - 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 { - maxTTL = b.System.MaxLeaseTTL() + maxTTL = maxSystemTTL } else { maxTTL, err = time.ParseDuration(entry.MaxTTL) if err != nil { @@ -267,18 +270,18 @@ func (b *backend) pathRoleCreate( "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 } - var ttl time.Duration if len(entry.TTL) == 0 { entry.TTL = data.Get("lease").(string) } - switch len(entry.TTL) { - case 0: - ttl = b.System.DefaultLeaseTTL() - default: + ttl, err := b.System().DefaultLeaseTTL() + if err != nil { + return nil, fmt.Errorf("Error fetching max TTL: %s", err) + } + if len(entry.TTL) != 0 { ttl, err = time.ParseDuration(entry.TTL) if err != nil { return logical.ErrorResponse(fmt.Sprintf( diff --git a/cli/commands.go b/cli/commands.go index ea62802ce..dabcca873 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -226,6 +226,12 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory { }, nil }, + "mount-tune": func() (cli.Command, error) { + return &command.MountTuneCommand{ + Meta: meta, + }, nil + }, + "remount": func() (cli.Command, error) { return &command.RemountCommand{ Meta: meta, diff --git a/command/mounts.go b/command/mounts.go index fcdc1648b..9c2462495 100644 --- a/command/mounts.go +++ b/command/mounts.go @@ -35,17 +35,17 @@ func (c *MountsCommand) Run(args []string) int { } paths := make([]string, 0, len(mounts)) - for path, _ := range mounts { + for path := range mounts { paths = append(paths, path) } sort.Strings(paths) - columns := []string{"Path | Type | Description"} + columns := []string{"Path | Type | Default TTL | Max TTL | Description"} for _, path := range paths { mount := mounts[path] 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)) @@ -62,8 +62,9 @@ Usage: vault mounts [options] Outputs information about the mounted backends. - This command lists the mounted backends, their mount points, and - a human-friendly description of the mount point. + This command lists the mounted backends, their mount points, the + 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: diff --git a/command/mounttune.go b/command/mounttune.go new file mode 100644 index 000000000..83211c40c --- /dev/null +++ b/command/mounttune.go @@ -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= 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= 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) +} diff --git a/command/remount.go b/command/remount.go index 491c1075b..ebf3eb517 100644 --- a/command/remount.go +++ b/command/remount.go @@ -3,9 +3,6 @@ package command import ( "fmt" "strings" - "time" - - "github.com/hashicorp/vault/vault" ) // RemountCommand is a Command that remounts a mounted secret backend @@ -15,10 +12,7 @@ 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.Usage = func() { c.Ui.Error(c.Help()) } if err := flags.Parse(args); err != nil { return 1 @@ -35,26 +29,6 @@ func (c *RemountCommand) Run(args []string) int { from := args[0] 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() if err != nil { c.Ui.Error(fmt.Sprintf( @@ -62,7 +36,7 @@ func (c *RemountCommand) Run(args []string) int { 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( "Unmount error: %s", err)) return 2 @@ -89,27 +63,11 @@ 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= 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= 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) } diff --git a/logical/framework/backend.go b/logical/framework/backend.go index 4ebda31b9..853a8b869 100644 --- a/logical/framework/backend.go +++ b/logical/framework/backend.go @@ -56,11 +56,8 @@ type Backend struct { // See the built-in AuthRenew helpers in lease.go for common callbacks. 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 + system logical.SystemView once sync.Once 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 func (b *Backend) Setup(config *logical.BackendConfig) (logical.Backend, error) { b.logger = config.Logger - b.System = config.System + b.system = config.System return b, nil } @@ -160,6 +157,10 @@ func (b *Backend) Logger() *log.Logger { 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. func (b *Backend) Route(path string) *Path { result, _ := b.route(path) diff --git a/logical/logical.go b/logical/logical.go index cd0a677d7..17c949a98 100644 --- a/logical/logical.go +++ b/logical/logical.go @@ -22,6 +22,10 @@ type Backend interface { // ends in '*' then it is a prefix-based match. The '*' can only appear // at the end. 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 diff --git a/logical/system_view.go b/logical/system_view.go index f1eac8d2a..444944887 100644 --- a/logical/system_view.go +++ b/logical/system_view.go @@ -6,12 +6,12 @@ import "time" // for logical backends to consume type SystemView interface { // 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 // authors should take care not to issue credentials that last longer than // this value, as Vault will revoke them - MaxLeaseTTL() time.Duration + MaxLeaseTTL() (time.Duration, error) } type StaticSystemView struct { @@ -19,29 +19,10 @@ type StaticSystemView struct { MaxLeaseTTLVal time.Duration } -func (d *StaticSystemView) DefaultLeaseTTL() time.Duration { - return d.DefaultLeaseTTLVal +func (d StaticSystemView) DefaultLeaseTTL() (time.Duration, error) { + return d.DefaultLeaseTTLVal, nil } -func (d *StaticSystemView) MaxLeaseTTL() time.Duration { - 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 +func (d StaticSystemView) MaxLeaseTTL() (time.Duration, error) { + return d.MaxLeaseTTLVal, nil } diff --git a/vault/core.go b/vault/core.go index 7546ff500..82b85a2b6 100644 --- a/vault/core.go +++ b/vault/core.go @@ -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 if resp != nil && resp.Secret != nil && !strings.HasPrefix(req.Path, "sys/renew/") { // Get the SystemView for the mount - sysView, err := c.PathSysView(req.Path) + sysView, err := c.sysViewByPath(req.Path) if err != nil { c.logger.Println(err) 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 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 - if resp.Secret.TTL > sysView.MaxLeaseTTL() { - resp.Secret.TTL = sysView.MaxLeaseTTL() + maxTTL, err := 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 diff --git a/vault/logical_system.go b/vault/logical_system.go index 3ae8e82cf..98440cee0 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -56,14 +56,36 @@ func NewSystemBackend(core *Core) logical.Backend { }, &framework.Path{ - Pattern: "mounts/(?P.+)", + Pattern: "mounts/(?P.+?)/tune$", 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.+?)", + + Fields: map[string]*framework.FieldSchema{ + "path": &framework.FieldSchema{ + Type: framework.TypeString, + Description: strings.TrimSpace(sysHelp["mount_path"][0]), + }, "type": &framework.FieldSchema{ Type: framework.TypeString, Description: strings.TrimSpace(sysHelp["mount_type"][0]), @@ -99,10 +121,6 @@ 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{ @@ -449,20 +467,73 @@ func (b *SystemBackend) handleRemount( logical.ErrInvalidRequest } - 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 - } + // 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) } - // Attempt remount - 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) + 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) } @@ -893,6 +964,10 @@ Change the mount point of an already-mounted backend. "", }, + "mount_tune": { + "Tune backend configuration parameters for this mount.", + }, + "renew": { "Renew a lease on a secret", ` diff --git a/vault/mount.go b/vault/mount.go index e2800ca5a..0fd108d19 100644 --- a/vault/mount.go +++ b/vault/mount.go @@ -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 type MountTable struct { // This lock should be held whenever modifying the Entries field. @@ -118,8 +139,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" mapstructure:"default_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 @@ -165,7 +186,7 @@ func (c *Core) mount(me *MountEntry) error { view := NewBarrierView(c.barrier, backendBarrierPrefix+me.UUID+"/") // Create the new backend - sysView, err := c.MountEntrySysView(me) + sysView, err := c.mountEntrySysView(me) if err != nil { 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. -func (c *Core) remount(src, dst string, config MountConfig) error { +func (c *Core) remount(src, dst string) error { c.mounts.Lock() 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 match := c.router.MatchingMount(src) if match == "" || src != match { 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 != "" { 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 newTable := c.mounts.Clone() for _, ent := range newTable.Entries { + //FIXME: Update systemview in each logical backend if ent.Path == src { ent.Path = dst 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 } } @@ -412,6 +384,67 @@ func (c *Core) remount(src, dst string, config MountConfig) error { 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 func (c *Core) loadMounts() error { // Load the existing mount table @@ -481,7 +514,7 @@ func (c *Core) setupMounts() error { // Initialize the backend // Create the new backend - sysView, err := c.MountEntrySysView(entry) + sysView, err := c.mountEntrySysView(entry) if err != nil { return err } @@ -542,32 +575,32 @@ func (c *Core) newLogicalBackend(t string, sysView logical.SystemView, view logi return b, nil } -// MountEntrySysView creates a logical.SystemView from global and +// mountEntrySysView creates a logical.SystemView from global and // mount-specific entries -func (c *Core) MountEntrySysView(me *MountEntry) (logical.SystemView, error) { +func (c *Core) mountEntrySysView(me *MountEntry) (logical.SystemView, error) { if me == nil { return nil, fmt.Errorf("[ERR] core: nil MountEntry when generating SystemView") } - sysDefaultLeaseTTLPtr := &c.defaultLeaseTTL - sysMaxLeaseTTLPtr := &c.maxLeaseTTL - sysView := &logical.DynamicSystemView{ - 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 + sysView := dynamicSystemView{ + core: c, + path: me.Path, } return sysView, nil } -// PathSysView is a simple helper for MountEntrySysView -func (c *Core) PathSysView(path string) (logical.SystemView, error) { +// sysViewByPath is a simple helper for MountEntrySysView +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, '/') if pathSep == -1 { 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 { 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 diff --git a/vault/router.go b/vault/router.go index aef44b674..56ab811b9 100644 --- a/vault/router.go +++ b/vault/router.go @@ -91,6 +91,15 @@ func (r *Router) Remount(src, dst string) error { // Update the mount point 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) return nil } diff --git a/vault/router_test.go b/vault/router_test.go index a502dc995..94450c9be 100644 --- a/vault/router_test.go +++ b/vault/router_test.go @@ -5,6 +5,7 @@ import ( "strings" "sync" "testing" + "time" "github.com/hashicorp/vault/helper/uuid" "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) { r := NewRouter() _, barrier, _ := mockBarrier(t) diff --git a/vault/testing.go b/vault/testing.go index 73bf9ecbc..a4b2b085a 100644 --- a/vault/testing.go +++ b/vault/testing.go @@ -6,6 +6,7 @@ import ( "net" "os/exec" "testing" + "time" "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 { return &logical.Paths{Unauthenticated: []string{"*"}} } + +func (n *rawHTTP) System() logical.SystemView { + return logical.StaticSystemView{ + DefaultLeaseTTLVal: time.Hour * 24, + MaxLeaseTTLVal: time.Hour * 24 * 30, + } +} diff --git a/website/source/docs/http/sys-mounts.html.md b/website/source/docs/http/sys-mounts.html.md index b34e5c64d..2116eabf6 100644 --- a/website/source/docs/http/sys-mounts.html.md +++ b/website/source/docs/http/sys-mounts.html.md @@ -21,6 +21,9 @@ description: |-
Method
GET
+
URL
+
`/sys/mounts`
+
Parameters
None @@ -54,6 +57,40 @@ description: |-
+
+
Description
+
+ 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. +
+ +
Method
+
GET
+ +
URL
+
`/sys/mounts//tune`
+ +
Parameters
+
+ None +
+ +
Returns
+
+ + ```javascript + { + "config": { + "default_lease_ttl": 0, + "max_lease_ttl": 0 + } + } + ``` + +
+
+ ## POST
@@ -99,6 +136,39 @@ description: |-
+
+
Description
+
+ Tune configuration parameters for a given mount point. +
+ +
Method
+
POST
+ +
URL
+
`/sys/mounts//tune`
+ +
Parameters
+
+
    +
  • + config + required + 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. +
  • +
+
+ +
Returns
+
`204` response code. +
+
+ ## DELETE
diff --git a/website/source/docs/http/sys-remount.html.md b/website/source/docs/http/sys-remount.html.md index 22e5141dd..231e77092 100644 --- a/website/source/docs/http/sys-remount.html.md +++ b/website/source/docs/http/sys-remount.html.md @@ -28,21 +28,8 @@ description: |-
  • to required - The new mount point. This can be the same - as `from` if you simply want to change - backend configuration with the `config` - parameter. + The new mount point.
  • -
  • - config - optional - 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. -