Update mount table and CLI with plugin version for auth (#16856)

This commit is contained in:
Christopher Swenson 2022-08-31 11:23:05 -07:00 committed by GitHub
parent 96b97017b1
commit 09ad6ab72c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 1479 additions and 328 deletions

View File

@ -247,6 +247,7 @@ type MountInput struct {
SealWrap bool `json:"seal_wrap" mapstructure:"seal_wrap"` SealWrap bool `json:"seal_wrap" mapstructure:"seal_wrap"`
ExternalEntropyAccess bool `json:"external_entropy_access" mapstructure:"external_entropy_access"` ExternalEntropyAccess bool `json:"external_entropy_access" mapstructure:"external_entropy_access"`
Options map[string]string `json:"options"` Options map[string]string `json:"options"`
Version string `json:"version,omitempty"`
// Deprecated: Newer server responses should be returning this information in the // Deprecated: Newer server responses should be returning this information in the
// Type field (json: "type") instead. // Type field (json: "type") instead.
@ -281,6 +282,10 @@ type MountOutput struct {
Local bool `json:"local"` Local bool `json:"local"`
SealWrap bool `json:"seal_wrap" mapstructure:"seal_wrap"` SealWrap bool `json:"seal_wrap" mapstructure:"seal_wrap"`
ExternalEntropyAccess bool `json:"external_entropy_access" mapstructure:"external_entropy_access"` ExternalEntropyAccess bool `json:"external_entropy_access" mapstructure:"external_entropy_access"`
Version string `json:"version"`
RunningVersion string `json:"running_version"`
Sha string `json:"sha"`
RunningSha string `json:"running_sha"`
} }
type MountConfigOutput struct { type MountConfigOutput struct {

153
api/sys_mounts_test.go Normal file
View File

@ -0,0 +1,153 @@
package api
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestListMounts(t *testing.T) {
mockVaultServer := httptest.NewServer(http.HandlerFunc(mockVaultMountsHandler))
defer mockVaultServer.Close()
cfg := DefaultConfig()
cfg.Address = mockVaultServer.URL
client, err := NewClient(cfg)
if err != nil {
t.Fatal(err)
}
resp, err := client.Sys().ListMounts()
if err != nil {
t.Fatal(err)
}
expectedMounts := map[string]struct {
Type string
Version string
}{
"cubbyhole/": {Type: "cubbyhole", Version: "v1.0.0"},
"identity/": {Type: "identity", Version: ""},
"secret/": {Type: "kv", Version: ""},
"sys/": {Type: "system", Version: ""},
}
for path, mount := range resp {
expected, ok := expectedMounts[path]
if !ok {
t.Errorf("Unexpected mount: %s: %+v", path, mount)
continue
}
if expected.Type != mount.Type || expected.Version != mount.Version {
t.Errorf("Mount did not match: %s -> expected %+v but got %+v", path, expected, mount)
}
}
for path, expected := range expectedMounts {
mount, ok := resp[path]
if !ok {
t.Errorf("Expected mount not found mount: %s: %+v", path, expected)
continue
}
if expected.Type != mount.Type || expected.Version != mount.Version {
t.Errorf("Mount did not match: %s -> expected %+v but got %+v", path, expected, mount)
}
}
}
func mockVaultMountsHandler(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte(listMountsResponse))
}
const listMountsResponse = `{
"request_id": "3cd881e9-ea50-2e06-90b2-5641667485fa",
"lease_id": "",
"lease_duration": 0,
"renewable": false,
"data": {
"cubbyhole/": {
"accessor": "cubbyhole_2e3fc28d",
"config": {
"default_lease_ttl": 0,
"force_no_cache": false,
"max_lease_ttl": 0
},
"description": "per-token private secret storage",
"external_entropy_access": false,
"local": true,
"options": null,
"running_sha": "",
"running_version": "",
"seal_wrap": false,
"sha": "",
"type": "cubbyhole",
"uuid": "575063dc-5ef8-4487-c842-22c494c19a6f",
"version": "v1.0.0"
},
"identity/": {
"accessor": "identity_6e01c327",
"config": {
"default_lease_ttl": 0,
"force_no_cache": false,
"max_lease_ttl": 0,
"passthrough_request_headers": [
"Authorization"
]
},
"description": "identity store",
"external_entropy_access": false,
"local": false,
"options": null,
"running_sha": "",
"running_version": "",
"seal_wrap": false,
"sha": "",
"type": "identity",
"uuid": "187d7eba-3471-554b-c2d9-1479612c8046",
"version": ""
},
"secret/": {
"accessor": "kv_3e2f282f",
"config": {
"default_lease_ttl": 0,
"force_no_cache": false,
"max_lease_ttl": 0
},
"description": "key/value secret storage",
"external_entropy_access": false,
"local": false,
"options": {
"version": "2"
},
"running_sha": "",
"running_version": "",
"seal_wrap": false,
"sha": "",
"type": "kv",
"uuid": "13375e0f-876e-7e96-0a3e-076f37b6b69d",
"version": ""
},
"sys/": {
"accessor": "system_93503264",
"config": {
"default_lease_ttl": 0,
"force_no_cache": false,
"max_lease_ttl": 0,
"passthrough_request_headers": [
"Accept"
]
},
"description": "system endpoints used for control, policy and debugging",
"external_entropy_access": false,
"local": false,
"options": null,
"running_sha": "",
"running_version": "",
"seal_wrap": true,
"sha": "",
"type": "system",
"uuid": "1373242d-cc4d-c023-410b-7f336e7ba0a8",
"version": ""
}
}
}`

View File

@ -22,6 +22,8 @@ type ListPluginsResponse struct {
// PluginsByType is the list of plugins by type. // PluginsByType is the list of plugins by type.
PluginsByType map[consts.PluginType][]string `json:"types"` PluginsByType map[consts.PluginType][]string `json:"types"`
Details []PluginDetails `json:"details,omitempty"`
// Names is the list of names of the plugins. // Names is the list of names of the plugins.
// //
// Deprecated: Newer server responses should be returning PluginsByType (json: // Deprecated: Newer server responses should be returning PluginsByType (json:
@ -29,6 +31,13 @@ type ListPluginsResponse struct {
Names []string `json:"names"` Names []string `json:"names"`
} }
type PluginDetails struct {
Type string `json:"string"`
Name string `json:"name"`
Version string `json:"version,omitempty"`
Builtin bool `json:"builtin"`
}
// ListPlugins wraps ListPluginsWithContext using context.Background. // ListPlugins wraps ListPluginsWithContext using context.Background.
func (c *Sys) ListPlugins(i *ListPluginsInput) (*ListPluginsResponse, error) { func (c *Sys) ListPlugins(i *ListPluginsInput) (*ListPluginsResponse, error) {
return c.ListPluginsWithContext(context.Background(), i) return c.ListPluginsWithContext(context.Background(), i)
@ -98,6 +107,7 @@ func (c *Sys) ListPluginsWithContext(ctx context.Context, i *ListPluginsInput) (
result := &ListPluginsResponse{ result := &ListPluginsResponse{
PluginsByType: make(map[consts.PluginType][]string), PluginsByType: make(map[consts.PluginType][]string),
Details: []PluginDetails{},
} }
if i.Type == consts.PluginTypeUnknown { if i.Type == consts.PluginTypeUnknown {
for _, pluginType := range consts.PluginTypes { for _, pluginType := range consts.PluginTypes {
@ -129,6 +139,12 @@ func (c *Sys) ListPluginsWithContext(ctx context.Context, i *ListPluginsInput) (
result.PluginsByType[i.Type] = respKeys result.PluginsByType[i.Type] = respKeys
} }
if detailed, ok := secret.Data["detailed"]; ok {
if err := mapstructure.Decode(detailed, &result.Details); err != nil {
return nil, err
}
}
return result, nil return result, nil
} }
@ -194,6 +210,9 @@ type RegisterPluginInput struct {
// SHA256 is the shasum of the plugin. // SHA256 is the shasum of the plugin.
SHA256 string `json:"sha256,omitempty"` SHA256 string `json:"sha256,omitempty"`
// Version is the optional version of the plugin being registered
Version string `json:"version,omitempty"`
} }
// RegisterPlugin wraps RegisterPluginWithContext using context.Background. // RegisterPlugin wraps RegisterPluginWithContext using context.Background.
@ -227,6 +246,9 @@ type DeregisterPluginInput struct {
// Type of the plugin. Required. // Type of the plugin. Required.
Type consts.PluginType `json:"type"` Type consts.PluginType `json:"type"`
// Version of the plugin. Optional.
Version string `json:"version,omitempty"`
} }
// DeregisterPlugin wraps DeregisterPluginWithContext using context.Background. // DeregisterPlugin wraps DeregisterPluginWithContext using context.Background.
@ -242,7 +264,7 @@ func (c *Sys) DeregisterPluginWithContext(ctx context.Context, i *DeregisterPlug
path := catalogPathByType(i.Type, i.Name) path := catalogPathByType(i.Type, i.Name)
req := c.c.NewRequest(http.MethodDelete, path) req := c.c.NewRequest(http.MethodDelete, path)
req.Params.Set("version", i.Version)
resp, err := c.c.rawRequestWithContext(ctx, req) resp, err := c.c.rawRequestWithContext(ctx, req)
if err == nil { if err == nil {
defer resp.Body.Close() defer resp.Body.Close()

View File

@ -9,8 +9,27 @@ import (
"github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/consts"
) )
func TestRegisterPlugin(t *testing.T) {
mockVaultServer := httptest.NewServer(http.HandlerFunc(mockVaultHandlerRegister))
defer mockVaultServer.Close()
cfg := DefaultConfig()
cfg.Address = mockVaultServer.URL
client, err := NewClient(cfg)
if err != nil {
t.Fatal(err)
}
err = client.Sys().RegisterPluginWithContext(context.Background(), &RegisterPluginInput{
Version: "v1.0.0",
})
if err != nil {
t.Fatal(err)
}
}
func TestListPlugins(t *testing.T) { func TestListPlugins(t *testing.T) {
mockVaultServer := httptest.NewServer(http.HandlerFunc(mockVaultHandler)) mockVaultServer := httptest.NewServer(http.HandlerFunc(mockVaultHandlerList))
defer mockVaultServer.Close() defer mockVaultServer.Close()
cfg := DefaultConfig() cfg := DefaultConfig()
@ -44,7 +63,7 @@ func TestListPlugins(t *testing.T) {
} }
} }
func mockVaultHandler(w http.ResponseWriter, _ *http.Request) { func mockVaultHandlerList(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte(listUntypedResponse)) _, _ = w.Write([]byte(listUntypedResponse))
} }
@ -77,3 +96,9 @@ const listUntypedResponse = `{
"warnings": null, "warnings": null,
"auth": null "auth": null
}` }`
func mockVaultHandlerRegister(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte(registerResponse))
}
const registerResponse = `{}`

View File

@ -62,11 +62,12 @@ func Backend(ctx context.Context, conf *logical.BackendConfig) (*PluginBackend,
if err != nil { if err != nil {
return nil, err return nil, err
} }
version := conf.Config["plugin_version"]
sys := conf.System sys := conf.System
// NewBackend with isMetadataMode set to true // NewBackendWithVersion with isMetadataMode set to true
raw, err := bplugin.NewBackend(ctx, name, pluginType, sys, conf, true) raw, err := bplugin.NewBackendWithVersion(ctx, name, pluginType, sys, conf, true, version)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -119,7 +120,7 @@ func (b *PluginBackend) startBackend(ctx context.Context, storage logical.Storag
// Ensure proper cleanup of the backend (i.e. call client.Kill()) // Ensure proper cleanup of the backend (i.e. call client.Kill())
b.Backend.Cleanup(ctx) b.Backend.Cleanup(ctx)
nb, err := bplugin.NewBackend(ctx, pluginName, pluginType, b.config.System, b.config, false) nb, err := bplugin.NewBackendWithVersion(ctx, pluginName, pluginType, b.config.System, b.config, false, b.config.Config["plugin_version"])
if err != nil { if err != nil {
return err return err
} }

View File

@ -183,3 +183,13 @@ func (v testSystemView) LookupPlugin(context.Context, string, consts.PluginType)
}, },
}, nil }, nil
} }
func (v testSystemView) LookupPluginVersion(context.Context, string, consts.PluginType, string) (*pluginutil.PluginRunner, error) {
return &pluginutil.PluginRunner{
Name: "test-plugin-runner",
Builtin: true,
BuiltinFactory: func() (interface{}, error) {
return v.factory, nil
},
}, nil
}

3
changelog/16856.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:change
plugins: Add plugin version to auth register, list, and mount table
```

View File

@ -37,6 +37,7 @@ type AuthEnableCommand struct {
flagExternalEntropyAccess bool flagExternalEntropyAccess bool
flagTokenType string flagTokenType string
flagVersion int flagVersion int
flagPluginVersion string
} }
func (c *AuthEnableCommand) Synopsis() string { func (c *AuthEnableCommand) Synopsis() string {
@ -199,6 +200,13 @@ func (c *AuthEnableCommand) Flags() *FlagSets {
Usage: "Select the version of the auth method to run. Not supported by all auth methods.", Usage: "Select the version of the auth method to run. Not supported by all auth methods.",
}) })
f.StringVar(&StringVar{
Name: "plugin-version",
Target: &c.flagPluginVersion,
Default: "",
Usage: "Select the version of the plugin to enable.",
})
return set return set
} }
@ -262,6 +270,7 @@ func (c *AuthEnableCommand) Run(args []string) int {
authOpts := &api.EnableAuthOptions{ authOpts := &api.EnableAuthOptions{
Type: authType, Type: authType,
Version: c.flagPluginVersion,
Description: c.flagDescription, Description: c.flagDescription,
Local: c.flagLocal, Local: c.flagLocal,
SealWrap: c.flagSealWrap, SealWrap: c.flagSealWrap,

View File

@ -118,10 +118,10 @@ func (c *AuthListCommand) simpleMounts(auths map[string]*api.AuthMount) []string
} }
sort.Strings(paths) sort.Strings(paths)
out := []string{"Path | Type | Accessor | Description"} out := []string{"Path | Type | Accessor | Description | Version"}
for _, path := range paths { for _, path := range paths {
mount := auths[path] mount := auths[path]
out = append(out, fmt.Sprintf("%s | %s | %s | %s", path, mount.Type, mount.Accessor, mount.Description)) out = append(out, fmt.Sprintf("%s | %s | %s | %s | %s", path, mount.Type, mount.Accessor, mount.Description, mount.Version))
} }
return out return out
@ -145,7 +145,7 @@ func (c *AuthListCommand) detailedMounts(auths map[string]*api.AuthMount) []stri
} }
} }
out := []string{"Path | Plugin | Accessor | Default TTL | Max TTL | Token Type | Replication | Seal Wrap | External Entropy Access | Options | Description | UUID"} out := []string{"Path | Plugin | Accessor | Default TTL | Max TTL | Token Type | Replication | Seal Wrap | External Entropy Access | Options | Description | UUID | Version"}
for _, path := range paths { for _, path := range paths {
mount := auths[path] mount := auths[path]
@ -162,7 +162,7 @@ func (c *AuthListCommand) detailedMounts(auths map[string]*api.AuthMount) []stri
pluginName = mount.Config.PluginName pluginName = mount.Config.PluginName
} }
out = append(out, fmt.Sprintf("%s | %s | %s | %s | %s | %s | %s | %t | %v | %s | %s | %s", out = append(out, fmt.Sprintf("%s | %s | %s | %s | %s | %s | %s | %t | %v | %s | %s | %s | %s",
path, path,
pluginName, pluginName,
mount.Accessor, mount.Accessor,
@ -175,6 +175,7 @@ func (c *AuthListCommand) detailedMounts(auths map[string]*api.AuthMount) []stri
mount.Options, mount.Options,
mount.Description, mount.Description,
mount.UUID, mount.UUID,
mount.Version,
)) ))
} }

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"strings" "strings"
semver "github.com/hashicorp/go-version"
"github.com/hashicorp/vault/api" "github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/consts"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
@ -17,6 +18,8 @@ var (
type PluginDeregisterCommand struct { type PluginDeregisterCommand struct {
*BaseCommand *BaseCommand
flagVersion string
} }
func (c *PluginDeregisterCommand) Synopsis() string { func (c *PluginDeregisterCommand) Synopsis() string {
@ -28,20 +31,36 @@ func (c *PluginDeregisterCommand) Help() string {
Usage: vault plugin deregister [options] TYPE NAME Usage: vault plugin deregister [options] TYPE NAME
Deregister an existing plugin in the catalog. If the plugin does not exist, Deregister an existing plugin in the catalog. If the plugin does not exist,
no action is taken (the command is idempotent). The argument of type no action is taken (the command is idempotent). The TYPE argument
takes "auth", "database", or "secret". takes "auth", "database", or "secret".
Deregister the plugin named my-custom-plugin: Deregister the unversioned auth plugin named my-custom-plugin:
$ vault plugin deregister auth my-custom-plugin $ vault plugin deregister auth my-custom-plugin
Deregister the auth plugin named my-custom-plugin, version 1.0.0:
$ vault plugin deregister -version=v1.0.0 auth my-custom-plugin
` + c.Flags().Help() ` + c.Flags().Help()
return strings.TrimSpace(helpText) return strings.TrimSpace(helpText)
} }
func (c *PluginDeregisterCommand) Flags() *FlagSets { func (c *PluginDeregisterCommand) Flags() *FlagSets {
return c.flagSet(FlagSetHTTP) set := c.flagSet(FlagSetHTTP)
f := set.NewFlagSet("Command Options")
f.StringVar(&StringVar{
Name: "version",
Target: &c.flagVersion,
Completion: complete.PredictAnything,
Usage: "Version of the plugin to deregister. If unset, " +
"only an unversioned plugin may be deregistered.",
})
return set
} }
func (c *PluginDeregisterCommand) AutocompleteArgs() complete.Predictor { func (c *PluginDeregisterCommand) AutocompleteArgs() complete.Predictor {
@ -62,21 +81,19 @@ func (c *PluginDeregisterCommand) Run(args []string) int {
var pluginNameRaw, pluginTypeRaw string var pluginNameRaw, pluginTypeRaw string
args = f.Args() args = f.Args()
switch { switch len(args) {
case len(args) < 1: case 0:
c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1 or 2, got %d)", len(args))) c.UI.Error("Not enough arguments (expected 1, or 2, got 0)")
return 1 return 1
case len(args) > 2: case 1:
c.UI.Error(fmt.Sprintf("Too many arguments (expected 1 or 2, got %d)", len(args)))
return 1
// These cases should come after invalid cases have been checked
case len(args) == 1:
pluginTypeRaw = "unknown" pluginTypeRaw = "unknown"
pluginNameRaw = args[0] pluginNameRaw = args[0]
case len(args) == 2: case 2:
pluginTypeRaw = args[0] pluginTypeRaw = args[0]
pluginNameRaw = args[1] pluginNameRaw = args[1]
default:
c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, or 2, got %d)", len(args)))
return 1
} }
client, err := c.Client() client, err := c.Client()
@ -91,10 +108,18 @@ func (c *PluginDeregisterCommand) Run(args []string) int {
return 2 return 2
} }
pluginName := strings.TrimSpace(pluginNameRaw) pluginName := strings.TrimSpace(pluginNameRaw)
if c.flagVersion != "" {
_, err := semver.NewSemver(c.flagVersion)
if err != nil {
c.UI.Error(fmt.Sprintf("version %q is not a valid semantic version: %v", c.flagVersion, err))
return 2
}
}
if err := client.Sys().DeregisterPlugin(&api.DeregisterPluginInput{ if err := client.Sys().DeregisterPlugin(&api.DeregisterPluginInput{
Name: pluginName, Name: pluginName,
Type: pluginType, Type: pluginType,
Version: c.flagVersion,
}); err != nil { }); err != nil {
c.UI.Error(fmt.Sprintf("Error deregistering plugin named %s: %s", pluginName, err)) c.UI.Error(fmt.Sprintf("Error deregistering plugin named %s: %s", pluginName, err))
return 2 return 2

View File

@ -6,6 +6,7 @@ import (
"github.com/hashicorp/vault/api" "github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/vault"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
@ -76,7 +77,7 @@ func TestPluginDeregisterCommand_Run(t *testing.T) {
t.Run("integration", func(t *testing.T) { t.Run("integration", func(t *testing.T) {
t.Parallel() t.Parallel()
pluginDir, cleanup := testPluginDir(t) pluginDir, cleanup := vault.MakeTestPluginDir(t)
defer cleanup(t) defer cleanup(t)
client, _, closer := testVaultServerPluginDir(t, pluginDir) client, _, closer := testVaultServerPluginDir(t, pluginDir)
@ -131,6 +132,101 @@ func TestPluginDeregisterCommand_Run(t *testing.T) {
} }
}) })
t.Run("integration with version", func(t *testing.T) {
t.Parallel()
pluginDir, cleanup := vault.MakeTestPluginDir(t)
defer cleanup(t)
client, _, closer := testVaultServerPluginDir(t, pluginDir)
defer closer()
pluginName := "my-plugin"
_, _, version := testPluginCreateAndRegisterVersioned(t, client, pluginDir, pluginName, consts.PluginTypeCredential)
ui, cmd := testPluginDeregisterCommand(t)
cmd.client = client
code := cmd.Run([]string{
"-version=" + version,
consts.PluginTypeCredential.String(),
pluginName,
})
if exp := 0; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Success! Deregistered plugin (if it was registered): "
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
}
resp, err := client.Sys().ListPlugins(&api.ListPluginsInput{
Type: consts.PluginTypeUnknown,
})
if err != nil {
t.Fatal(err)
}
found := false
for _, p := range resp.Details {
if p.Name == pluginName {
found = true
}
}
if found {
t.Errorf("expected %q to not be in %#v", pluginName, resp.Details)
}
})
t.Run("integration with missing version", func(t *testing.T) {
t.Parallel()
pluginDir, cleanup := vault.MakeTestPluginDir(t)
defer cleanup(t)
client, _, closer := testVaultServerPluginDir(t, pluginDir)
defer closer()
pluginName := "my-plugin"
testPluginCreateAndRegisterVersioned(t, client, pluginDir, pluginName, consts.PluginTypeCredential)
ui, cmd := testPluginDeregisterCommand(t)
cmd.client = client
code := cmd.Run([]string{
consts.PluginTypeCredential.String(),
pluginName,
})
if exp := 0; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Success! Deregistered plugin (if it was registered): "
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
}
resp, err := client.Sys().ListPlugins(&api.ListPluginsInput{
Type: consts.PluginTypeUnknown,
})
if err != nil {
t.Fatal(err)
}
found := false
for _, p := range resp.Details {
if p.Name == pluginName {
found = true
}
}
if !found {
t.Errorf("expected %q to be in %#v", pluginName, resp.Details)
}
})
t.Run("communication_failure", func(t *testing.T) { t.Run("communication_failure", func(t *testing.T) {
t.Parallel() t.Parallel()

View File

@ -5,6 +5,7 @@ import (
"testing" "testing"
"github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/vault"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
@ -73,7 +74,7 @@ func TestPluginInfoCommand_Run(t *testing.T) {
t.Run("default", func(t *testing.T) { t.Run("default", func(t *testing.T) {
t.Parallel() t.Parallel()
pluginDir, cleanup := testPluginDir(t) pluginDir, cleanup := vault.MakeTestPluginDir(t)
defer cleanup(t) defer cleanup(t)
client, _, closer := testVaultServerPluginDir(t, pluginDir) client, _, closer := testVaultServerPluginDir(t, pluginDir)
@ -104,7 +105,7 @@ func TestPluginInfoCommand_Run(t *testing.T) {
t.Run("field", func(t *testing.T) { t.Run("field", func(t *testing.T) {
t.Parallel() t.Parallel()
pluginDir, cleanup := testPluginDir(t) pluginDir, cleanup := vault.MakeTestPluginDir(t)
defer cleanup(t) defer cleanup(t)
client, _, closer := testVaultServerPluginDir(t, pluginDir) client, _, closer := testVaultServerPluginDir(t, pluginDir)

View File

@ -21,6 +21,7 @@ type PluginRegisterCommand struct {
flagArgs []string flagArgs []string
flagCommand string flagCommand string
flagSHA256 string flagSHA256 string
flagVersion string
} }
func (c *PluginRegisterCommand) Synopsis() string { func (c *PluginRegisterCommand) Synopsis() string {
@ -37,12 +38,13 @@ Usage: vault plugin register [options] TYPE NAME
Register the plugin named my-custom-plugin: Register the plugin named my-custom-plugin:
$ vault plugin register -sha256=d3f0a8b... auth my-custom-plugin $ vault plugin register -sha256=d3f0a8b... -version=v1.0.0 auth my-custom-plugin
Register a plugin with custom arguments: Register a plugin with custom arguments:
$ vault plugin register \ $ vault plugin register \
-sha256=d3f0a8b... \ -sha256=d3f0a8b... \
-version=v1.0.0 \
-args=--with-glibc,--with-cgo \ -args=--with-glibc,--with-cgo \
auth my-custom-plugin auth my-custom-plugin
@ -79,6 +81,13 @@ func (c *PluginRegisterCommand) Flags() *FlagSets {
Usage: "SHA256 of the plugin binary. This is required for all plugins.", Usage: "SHA256 of the plugin binary. This is required for all plugins.",
}) })
f.StringVar(&StringVar{
Name: "version",
Target: &c.flagVersion,
Completion: complete.PredictAnything,
Usage: "Version of the plugin. Optional.",
})
return set return set
} }
@ -144,6 +153,7 @@ func (c *PluginRegisterCommand) Run(args []string) int {
Args: c.flagArgs, Args: c.flagArgs,
Command: command, Command: command,
SHA256: c.flagSHA256, SHA256: c.flagSHA256,
Version: c.flagVersion,
}); err != nil { }); err != nil {
c.UI.Error(fmt.Sprintf("Error registering plugin %s: %s", pluginName, err)) c.UI.Error(fmt.Sprintf("Error registering plugin %s: %s", pluginName, err))
return 2 return 2

View File

@ -6,6 +6,7 @@ import (
"github.com/hashicorp/vault/api" "github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/vault"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
@ -77,7 +78,7 @@ func TestPluginRegisterCommand_Run(t *testing.T) {
t.Run("integration", func(t *testing.T) { t.Run("integration", func(t *testing.T) {
t.Parallel() t.Parallel()
pluginDir, cleanup := testPluginDir(t) pluginDir, cleanup := vault.MakeTestPluginDir(t)
defer cleanup(t) defer cleanup(t)
client, _, closer := testVaultServerPluginDir(t, pluginDir) client, _, closer := testVaultServerPluginDir(t, pluginDir)

View File

@ -6,6 +6,7 @@ import (
"github.com/hashicorp/vault/api" "github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/vault"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
@ -82,7 +83,7 @@ func TestPluginReloadCommand_Run(t *testing.T) {
t.Run("integration", func(t *testing.T) { t.Run("integration", func(t *testing.T) {
t.Parallel() t.Parallel()
pluginDir, cleanup := testPluginDir(t) pluginDir, cleanup := vault.MakeTestPluginDir(t)
defer cleanup(t) defer cleanup(t)
client, _, closer := testVaultServerPluginDir(t, pluginDir) client, _, closer := testVaultServerPluginDir(t, pluginDir)

View File

@ -6,36 +6,12 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath"
"testing" "testing"
"github.com/hashicorp/vault/api" "github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/consts"
) )
// testPluginDir creates a temporary directory suitable for holding plugins.
// This helper also resolves symlinks to make tests happy on OS X.
func testPluginDir(tb testing.TB) (string, func(tb testing.TB)) {
tb.Helper()
dir, err := ioutil.TempDir("", "")
if err != nil {
tb.Fatal(err)
}
// OSX tempdir are /var, but actually symlinked to /private/var
dir, err = filepath.EvalSymlinks(dir)
if err != nil {
tb.Fatal(err)
}
return dir, func(tb testing.TB) {
if err := os.RemoveAll(dir); err != nil {
tb.Fatal(err)
}
}
}
// testPluginCreate creates a sample plugin in a tempdir and returns the shasum // testPluginCreate creates a sample plugin in a tempdir and returns the shasum
// and filepath to the plugin. // and filepath to the plugin.
func testPluginCreate(tb testing.TB, dir, name string) (string, string) { func testPluginCreate(tb testing.TB, dir, name string) (string, string) {
@ -78,3 +54,22 @@ func testPluginCreateAndRegister(tb testing.TB, client *api.Client, dir, name st
return pth, sha256Sum return pth, sha256Sum
} }
// testPluginCreateAndRegisterVersioned creates a versioned plugin and registers it in the catalog.
func testPluginCreateAndRegisterVersioned(tb testing.TB, client *api.Client, dir, name string, pluginType consts.PluginType) (string, string, string) {
tb.Helper()
pth, sha256Sum := testPluginCreate(tb, dir, name)
if err := client.Sys().RegisterPlugin(&api.RegisterPluginInput{
Name: name,
Type: pluginType,
Command: name,
SHA256: sha256Sum,
Version: "v1.0.0",
}); err != nil {
tb.Fatal(err)
}
return pth, sha256Sum, "v1.0.0"
}

View File

@ -410,9 +410,13 @@ func TestSysMounts_headerAuth(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{"version": "1"}, "options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"sys/": map[string]interface{}{ "sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging", "description": "system endpoints used for control, policy and debugging",
@ -424,9 +428,13 @@ func TestSysMounts_headerAuth(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Accept"}, "passthrough_request_headers": []interface{}{"Accept"},
}, },
"local": false, "local": false,
"seal_wrap": true, "seal_wrap": true,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"cubbyhole/": map[string]interface{}{ "cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage", "description": "per-token private secret storage",
@ -437,9 +445,13 @@ func TestSysMounts_headerAuth(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": true, "local": true,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"identity/": map[string]interface{}{ "identity/": map[string]interface{}{
"description": "identity store", "description": "identity store",
@ -451,9 +463,13 @@ func TestSysMounts_headerAuth(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Authorization"}, "passthrough_request_headers": []interface{}{"Authorization"},
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
}, },
"secret/": map[string]interface{}{ "secret/": map[string]interface{}{
@ -465,9 +481,13 @@ func TestSysMounts_headerAuth(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{"version": "1"}, "options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"sys/": map[string]interface{}{ "sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging", "description": "system endpoints used for control, policy and debugging",
@ -479,9 +499,13 @@ func TestSysMounts_headerAuth(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Accept"}, "passthrough_request_headers": []interface{}{"Accept"},
}, },
"local": false, "local": false,
"seal_wrap": true, "seal_wrap": true,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"cubbyhole/": map[string]interface{}{ "cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage", "description": "per-token private secret storage",
@ -492,9 +516,13 @@ func TestSysMounts_headerAuth(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": true, "local": true,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"identity/": map[string]interface{}{ "identity/": map[string]interface{}{
"description": "identity store", "description": "identity store",
@ -506,9 +534,13 @@ func TestSysMounts_headerAuth(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Authorization"}, "passthrough_request_headers": []interface{}{"Authorization"},
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
} }
testResponseStatus(t, resp, 200) testResponseStatus(t, resp, 200)

View File

@ -38,9 +38,13 @@ func TestSysAuth(t *testing.T) {
"token_type": "default-service", "token_type": "default-service",
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
}, },
"token/": map[string]interface{}{ "token/": map[string]interface{}{
@ -53,9 +57,13 @@ func TestSysAuth(t *testing.T) {
"token_type": "default-service", "token_type": "default-service",
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
} }
testResponseStatus(t, resp, 200) testResponseStatus(t, resp, 200)
@ -114,9 +122,13 @@ func TestSysEnableAuth(t *testing.T) {
"token_type": "default-service", "token_type": "default-service",
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{}, "options": map[string]interface{}{},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"token/": map[string]interface{}{ "token/": map[string]interface{}{
"description": "token based credentials", "description": "token based credentials",
@ -128,9 +140,13 @@ func TestSysEnableAuth(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"token_type": "default-service", "token_type": "default-service",
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
}, },
"foo/": map[string]interface{}{ "foo/": map[string]interface{}{
@ -143,9 +159,13 @@ func TestSysEnableAuth(t *testing.T) {
"token_type": "default-service", "token_type": "default-service",
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{}, "options": map[string]interface{}{},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"token/": map[string]interface{}{ "token/": map[string]interface{}{
"description": "token based credentials", "description": "token based credentials",
@ -157,9 +177,13 @@ func TestSysEnableAuth(t *testing.T) {
"token_type": "default-service", "token_type": "default-service",
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
} }
testResponseStatus(t, resp, 200) testResponseStatus(t, resp, 200)
@ -224,6 +248,10 @@ func TestSysDisableAuth(t *testing.T) {
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
}, },
"token/": map[string]interface{}{ "token/": map[string]interface{}{
@ -239,6 +267,10 @@ func TestSysDisableAuth(t *testing.T) {
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
} }
testResponseStatus(t, resp, 200) testResponseStatus(t, resp, 200)
@ -492,9 +524,13 @@ func TestSysRemountAuth(t *testing.T) {
"token_type": "default-service", "token_type": "default-service",
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{}, "options": map[string]interface{}{},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"token/": map[string]interface{}{ "token/": map[string]interface{}{
"description": "token based credentials", "description": "token based credentials",
@ -506,9 +542,13 @@ func TestSysRemountAuth(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"token_type": "default-service", "token_type": "default-service",
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
}, },
"bar/": map[string]interface{}{ "bar/": map[string]interface{}{
@ -521,9 +561,13 @@ func TestSysRemountAuth(t *testing.T) {
"token_type": "default-service", "token_type": "default-service",
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{}, "options": map[string]interface{}{},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"token/": map[string]interface{}{ "token/": map[string]interface{}{
"description": "token based credentials", "description": "token based credentials",
@ -535,9 +579,13 @@ func TestSysRemountAuth(t *testing.T) {
"token_type": "default-service", "token_type": "default-service",
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
} }
testResponseStatus(t, resp, 200) testResponseStatus(t, resp, 200)

View File

@ -39,9 +39,13 @@ func TestSysMounts(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{"version": "1"}, "options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"sys/": map[string]interface{}{ "sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging", "description": "system endpoints used for control, policy and debugging",
@ -53,9 +57,13 @@ func TestSysMounts(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Accept"}, "passthrough_request_headers": []interface{}{"Accept"},
}, },
"local": false, "local": false,
"seal_wrap": true, "seal_wrap": true,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"cubbyhole/": map[string]interface{}{ "cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage", "description": "per-token private secret storage",
@ -66,9 +74,13 @@ func TestSysMounts(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": true, "local": true,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"identity/": map[string]interface{}{ "identity/": map[string]interface{}{
"description": "identity store", "description": "identity store",
@ -80,9 +92,13 @@ func TestSysMounts(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Authorization"}, "passthrough_request_headers": []interface{}{"Authorization"},
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
}, },
"secret/": map[string]interface{}{ "secret/": map[string]interface{}{
@ -94,9 +110,13 @@ func TestSysMounts(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{"version": "1"}, "options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"sys/": map[string]interface{}{ "sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging", "description": "system endpoints used for control, policy and debugging",
@ -108,9 +128,13 @@ func TestSysMounts(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Accept"}, "passthrough_request_headers": []interface{}{"Accept"},
}, },
"local": false, "local": false,
"seal_wrap": true, "seal_wrap": true,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"cubbyhole/": map[string]interface{}{ "cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage", "description": "per-token private secret storage",
@ -121,9 +145,13 @@ func TestSysMounts(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": true, "local": true,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"identity/": map[string]interface{}{ "identity/": map[string]interface{}{
"description": "identity store", "description": "identity store",
@ -135,9 +163,13 @@ func TestSysMounts(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Authorization"}, "passthrough_request_headers": []interface{}{"Authorization"},
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
} }
testResponseStatus(t, resp, 200) testResponseStatus(t, resp, 200)
@ -196,9 +228,13 @@ func TestSysMount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{"version": "1"}, "options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"secret/": map[string]interface{}{ "secret/": map[string]interface{}{
"description": "key/value secret storage", "description": "key/value secret storage",
@ -209,9 +245,13 @@ func TestSysMount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{"version": "1"}, "options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"sys/": map[string]interface{}{ "sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging", "description": "system endpoints used for control, policy and debugging",
@ -223,9 +263,13 @@ func TestSysMount(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Accept"}, "passthrough_request_headers": []interface{}{"Accept"},
}, },
"local": false, "local": false,
"seal_wrap": true, "seal_wrap": true,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"cubbyhole/": map[string]interface{}{ "cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage", "description": "per-token private secret storage",
@ -236,9 +280,13 @@ func TestSysMount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": true, "local": true,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"identity/": map[string]interface{}{ "identity/": map[string]interface{}{
"description": "identity store", "description": "identity store",
@ -250,9 +298,13 @@ func TestSysMount(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Authorization"}, "passthrough_request_headers": []interface{}{"Authorization"},
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
}, },
"foo/": map[string]interface{}{ "foo/": map[string]interface{}{
@ -264,9 +316,13 @@ func TestSysMount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{"version": "1"}, "options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"secret/": map[string]interface{}{ "secret/": map[string]interface{}{
"description": "key/value secret storage", "description": "key/value secret storage",
@ -277,9 +333,13 @@ func TestSysMount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{"version": "1"}, "options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"sys/": map[string]interface{}{ "sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging", "description": "system endpoints used for control, policy and debugging",
@ -291,9 +351,13 @@ func TestSysMount(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Accept"}, "passthrough_request_headers": []interface{}{"Accept"},
}, },
"local": false, "local": false,
"seal_wrap": true, "seal_wrap": true,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"cubbyhole/": map[string]interface{}{ "cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage", "description": "per-token private secret storage",
@ -304,9 +368,13 @@ func TestSysMount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": true, "local": true,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"identity/": map[string]interface{}{ "identity/": map[string]interface{}{
"description": "identity store", "description": "identity store",
@ -318,9 +386,13 @@ func TestSysMount(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Authorization"}, "passthrough_request_headers": []interface{}{"Authorization"},
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
} }
testResponseStatus(t, resp, 200) testResponseStatus(t, resp, 200)
@ -414,9 +486,13 @@ func TestSysRemount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{}, "options": map[string]interface{}{},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"secret/": map[string]interface{}{ "secret/": map[string]interface{}{
"description": "key/value secret storage", "description": "key/value secret storage",
@ -427,9 +503,13 @@ func TestSysRemount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{"version": "1"}, "options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"sys/": map[string]interface{}{ "sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging", "description": "system endpoints used for control, policy and debugging",
@ -441,9 +521,13 @@ func TestSysRemount(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Accept"}, "passthrough_request_headers": []interface{}{"Accept"},
}, },
"local": false, "local": false,
"seal_wrap": true, "seal_wrap": true,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"cubbyhole/": map[string]interface{}{ "cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage", "description": "per-token private secret storage",
@ -454,9 +538,13 @@ func TestSysRemount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": true, "local": true,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"identity/": map[string]interface{}{ "identity/": map[string]interface{}{
"description": "identity store", "description": "identity store",
@ -468,9 +556,13 @@ func TestSysRemount(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Authorization"}, "passthrough_request_headers": []interface{}{"Authorization"},
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
}, },
"bar/": map[string]interface{}{ "bar/": map[string]interface{}{
@ -482,9 +574,13 @@ func TestSysRemount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{}, "options": map[string]interface{}{},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"secret/": map[string]interface{}{ "secret/": map[string]interface{}{
"description": "key/value secret storage", "description": "key/value secret storage",
@ -495,9 +591,13 @@ func TestSysRemount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{"version": "1"}, "options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"sys/": map[string]interface{}{ "sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging", "description": "system endpoints used for control, policy and debugging",
@ -509,9 +609,13 @@ func TestSysRemount(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Accept"}, "passthrough_request_headers": []interface{}{"Accept"},
}, },
"local": false, "local": false,
"seal_wrap": true, "seal_wrap": true,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"cubbyhole/": map[string]interface{}{ "cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage", "description": "per-token private secret storage",
@ -522,9 +626,13 @@ func TestSysRemount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": true, "local": true,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"identity/": map[string]interface{}{ "identity/": map[string]interface{}{
"description": "identity store", "description": "identity store",
@ -536,9 +644,13 @@ func TestSysRemount(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Authorization"}, "passthrough_request_headers": []interface{}{"Authorization"},
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
} }
testResponseStatus(t, resp, 200) testResponseStatus(t, resp, 200)
@ -597,9 +709,13 @@ func TestSysUnmount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{"version": "1"}, "options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"sys/": map[string]interface{}{ "sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging", "description": "system endpoints used for control, policy and debugging",
@ -611,9 +727,13 @@ func TestSysUnmount(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Accept"}, "passthrough_request_headers": []interface{}{"Accept"},
}, },
"local": false, "local": false,
"seal_wrap": true, "seal_wrap": true,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"cubbyhole/": map[string]interface{}{ "cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage", "description": "per-token private secret storage",
@ -624,9 +744,13 @@ func TestSysUnmount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": true, "local": true,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"identity/": map[string]interface{}{ "identity/": map[string]interface{}{
"description": "identity store", "description": "identity store",
@ -638,9 +762,13 @@ func TestSysUnmount(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Authorization"}, "passthrough_request_headers": []interface{}{"Authorization"},
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
}, },
"secret/": map[string]interface{}{ "secret/": map[string]interface{}{
@ -652,9 +780,13 @@ func TestSysUnmount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{"version": "1"}, "options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"sys/": map[string]interface{}{ "sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging", "description": "system endpoints used for control, policy and debugging",
@ -666,9 +798,13 @@ func TestSysUnmount(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Accept"}, "passthrough_request_headers": []interface{}{"Accept"},
}, },
"local": false, "local": false,
"seal_wrap": true, "seal_wrap": true,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"cubbyhole/": map[string]interface{}{ "cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage", "description": "per-token private secret storage",
@ -679,9 +815,13 @@ func TestSysUnmount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": true, "local": true,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"identity/": map[string]interface{}{ "identity/": map[string]interface{}{
"description": "identity store", "description": "identity store",
@ -693,9 +833,13 @@ func TestSysUnmount(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Authorization"}, "passthrough_request_headers": []interface{}{"Authorization"},
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
} }
testResponseStatus(t, resp, 200) testResponseStatus(t, resp, 200)
@ -840,9 +984,13 @@ func TestSysTuneMount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{}, "options": map[string]interface{}{},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"secret/": map[string]interface{}{ "secret/": map[string]interface{}{
"description": "key/value secret storage", "description": "key/value secret storage",
@ -853,9 +1001,13 @@ func TestSysTuneMount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{"version": "1"}, "options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"sys/": map[string]interface{}{ "sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging", "description": "system endpoints used for control, policy and debugging",
@ -867,9 +1019,13 @@ func TestSysTuneMount(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Accept"}, "passthrough_request_headers": []interface{}{"Accept"},
}, },
"local": false, "local": false,
"seal_wrap": true, "seal_wrap": true,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"cubbyhole/": map[string]interface{}{ "cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage", "description": "per-token private secret storage",
@ -880,9 +1036,13 @@ func TestSysTuneMount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": true, "local": true,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"identity/": map[string]interface{}{ "identity/": map[string]interface{}{
"description": "identity store", "description": "identity store",
@ -894,9 +1054,13 @@ func TestSysTuneMount(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Authorization"}, "passthrough_request_headers": []interface{}{"Authorization"},
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
}, },
"foo/": map[string]interface{}{ "foo/": map[string]interface{}{
@ -908,9 +1072,13 @@ func TestSysTuneMount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{}, "options": map[string]interface{}{},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"secret/": map[string]interface{}{ "secret/": map[string]interface{}{
"description": "key/value secret storage", "description": "key/value secret storage",
@ -921,9 +1089,13 @@ func TestSysTuneMount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{"version": "1"}, "options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"sys/": map[string]interface{}{ "sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging", "description": "system endpoints used for control, policy and debugging",
@ -935,9 +1107,13 @@ func TestSysTuneMount(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Accept"}, "passthrough_request_headers": []interface{}{"Accept"},
}, },
"local": false, "local": false,
"seal_wrap": true, "seal_wrap": true,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"cubbyhole/": map[string]interface{}{ "cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage", "description": "per-token private secret storage",
@ -948,9 +1124,13 @@ func TestSysTuneMount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": true, "local": true,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"identity/": map[string]interface{}{ "identity/": map[string]interface{}{
"description": "identity store", "description": "identity store",
@ -962,9 +1142,13 @@ func TestSysTuneMount(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Authorization"}, "passthrough_request_headers": []interface{}{"Authorization"},
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
} }
testResponseStatus(t, resp, 200) testResponseStatus(t, resp, 200)
@ -1049,9 +1233,13 @@ func TestSysTuneMount(t *testing.T) {
"max_lease_ttl": json.Number("259200000"), "max_lease_ttl": json.Number("259200000"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{"version": "1"}, "options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"secret/": map[string]interface{}{ "secret/": map[string]interface{}{
"description": "key/value secret storage", "description": "key/value secret storage",
@ -1062,9 +1250,13 @@ func TestSysTuneMount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{"version": "1"}, "options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"sys/": map[string]interface{}{ "sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging", "description": "system endpoints used for control, policy and debugging",
@ -1076,9 +1268,13 @@ func TestSysTuneMount(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Accept"}, "passthrough_request_headers": []interface{}{"Accept"},
}, },
"local": false, "local": false,
"seal_wrap": true, "seal_wrap": true,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"cubbyhole/": map[string]interface{}{ "cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage", "description": "per-token private secret storage",
@ -1089,9 +1285,13 @@ func TestSysTuneMount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": true, "local": true,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"identity/": map[string]interface{}{ "identity/": map[string]interface{}{
"description": "identity store", "description": "identity store",
@ -1103,9 +1303,13 @@ func TestSysTuneMount(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Authorization"}, "passthrough_request_headers": []interface{}{"Authorization"},
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
}, },
"foo/": map[string]interface{}{ "foo/": map[string]interface{}{
@ -1117,9 +1321,13 @@ func TestSysTuneMount(t *testing.T) {
"max_lease_ttl": json.Number("259200000"), "max_lease_ttl": json.Number("259200000"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{"version": "1"}, "options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"secret/": map[string]interface{}{ "secret/": map[string]interface{}{
"description": "key/value secret storage", "description": "key/value secret storage",
@ -1130,9 +1338,13 @@ func TestSysTuneMount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]interface{}{"version": "1"}, "options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"sys/": map[string]interface{}{ "sys/": map[string]interface{}{
"description": "system endpoints used for control, policy and debugging", "description": "system endpoints used for control, policy and debugging",
@ -1144,9 +1356,13 @@ func TestSysTuneMount(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Accept"}, "passthrough_request_headers": []interface{}{"Accept"},
}, },
"local": false, "local": false,
"seal_wrap": true, "seal_wrap": true,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"cubbyhole/": map[string]interface{}{ "cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage", "description": "per-token private secret storage",
@ -1157,9 +1373,13 @@ func TestSysTuneMount(t *testing.T) {
"max_lease_ttl": json.Number("0"), "max_lease_ttl": json.Number("0"),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": true, "local": true,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
"identity/": map[string]interface{}{ "identity/": map[string]interface{}{
"description": "identity store", "description": "identity store",
@ -1171,9 +1391,13 @@ func TestSysTuneMount(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []interface{}{"Authorization"}, "passthrough_request_headers": []interface{}{"Authorization"},
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": interface{}(nil), "options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"version": "",
}, },
} }

View File

@ -5,7 +5,7 @@ import (
"time" "time"
log "github.com/hashicorp/go-hclog" log "github.com/hashicorp/go-hclog"
plugin "github.com/hashicorp/go-plugin" "github.com/hashicorp/go-plugin"
"github.com/hashicorp/go-version" "github.com/hashicorp/go-version"
"github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/wrapping" "github.com/hashicorp/vault/sdk/helper/wrapping"
@ -15,7 +15,8 @@ import (
// Looker defines the plugin Lookup function that looks into the plugin catalog // Looker defines the plugin Lookup function that looks into the plugin catalog
// for available plugins and returns a PluginRunner // for available plugins and returns a PluginRunner
type Looker interface { type Looker interface {
LookupPlugin(context.Context, string, consts.PluginType) (*PluginRunner, error) LookupPlugin(ctx context.Context, pluginName string, pluginType consts.PluginType) (*PluginRunner, error)
LookupPluginVersion(ctx context.Context, pluginName string, pluginType consts.PluginType, version string) (*PluginRunner, error)
} }
// RunnerUtil interface defines the functions needed by the runner to wrap the // RunnerUtil interface defines the functions needed by the runner to wrap the

View File

@ -54,7 +54,11 @@ type SystemView interface {
// LookupPlugin looks into the plugin catalog for a plugin with the given // LookupPlugin looks into the plugin catalog for a plugin with the given
// name. Returns a PluginRunner or an error if a plugin can not be found. // name. Returns a PluginRunner or an error if a plugin can not be found.
LookupPlugin(context.Context, string, consts.PluginType) (*pluginutil.PluginRunner, error) LookupPlugin(ctx context.Context, pluginName string, pluginType consts.PluginType) (*pluginutil.PluginRunner, error)
// LookupPluginVersion looks into the plugin catalog for a plugin with the given
// name and version. Returns a PluginRunner or an error if a plugin can not be found.
LookupPluginVersion(ctx context.Context, pluginName string, pluginType consts.PluginType, version string) (*pluginutil.PluginRunner, error)
// NewPluginClient returns a client for managing the lifecycle of plugin // NewPluginClient returns a client for managing the lifecycle of plugin
// processes // processes
@ -168,6 +172,10 @@ func (d StaticSystemView) LookupPlugin(_ context.Context, _ string, _ consts.Plu
return nil, errors.New("LookupPlugin is not implemented in StaticSystemView") return nil, errors.New("LookupPlugin is not implemented in StaticSystemView")
} }
func (d StaticSystemView) LookupPluginVersion(_ context.Context, _ string, _ consts.PluginType, _ string) (*pluginutil.PluginRunner, error) {
return nil, errors.New("LookupPluginVersion is not implemented in StaticSystemView")
}
func (d StaticSystemView) MlockEnabled() bool { func (d StaticSystemView) MlockEnabled() bool {
return d.EnableMlock return d.EnableMlock
} }

View File

@ -107,6 +107,10 @@ func (s *gRPCSystemViewClient) LookupPlugin(_ context.Context, _ string, _ const
return nil, fmt.Errorf("cannot call LookupPlugin from a plugin backend") return nil, fmt.Errorf("cannot call LookupPlugin from a plugin backend")
} }
func (s *gRPCSystemViewClient) LookupPluginVersion(_ context.Context, _ string, _ consts.PluginType, _ string) (*pluginutil.PluginRunner, error) {
return nil, fmt.Errorf("cannot call LookupPluginVersion from a plugin backend")
}
func (s *gRPCSystemViewClient) MlockEnabled() bool { func (s *gRPCSystemViewClient) MlockEnabled() bool {
reply, err := s.client.MlockEnabled(context.Background(), &pb.Empty{}) reply, err := s.client.MlockEnabled(context.Background(), &pb.Empty{})
if err != nil { if err != nil {

View File

@ -6,7 +6,7 @@ import (
"fmt" "fmt"
log "github.com/hashicorp/go-hclog" log "github.com/hashicorp/go-hclog"
plugin "github.com/hashicorp/go-plugin" "github.com/hashicorp/go-plugin"
"github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/pluginutil" "github.com/hashicorp/vault/sdk/helper/pluginutil"
"github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/logical"
@ -28,13 +28,13 @@ func (b *BackendPluginClient) Cleanup(ctx context.Context) {
b.client.Kill() b.client.Kill()
} }
// NewBackend will return an instance of an RPC-based client implementation of the backend for // NewBackendWithVersion will return an instance of an RPC-based client implementation of the backend for
// external plugins, or a concrete implementation of the backend if it is a builtin backend. // external plugins, or a concrete implementation of the backend if it is a builtin backend.
// The backend is returned as a logical.Backend interface. The isMetadataMode param determines whether // The backend is returned as a logical.Backend interface. The isMetadataMode param determines whether
// the plugin should run in metadata mode. // the plugin should run in metadata mode.
func NewBackend(ctx context.Context, pluginName string, pluginType consts.PluginType, sys pluginutil.LookRunnerUtil, conf *logical.BackendConfig, isMetadataMode bool) (logical.Backend, error) { func NewBackendWithVersion(ctx context.Context, pluginName string, pluginType consts.PluginType, sys pluginutil.LookRunnerUtil, conf *logical.BackendConfig, isMetadataMode bool, version string) (logical.Backend, error) {
// Look for plugin in the plugin catalog // Look for plugin in the plugin catalog
pluginRunner, err := sys.LookupPlugin(ctx, pluginName, pluginType) pluginRunner, err := sys.LookupPluginVersion(ctx, pluginName, pluginType, version)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -66,6 +66,14 @@ func NewBackend(ctx context.Context, pluginName string, pluginType consts.Plugin
return backend, nil return backend, nil
} }
// NewBackend will return an instance of an RPC-based client implementation of the backend for
// external plugins, or a concrete implementation of the backend if it is a builtin backend.
// The backend is returned as a logical.Backend interface. The isMetadataMode param determines whether
// the plugin should run in metadata mode.
func NewBackend(ctx context.Context, pluginName string, pluginType consts.PluginType, sys pluginutil.LookRunnerUtil, conf *logical.BackendConfig, isMetadataMode bool) (logical.Backend, error) {
return NewBackendWithVersion(ctx, pluginName, pluginType, sys, conf, isMetadataMode, "")
}
func NewPluginClient(ctx context.Context, sys pluginutil.RunnerUtil, pluginRunner *pluginutil.PluginRunner, logger log.Logger, isMetadataMode bool) (logical.Backend, error) { func NewPluginClient(ctx context.Context, sys pluginutil.RunnerUtil, pluginRunner *pluginutil.PluginRunner, logger log.Logger, isMetadataMode bool) (logical.Backend, error) {
// pluginMap is the map of plugins we can dispense. // pluginMap is the map of plugins we can dispense.
pluginSet := map[int]plugin.PluginSet{ pluginSet := map[int]plugin.PluginSet{

View File

@ -911,7 +911,7 @@ func (c *Core) newCredentialBackend(ctx context.Context, entry *MountEntry, sysV
f, ok := c.credentialBackends[t] f, ok := c.credentialBackends[t]
if !ok { if !ok {
plug, err := c.pluginCatalog.Get(ctx, t, consts.PluginTypeCredential, "") plug, err := c.pluginCatalog.Get(ctx, t, consts.PluginTypeCredential, entry.Version)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -938,6 +938,7 @@ func (c *Core) newCredentialBackend(ctx context.Context, entry *MountEntry, sysV
} }
conf["plugin_type"] = consts.PluginTypeCredential.String() conf["plugin_type"] = consts.PluginTypeCredential.String()
conf["plugin_version"] = entry.Version
authLogger := c.baseLogger.Named(fmt.Sprintf("auth.%s.%s", t, entry.Accessor)) authLogger := c.baseLogger.Named(fmt.Sprintf("auth.%s.%s", t, entry.Accessor))
c.AddLogger(authLogger) c.AddLogger(authLogger)

View File

@ -0,0 +1,318 @@
package vault
import (
"context"
"crypto/sha256"
"errors"
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"sync"
"testing"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/sdk/framework"
)
var (
compileOnce sync.Once
pluginBytes []byte
)
func testCoreWithPlugin(t *testing.T) (*Core, string, string) {
t.Helper()
pluginName, pluginSha256, pluginDir := compilePlugin(t)
conf := &CoreConfig{
BuiltinRegistry: NewMockBuiltinRegistry(),
PluginDirectory: pluginDir,
}
core := TestCoreWithSealAndUI(t, conf)
core, _, _ = testCoreUnsealed(t, core)
return core, pluginName, pluginSha256
}
// to mount a plugin, we need a working binary plugin, so we compile one here.
func compilePlugin(t *testing.T) (string, string, string) {
pluginType := "approle"
pluginName := "vault-plugin-auth-" + pluginType
dir := ""
// detect if we are in the "vault/" or the root directory and compensate
if _, err := os.Stat("builtin"); os.IsNotExist(err) {
wd, err := os.Getwd()
if err != nil {
panic(err)
}
dir = filepath.Dir(wd)
}
pluginDir, cleanup := MakeTestPluginDir(t)
t.Cleanup(func() { cleanup(t) })
pluginPath := path.Join(pluginDir, pluginName)
// cache the compilation to only run once
compileOnce.Do(func() {
cmd := exec.Command("go", "build", "-o", pluginPath, fmt.Sprintf("builtin/credential/%s/cmd/%s/main.go", pluginType, pluginType))
cmd.Dir = dir
output, err := cmd.CombinedOutput()
if err != nil {
panic(fmt.Errorf("error running go build %v output: %s", err, output))
}
pluginBytes, err = os.ReadFile(pluginPath)
if err != nil {
panic(err)
}
})
// write the cached plugin if necessary
var err error
if _, err := os.Stat(pluginPath); os.IsNotExist(err) {
err = os.WriteFile(pluginPath, pluginBytes, 0o777)
}
if err != nil {
t.Fatal(err)
}
sha := sha256.New()
_, err = sha.Write(pluginBytes)
if err != nil {
t.Fatal(err)
}
return pluginName, fmt.Sprintf("%x", sha.Sum(nil)), pluginDir
}
func TestCore_EnableExternalCredentialPlugin(t *testing.T) {
c, pluginName, pluginSha256 := testCoreWithPlugin(t)
d := &framework.FieldData{
Raw: map[string]interface{}{
"name": pluginName,
"sha256": pluginSha256,
"version": "v1.0.0",
"command": pluginName,
},
Schema: c.systemBackend.pluginsCatalogCRUDPath().Fields,
}
resp, err := c.systemBackend.handlePluginCatalogUpdate(context.Background(), nil, d)
if err != nil {
t.Fatal(err)
}
if resp.Error() != nil {
t.Fatalf("%#v", resp)
}
me := &MountEntry{
Table: credentialTableType,
Path: "foo",
Type: pluginName,
Version: "v1.0.0",
}
err = c.enableCredential(namespace.RootContext(nil), me)
if err != nil {
t.Fatalf("err: %v", err)
}
match := c.router.MatchingMount(namespace.RootContext(nil), "auth/foo/bar")
if match != "auth/foo/" {
t.Fatalf("missing mount, match: %q", match)
}
}
func TestCore_EnableExternalCredentialPlugin_MultipleVersions(t *testing.T) {
c, pluginName, pluginSha256 := testCoreWithPlugin(t)
d := &framework.FieldData{
Raw: map[string]interface{}{
"name": pluginName,
"sha256": pluginSha256,
"version": "v1.0.0",
"command": pluginName,
},
Schema: c.systemBackend.pluginsCatalogCRUDPath().Fields,
}
resp, err := c.systemBackend.handlePluginCatalogUpdate(context.Background(), nil, d)
if err != nil {
t.Fatal(err)
}
if resp.Error() != nil {
t.Fatalf("%#v", resp)
}
d = &framework.FieldData{
Raw: map[string]interface{}{
"name": pluginName,
"sha256": pluginSha256,
"version": "v1.0.1",
"command": pluginName,
},
Schema: c.systemBackend.pluginsCatalogCRUDPath().Fields,
}
resp, err = c.systemBackend.handlePluginCatalogUpdate(context.Background(), nil, d)
if err != nil {
t.Fatal(err)
}
if resp.Error() != nil {
t.Fatalf("%#v", resp)
}
me := &MountEntry{
Table: credentialTableType,
Path: "foo",
Type: pluginName,
Version: "v1.0.0",
}
err = c.enableCredential(namespace.RootContext(nil), me)
if err != nil {
t.Fatalf("err: %v", err)
}
match := c.router.MatchingMount(namespace.RootContext(nil), "auth/foo/bar")
if match != "auth/foo/" {
t.Fatalf("missing mount, match: %q", match)
}
raw, _ := c.router.root.Get(match)
if raw.(*routeEntry).mountEntry.Version != "v1.0.0" {
t.Errorf("Expected mount to be version v1.0.0 but got %s", raw.(*routeEntry).mountEntry.Version)
}
}
func TestCore_EnableExternalCredentialPlugin_MultipleVersions_MountSecond(t *testing.T) {
c, pluginName, pluginSha256 := testCoreWithPlugin(t)
d := &framework.FieldData{
Raw: map[string]interface{}{
"name": pluginName,
"sha256": pluginSha256,
"command": pluginName,
"version": "v1.0.0",
},
Schema: c.systemBackend.pluginsCatalogCRUDPath().Fields,
}
resp, err := c.systemBackend.handlePluginCatalogUpdate(context.Background(), nil, d)
if err != nil {
t.Fatal(err)
}
if resp.Error() != nil {
t.Fatalf("%#v", resp)
}
d = &framework.FieldData{
Raw: map[string]interface{}{
"name": pluginName,
"sha256": pluginSha256,
"version": "v1.0.1",
"command": pluginName,
},
Schema: c.systemBackend.pluginsCatalogCRUDPath().Fields,
}
resp, err = c.systemBackend.handlePluginCatalogUpdate(context.Background(), nil, d)
if err != nil {
t.Fatal(err)
}
if resp.Error() != nil {
t.Fatalf("%#v", resp)
}
me := &MountEntry{
Table: credentialTableType,
Path: "foo",
Type: pluginName,
Version: "v1.0.1",
}
err = c.enableCredential(namespace.RootContext(nil), me)
if err != nil {
t.Fatalf("err: %v", err)
}
match := c.router.MatchingMount(namespace.RootContext(nil), "auth/foo/bar")
if match != "auth/foo/" {
t.Fatalf("missing mount, match: %q", match)
}
raw, _ := c.router.root.Get(match)
if raw.(*routeEntry).mountEntry.Version != "v1.0.1" {
t.Errorf("Expected mount to be version v1.0.1 but got %s", raw.(*routeEntry).mountEntry.Version)
}
}
func TestCore_EnableExternalCredentialPlugin_NoVersionsOkay(t *testing.T) {
c, pluginName, pluginSha256 := testCoreWithPlugin(t)
d := &framework.FieldData{
Raw: map[string]interface{}{
"name": pluginName,
"sha256": pluginSha256,
"command": pluginName,
},
Schema: c.systemBackend.pluginsCatalogCRUDPath().Fields,
}
resp, err := c.systemBackend.handlePluginCatalogUpdate(context.Background(), nil, d)
if err != nil {
t.Fatal(err)
}
if resp.Error() != nil {
t.Fatalf("%#v", resp)
}
me := &MountEntry{
Table: credentialTableType,
Path: "foo",
Type: pluginName,
}
err = c.enableCredential(namespace.RootContext(nil), me)
if err != nil {
t.Fatalf("err: %v", err)
}
match := c.router.MatchingMount(namespace.RootContext(nil), "auth/foo/bar")
if match != "auth/foo/" {
t.Fatalf("missing mount, match: %q", match)
}
}
func TestCore_EnableExternalCredentialPlugin_NoVersionOnRegister(t *testing.T) {
c, pluginName, pluginSha256 := testCoreWithPlugin(t)
d := &framework.FieldData{
Raw: map[string]interface{}{
"name": pluginName,
"sha256": pluginSha256,
"command": pluginName,
},
Schema: c.systemBackend.pluginsCatalogCRUDPath().Fields,
}
resp, err := c.systemBackend.handlePluginCatalogUpdate(context.Background(), nil, d)
if err != nil {
t.Fatal(err)
}
if resp.Error() != nil {
t.Fatalf("%#v", resp)
}
me := &MountEntry{
Table: credentialTableType,
Path: "foo",
Type: pluginName,
Version: "v1.0.0",
}
err = c.enableCredential(namespace.RootContext(nil), me)
if err == nil || !errors.Is(err, ErrPluginNotFound) {
t.Fatalf("Expected to get plugin not found but got: %v", err)
}
}
func TestCore_EnableExternalCredentialPlugin_InvalidName(t *testing.T) {
c, pluginName, pluginSha256 := testCoreWithPlugin(t)
d := &framework.FieldData{
Raw: map[string]interface{}{
"name": pluginName,
"sha256": pluginSha256,
"version": "v1.0.0",
"command": pluginName + "xyz",
},
Schema: c.systemBackend.pluginsCatalogCRUDPath().Fields,
}
_, err := c.systemBackend.handlePluginCatalogUpdate(context.Background(), nil, d)
if err == nil || !strings.Contains(err.Error(), "no such file or directory") {
t.Fatalf("should have gotten a no such file or directory error inserting the plugin: %v", err)
}
}

View File

@ -7,7 +7,7 @@ import (
"testing" "testing"
"time" "time"
metrics "github.com/armon/go-metrics" "github.com/armon/go-metrics"
"github.com/hashicorp/vault/helper/metricsutil" "github.com/hashicorp/vault/helper/metricsutil"
"github.com/hashicorp/vault/helper/namespace" "github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/sdk/helper/jsonutil" "github.com/hashicorp/vault/sdk/helper/jsonutil"

View File

@ -234,13 +234,19 @@ func (d dynamicSystemView) NewPluginClient(ctx context.Context, config pluginuti
// LookupPlugin looks for a plugin with the given name in the plugin catalog. It // LookupPlugin looks for a plugin with the given name in the plugin catalog. It
// returns a PluginRunner or an error if no plugin was found. // returns a PluginRunner or an error if no plugin was found.
func (d dynamicSystemView) LookupPlugin(ctx context.Context, name string, pluginType consts.PluginType) (*pluginutil.PluginRunner, error) { func (d dynamicSystemView) LookupPlugin(ctx context.Context, name string, pluginType consts.PluginType) (*pluginutil.PluginRunner, error) {
return d.LookupPluginVersion(ctx, name, pluginType, "")
}
// LookupPluginVersion looks for a plugin with the given name and version in the plugin catalog. It
// returns a PluginRunner or an error if no plugin was found.
func (d dynamicSystemView) LookupPluginVersion(ctx context.Context, name string, pluginType consts.PluginType, version string) (*pluginutil.PluginRunner, error) {
if d.core == nil { if d.core == nil {
return nil, fmt.Errorf("system view core is nil") return nil, fmt.Errorf("system view core is nil")
} }
if d.core.pluginCatalog == nil { if d.core.pluginCatalog == nil {
return nil, fmt.Errorf("system view core plugin catalog is nil") return nil, fmt.Errorf("system view core plugin catalog is nil")
} }
r, err := d.core.pluginCatalog.Get(ctx, name, pluginType, "") r, err := d.core.pluginCatalog.Get(ctx, name, pluginType, version)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -620,7 +620,8 @@ func getVersion(d *framework.FieldData) (string, error) {
} }
// Canonicalize the version string. // Canonicalize the version string.
version = semanticVersion.String() // Add the 'v' back in, since semantic version strips it out, and we want to be consistent with internal plugins.
version = "v" + semanticVersion.String()
} }
return version, nil return version, nil
@ -888,6 +889,10 @@ func mountInfo(entry *MountEntry) map[string]interface{} {
"external_entropy_access": entry.ExternalEntropyAccess, "external_entropy_access": entry.ExternalEntropyAccess,
"options": entry.Options, "options": entry.Options,
"uuid": entry.UUID, "uuid": entry.UUID,
"version": entry.Version,
"sha": entry.Sha,
"running_version": entry.RunningVersion,
"running_sha": entry.RunningSha,
} }
entryConfig := map[string]interface{}{ entryConfig := map[string]interface{}{
"default_lease_ttl": int64(entry.Config.DefaultLeaseTTL.Seconds()), "default_lease_ttl": int64(entry.Config.DefaultLeaseTTL.Seconds()),
@ -981,6 +986,14 @@ func (b *SystemBackend) handleMount(ctx context.Context, req *logical.Request, d
sealWrap := data.Get("seal_wrap").(bool) sealWrap := data.Get("seal_wrap").(bool)
externalEntropyAccess := data.Get("external_entropy_access").(bool) externalEntropyAccess := data.Get("external_entropy_access").(bool)
options := data.Get("options").(map[string]string) options := data.Get("options").(map[string]string)
version := data.Get("version").(string)
if version != "" {
v, err := semver.NewSemver(version)
if err != nil {
return nil, err
}
version = "v" + v.String()
}
var config MountConfig var config MountConfig
var apiConfig APIMountConfig var apiConfig APIMountConfig
@ -1123,6 +1136,7 @@ func (b *SystemBackend) handleMount(ctx context.Context, req *logical.Request, d
SealWrap: sealWrap, SealWrap: sealWrap,
ExternalEntropyAccess: externalEntropyAccess, ExternalEntropyAccess: externalEntropyAccess,
Options: options, Options: options,
Version: version,
} }
// Attempt mount // Attempt mount
@ -2223,6 +2237,14 @@ func (b *SystemBackend) handleEnableAuth(ctx context.Context, req *logical.Reque
sealWrap := data.Get("seal_wrap").(bool) sealWrap := data.Get("seal_wrap").(bool)
externalEntropyAccess := data.Get("external_entropy_access").(bool) externalEntropyAccess := data.Get("external_entropy_access").(bool)
options := data.Get("options").(map[string]string) options := data.Get("options").(map[string]string)
version := data.Get("version").(string)
if version != "" {
v, err := semver.NewSemver(version)
if err != nil {
return nil, err
}
version = "v" + v.String()
}
var config MountConfig var config MountConfig
var apiConfig APIMountConfig var apiConfig APIMountConfig
@ -2354,6 +2376,7 @@ func (b *SystemBackend) handleEnableAuth(ctx context.Context, req *logical.Reque
SealWrap: sealWrap, SealWrap: sealWrap,
ExternalEntropyAccess: externalEntropyAccess, ExternalEntropyAccess: externalEntropyAccess,
Options: options, Options: options,
Version: version,
} }
// Attempt enabling // Attempt enabling

View File

@ -1600,6 +1600,10 @@ func (b *SystemBackend) authPaths() []*framework.Path {
Type: framework.TypeKVPairs, Type: framework.TypeKVPairs,
Description: strings.TrimSpace(sysHelp["auth_options"][0]), Description: strings.TrimSpace(sysHelp["auth_options"][0]),
}, },
"version": {
Type: framework.TypeString,
Description: strings.TrimSpace(sysHelp["plugin-catalog_version"][0]),
},
}, },
Operations: map[logical.Operation]framework.OperationHandler{ Operations: map[logical.Operation]framework.OperationHandler{
logical.ReadOperation: &framework.PathOperation{ logical.ReadOperation: &framework.PathOperation{
@ -1971,6 +1975,10 @@ func (b *SystemBackend) mountPaths() []*framework.Path {
Type: framework.TypeKVPairs, Type: framework.TypeKVPairs,
Description: strings.TrimSpace(sysHelp["mount_options"][0]), Description: strings.TrimSpace(sysHelp["mount_options"][0]),
}, },
"version": {
Type: framework.TypeString,
Description: strings.TrimSpace(sysHelp["plugin-catalog_version"][0]),
},
}, },
Operations: map[logical.Operation]framework.OperationHandler{ Operations: map[logical.Operation]framework.OperationHandler{

View File

@ -17,7 +17,7 @@ import (
"github.com/fatih/structs" "github.com/fatih/structs"
"github.com/go-test/deep" "github.com/go-test/deep"
hclog "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
semver "github.com/hashicorp/go-version" semver "github.com/hashicorp/go-version"
"github.com/hashicorp/vault/audit" "github.com/hashicorp/vault/audit"
credUserpass "github.com/hashicorp/vault/builtin/credential/userpass" credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
@ -172,6 +172,10 @@ func TestSystemBackend_mounts(t *testing.T) {
"options": map[string]string{ "options": map[string]string{
"version": "1", "version": "1",
}, },
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
}, },
"sys/": map[string]interface{}{ "sys/": map[string]interface{}{
"type": "system", "type": "system",
@ -185,9 +189,13 @@ func TestSystemBackend_mounts(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []string{"Accept"}, "passthrough_request_headers": []string{"Accept"},
}, },
"local": false, "local": false,
"seal_wrap": true, "seal_wrap": true,
"options": map[string]string(nil), "options": map[string]string(nil),
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
}, },
"cubbyhole/": map[string]interface{}{ "cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage", "description": "per-token private secret storage",
@ -200,9 +208,13 @@ func TestSystemBackend_mounts(t *testing.T) {
"max_lease_ttl": resp.Data["cubbyhole/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64), "max_lease_ttl": resp.Data["cubbyhole/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": true, "local": true,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]string(nil), "options": map[string]string(nil),
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
}, },
"identity/": map[string]interface{}{ "identity/": map[string]interface{}{
"description": "identity store", "description": "identity store",
@ -216,9 +228,13 @@ func TestSystemBackend_mounts(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []string{"Authorization"}, "passthrough_request_headers": []string{"Authorization"},
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]string(nil), "options": map[string]string(nil),
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
}, },
} }
if diff := deep.Equal(resp.Data, exp); len(diff) > 0 { if diff := deep.Equal(resp.Data, exp); len(diff) > 0 {
@ -285,6 +301,10 @@ func TestSystemBackend_mount(t *testing.T) {
"options": map[string]string{ "options": map[string]string{
"version": "1", "version": "1",
}, },
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
}, },
"sys/": map[string]interface{}{ "sys/": map[string]interface{}{
"type": "system", "type": "system",
@ -298,9 +318,13 @@ func TestSystemBackend_mount(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []string{"Accept"}, "passthrough_request_headers": []string{"Accept"},
}, },
"local": false, "local": false,
"seal_wrap": true, "seal_wrap": true,
"options": map[string]string(nil), "options": map[string]string(nil),
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
}, },
"cubbyhole/": map[string]interface{}{ "cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage", "description": "per-token private secret storage",
@ -313,9 +337,13 @@ func TestSystemBackend_mount(t *testing.T) {
"max_lease_ttl": resp.Data["cubbyhole/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64), "max_lease_ttl": resp.Data["cubbyhole/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": true, "local": true,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]string(nil), "options": map[string]string(nil),
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
}, },
"identity/": map[string]interface{}{ "identity/": map[string]interface{}{
"description": "identity store", "description": "identity store",
@ -329,9 +357,13 @@ func TestSystemBackend_mount(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []string{"Authorization"}, "passthrough_request_headers": []string{"Authorization"},
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]string(nil), "options": map[string]string(nil),
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
}, },
"prod/secret/": map[string]interface{}{ "prod/secret/": map[string]interface{}{
"description": "", "description": "",
@ -349,6 +381,10 @@ func TestSystemBackend_mount(t *testing.T) {
"options": map[string]string{ "options": map[string]string{
"version": "1", "version": "1",
}, },
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
}, },
} }
if diff := deep.Equal(resp.Data, exp); len(diff) > 0 { if diff := deep.Equal(resp.Data, exp); len(diff) > 0 {
@ -1818,9 +1854,13 @@ func TestSystemBackend_authTable(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"token_type": "default-service", "token_type": "default-service",
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]string(nil), "options": map[string]string(nil),
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
}, },
} }
if diff := deep.Equal(resp.Data, exp); diff != nil { if diff := deep.Equal(resp.Data, exp); diff != nil {
@ -1882,9 +1922,13 @@ func TestSystemBackend_enableAuth(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"token_type": "default-service", "token_type": "default-service",
}, },
"local": true, "local": true,
"seal_wrap": true, "seal_wrap": true,
"options": map[string]string{}, "options": map[string]string{},
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
}, },
"token/": map[string]interface{}{ "token/": map[string]interface{}{
"type": "token", "type": "token",
@ -1898,9 +1942,13 @@ func TestSystemBackend_enableAuth(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"token_type": "default-service", "token_type": "default-service",
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]string(nil), "options": map[string]string(nil),
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
}, },
} }
if diff := deep.Equal(resp.Data, exp); diff != nil { if diff := deep.Equal(resp.Data, exp); diff != nil {
@ -2893,21 +2941,25 @@ func TestSystemBackend_rotate(t *testing.T) {
} }
func testSystemBackend(t *testing.T) logical.Backend { func testSystemBackend(t *testing.T) logical.Backend {
t.Helper()
c, _, _ := TestCoreUnsealed(t) c, _, _ := TestCoreUnsealed(t)
return c.systemBackend return c.systemBackend
} }
func testSystemBackendRaw(t *testing.T) logical.Backend { func testSystemBackendRaw(t *testing.T) logical.Backend {
t.Helper()
c, _, _ := TestCoreUnsealedRaw(t) c, _, _ := TestCoreUnsealedRaw(t)
return c.systemBackend return c.systemBackend
} }
func testCoreSystemBackend(t *testing.T) (*Core, logical.Backend, string) { func testCoreSystemBackend(t *testing.T) (*Core, logical.Backend, string) {
t.Helper()
c, _, root := TestCoreUnsealed(t) c, _, root := TestCoreUnsealed(t)
return c, c.systemBackend, root return c, c.systemBackend, root
} }
func testCoreSystemBackendRaw(t *testing.T) (*Core, logical.Backend, string) { func testCoreSystemBackendRaw(t *testing.T) (*Core, logical.Backend, string) {
t.Helper()
c, _, root := TestCoreUnsealedRaw(t) c, _, root := TestCoreUnsealedRaw(t)
return c, c.systemBackend, root return c, c.systemBackend, root
} }
@ -3033,7 +3085,7 @@ func TestSystemBackend_PluginCatalog_CRUD(t *testing.T) {
"args": []string{"--test"}, "args": []string{"--test"},
"sha256": "31", "sha256": "31",
"builtin": false, "builtin": false,
"version": "0.1.0", "version": "v0.1.0",
} }
if !reflect.DeepEqual(actual, expected) { if !reflect.DeepEqual(actual, expected) {
t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", actual, expected) t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", actual, expected)
@ -3270,6 +3322,10 @@ func TestSystemBackend_InternalUIMounts(t *testing.T) {
"options": map[string]string{ "options": map[string]string{
"version": "1", "version": "1",
}, },
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
}, },
"sys/": map[string]interface{}{ "sys/": map[string]interface{}{
"type": "system", "type": "system",
@ -3283,9 +3339,13 @@ func TestSystemBackend_InternalUIMounts(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []string{"Accept"}, "passthrough_request_headers": []string{"Accept"},
}, },
"local": false, "local": false,
"seal_wrap": true, "seal_wrap": true,
"options": map[string]string(nil), "options": map[string]string(nil),
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
}, },
"cubbyhole/": map[string]interface{}{ "cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage", "description": "per-token private secret storage",
@ -3298,9 +3358,13 @@ func TestSystemBackend_InternalUIMounts(t *testing.T) {
"max_lease_ttl": resp.Data["secret"].(map[string]interface{})["cubbyhole/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64), "max_lease_ttl": resp.Data["secret"].(map[string]interface{})["cubbyhole/"].(map[string]interface{})["config"].(map[string]interface{})["max_lease_ttl"].(int64),
"force_no_cache": false, "force_no_cache": false,
}, },
"local": true, "local": true,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]string(nil), "options": map[string]string(nil),
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
}, },
"identity/": map[string]interface{}{ "identity/": map[string]interface{}{
"description": "identity store", "description": "identity store",
@ -3314,9 +3378,13 @@ func TestSystemBackend_InternalUIMounts(t *testing.T) {
"force_no_cache": false, "force_no_cache": false,
"passthrough_request_headers": []string{"Authorization"}, "passthrough_request_headers": []string{"Authorization"},
}, },
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"options": map[string]string(nil), "options": map[string]string(nil),
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
}, },
}, },
"auth": map[string]interface{}{ "auth": map[string]interface{}{
@ -3335,6 +3403,10 @@ func TestSystemBackend_InternalUIMounts(t *testing.T) {
"uuid": resp.Data["auth"].(map[string]interface{})["token/"].(map[string]interface{})["uuid"], "uuid": resp.Data["auth"].(map[string]interface{})["token/"].(map[string]interface{})["uuid"],
"local": false, "local": false,
"seal_wrap": false, "seal_wrap": false,
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
}, },
}, },
} }

View File

@ -12,7 +12,7 @@ import (
"github.com/armon/go-metrics" "github.com/armon/go-metrics"
"github.com/hashicorp/go-secure-stdlib/strutil" "github.com/hashicorp/go-secure-stdlib/strutil"
uuid "github.com/hashicorp/go-uuid" "github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/builtin/plugin" "github.com/hashicorp/vault/builtin/plugin"
"github.com/hashicorp/vault/helper/metricsutil" "github.com/hashicorp/vault/helper/metricsutil"
"github.com/hashicorp/vault/helper/namespace" "github.com/hashicorp/vault/helper/namespace"
@ -328,6 +328,12 @@ type MountEntry struct {
// without separately managing their locks individually. See SyncCache() for // without separately managing their locks individually. See SyncCache() for
// the specific values that are being cached. // the specific values that are being cached.
synthesizedConfigCache sync.Map synthesizedConfigCache sync.Map
// version info
Version string `json:"version,omitempty"`
Sha string `json:"sha,omitempty"`
RunningVersion string `json:"running_version,omitempty"`
RunningSha string `json:"running_sha,omitempty"`
} }
// MountConfig is used to hold settable options // MountConfig is used to hold settable options
@ -1419,7 +1425,7 @@ func (c *Core) newLogicalBackend(ctx context.Context, entry *MountEntry, sysView
f, ok := c.logicalBackends[t] f, ok := c.logicalBackends[t]
if !ok { if !ok {
plug, err := c.pluginCatalog.Get(ctx, t, consts.PluginTypeSecrets, "") plug, err := c.pluginCatalog.Get(ctx, t, consts.PluginTypeSecrets, entry.Version)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -2424,3 +2424,37 @@ func CreateTestClusterWithRollbackPeriod(t testing.T, newPeriod time.Duration, b
// Return the cluster. // Return the cluster.
return cluster return cluster
} }
// MakeTestPluginDir creates a temporary directory suitable for holding plugins.
// This helper also resolves symlinks to make tests happy on OS X.
func MakeTestPluginDir(t testing.T) (string, func(t testing.T)) {
if t != nil {
t.Helper()
}
dir, err := os.MkdirTemp("", "")
if err != nil {
if t == nil {
panic(err)
}
t.Fatal(err)
}
// OSX tempdir are /var, but actually symlinked to /private/var
dir, err = filepath.EvalSymlinks(dir)
if err != nil {
if t == nil {
panic(err)
}
t.Fatal(err)
}
return dir, func(t testing.T) {
if err := os.RemoveAll(dir); err != nil {
if t == nil {
panic(err)
}
t.Fatal(err)
}
}
}