Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 15 commits into from
Aug 31, 2022
5 changes: 5 additions & 0 deletions api/sys_mounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ type MountInput struct {
SealWrap bool `json:"seal_wrap" mapstructure:"seal_wrap"`
ExternalEntropyAccess bool `json:"external_entropy_access" mapstructure:"external_entropy_access"`
Options map[string]string `json:"options"`
Version string `json:"version,omitempty"`

// Deprecated: Newer server responses should be returning this information in the
// Type field (json: "type") instead.
Expand Down Expand Up @@ -281,6 +282,10 @@ type MountOutput struct {
Local bool `json:"local"`
SealWrap bool `json:"seal_wrap" mapstructure:"seal_wrap"`
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 {
Expand Down
153 changes: 153 additions & 0 deletions api/sys_mounts_test.go
Original file line number Diff line number Diff line change
@@ -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": ""
}
}
}`
24 changes: 23 additions & 1 deletion api/sys_plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,22 @@ type ListPluginsResponse struct {
// PluginsByType is the list of plugins by type.
PluginsByType map[consts.PluginType][]string `json:"types"`

Details []PluginDetails `json:"details,omitempty"`

// Names is the list of names of the plugins.
//
// Deprecated: Newer server responses should be returning PluginsByType (json:
// "types") instead.
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.
func (c *Sys) ListPlugins(i *ListPluginsInput) (*ListPluginsResponse, error) {
return c.ListPluginsWithContext(context.Background(), i)
Expand Down Expand Up @@ -98,6 +107,7 @@ func (c *Sys) ListPluginsWithContext(ctx context.Context, i *ListPluginsInput) (

result := &ListPluginsResponse{
PluginsByType: make(map[consts.PluginType][]string),
Details: []PluginDetails{},
}
if i.Type == consts.PluginTypeUnknown {
for _, pluginType := range consts.PluginTypes {
Expand Down Expand Up @@ -129,6 +139,12 @@ func (c *Sys) ListPluginsWithContext(ctx context.Context, i *ListPluginsInput) (
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
}

Expand Down Expand Up @@ -194,6 +210,9 @@ type RegisterPluginInput struct {

// SHA256 is the shasum of the plugin.
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.
Expand Down Expand Up @@ -227,6 +246,9 @@ type DeregisterPluginInput struct {

// Type of the plugin. Required.
Type consts.PluginType `json:"type"`

// Version of the plugin. Optional.
Version string `json:"version,omitempty"`
}

// DeregisterPlugin wraps DeregisterPluginWithContext using context.Background.
Expand All @@ -242,7 +264,7 @@ func (c *Sys) DeregisterPluginWithContext(ctx context.Context, i *DeregisterPlug

path := catalogPathByType(i.Type, i.Name)
req := c.c.NewRequest(http.MethodDelete, path)

req.Params.Set("version", i.Version)
resp, err := c.c.rawRequestWithContext(ctx, req)
if err == nil {
defer resp.Body.Close()
Expand Down
29 changes: 27 additions & 2 deletions api/sys_plugins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,27 @@ import (
"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) {
mockVaultServer := httptest.NewServer(http.HandlerFunc(mockVaultHandler))
mockVaultServer := httptest.NewServer(http.HandlerFunc(mockVaultHandlerList))
defer mockVaultServer.Close()

cfg := DefaultConfig()
Expand Down Expand Up @@ -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))
}

Expand Down Expand Up @@ -77,3 +96,9 @@ const listUntypedResponse = `{
"warnings": null,
"auth": null
}`

func mockVaultHandlerRegister(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte(registerResponse))
}

const registerResponse = `{}`
9 changes: 5 additions & 4 deletions builtin/plugin/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (

log "github.com/hashicorp/go-hclog"

uuid "github.com/hashicorp/go-uuid"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/logical"
Expand Down Expand Up @@ -48,11 +48,12 @@ func Backend(ctx context.Context, conf *logical.BackendConfig) (*PluginBackend,
if err != nil {
return nil, err
}
version := conf.Config["plugin_version"]

sys := conf.System

// NewBackend with isMetadataMode set to true
raw, err := bplugin.NewBackend(ctx, name, pluginType, sys, conf, true)
// NewBackendWithVersion with isMetadataMode set to true
raw, err := bplugin.NewBackendWithVersion(ctx, name, pluginType, sys, conf, true, version)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -105,7 +106,7 @@ func (b *PluginBackend) startBackend(ctx context.Context, storage logical.Storag
// Ensure proper cleanup of the backend (i.e. call client.Kill())
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 {
return err
}
Expand Down
10 changes: 10 additions & 0 deletions builtin/plugin/backend_lazyLoad_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,13 @@ func (v testSystemView) LookupPlugin(context.Context, string, consts.PluginType)
},
}, 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 changes: 3 additions & 0 deletions changelog/16856.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:change
plugins: Add plugin version to auth register, list, and mount table
```
9 changes: 9 additions & 0 deletions command/auth_enable.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type AuthEnableCommand struct {
flagExternalEntropyAccess bool
flagTokenType string
flagVersion int
flagPluginVersion string
}

func (c *AuthEnableCommand) Synopsis() string {
Expand Down Expand Up @@ -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.",
})

f.StringVar(&StringVar{
Name: "plugin-version",
tomhjp marked this conversation as resolved.
Show resolved Hide resolved
Target: &c.flagPluginVersion,
Default: "",
Usage: "Select the version of the plugin to enable.",
})

return set
}

Expand Down Expand Up @@ -262,6 +270,7 @@ func (c *AuthEnableCommand) Run(args []string) int {

authOpts := &api.EnableAuthOptions{
Type: authType,
Version: c.flagPluginVersion,
Description: c.flagDescription,
Local: c.flagLocal,
SealWrap: c.flagSealWrap,
Expand Down
Loading