Plumb per-mount config options through API
This commit is contained in:
parent
893d2d9b00
commit
696d0c7b1d
|
@ -2,6 +2,9 @@ package api
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fatih/structs"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
)
|
||||
|
||||
func (c *Sys) ListMounts() (map[string]*Mount, error) {
|
||||
|
@ -17,15 +20,12 @@ func (c *Sys) ListMounts() (map[string]*Mount, error) {
|
|||
return result, err
|
||||
}
|
||||
|
||||
func (c *Sys) Mount(path, mountType, description string) error {
|
||||
func (c *Sys) Mount(path string, mountInfo *Mount) error {
|
||||
if err := c.checkMountPath(path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body := map[string]string{
|
||||
"type": mountType,
|
||||
"description": description,
|
||||
}
|
||||
body := structs.Map(mountInfo)
|
||||
|
||||
r := c.c.NewRequest("POST", fmt.Sprintf("/v1/sys/mounts/%s", path))
|
||||
if err := r.SetJSONBody(body); err != nil {
|
||||
|
@ -54,7 +54,7 @@ func (c *Sys) Unmount(path string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func (c *Sys) Remount(from, to string) error {
|
||||
func (c *Sys) Remount(from, to string, config *vault.MountConfig) error {
|
||||
if err := c.checkMountPath(from); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -62,10 +62,13 @@ func (c *Sys) Remount(from, to string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
body := map[string]string{
|
||||
body := map[string]interface{}{
|
||||
"from": from,
|
||||
"to": to,
|
||||
}
|
||||
if config != nil {
|
||||
body["config"] = *config
|
||||
}
|
||||
|
||||
r := c.c.NewRequest("POST", "/v1/sys/remount")
|
||||
if err := r.SetJSONBody(body); err != nil {
|
||||
|
@ -88,6 +91,7 @@ func (c *Sys) checkMountPath(path string) error {
|
|||
}
|
||||
|
||||
type Mount struct {
|
||||
Type string
|
||||
Description string
|
||||
Type string `json:"type" structs:"type"`
|
||||
Description string `json:"description" structs:"description"`
|
||||
Config *vault.MountConfig `json:"config" structs:"config"`
|
||||
}
|
||||
|
|
|
@ -3,6 +3,10 @@ package command
|
|||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
)
|
||||
|
||||
// MountCommand is a Command that mounts a new mount.
|
||||
|
@ -11,10 +15,12 @@ type MountCommand struct {
|
|||
}
|
||||
|
||||
func (c *MountCommand) Run(args []string) int {
|
||||
var description, path string
|
||||
var description, path, defaultLeaseTTL, maxLeaseTTL string
|
||||
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.Usage = func() { c.Ui.Error(c.Help()) }
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return 1
|
||||
|
@ -42,7 +48,37 @@ func (c *MountCommand) Run(args []string) int {
|
|||
return 2
|
||||
}
|
||||
|
||||
if err := client.Sys().Mount(path, mountType, description); err != nil {
|
||||
mountInfo := &api.Mount{
|
||||
Type: mountType,
|
||||
Description: description,
|
||||
Config: &vault.MountConfig{},
|
||||
}
|
||||
|
||||
var passConfig bool
|
||||
if defaultLeaseTTL != "" {
|
||||
mountInfo.Config.DefaultLeaseTTL, err = time.ParseDuration(defaultLeaseTTL)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"Error parsing default lease TTL duration: %s", err))
|
||||
return 2
|
||||
}
|
||||
passConfig = true
|
||||
}
|
||||
if maxLeaseTTL != "" {
|
||||
mountInfo.Config.MaxLeaseTTL, 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
|
||||
}
|
||||
|
||||
if err := client.Sys().Mount(path, mountInfo); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"Mount error: %s", err))
|
||||
return 2
|
||||
|
|
|
@ -3,6 +3,9 @@ package command
|
|||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/vault"
|
||||
)
|
||||
|
||||
// RemountCommand is a Command that remounts a mounted secret backend
|
||||
|
@ -12,7 +15,10 @@ 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
|
||||
|
@ -29,6 +35,32 @@ func (c *RemountCommand) Run(args []string) int {
|
|||
from := args[0]
|
||||
to := args[1]
|
||||
|
||||
mountConfig := &vault.MountConfig{}
|
||||
var err error
|
||||
var passConfig bool
|
||||
if defaultLeaseTTL != "" {
|
||||
mountConfig.DefaultLeaseTTL, err = time.ParseDuration(defaultLeaseTTL)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"Error parsing default lease TTL duration: %s", err))
|
||||
return 1
|
||||
}
|
||||
passConfig = true
|
||||
}
|
||||
if maxLeaseTTL != "" {
|
||||
mountConfig.MaxLeaseTTL, err = time.ParseDuration(maxLeaseTTL)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"Error parsing max lease TTL duration: %s", err))
|
||||
return 1
|
||||
}
|
||||
passConfig = true
|
||||
}
|
||||
|
||||
if !passConfig {
|
||||
mountConfig = nil
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
|
@ -36,7 +68,7 @@ func (c *RemountCommand) Run(args []string) int {
|
|||
return 2
|
||||
}
|
||||
|
||||
if err := client.Sys().Remount(from, to); err != nil {
|
||||
if err := client.Sys().Remount(from, to, mountConfig); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"Unmount error: %s", err))
|
||||
return 2
|
||||
|
|
|
@ -34,16 +34,24 @@ func TestSysMounts_headerAuth(t *testing.T) {
|
|||
"secret/": map[string]interface{}{
|
||||
"description": "generic secret storage",
|
||||
"type": "generic",
|
||||
"config": map[string]interface{}{
|
||||
"default_lease_ttl": float64(0),
|
||||
"max_lease_ttl": float64(0),
|
||||
},
|
||||
},
|
||||
"sys/": map[string]interface{}{
|
||||
"description": "system endpoints used for control, policy and debugging",
|
||||
"type": "system",
|
||||
"config": map[string]interface{}{
|
||||
"default_lease_ttl": float64(0),
|
||||
"max_lease_ttl": float64(0),
|
||||
},
|
||||
},
|
||||
}
|
||||
testResponseStatus(t, resp, 200)
|
||||
testResponseBody(t, resp, &actual)
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
t.Fatalf("bad:\nExpected: %#v\nActual: %#v\n", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,10 +20,18 @@ func TestSysMounts(t *testing.T) {
|
|||
"secret/": map[string]interface{}{
|
||||
"description": "generic secret storage",
|
||||
"type": "generic",
|
||||
"config": map[string]interface{}{
|
||||
"default_lease_ttl": float64(0),
|
||||
"max_lease_ttl": float64(0),
|
||||
},
|
||||
},
|
||||
"sys/": map[string]interface{}{
|
||||
"description": "system endpoints used for control, policy and debugging",
|
||||
"type": "system",
|
||||
"config": map[string]interface{}{
|
||||
"default_lease_ttl": float64(0),
|
||||
"max_lease_ttl": float64(0),
|
||||
},
|
||||
},
|
||||
}
|
||||
testResponseStatus(t, resp, 200)
|
||||
|
@ -52,14 +60,26 @@ func TestSysMount(t *testing.T) {
|
|||
"foo/": map[string]interface{}{
|
||||
"description": "foo",
|
||||
"type": "generic",
|
||||
"config": map[string]interface{}{
|
||||
"default_lease_ttl": float64(0),
|
||||
"max_lease_ttl": float64(0),
|
||||
},
|
||||
},
|
||||
"secret/": map[string]interface{}{
|
||||
"description": "generic secret storage",
|
||||
"type": "generic",
|
||||
"config": map[string]interface{}{
|
||||
"default_lease_ttl": float64(0),
|
||||
"max_lease_ttl": float64(0),
|
||||
},
|
||||
},
|
||||
"sys/": map[string]interface{}{
|
||||
"description": "system endpoints used for control, policy and debugging",
|
||||
"type": "system",
|
||||
"config": map[string]interface{}{
|
||||
"default_lease_ttl": float64(0),
|
||||
"max_lease_ttl": float64(0),
|
||||
},
|
||||
},
|
||||
}
|
||||
testResponseStatus(t, resp, 200)
|
||||
|
@ -110,14 +130,26 @@ func TestSysRemount(t *testing.T) {
|
|||
"bar/": map[string]interface{}{
|
||||
"description": "foo",
|
||||
"type": "generic",
|
||||
"config": map[string]interface{}{
|
||||
"default_lease_ttl": float64(0),
|
||||
"max_lease_ttl": float64(0),
|
||||
},
|
||||
},
|
||||
"secret/": map[string]interface{}{
|
||||
"description": "generic secret storage",
|
||||
"type": "generic",
|
||||
"config": map[string]interface{}{
|
||||
"default_lease_ttl": float64(0),
|
||||
"max_lease_ttl": float64(0),
|
||||
},
|
||||
},
|
||||
"sys/": map[string]interface{}{
|
||||
"description": "system endpoints used for control, policy and debugging",
|
||||
"type": "system",
|
||||
"config": map[string]interface{}{
|
||||
"default_lease_ttl": float64(0),
|
||||
"max_lease_ttl": float64(0),
|
||||
},
|
||||
},
|
||||
}
|
||||
testResponseStatus(t, resp, 200)
|
||||
|
@ -149,10 +181,18 @@ func TestSysUnmount(t *testing.T) {
|
|||
"secret/": map[string]interface{}{
|
||||
"description": "generic secret storage",
|
||||
"type": "generic",
|
||||
"config": map[string]interface{}{
|
||||
"default_lease_ttl": float64(0),
|
||||
"max_lease_ttl": float64(0),
|
||||
},
|
||||
},
|
||||
"sys/": map[string]interface{}{
|
||||
"description": "system endpoints used for control, policy and debugging",
|
||||
"type": "system",
|
||||
"config": map[string]interface{}{
|
||||
"default_lease_ttl": float64(0),
|
||||
"max_lease_ttl": float64(0),
|
||||
},
|
||||
},
|
||||
}
|
||||
testResponseStatus(t, resp, 200)
|
||||
|
|
|
@ -167,7 +167,11 @@ func Test(t TestT, c TestCase) {
|
|||
|
||||
// Mount the backend
|
||||
prefix := "mnt"
|
||||
if err := client.Sys().Mount(prefix, "test", "acceptance test"); err != nil {
|
||||
mountInfo := &api.Mount{
|
||||
Type: "test",
|
||||
Description: "acceptance test",
|
||||
}
|
||||
if err := client.Sys().Mount(prefix, mountInfo); err != nil {
|
||||
t.Fatal("error mounting backend: ", err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -5,8 +5,10 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/structs"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/logical/framework"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -70,6 +72,10 @@ func NewSystemBackend(core *Core) logical.Backend {
|
|||
Type: framework.TypeString,
|
||||
Description: strings.TrimSpace(sysHelp["mount_desc"][0]),
|
||||
},
|
||||
"config": &framework.FieldSchema{
|
||||
Type: framework.TypeMap,
|
||||
Description: strings.TrimSpace(sysHelp["mount_config"][0]),
|
||||
},
|
||||
},
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
|
@ -339,9 +345,10 @@ func (b *SystemBackend) handleMountTable(
|
|||
Data: make(map[string]interface{}),
|
||||
}
|
||||
for _, entry := range b.Core.mounts.Entries {
|
||||
info := map[string]string{
|
||||
info := map[string]interface{}{
|
||||
"type": entry.Type,
|
||||
"description": entry.Description,
|
||||
"config": structs.Map(entry.Config),
|
||||
}
|
||||
resp.Data[entry.Path] = info
|
||||
}
|
||||
|
@ -356,6 +363,24 @@ 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 {
|
||||
return logical.ErrorResponse(
|
||||
"cannot convert mount config information into proper values"),
|
||||
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 == "" {
|
||||
return logical.ErrorResponse(
|
||||
|
@ -369,6 +394,9 @@ func (b *SystemBackend) handleMount(
|
|||
Type: logicalType,
|
||||
Description: description,
|
||||
}
|
||||
if config != nil {
|
||||
me.Config = *config
|
||||
}
|
||||
|
||||
// Attempt mount
|
||||
if err := b.Core.mount(me); err != nil {
|
||||
|
@ -418,9 +446,27 @@ 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"),
|
||||
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
|
||||
if err := b.Core.remount(fromPath, toPath); err != 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)
|
||||
return handleError(err)
|
||||
}
|
||||
|
@ -830,6 +876,11 @@ west coast.
|
|||
"",
|
||||
},
|
||||
|
||||
"mount_config": {
|
||||
`Configuration for this mount, such as default_lease_ttl
|
||||
and max_lease_ttl.`,
|
||||
},
|
||||
|
||||
"remount": {
|
||||
"Move the mount point of an already-mounted backend.",
|
||||
`
|
||||
|
|
|
@ -3,6 +3,7 @@ package vault
|
|||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/audit"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
|
@ -39,17 +40,25 @@ func TestSystemBackend_mounts(t *testing.T) {
|
|||
}
|
||||
|
||||
exp := map[string]interface{}{
|
||||
"secret/": map[string]string{
|
||||
"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),
|
||||
},
|
||||
},
|
||||
"sys/": map[string]string{
|
||||
"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),
|
||||
},
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(resp.Data, exp) {
|
||||
t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
|
||||
t.Fatalf("Got:\n%#v\nExpected:\n%#v", resp.Data, exp)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -118,8 +118,8 @@ type MountEntry struct {
|
|||
|
||||
// MountConfig is used to hold settable options
|
||||
type MountConfig struct {
|
||||
DefaultLeaseTTL time.Duration `json:"default_lease_ttl"` // Override for global default
|
||||
MaxLeaseTTL time.Duration `json:"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) error {
|
||||
func (c *Core) remount(src, dst string, config *MountConfig) error {
|
||||
c.mounts.Lock()
|
||||
defer c.mounts.Unlock()
|
||||
|
||||
|
@ -339,6 +339,9 @@ func (c *Core) remount(src, dst string) error {
|
|||
if ent.Path == src {
|
||||
ent.Path = dst
|
||||
ent.Tainted = false
|
||||
if config != nil {
|
||||
ent.Config = *config
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
err := c.remount("secret", "foo", nil)
|
||||
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/"); err != nil {
|
||||
if err := c.remount("test/", "new/", nil); 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")
|
||||
err := c.remount("sys", "foo", nil)
|
||||
if err.Error() != "cannot remount 'sys/'" {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue