Add plugin version to GRPC interface (#17088)

Add plugin version to GRPC interface

Added a version interface in the sdk/logical so that it can be shared between all plugin types, and then wired it up to RunningVersion in the mounts, auth list, and database systems.

I've tested that this works with auth, database, and secrets plugin types, with the following logic to populate RunningVersion:

If a plugin has a PluginVersion() method implemented, then that is used
If not, and the plugin is built into the Vault binary, then the go.mod version is used
Otherwise, the it will be the empty string.
My apologies for the length of this PR.

* Placeholder backend should be external

We use a placeholder backend (previously a framework.Backend) before a
GRPC plugin is lazy-loaded. This makes us later think the plugin is a
builtin plugin.

So we added a `placeholderBackend` type that overrides the
`IsExternal()` method so that later we know that the plugin is external,
and don't give it a default builtin version.
This commit is contained in:
Christopher Swenson 2022-09-15 16:37:59 -07:00 committed by GitHub
parent aa503ef7ff
commit b136a7ecd8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 954 additions and 178 deletions

View File

@ -12,6 +12,7 @@ EXTERNAL_TOOLS_CI=\
EXTERNAL_TOOLS=\
github.com/client9/misspell/cmd/misspell
GOFMT_FILES?=$$(find . -name '*.go' | grep -v pb.go | grep -v vendor)
SED?=$(shell command -v gsed || command -v sed)
GO_VERSION_MIN=1.19.1
@ -190,8 +191,8 @@ proto: bootstrap
# No additional sed expressions should be added to this list. Going forward
# we should just use the variable names choosen by protobuf. These are left
# here for backwards compatability, namely for SDK compilation.
sed -i -e 's/Id/ID/' vault/request_forwarding_service.pb.go
sed -i -e 's/Idp/IDP/' -e 's/Url/URL/' -e 's/Id/ID/' -e 's/IDentity/Identity/' -e 's/EntityId/EntityID/' -e 's/Api/API/' -e 's/Qr/QR/' -e 's/Totp/TOTP/' -e 's/Mfa/MFA/' -e 's/Pingid/PingID/' -e 's/namespaceId/namespaceID/' -e 's/Ttl/TTL/' -e 's/BoundCidrs/BoundCIDRs/' helper/identity/types.pb.go helper/identity/mfa/types.pb.go helper/storagepacker/types.pb.go sdk/plugin/pb/backend.pb.go sdk/logical/identity.pb.go vault/activity/activity_log.pb.go
$(SED) -i -e 's/Id/ID/' vault/request_forwarding_service.pb.go
$(SED) -i -e 's/Idp/IDP/' -e 's/Url/URL/' -e 's/Id/ID/' -e 's/IDentity/Identity/' -e 's/EntityId/EntityID/' -e 's/Api/API/' -e 's/Qr/QR/' -e 's/Totp/TOTP/' -e 's/Mfa/MFA/' -e 's/Pingid/PingID/' -e 's/namespaceId/namespaceID/' -e 's/Ttl/TTL/' -e 's/BoundCidrs/BoundCIDRs/' helper/identity/types.pb.go helper/identity/mfa/types.pb.go helper/storagepacker/types.pb.go sdk/plugin/pb/backend.pb.go sdk/logical/identity.pb.go vault/activity/activity_log.pb.go
# This will inject the sentinel struct tags as decorated in the proto files.
protoc-go-inject-tag -input=./helper/identity/types.pb.go

View File

@ -9,6 +9,7 @@ import (
v4 "github.com/hashicorp/vault/sdk/database/dbplugin"
v5 "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
"github.com/hashicorp/vault/sdk/helper/pluginutil"
"github.com/hashicorp/vault/sdk/logical"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
@ -18,6 +19,8 @@ type databaseVersionWrapper struct {
v5 v5.Database
}
var _ logical.PluginVersioner = databaseVersionWrapper{}
// newDatabaseWrapper figures out which version of the database the pluginName is referring to and returns a wrapper object
// that can be used to make operations on the underlying database plugin.
func newDatabaseWrapper(ctx context.Context, pluginName string, pluginVersion string, sys pluginutil.LookRunnerUtil, logger log.Logger) (dbw databaseVersionWrapper, err error) {
@ -227,6 +230,21 @@ func (d databaseVersionWrapper) Close() error {
return d.v4.Close()
}
func (d databaseVersionWrapper) PluginVersion() logical.PluginVersion {
// v5 Database
if d.isV5() {
if versioner, ok := d.v5.(logical.PluginVersioner); ok {
return versioner.PluginVersion()
}
}
// v4 Database
if versioner, ok := d.v4.(logical.PluginVersioner); ok {
return versioner.PluginVersion()
}
return logical.EmptyPluginVersion
}
func (d databaseVersionWrapper) isV5() bool {
return d.v5 != nil
}

View File

@ -10,7 +10,7 @@ import (
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-multierror"
uuid "github.com/hashicorp/go-uuid"
"github.com/hashicorp/go-uuid"
v5 "github.com/hashicorp/vault/builtin/plugin/v5"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/consts"
@ -79,15 +79,28 @@ func Backend(ctx context.Context, conf *logical.BackendConfig) (*PluginBackend,
// Get SpecialPaths and BackendType
paths := raw.SpecialPaths()
btype := raw.Type()
runningVersion := ""
if versioner, ok := raw.(logical.PluginVersioner); ok {
runningVersion = versioner.PluginVersion().Version
}
external := false
if externaler, ok := raw.(logical.Externaler); ok {
external = externaler.IsExternal()
}
// Cleanup meta plugin backend
raw.Cleanup(ctx)
// Initialize b.Backend with dummy backend since plugin
// Initialize b.Backend with placeholder backend since plugin
// backends will need to be lazy loaded.
b.Backend = &framework.Backend{
PathsSpecial: paths,
BackendType: btype,
b.Backend = &placeholderBackend{
Backend: framework.Backend{
PathsSpecial: paths,
BackendType: btype,
RunningVersion: runningVersion,
},
external: external,
}
b.config = conf
@ -95,6 +108,23 @@ func Backend(ctx context.Context, conf *logical.BackendConfig) (*PluginBackend,
return &b, nil
}
// placeholderBackend is used a placeholder before a backend is lazy-loaded.
// It is mostly used to mark that the backend is an external backend.
type placeholderBackend struct {
framework.Backend
external bool
}
func (p *placeholderBackend) IsExternal() bool {
return p.external
}
var (
_ logical.Externaler = (*placeholderBackend)(nil)
_ logical.PluginVersioner = (*placeholderBackend)(nil)
)
// PluginBackend is a thin wrapper around plugin.BackendPluginClient
type PluginBackend struct {
Backend logical.Backend
@ -286,3 +316,22 @@ func (b *PluginBackend) Type() logical.BackendType {
defer b.RUnlock()
return b.Backend.Type()
}
func (b *PluginBackend) PluginVersion() logical.PluginVersion {
if versioner, ok := b.Backend.(logical.PluginVersioner); ok {
return versioner.PluginVersion()
}
return logical.EmptyPluginVersion
}
func (b *PluginBackend) IsExternal() bool {
if externaler, ok := b.Backend.(logical.Externaler); ok {
return externaler.IsExternal()
}
return false
}
var (
_ logical.PluginVersioner = (*PluginBackend)(nil)
_ logical.Externaler = (*PluginBackend)(nil)
)

View File

@ -5,7 +5,7 @@ import (
"net/rpc"
"sync"
uuid "github.com/hashicorp/go-uuid"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/sdk/plugin"
@ -35,7 +35,7 @@ func Backend(ctx context.Context, conf *logical.BackendConfig) (logical.Backend,
return &b, nil
}
// backend is a thin wrapper around plugin.BackendPluginClientV5
// backend is a thin wrapper around a builtin plugin or a plugin.BackendPluginClientV5
type backend struct {
logical.Backend
mu sync.RWMutex
@ -147,3 +147,11 @@ func (b *backend) InvalidateKey(ctx context.Context, key string) {
defer b.mu.RUnlock()
b.Backend.InvalidateKey(ctx, key)
}
func (b *backend) IsExternal() bool {
switch b.Backend.(type) {
case *plugin.BackendPluginClientV5:
return true
}
return false
}

3
changelog/17088.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
plugins: Adding version to plugin GRPC interface
```

View File

@ -0,0 +1,54 @@
package versions
import (
"fmt"
"runtime/debug"
"strings"
"sync"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/version"
)
var (
buildInfoOnce sync.Once // once is used to ensure we only parse build info once.
buildInfo *debug.BuildInfo
DefaultBuiltinVersion = "v" + version.GetVersion().Version + "+builtin.vault"
)
func GetBuiltinVersion(pluginType consts.PluginType, pluginName string) string {
buildInfoOnce.Do(func() {
buildInfo, _ = debug.ReadBuildInfo()
})
// Should never happen, means the binary was built without Go modules.
// Fall back to just the Vault version.
if buildInfo == nil {
return DefaultBuiltinVersion
}
// Vault builtin plugins are all either:
// a) An external repo within the hashicorp org - return external repo version with +builtin
// b) Within the Vault repo itself - return Vault version with +builtin.vault
//
// The repo names are predictable, but follow slightly different patterns
// for each plugin type.
t := pluginType.String()
switch pluginType {
case consts.PluginTypeDatabase:
// Database plugin built-ins are registered as e.g. "postgresql-database-plugin"
pluginName = strings.TrimSuffix(pluginName, "-database-plugin")
case consts.PluginTypeSecrets:
// Repos use "secrets", pluginType.String() is "secret".
t = "secrets"
}
pluginModulePath := fmt.Sprintf("github.com/hashicorp/vault-plugin-%s-%s", t, pluginName)
for _, dep := range buildInfo.Deps {
if dep.Path == pluginModulePath {
return dep.Version + "+builtin"
}
}
return DefaultBuiltinVersion
}

View File

@ -17,6 +17,7 @@ import (
"github.com/go-test/deep"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/helper/versions"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/vault"
@ -415,7 +416,7 @@ func TestSysMounts_headerAuth(t *testing.T) {
"options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"sys/": map[string]interface{}{
@ -433,7 +434,7 @@ func TestSysMounts_headerAuth(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.DefaultBuiltinVersion,
"version": "",
},
"cubbyhole/": map[string]interface{}{
@ -450,7 +451,7 @@ func TestSysMounts_headerAuth(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "cubbyhole"),
"version": "",
},
"identity/": map[string]interface{}{
@ -468,7 +469,7 @@ func TestSysMounts_headerAuth(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "identity"),
"version": "",
},
},
@ -486,7 +487,7 @@ func TestSysMounts_headerAuth(t *testing.T) {
"options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"sys/": map[string]interface{}{
@ -504,7 +505,7 @@ func TestSysMounts_headerAuth(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.DefaultBuiltinVersion,
"version": "",
},
"cubbyhole/": map[string]interface{}{
@ -521,7 +522,7 @@ func TestSysMounts_headerAuth(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "cubbyhole"),
"version": "",
},
"identity/": map[string]interface{}{
@ -539,7 +540,7 @@ func TestSysMounts_headerAuth(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "identity"),
"version": "",
},
}

View File

@ -8,6 +8,8 @@ import (
"time"
"github.com/go-test/deep"
"github.com/hashicorp/vault/helper/versions"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/vault"
)
@ -128,7 +130,7 @@ func TestSysEnableAuth(t *testing.T) {
"options": map[string]interface{}{},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeCredential, "approle"),
"version": "",
},
"token/": map[string]interface{}{
@ -166,7 +168,7 @@ func TestSysEnableAuth(t *testing.T) {
"options": map[string]interface{}{},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeCredential, "approle"),
"version": "",
},
"token/": map[string]interface{}{
@ -531,7 +533,7 @@ func TestSysRemountAuth(t *testing.T) {
"options": map[string]interface{}{},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"token/": map[string]interface{}{
@ -568,7 +570,7 @@ func TestSysRemountAuth(t *testing.T) {
"options": map[string]interface{}{},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"token/": map[string]interface{}{

View File

@ -7,9 +7,10 @@ import (
"testing"
"time"
"github.com/go-test/deep"
"github.com/fatih/structs"
"github.com/go-test/deep"
"github.com/hashicorp/vault/helper/versions"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/vault"
)
@ -44,7 +45,7 @@ func TestSysMounts(t *testing.T) {
"options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"sys/": map[string]interface{}{
@ -62,7 +63,7 @@ func TestSysMounts(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.DefaultBuiltinVersion,
"version": "",
},
"cubbyhole/": map[string]interface{}{
@ -79,7 +80,7 @@ func TestSysMounts(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "cubbyhole"),
"version": "",
},
"identity/": map[string]interface{}{
@ -97,7 +98,7 @@ func TestSysMounts(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "identity"),
"version": "",
},
},
@ -115,7 +116,7 @@ func TestSysMounts(t *testing.T) {
"options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"sys/": map[string]interface{}{
@ -133,7 +134,7 @@ func TestSysMounts(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.DefaultBuiltinVersion,
"version": "",
},
"cubbyhole/": map[string]interface{}{
@ -150,7 +151,7 @@ func TestSysMounts(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "cubbyhole"),
"version": "",
},
"identity/": map[string]interface{}{
@ -168,7 +169,7 @@ func TestSysMounts(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "identity"),
"version": "",
},
}
@ -233,7 +234,7 @@ func TestSysMount(t *testing.T) {
"options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"secret/": map[string]interface{}{
@ -250,7 +251,7 @@ func TestSysMount(t *testing.T) {
"options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"sys/": map[string]interface{}{
@ -268,7 +269,7 @@ func TestSysMount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.DefaultBuiltinVersion,
"version": "",
},
"cubbyhole/": map[string]interface{}{
@ -285,7 +286,7 @@ func TestSysMount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "cubbyhole"),
"version": "",
},
"identity/": map[string]interface{}{
@ -303,7 +304,7 @@ func TestSysMount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "identity"),
"version": "",
},
},
@ -321,7 +322,7 @@ func TestSysMount(t *testing.T) {
"options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"secret/": map[string]interface{}{
@ -338,7 +339,7 @@ func TestSysMount(t *testing.T) {
"options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"sys/": map[string]interface{}{
@ -356,7 +357,7 @@ func TestSysMount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.DefaultBuiltinVersion,
"version": "",
},
"cubbyhole/": map[string]interface{}{
@ -373,7 +374,7 @@ func TestSysMount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "cubbyhole"),
"version": "",
},
"identity/": map[string]interface{}{
@ -391,7 +392,7 @@ func TestSysMount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "identity"),
"version": "",
},
}
@ -491,7 +492,7 @@ func TestSysRemount(t *testing.T) {
"options": map[string]interface{}{},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"secret/": map[string]interface{}{
@ -508,7 +509,7 @@ func TestSysRemount(t *testing.T) {
"options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"sys/": map[string]interface{}{
@ -526,7 +527,7 @@ func TestSysRemount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.DefaultBuiltinVersion,
"version": "",
},
"cubbyhole/": map[string]interface{}{
@ -543,7 +544,7 @@ func TestSysRemount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "cubbyhole"),
"version": "",
},
"identity/": map[string]interface{}{
@ -561,7 +562,7 @@ func TestSysRemount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "identity"),
"version": "",
},
},
@ -579,7 +580,7 @@ func TestSysRemount(t *testing.T) {
"options": map[string]interface{}{},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"secret/": map[string]interface{}{
@ -596,7 +597,7 @@ func TestSysRemount(t *testing.T) {
"options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"sys/": map[string]interface{}{
@ -614,7 +615,7 @@ func TestSysRemount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.DefaultBuiltinVersion,
"version": "",
},
"cubbyhole/": map[string]interface{}{
@ -631,7 +632,7 @@ func TestSysRemount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "cubbyhole"),
"version": "",
},
"identity/": map[string]interface{}{
@ -649,7 +650,7 @@ func TestSysRemount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "identity"),
"version": "",
},
}
@ -714,7 +715,7 @@ func TestSysUnmount(t *testing.T) {
"options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"sys/": map[string]interface{}{
@ -732,7 +733,7 @@ func TestSysUnmount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.DefaultBuiltinVersion,
"version": "",
},
"cubbyhole/": map[string]interface{}{
@ -749,7 +750,7 @@ func TestSysUnmount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "cubbyhole"),
"version": "",
},
"identity/": map[string]interface{}{
@ -767,7 +768,7 @@ func TestSysUnmount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "identity"),
"version": "",
},
},
@ -785,7 +786,7 @@ func TestSysUnmount(t *testing.T) {
"options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"sys/": map[string]interface{}{
@ -803,7 +804,7 @@ func TestSysUnmount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.DefaultBuiltinVersion,
"version": "",
},
"cubbyhole/": map[string]interface{}{
@ -820,7 +821,7 @@ func TestSysUnmount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "cubbyhole"),
"version": "",
},
"identity/": map[string]interface{}{
@ -838,7 +839,7 @@ func TestSysUnmount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "identity"),
"version": "",
},
}
@ -989,7 +990,7 @@ func TestSysTuneMount(t *testing.T) {
"options": map[string]interface{}{},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"secret/": map[string]interface{}{
@ -1006,7 +1007,7 @@ func TestSysTuneMount(t *testing.T) {
"options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"sys/": map[string]interface{}{
@ -1024,7 +1025,7 @@ func TestSysTuneMount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.DefaultBuiltinVersion,
"version": "",
},
"cubbyhole/": map[string]interface{}{
@ -1041,7 +1042,7 @@ func TestSysTuneMount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "cubbyhole"),
"version": "",
},
"identity/": map[string]interface{}{
@ -1059,7 +1060,7 @@ func TestSysTuneMount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "identity"),
"version": "",
},
},
@ -1077,7 +1078,7 @@ func TestSysTuneMount(t *testing.T) {
"options": map[string]interface{}{},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"secret/": map[string]interface{}{
@ -1094,7 +1095,7 @@ func TestSysTuneMount(t *testing.T) {
"options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"sys/": map[string]interface{}{
@ -1112,7 +1113,7 @@ func TestSysTuneMount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.DefaultBuiltinVersion,
"version": "",
},
"cubbyhole/": map[string]interface{}{
@ -1129,7 +1130,7 @@ func TestSysTuneMount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "cubbyhole"),
"version": "",
},
"identity/": map[string]interface{}{
@ -1147,7 +1148,7 @@ func TestSysTuneMount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "identity"),
"version": "",
},
}
@ -1238,7 +1239,7 @@ func TestSysTuneMount(t *testing.T) {
"options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"secret/": map[string]interface{}{
@ -1255,7 +1256,7 @@ func TestSysTuneMount(t *testing.T) {
"options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"sys/": map[string]interface{}{
@ -1273,7 +1274,7 @@ func TestSysTuneMount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.DefaultBuiltinVersion,
"version": "",
},
"cubbyhole/": map[string]interface{}{
@ -1290,7 +1291,7 @@ func TestSysTuneMount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "cubbyhole"),
"version": "",
},
"identity/": map[string]interface{}{
@ -1308,7 +1309,7 @@ func TestSysTuneMount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "identity"),
"version": "",
},
},
@ -1326,7 +1327,7 @@ func TestSysTuneMount(t *testing.T) {
"options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"secret/": map[string]interface{}{
@ -1343,7 +1344,7 @@ func TestSysTuneMount(t *testing.T) {
"options": map[string]interface{}{"version": "1"},
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"sys/": map[string]interface{}{
@ -1361,7 +1362,7 @@ func TestSysTuneMount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
"version": "",
},
"cubbyhole/": map[string]interface{}{
@ -1378,7 +1379,7 @@ func TestSysTuneMount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "cubbyhole"),
"version": "",
},
"identity/": map[string]interface{}{
@ -1396,7 +1397,7 @@ func TestSysTuneMount(t *testing.T) {
"options": interface{}(nil),
"sha": "",
"running_sha": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "identity"),
"version": "",
},
}

View File

@ -9,17 +9,28 @@ import (
"github.com/golang/protobuf/ptypes"
"github.com/hashicorp/vault/sdk/database/dbplugin/v5/proto"
"github.com/hashicorp/vault/sdk/helper/pluginutil"
"github.com/hashicorp/vault/sdk/logical"
)
var (
_ Database = gRPCClient{}
_ Database = gRPCClient{}
_ logical.PluginVersioner = gRPCClient{}
ErrPluginShutdown = errors.New("plugin shutdown")
)
type gRPCClient struct {
client proto.DatabaseClient
doneCtx context.Context
client proto.DatabaseClient
versionClient logical.PluginVersionClient
doneCtx context.Context
}
func (c gRPCClient) PluginVersion() logical.PluginVersion {
version, _ := c.versionClient.Version(context.Background(), &logical.Empty{})
if version != nil {
return logical.PluginVersion{Version: version.PluginVersion}
}
return logical.EmptyPluginVersion
}
func (c gRPCClient) Initialize(ctx context.Context, req InitializeRequest) (InitializeResponse, error) {

View File

@ -6,6 +6,7 @@ import (
"github.com/hashicorp/go-plugin"
"github.com/hashicorp/vault/sdk/database/dbplugin/v5/proto"
"github.com/hashicorp/vault/sdk/helper/pluginutil"
"github.com/hashicorp/vault/sdk/logical"
"google.golang.org/grpc"
)
@ -54,13 +55,15 @@ func (d GRPCDatabasePlugin) GRPCServer(_ *plugin.GRPCBroker, s *grpc.Server) err
}
proto.RegisterDatabaseServer(s, &server)
logical.RegisterPluginVersionServer(s, &server)
return nil
}
func (GRPCDatabasePlugin) GRPCClient(doneCtx context.Context, _ *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) {
client := gRPCClient{
client: proto.NewDatabaseClient(c),
doneCtx: doneCtx,
client: proto.NewDatabaseClient(c),
versionClient: logical.NewPluginVersionClient(c),
doneCtx: doneCtx,
}
return client, nil
}

View File

@ -9,6 +9,7 @@ import (
"github.com/golang/protobuf/ptypes"
"github.com/hashicorp/vault/sdk/database/dbplugin/v5/proto"
"github.com/hashicorp/vault/sdk/helper/pluginutil"
"github.com/hashicorp/vault/sdk/logical"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
@ -17,6 +18,7 @@ var _ proto.DatabaseServer = &gRPCServer{}
type gRPCServer struct {
proto.UnimplementedDatabaseServer
logical.UnimplementedPluginVersionServer
// holds the non-multiplexed Database
// when this is set the plugin does not support multiplexing
@ -302,6 +304,18 @@ func (g *gRPCServer) Close(ctx context.Context, _ *proto.Empty) (*proto.Empty, e
return &proto.Empty{}, nil
}
// Version forwards the version request to the underlying Database implementation.
func (g *gRPCServer) Version(ctx context.Context, _ *logical.Empty) (*logical.VersionReply, error) {
impl, err := g.getDatabaseInternal(ctx)
if err != nil {
return nil, err
}
if versioner, ok := impl.(logical.PluginVersioner); ok {
return &logical.VersionReply{PluginVersion: versioner.PluginVersion().Version}, nil
}
return &logical.VersionReply{}, nil
}
func getStatementsFromProto(protoStmts *proto.Statements) (statements Statements) {
if protoStmts == nil {
return statements

View File

@ -8,6 +8,7 @@ import (
"testing"
"time"
"github.com/hashicorp/vault/sdk/logical"
"google.golang.org/protobuf/types/known/structpb"
"github.com/golang/protobuf/ptypes"
@ -581,6 +582,55 @@ func TestGRPCServer_Close(t *testing.T) {
}
}
func TestGRPCServer_Version(t *testing.T) {
type testCase struct {
db Database
expectedResp string
expectErr bool
expectCode codes.Code
}
tests := map[string]testCase{
"backend that does not implement version": {
db: fakeDatabase{},
expectedResp: "",
expectErr: false,
expectCode: codes.OK,
},
"backend with version": {
db: fakeDatabaseWithVersion{
version: "v123",
},
expectedResp: "v123",
expectErr: false,
expectCode: codes.OK,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
idCtx, g := testGrpcServer(t, test.db)
resp, err := g.Version(idCtx, &logical.Empty{})
if test.expectErr && err == nil {
t.Fatalf("err expected, got nil")
}
if !test.expectErr && err != nil {
t.Fatalf("no error expected, got: %s", err)
}
actualCode := status.Code(err)
if actualCode != test.expectCode {
t.Fatalf("Actual code: %s Expected code: %s", actualCode, test.expectCode)
}
if !reflect.DeepEqual(resp.PluginVersion, test.expectedResp) {
t.Fatalf("Actual response: %#v\nExpected response: %#v", resp, test.expectedResp)
}
})
}
}
// testGrpcServer is a test helper that returns a context with an ID set in its
// metadata and a gRPCServer instance for a multiplexed plugin
func testGrpcServer(t *testing.T, db Database) (context.Context, gRPCServer) {
@ -747,3 +797,40 @@ func (f *recordingDatabase) Close() error {
}
return f.next.Close()
}
type fakeDatabaseWithVersion struct {
version string
}
func (e fakeDatabaseWithVersion) PluginVersion() logical.PluginVersion {
return logical.PluginVersion{Version: e.version}
}
func (e fakeDatabaseWithVersion) Initialize(_ context.Context, _ InitializeRequest) (InitializeResponse, error) {
return InitializeResponse{}, nil
}
func (e fakeDatabaseWithVersion) NewUser(_ context.Context, _ NewUserRequest) (NewUserResponse, error) {
return NewUserResponse{}, nil
}
func (e fakeDatabaseWithVersion) UpdateUser(_ context.Context, _ UpdateUserRequest) (UpdateUserResponse, error) {
return UpdateUserResponse{}, nil
}
func (e fakeDatabaseWithVersion) DeleteUser(_ context.Context, _ DeleteUserRequest) (DeleteUserResponse, error) {
return DeleteUserResponse{}, nil
}
func (e fakeDatabaseWithVersion) Type() (string, error) {
return "", nil
}
func (e fakeDatabaseWithVersion) Close() error {
return nil
}
var (
_ Database = (*fakeDatabaseWithVersion)(nil)
_ logical.PluginVersioner = (*fakeDatabaseWithVersion)(nil)
)

View File

@ -7,9 +7,10 @@ import (
"strings"
"time"
metrics "github.com/armon/go-metrics"
"github.com/armon/go-metrics"
"github.com/hashicorp/errwrap"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/sdk/logical"
"google.golang.org/grpc/status"
)
@ -17,7 +18,10 @@ import (
// Tracing Middleware
// ///////////////////////////////////////////////////
var _ Database = databaseTracingMiddleware{}
var (
_ Database = databaseTracingMiddleware{}
_ logical.PluginVersioner = databaseTracingMiddleware{}
)
// databaseTracingMiddleware wraps a implementation of Database and executes
// trace logging on function call.
@ -26,6 +30,21 @@ type databaseTracingMiddleware struct {
logger log.Logger
}
func (mw databaseTracingMiddleware) PluginVersion() (resp logical.PluginVersion) {
defer func(then time.Time) {
mw.logger.Trace("version",
"status", "finished",
"version", resp,
"took", time.Since(then))
}(time.Now())
mw.logger.Trace("version", "status", "started")
if versioner, ok := mw.next.(logical.PluginVersioner); ok {
return versioner.PluginVersion()
}
return logical.EmptyPluginVersion
}
func (mw databaseTracingMiddleware) Initialize(ctx context.Context, req InitializeRequest) (resp InitializeResponse, err error) {
defer func(then time.Time) {
mw.logger.Trace("initialize",
@ -98,7 +117,10 @@ func (mw databaseTracingMiddleware) Close() (err error) {
// Metrics Middleware Domain
// ///////////////////////////////////////////////////
var _ Database = databaseMetricsMiddleware{}
var (
_ Database = databaseMetricsMiddleware{}
_ logical.PluginVersioner = databaseMetricsMiddleware{}
)
// databaseMetricsMiddleware wraps an implementation of Databases and on
// function call logs metrics about this instance.
@ -108,6 +130,21 @@ type databaseMetricsMiddleware struct {
typeStr string
}
func (mw databaseMetricsMiddleware) PluginVersion() logical.PluginVersion {
defer func(now time.Time) {
metrics.MeasureSince([]string{"database", "PluginVersion"}, now)
metrics.MeasureSince([]string{"database", mw.typeStr, "PluginVersion"}, now)
}(time.Now())
metrics.IncrCounter([]string{"database", "PluginVersion"}, 1)
metrics.IncrCounter([]string{"database", mw.typeStr, "PluginVersion"}, 1)
if versioner, ok := mw.next.(logical.PluginVersioner); ok {
return versioner.PluginVersion()
}
return logical.EmptyPluginVersion
}
func (mw databaseMetricsMiddleware) Initialize(ctx context.Context, req InitializeRequest) (resp InitializeResponse, err error) {
defer func(now time.Time) {
metrics.MeasureSince([]string{"database", "Initialize"}, now)

View File

@ -4,16 +4,26 @@ import (
"context"
"errors"
plugin "github.com/hashicorp/go-plugin"
"github.com/hashicorp/go-plugin"
"github.com/hashicorp/vault/sdk/database/dbplugin/v5/proto"
"github.com/hashicorp/vault/sdk/helper/pluginutil"
"github.com/hashicorp/vault/sdk/logical"
)
var _ logical.PluginVersioner = (*DatabasePluginClient)(nil)
type DatabasePluginClient struct {
client pluginutil.PluginClient
Database
}
func (dc *DatabasePluginClient) PluginVersion() logical.PluginVersion {
if versioner, ok := dc.Database.(logical.PluginVersioner); ok {
return versioner.PluginVersion()
}
return logical.EmptyPluginVersion
}
// This wraps the Close call and ensures we both close the database connection
// and kill the plugin.
func (dc *DatabasePluginClient) Close() error {
@ -55,6 +65,7 @@ func NewPluginClient(ctx context.Context, sys pluginutil.RunnerUtil, config plug
// This is an abstraction leak from go-plugin but it is necessary in
// order to enable multiplexing on multiplexed plugins
c.client = proto.NewDatabaseClient(pluginClient.Conn())
c.versionClient = logical.NewPluginVersionClient(pluginClient.Conn())
db = c
default:

View File

@ -12,6 +12,7 @@ import (
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/pluginutil"
"github.com/hashicorp/vault/sdk/helper/wrapping"
"github.com/hashicorp/vault/sdk/logical"
"github.com/stretchr/testify/mock"
"google.golang.org/grpc"
)
@ -38,7 +39,7 @@ func TestNewPluginClient(t *testing.T) {
dispenseResp: gRPCClient{client: fakeClient{}},
dispenseErr: nil,
},
Database: gRPCClient{proto.NewDatabaseClient(nil), context.Context(nil)},
Database: gRPCClient{client: proto.NewDatabaseClient(nil), versionClient: logical.NewPluginVersionClient(nil), doneCtx: context.Context(nil)},
},
expectedErr: nil,
},

View File

@ -101,4 +101,4 @@ service Database {
rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse);
rpc Type(Empty) returns (TypeResponse);
rpc Close(Empty) returns (Empty);
}
}

View File

@ -91,6 +91,9 @@ type Backend struct {
// BackendType is the logical.BackendType for the backend implementation
BackendType logical.BackendType
// RunningVersion is the optional version that will be self-reported
RunningVersion string
logger log.Logger
system logical.SystemView
once sync.Once
@ -428,6 +431,13 @@ func (b *Backend) Type() logical.BackendType {
return b.BackendType
}
// Version returns the plugin version information
func (b *Backend) PluginVersion() logical.PluginVersion {
return logical.PluginVersion{
Version: b.RunningVersion,
}
}
// Route looks up the path that would be used for a given path string.
func (b *Backend) Route(path string) *Path {
result, _ := b.route(path)

View File

@ -137,3 +137,20 @@ type Auditor interface {
AuditRequest(ctx context.Context, input *LogInput) error
AuditResponse(ctx context.Context, input *LogInput) error
}
// Externaler allows us to check if a backend is running externally (i.e., over GRPC)
type Externaler interface {
IsExternal() bool
}
type PluginVersion struct {
Version string
}
// PluginVersioner is an optional interface to return version info.
type PluginVersioner interface {
// PluginVersion returns the version for the backend
PluginVersion() PluginVersion
}
var EmptyPluginVersion = PluginVersion{""}

204
sdk/logical/version.pb.go Normal file
View File

@ -0,0 +1,204 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.0
// protoc v3.21.5
// source: sdk/logical/version.proto
package logical
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Empty struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *Empty) Reset() {
*x = Empty{}
if protoimpl.UnsafeEnabled {
mi := &file_sdk_logical_version_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Empty) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Empty) ProtoMessage() {}
func (x *Empty) ProtoReflect() protoreflect.Message {
mi := &file_sdk_logical_version_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Empty.ProtoReflect.Descriptor instead.
func (*Empty) Descriptor() ([]byte, []int) {
return file_sdk_logical_version_proto_rawDescGZIP(), []int{0}
}
// VersionReply is the reply for the Version method.
type VersionReply struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
PluginVersion string `protobuf:"bytes,1,opt,name=plugin_version,json=pluginVersion,proto3" json:"plugin_version,omitempty"`
}
func (x *VersionReply) Reset() {
*x = VersionReply{}
if protoimpl.UnsafeEnabled {
mi := &file_sdk_logical_version_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *VersionReply) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*VersionReply) ProtoMessage() {}
func (x *VersionReply) ProtoReflect() protoreflect.Message {
mi := &file_sdk_logical_version_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use VersionReply.ProtoReflect.Descriptor instead.
func (*VersionReply) Descriptor() ([]byte, []int) {
return file_sdk_logical_version_proto_rawDescGZIP(), []int{1}
}
func (x *VersionReply) GetPluginVersion() string {
if x != nil {
return x.PluginVersion
}
return ""
}
var File_sdk_logical_version_proto protoreflect.FileDescriptor
var file_sdk_logical_version_proto_rawDesc = []byte{
0x0a, 0x19, 0x73, 0x64, 0x6b, 0x2f, 0x6c, 0x6f, 0x67, 0x69, 0x63, 0x61, 0x6c, 0x2f, 0x76, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x6c, 0x6f, 0x67,
0x69, 0x63, 0x61, 0x6c, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x35, 0x0a,
0x0c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x25, 0x0a,
0x0e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x56, 0x65, 0x72,
0x73, 0x69, 0x6f, 0x6e, 0x32, 0x41, 0x0a, 0x0d, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x56, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
0x12, 0x0e, 0x2e, 0x6c, 0x6f, 0x67, 0x69, 0x63, 0x61, 0x6c, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
0x1a, 0x15, 0x2e, 0x6c, 0x6f, 0x67, 0x69, 0x63, 0x61, 0x6c, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69,
0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x42, 0x28, 0x5a, 0x26, 0x67, 0x69, 0x74, 0x68, 0x75,
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f,
0x76, 0x61, 0x75, 0x6c, 0x74, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x6c, 0x6f, 0x67, 0x69, 0x63, 0x61,
0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_sdk_logical_version_proto_rawDescOnce sync.Once
file_sdk_logical_version_proto_rawDescData = file_sdk_logical_version_proto_rawDesc
)
func file_sdk_logical_version_proto_rawDescGZIP() []byte {
file_sdk_logical_version_proto_rawDescOnce.Do(func() {
file_sdk_logical_version_proto_rawDescData = protoimpl.X.CompressGZIP(file_sdk_logical_version_proto_rawDescData)
})
return file_sdk_logical_version_proto_rawDescData
}
var file_sdk_logical_version_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_sdk_logical_version_proto_goTypes = []interface{}{
(*Empty)(nil), // 0: logical.Empty
(*VersionReply)(nil), // 1: logical.VersionReply
}
var file_sdk_logical_version_proto_depIdxs = []int32{
0, // 0: logical.PluginVersion.Version:input_type -> logical.Empty
1, // 1: logical.PluginVersion.Version:output_type -> logical.VersionReply
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_sdk_logical_version_proto_init() }
func file_sdk_logical_version_proto_init() {
if File_sdk_logical_version_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_sdk_logical_version_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Empty); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_sdk_logical_version_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*VersionReply); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_sdk_logical_version_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_sdk_logical_version_proto_goTypes,
DependencyIndexes: file_sdk_logical_version_proto_depIdxs,
MessageInfos: file_sdk_logical_version_proto_msgTypes,
}.Build()
File_sdk_logical_version_proto = out.File
file_sdk_logical_version_proto_rawDesc = nil
file_sdk_logical_version_proto_goTypes = nil
file_sdk_logical_version_proto_depIdxs = nil
}

17
sdk/logical/version.proto Normal file
View File

@ -0,0 +1,17 @@
syntax = "proto3";
package logical;
option go_package = "github.com/hashicorp/vault/sdk/logical";
message Empty {}
// VersionReply is the reply for the Version method.
message VersionReply {
string plugin_version = 1;
}
// PluginVersion is an optional RPC service implemented by plugins.
service PluginVersion {
// Version returns version information for the plugin.
rpc Version(Empty) returns (VersionReply);
}

View File

@ -0,0 +1,103 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package logical
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// PluginVersionClient is the client API for PluginVersion service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type PluginVersionClient interface {
// Version returns version information for the plugin.
Version(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VersionReply, error)
}
type pluginVersionClient struct {
cc grpc.ClientConnInterface
}
func NewPluginVersionClient(cc grpc.ClientConnInterface) PluginVersionClient {
return &pluginVersionClient{cc}
}
func (c *pluginVersionClient) Version(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VersionReply, error) {
out := new(VersionReply)
err := c.cc.Invoke(ctx, "/logical.PluginVersion/Version", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// PluginVersionServer is the server API for PluginVersion service.
// All implementations must embed UnimplementedPluginVersionServer
// for forward compatibility
type PluginVersionServer interface {
// Version returns version information for the plugin.
Version(context.Context, *Empty) (*VersionReply, error)
mustEmbedUnimplementedPluginVersionServer()
}
// UnimplementedPluginVersionServer must be embedded to have forward compatible implementations.
type UnimplementedPluginVersionServer struct {
}
func (UnimplementedPluginVersionServer) Version(context.Context, *Empty) (*VersionReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method Version not implemented")
}
func (UnimplementedPluginVersionServer) mustEmbedUnimplementedPluginVersionServer() {}
// UnsafePluginVersionServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to PluginVersionServer will
// result in compilation errors.
type UnsafePluginVersionServer interface {
mustEmbedUnimplementedPluginVersionServer()
}
func RegisterPluginVersionServer(s grpc.ServiceRegistrar, srv PluginVersionServer) {
s.RegisterService(&PluginVersion_ServiceDesc, srv)
}
func _PluginVersion_Version_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Empty)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(PluginVersionServer).Version(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/logical.PluginVersion/Version",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(PluginVersionServer).Version(ctx, req.(*Empty))
}
return interceptor(ctx, in, info, handler)
}
// PluginVersion_ServiceDesc is the grpc.ServiceDesc for PluginVersion service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var PluginVersion_ServiceDesc = grpc.ServiceDesc{
ServiceName: "logical.PluginVersion",
HandlerType: (*PluginVersionServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Version",
Handler: _PluginVersion_Version_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "sdk/logical/version.proto",
}

View File

@ -7,7 +7,7 @@ import (
"google.golang.org/grpc"
log "github.com/hashicorp/go-hclog"
plugin "github.com/hashicorp/go-plugin"
"github.com/hashicorp/go-plugin"
"github.com/hashicorp/vault/sdk/helper/pluginutil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/sdk/plugin/pb"
@ -51,17 +51,19 @@ func (b GRPCBackendPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server)
}
pb.RegisterBackendServer(s, &server)
logical.RegisterPluginVersionServer(s, &server)
return nil
}
func (b *GRPCBackendPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) {
ret := &backendGRPCPluginClient{
client: pb.NewBackendClient(c),
clientConn: c,
broker: broker,
cleanupCh: make(chan struct{}),
doneCtx: ctx,
metadataMode: b.MetadataMode,
client: pb.NewBackendClient(c),
versionClient: logical.NewPluginVersionClient(c),
clientConn: c,
broker: broker,
cleanupCh: make(chan struct{}),
doneCtx: ctx,
metadataMode: b.MetadataMode,
}
// Create the value and set the type

View File

@ -6,15 +6,14 @@ import (
"math"
"sync/atomic"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
log "github.com/hashicorp/go-hclog"
plugin "github.com/hashicorp/go-plugin"
"github.com/hashicorp/go-plugin"
"github.com/hashicorp/vault/sdk/helper/pluginutil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/sdk/plugin/pb"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
var (
@ -28,9 +27,10 @@ var _ logical.Backend = &backendGRPCPluginClient{}
// backendPluginClient implements logical.Backend and is the
// go-plugin client.
type backendGRPCPluginClient struct {
broker *plugin.GRPCBroker
client pb.BackendClient
metadataMode bool
broker *plugin.GRPCBroker
client pb.BackendClient
versionClient logical.PluginVersionClient
metadataMode bool
system logical.SystemView
logger log.Logger
@ -280,3 +280,23 @@ func (b *backendGRPCPluginClient) Type() logical.BackendType {
return logical.BackendType(reply.Type)
}
func (b *backendGRPCPluginClient) PluginVersion() logical.PluginVersion {
reply, err := b.versionClient.Version(b.doneCtx, &logical.Empty{})
if err != nil {
if stErr, ok := status.FromError(err); ok {
if stErr.Code() == codes.Unimplemented {
return logical.EmptyPluginVersion
}
}
b.Logger().Warn("Unknown error getting plugin version", "err", err)
return logical.EmptyPluginVersion
}
return logical.PluginVersion{
Version: reply.GetPluginVersion(),
}
}
func (b *backendGRPCPluginClient) IsExternal() bool {
return true
}

View File

@ -7,7 +7,7 @@ import (
"sync"
log "github.com/hashicorp/go-hclog"
plugin "github.com/hashicorp/go-plugin"
"github.com/hashicorp/go-plugin"
"github.com/hashicorp/vault/sdk/helper/pluginutil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/sdk/plugin/pb"
@ -27,6 +27,7 @@ type backendInstance struct {
type backendGRPCPluginServer struct {
pb.UnimplementedBackendServer
logical.UnimplementedPluginVersionServer
broker *plugin.GRPCBroker
@ -266,3 +267,19 @@ func (b *backendGRPCPluginServer) Type(ctx context.Context, _ *pb.Empty) (*pb.Ty
Type: uint32(backend.Type()),
}, nil
}
func (b *backendGRPCPluginServer) Version(ctx context.Context, _ *logical.Empty) (*logical.VersionReply, error) {
backend, _, err := b.getBackendAndBrokeredClient(ctx)
if err != nil {
return &logical.VersionReply{}, err
}
if versioner, ok := backend.(logical.PluginVersioner); ok {
return &logical.VersionReply{
PluginVersion: versioner.PluginVersion().Version,
}, nil
}
return &logical.VersionReply{
PluginVersion: "",
}, nil
}

View File

@ -147,6 +147,21 @@ func TestGRPCBackendPlugin_Initialize(t *testing.T) {
}
}
func TestGRPCBackendPlugin_Version(t *testing.T) {
b, cleanup := testGRPCBackend(t)
defer cleanup()
versioner, ok := b.(logical.PluginVersioner)
if !ok {
t.Fatalf("Expected %T to implement logical.PluginVersioner interface", b)
}
version := versioner.PluginVersion().Version
if version != "mock" {
t.Fatalf("Got version %s, expected 'mock'", version)
}
}
func testGRPCBackend(t *testing.T) (logical.Backend, func()) {
// Create a mock provider
pluginMap := map[string]gplugin.Plugin{

View File

@ -98,3 +98,15 @@ func (b *BackendTracingMiddleware) Type() logical.BackendType {
b.logger.Trace("type", "status", "started")
return b.next.Type()
}
func (b *BackendTracingMiddleware) PluginVersion() logical.PluginVersion {
defer func(then time.Time) {
b.logger.Trace("version", "status", "finished", "took", time.Since(then))
}(time.Now())
b.logger.Trace("version", "status", "started")
if versioner, ok := b.next.(logical.PluginVersioner); ok {
return versioner.PluginVersion()
}
return logical.EmptyPluginVersion
}

View File

@ -59,6 +59,7 @@ func Backend() *backend {
BackendType: logical.TypeLogical,
}
b.internal = "bar"
b.RunningVersion = "mock"
return &b
}

View File

@ -143,3 +143,22 @@ func NewPluginClient(ctx context.Context, sys pluginutil.RunnerUtil, pluginRunne
Backend: backend,
}, nil
}
func (b *BackendPluginClient) PluginVersion() logical.PluginVersion {
if versioner, ok := b.Backend.(logical.PluginVersioner); ok {
return versioner.PluginVersion()
}
return logical.EmptyPluginVersion
}
func (b *BackendPluginClient) IsExternal() bool {
if externaler, ok := b.Backend.(logical.Externaler); ok {
return externaler.IsExternal()
}
return true // default to true since this is only used for GRPC plugins
}
var (
_ logical.PluginVersioner = (*BackendPluginClient)(nil)
_ logical.Externaler = (*BackendPluginClient)(nil)
)

View File

@ -5,7 +5,7 @@ import (
"errors"
"fmt"
plugin "github.com/hashicorp/go-plugin"
"github.com/hashicorp/go-plugin"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/pluginutil"
"github.com/hashicorp/vault/sdk/logical"
@ -41,6 +41,22 @@ func (b *BackendPluginClientV5) Cleanup(ctx context.Context) {
b.client.Reload()
}
func (b *BackendPluginClientV5) IsExternal() bool {
return true
}
func (b *BackendPluginClientV5) PluginVersion() logical.PluginVersion {
if versioner, ok := b.Backend.(logical.PluginVersioner); ok {
return versioner.PluginVersion()
}
return logical.EmptyPluginVersion
}
var (
_ logical.PluginVersioner = (*BackendPluginClientV5)(nil)
_ logical.Externaler = (*BackendPluginClientV5)(nil)
)
// NewBackendV5 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
@ -111,6 +127,7 @@ func Dispense(rpcClient plugin.ClientProtocol, pluginClient pluginutil.PluginCli
// This is an abstraction leak from go-plugin but it is necessary in
// order to enable multiplexing on multiplexed plugins
c.client = pb.NewBackendClient(pluginClient.Conn())
c.versionClient = logical.NewPluginVersionClient(pluginClient.Conn())
backend = c
default:
@ -144,6 +161,7 @@ func NewPluginClientV5(ctx context.Context, sys pluginutil.RunnerUtil, config pl
// This is an abstraction leak from go-plugin but it is necessary in
// order to enable multiplexing on multiplexed plugins
c.client = pb.NewBackendClient(pluginClient.Conn())
c.versionClient = logical.NewPluginVersionClient(pluginClient.Conn())
backend = c
transport = "gRPC"

View File

@ -7,9 +7,10 @@ import (
"strings"
"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/helper/namespace"
"github.com/hashicorp/vault/helper/versions"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/jsonutil"
"github.com/hashicorp/vault/sdk/logical"
@ -182,7 +183,16 @@ func (c *Core) enableCredentialInternal(ctx context.Context, entry *MountEntry,
if backendType != logical.TypeCredential {
return fmt.Errorf("cannot mount %q of type %q as an auth backend", entry.Type, backendType)
}
// update the entry running version with the backend's reported version
if versioner, ok := backend.(logical.PluginVersioner); ok {
entry.RunningVersion = versioner.PluginVersion().Version
}
if entry.RunningVersion == "" {
// don't set the running version to a builtin if it is running as an external plugin
if externaler, ok := backend.(logical.Externaler); !ok || !externaler.IsExternal() {
entry.RunningVersion = versions.GetBuiltinVersion(consts.PluginTypeCredential, entry.Type)
}
}
addPathCheckers(c, entry, backend, viewPath)
// If the mount is filtered or we are on a DR secondary we don't want to

View File

@ -242,6 +242,11 @@ func TestCore_EnableExternalPlugin_MultipleVersions(t *testing.T) {
if raw.(*routeEntry).mountEntry.Version != tc.mountVersion {
t.Errorf("Expected mount to be version %s but got %s", tc.mountVersion, raw.(*routeEntry).mountEntry.Version)
}
// we don't override the running version of non-builtins, and they don't have the version set explicitly (yet)
if raw.(*routeEntry).mountEntry.RunningVersion != "" {
t.Errorf("Expected mount to have no running version but got %s", raw.(*routeEntry).mountEntry.RunningVersion)
}
})
}
}

View File

@ -15,6 +15,7 @@ import (
"github.com/hashicorp/vault/helper/metricsutil"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/helper/storagepacker"
"github.com/hashicorp/vault/helper/versions"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/logical"
@ -112,6 +113,7 @@ func NewIdentityStore(ctx context.Context, core *Core, config *logical.BackendCo
return nil
},
RunningVersion: versions.DefaultBuiltinVersion,
}
iStore.oidcCache = newOIDCCache(cache.NoExpiration, cache.NoExpiration)

View File

@ -6,7 +6,9 @@ import (
"fmt"
"strings"
"github.com/hashicorp/vault/helper/versions"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/jsonutil"
"github.com/hashicorp/vault/sdk/logical"
)
@ -15,7 +17,8 @@ import (
func CubbyholeBackendFactory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
b := &CubbyholeBackend{}
b.Backend = &framework.Backend{
Help: strings.TrimSpace(cubbyholeHelp),
Help: strings.TrimSpace(cubbyholeHelp),
RunningVersion: versions.GetBuiltinVersion(consts.PluginTypeSecrets, "cubbyhole"),
}
b.Backend.Paths = append(b.Backend.Paths, b.paths()...)

View File

@ -21,6 +21,7 @@ import (
"time"
"unicode"
"github.com/hashicorp/vault/helper/versions"
"golang.org/x/crypto/sha3"
"github.com/hashicorp/errwrap"
@ -84,7 +85,8 @@ func NewSystemBackend(core *Core, logger log.Logger) *SystemBackend {
}
b.Backend = &framework.Backend{
Help: strings.TrimSpace(sysHelpRoot),
RunningVersion: versions.DefaultBuiltinVersion,
Help: strings.TrimSpace(sysHelpRoot),
PathsSpecial: &logical.Paths{
Root: []string{

View File

@ -25,6 +25,7 @@ import (
"github.com/hashicorp/vault/helper/identity"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/helper/random"
"github.com/hashicorp/vault/helper/versions"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/compressutil"
"github.com/hashicorp/vault/sdk/helper/consts"
@ -175,7 +176,7 @@ func TestSystemBackend_mounts(t *testing.T) {
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
},
"sys/": map[string]interface{}{
"type": "system",
@ -195,7 +196,7 @@ func TestSystemBackend_mounts(t *testing.T) {
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
"running_version": versions.DefaultBuiltinVersion,
},
"cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage",
@ -214,7 +215,7 @@ func TestSystemBackend_mounts(t *testing.T) {
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "cubbyhole"),
},
"identity/": map[string]interface{}{
"description": "identity store",
@ -234,7 +235,7 @@ func TestSystemBackend_mounts(t *testing.T) {
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "identity"),
},
}
if diff := deep.Equal(resp.Data, exp); len(diff) > 0 {
@ -304,7 +305,7 @@ func TestSystemBackend_mount(t *testing.T) {
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
},
"sys/": map[string]interface{}{
"type": "system",
@ -324,7 +325,7 @@ func TestSystemBackend_mount(t *testing.T) {
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
"running_version": versions.DefaultBuiltinVersion,
},
"cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage",
@ -343,7 +344,7 @@ func TestSystemBackend_mount(t *testing.T) {
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "cubbyhole"),
},
"identity/": map[string]interface{}{
"description": "identity store",
@ -363,7 +364,7 @@ func TestSystemBackend_mount(t *testing.T) {
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "identity"),
},
"prod/secret/": map[string]interface{}{
"description": "",
@ -384,7 +385,7 @@ func TestSystemBackend_mount(t *testing.T) {
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
},
}
if diff := deep.Equal(resp.Data, exp); len(diff) > 0 {
@ -1928,7 +1929,7 @@ func TestSystemBackend_enableAuth(t *testing.T) {
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
"running_version": versions.DefaultBuiltinVersion,
},
"token/": map[string]interface{}{
"type": "token",
@ -2999,7 +3000,7 @@ func TestSystemBackend_PluginCatalog_CRUD(t *testing.T) {
"args": []string(nil),
"sha256": "",
"builtin": true,
"version": c.pluginCatalog.getBuiltinVersion(consts.PluginTypeDatabase, "mysql-database-plugin"),
"version": versions.GetBuiltinVersion(consts.PluginTypeDatabase, "mysql-database-plugin"),
"deprecation_status": deprecationStatus.String(),
}
if !reflect.DeepEqual(actualRespData, expectedRespData) {
@ -3329,7 +3330,7 @@ func TestSystemBackend_InternalUIMounts(t *testing.T) {
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
},
"sys/": map[string]interface{}{
"type": "system",
@ -3349,7 +3350,7 @@ func TestSystemBackend_InternalUIMounts(t *testing.T) {
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
"running_version": versions.DefaultBuiltinVersion,
},
"cubbyhole/": map[string]interface{}{
"description": "per-token private secret storage",
@ -3368,7 +3369,7 @@ func TestSystemBackend_InternalUIMounts(t *testing.T) {
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "cubbyhole"),
},
"identity/": map[string]interface{}{
"description": "identity store",
@ -3388,7 +3389,7 @@ func TestSystemBackend_InternalUIMounts(t *testing.T) {
"sha": "",
"running_sha": "",
"version": "",
"running_version": "",
"running_version": versions.GetBuiltinVersion(consts.PluginTypeSecrets, "identity"),
},
},
"auth": map[string]interface{}{

View File

@ -16,6 +16,7 @@ import (
"github.com/hashicorp/vault/builtin/plugin"
"github.com/hashicorp/vault/helper/metricsutil"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/helper/versions"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/jsonutil"
"github.com/hashicorp/vault/sdk/logical"
@ -624,6 +625,17 @@ func (c *Core) mountInternal(ctx context.Context, entry *MountEntry, updateStora
}
}
// update the entry running version with the backend's reported version
if versioner, ok := backend.(logical.PluginVersioner); ok {
entry.RunningVersion = versioner.PluginVersion().Version
}
if entry.RunningVersion == "" {
// don't set the running version to a builtin if it is running as an external plugin
if externaler, ok := backend.(logical.Externaler); !ok || !externaler.IsExternal() {
entry.RunningVersion = versions.GetBuiltinVersion(consts.PluginTypeSecrets, entry.Type)
}
}
addPathCheckers(c, entry, backend, viewPath)
c.setCoreBackend(entry, backend, view)
@ -1609,6 +1621,7 @@ func (c *Core) defaultMountTable() *MountTable {
Options: map[string]string{
"version": "2",
},
RunningVersion: versions.GetBuiltinVersion(consts.PluginTypeSecrets, "kv"),
}
table.Entries = append(table.Entries, kvMount)
}
@ -1643,6 +1656,7 @@ func (c *Core) requiredMountTable() *MountTable {
Accessor: cubbyholeAccessor,
Local: true,
BackendAwareUUID: cubbyholeBackendUUID,
RunningVersion: versions.GetBuiltinVersion(consts.PluginTypeSecrets, "cubbyhole"),
}
sysUUID, err := uuid.GenerateUUID()
@ -1669,6 +1683,7 @@ func (c *Core) requiredMountTable() *MountTable {
Config: MountConfig{
PassthroughRequestHeaders: []string{"Accept"},
},
RunningVersion: versions.DefaultBuiltinVersion,
}
identityUUID, err := uuid.GenerateUUID()
@ -1694,6 +1709,7 @@ func (c *Core) requiredMountTable() *MountTable {
Config: MountConfig{
PassthroughRequestHeaders: []string{"Authorization"},
},
RunningVersion: versions.DefaultBuiltinVersion,
}
table.Entries = append(table.Entries, cubbyholeMount)

View File

@ -8,7 +8,7 @@ import (
"testing"
"time"
metrics "github.com/armon/go-metrics"
"github.com/armon/go-metrics"
"github.com/go-test/deep"
"github.com/hashicorp/vault/audit"
"github.com/hashicorp/vault/helper/metricsutil"
@ -187,6 +187,30 @@ func TestCore_Mount(t *testing.T) {
}
}
func TestCore_Mount_secrets_builtin_RunningVersion(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
me := &MountEntry{
Table: mountTableType,
Path: "foo",
Type: "generic",
}
err := c.mount(namespace.RootContext(nil), me)
if err != nil {
t.Fatalf("err: %v", err)
}
match := c.router.MatchingMount(namespace.RootContext(nil), "foo/bar")
if match != "foo/" {
t.Fatalf("missing mount")
}
raw, _ := c.router.root.Get(match)
// we override the running version of builtins
if !strings.Contains(raw.(*routeEntry).mountEntry.RunningVersion, "builtin") {
t.Errorf("Expected mount to have builtin version but got %s", raw.(*routeEntry).mountEntry.RunningVersion)
}
}
// TestCore_Mount_kv_generic tests that we can successfully mount kv using the
// kv alias "generic"
func TestCore_Mount_kv_generic(t *testing.T) {

View File

@ -8,15 +8,15 @@ import (
"fmt"
"path"
"path/filepath"
"runtime/debug"
"strings"
"sync"
log "github.com/hashicorp/go-hclog"
multierror "github.com/hashicorp/go-multierror"
plugin "github.com/hashicorp/go-plugin"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-plugin"
"github.com/hashicorp/go-secure-stdlib/base62"
semver "github.com/hashicorp/go-version"
"github.com/hashicorp/vault/helper/versions"
v4 "github.com/hashicorp/vault/sdk/database/dbplugin"
v5 "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
"github.com/hashicorp/vault/sdk/helper/consts"
@ -24,7 +24,6 @@ import (
"github.com/hashicorp/vault/sdk/helper/pluginutil"
"github.com/hashicorp/vault/sdk/logical"
backendplugin "github.com/hashicorp/vault/sdk/plugin"
"github.com/hashicorp/vault/sdk/version"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
@ -55,10 +54,6 @@ type PluginCatalog struct {
externalPlugins map[string]*externalPlugin
mlockPlugins bool
// once is used to ensure we only parse build info once.
once sync.Once
buildInfo *debug.BuildInfo
lock sync.RWMutex
}
@ -630,7 +625,7 @@ func (c *PluginCatalog) get(ctx context.Context, name string, pluginType consts.
Type: pluginType,
Builtin: true,
BuiltinFactory: factory,
Version: c.getBuiltinVersion(pluginType, name),
Version: versions.GetBuiltinVersion(pluginType, name),
}, nil
}
}
@ -851,7 +846,7 @@ func (c *PluginCatalog) listInternal(ctx context.Context, pluginType consts.Plug
continue
}
version := c.getBuiltinVersion(pluginType, plugin)
version := versions.GetBuiltinVersion(pluginType, plugin)
semanticVersion, err := semver.NewVersion(version)
deprecationStatus, _ := c.builtinRegistry.DeprecationStatus(plugin, pluginType)
if err != nil {
@ -874,42 +869,3 @@ func isPluginType(s string) bool {
_, err := consts.ParsePluginType(s)
return err == nil
}
func (c *PluginCatalog) getBuiltinVersion(pluginType consts.PluginType, pluginName string) string {
defaultBuiltinVersion := "v" + version.GetVersion().Version + "+builtin.vault"
c.once.Do(func() {
c.buildInfo, _ = debug.ReadBuildInfo()
})
// Should never happen, means the binary was built without Go modules.
// Fall back to just the Vault version.
if c.buildInfo == nil {
return defaultBuiltinVersion
}
// Vault builtin plugins are all either:
// a) An external repo within the hashicorp org - return external repo version with +builtin
// b) Within the Vault repo itself - return Vault version with +builtin.vault
//
// The repo names are predictable, but follow slightly different patterns
// for each plugin type.
t := pluginType.String()
switch pluginType {
case consts.PluginTypeDatabase:
// Database plugin built-ins are registered as e.g. "postgresql-database-plugin"
pluginName = strings.TrimSuffix(pluginName, "-database-plugin")
case consts.PluginTypeSecrets:
// Repos use "secrets", pluginType.String() is "secret".
t = "secrets"
}
pluginModulePath := fmt.Sprintf("github.com/hashicorp/vault-plugin-%s-%s", t, pluginName)
for _, dep := range c.buildInfo.Deps {
if dep.Path == pluginModulePath {
return dep.Version + "+builtin"
}
}
return defaultBuiltinVersion
}

View File

@ -12,6 +12,7 @@ import (
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/builtin/credential/userpass"
"github.com/hashicorp/vault/helper/versions"
"github.com/hashicorp/vault/plugins/database/postgresql"
v5 "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
"github.com/hashicorp/vault/sdk/helper/consts"
@ -41,7 +42,7 @@ func TestPluginCatalog_CRUD(t *testing.T) {
Name: pluginName,
Type: consts.PluginTypeDatabase,
Builtin: true,
Version: core.pluginCatalog.getBuiltinVersion(consts.PluginTypeDatabase, pluginName),
Version: versions.GetBuiltinVersion(consts.PluginTypeDatabase, pluginName),
}
expectedBuiltin.BuiltinFactory, _ = builtinplugins.Registry.Get(pluginName, consts.PluginTypeDatabase)
@ -104,7 +105,7 @@ func TestPluginCatalog_CRUD(t *testing.T) {
Name: pluginName,
Type: consts.PluginTypeDatabase,
Builtin: true,
Version: core.pluginCatalog.getBuiltinVersion(consts.PluginTypeDatabase, pluginName),
Version: versions.GetBuiltinVersion(consts.PluginTypeDatabase, pluginName),
}
expectedBuiltin.BuiltinFactory, _ = builtinplugins.Registry.Get(pluginName, consts.PluginTypeDatabase)