Check if plugin version matches running version (#17182)

Check if plugin version matches running version

When registering a plugin, we check if the request version matches the
self-reported version from the plugin. If these do not match, we log a
warning.

This uncovered a few missing pieces for getting the database version
code fully working.

We added an environment variable that helps us unit test the running
version behavior as well, but only for approle, postgresql, and consul
plugins.

Return 400 on plugin not found or version mismatch

Populate the running SHA256 of plugins in the mount and auth tables (#17217)
This commit is contained in:
Christopher Swenson
2022-09-21 12:25:04 -07:00
committed by GitHub
parent c3c323d8d8
commit 0b34b73c47
13 changed files with 394 additions and 72 deletions

View File

@@ -2,12 +2,14 @@ package dbplugin
import (
"context"
"errors"
"fmt"
"sync"
"time"
"github.com/golang/protobuf/ptypes"
"github.com/hashicorp/vault/sdk/database/dbplugin/v5/proto"
"github.com/hashicorp/vault/sdk/helper/base62"
"github.com/hashicorp/vault/sdk/helper/pluginutil"
"github.com/hashicorp/vault/sdk/logical"
"google.golang.org/grpc/codes"
@@ -43,11 +45,14 @@ func (g *gRPCServer) getOrCreateDatabase(ctx context.Context) (Database, error)
if err != nil {
return nil, err
}
if db, ok := g.instances[id]; ok {
return db, nil
}
return g.createDatabase(id)
}
// must hold the g.Lock() to call this function
func (g *gRPCServer) createDatabase(id string) (Database, error) {
db, err := g.factoryFunc()
if err != nil {
return nil, err
@@ -304,12 +309,36 @@ func (g *gRPCServer) Close(ctx context.Context, _ *proto.Empty) (*proto.Empty, e
return &proto.Empty{}, nil
}
// getOrForceCreateDatabase will create a database even if the multiplexing ID is not present
func (g *gRPCServer) getOrForceCreateDatabase(ctx context.Context) (Database, error) {
impl, err := g.getOrCreateDatabase(ctx)
if errors.Is(err, pluginutil.ErrNoMultiplexingIDFound) {
// if this is called without a multiplexing context, like from the plugin catalog directly,
// then we won't have a database ID, so let's generate a new database instance
id, err := base62.Random(10)
if err != nil {
return nil, err
}
g.Lock()
defer g.Unlock()
impl, err = g.createDatabase(id)
if err != nil {
return nil, err
}
} else if err != nil {
return nil, err
}
return impl, 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)
impl, err := g.getOrForceCreateDatabase(ctx)
if err != nil {
return nil, err
}
if versioner, ok := impl.(logical.PluginVersioner); ok {
return &logical.VersionReply{PluginVersion: versioner.PluginVersion().Version}, nil
}

View File

@@ -233,7 +233,10 @@ func (mw databaseMetricsMiddleware) Close() (err error) {
// Error Sanitizer Middleware Domain
// ///////////////////////////////////////////////////
var _ Database = DatabaseErrorSanitizerMiddleware{}
var (
_ Database = (*DatabaseErrorSanitizerMiddleware)(nil)
_ logical.PluginVersioner = (*DatabaseErrorSanitizerMiddleware)(nil)
)
// DatabaseErrorSanitizerMiddleware wraps an implementation of Databases and
// sanitizes returned error messages
@@ -280,6 +283,13 @@ func (mw DatabaseErrorSanitizerMiddleware) Close() (err error) {
return mw.sanitize(mw.next.Close())
}
func (mw DatabaseErrorSanitizerMiddleware) PluginVersion() logical.PluginVersion {
if versioner, ok := mw.next.(logical.PluginVersioner); ok {
return versioner.PluginVersion()
}
return logical.EmptyPluginVersion
}
// sanitize errors by removing any sensitive strings within their messages. This uses
// the secretsFn to determine what fields should be sanitized.
func (mw DatabaseErrorSanitizerMiddleware) sanitize(err error) error {