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

[DBPW 3/5] Add DBv5 plugin serving & management functions #9745

Merged
merged 1 commit into from
Sep 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions sdk/database/newdbplugin/grpc_database_plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package newdbplugin

import (
"context"

"github.com/hashicorp/go-plugin"
"github.com/hashicorp/vault/sdk/database/newdbplugin/proto"
"google.golang.org/grpc"
)

// handshakeConfigs are used to just do a basic handshake between
// a plugin and host. If the handshake fails, a user friendly error is shown.
// This prevents users from executing bad plugins or executing a plugin
// directory. It is a UX feature, not a security feature.
var handshakeConfig = plugin.HandshakeConfig{
ProtocolVersion: 5,
MagicCookieKey: "VAULT_DATABASE_PLUGIN",
MagicCookieValue: "926a0820-aea2-be28-51d6-83cdf00e8edb",
}

type GRPCDatabasePlugin struct {
Impl Database

// Embeding this will disable the netRPC protocol
plugin.NetRPCUnsupportedPlugin
}

var _ plugin.Plugin = &GRPCDatabasePlugin{}
var _ plugin.GRPCPlugin = &GRPCDatabasePlugin{}

func (d GRPCDatabasePlugin) GRPCServer(_ *plugin.GRPCBroker, s *grpc.Server) error {
proto.RegisterDatabaseServer(s, gRPCServer{impl: d.Impl})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to wrap d.Impl with DatabaseErrorSanitizerMiddleware?

Copy link
Contributor Author

@pcman312 pcman312 Sep 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately we can't at this point in the code. The DatabaseErrorSantizerMiddleware doesn't know what secret values to redact at this point. It only knows those values within the database plugin itself. Here's an example from the existing MongoDB plugin:

dbType := dbplugin.NewDatabaseErrorSanitizerMiddleware(db, db.secretValues)

func (c *mongoDBConnectionProducer) secretValues() map[string]interface{} {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that in this case it's more of a passthrough/no-op in terms of sanitizing secret values, but sanitize is also performing an specific error check:

if errwrap.ContainsType(err, new(url.Error)) {
return errors.New("unable to parse connection url")
}
.

return nil
}

func (GRPCDatabasePlugin) GRPCClient(doneCtx context.Context, _ *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) {
client := gRPCClient{
client: proto.NewDatabaseClient(c),
doneCtx: doneCtx,
}
return client, nil
}
80 changes: 80 additions & 0 deletions sdk/database/newdbplugin/plugin_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package newdbplugin

import (
"context"
"errors"
"sync"

log "github.com/hashicorp/go-hclog"
plugin "github.com/hashicorp/go-plugin"
"github.com/hashicorp/vault/sdk/helper/pluginutil"
)

// DatabasePluginClient embeds a databasePluginRPCClient and wraps it's Close
// method to also call Kill() on the plugin.Client.
type DatabasePluginClient struct {
client *plugin.Client
sync.Mutex

Database
}

// This wraps the Close call and ensures we both close the database connection
// and kill the plugin.
func (dc *DatabasePluginClient) Close() error {
err := dc.Database.Close()
dc.client.Kill()

return err
}

// NewPluginClient returns a databaseRPCClient with a connection to a running
// plugin. The client is wrapped in a DatabasePluginClient object to ensure the
// plugin is killed on call of Close().
func NewPluginClient(ctx context.Context, sys pluginutil.RunnerUtil, pluginRunner *pluginutil.PluginRunner, logger log.Logger, isMetadataMode bool) (Database, error) {
// pluginSets is the map of plugins we can dispense.
pluginSets := map[int]plugin.PluginSet{
5: plugin.PluginSet{
"database": new(GRPCDatabasePlugin),
},
}
tvoran marked this conversation as resolved.
Show resolved Hide resolved

var client *plugin.Client
var err error
if isMetadataMode {
client, err = pluginRunner.RunMetadataMode(ctx, sys, pluginSets, handshakeConfig, []string{}, logger)
} else {
client, err = pluginRunner.Run(ctx, sys, pluginSets, handshakeConfig, []string{}, logger)
}
if err != nil {
return nil, err
}

// Connect via RPC
rpcClient, err := client.Client()
if err != nil {
return nil, err
}

// Request the plugin
raw, err := rpcClient.Dispense("database")
if err != nil {
return nil, err
}

// We should have a database type now. This feels like a normal interface
// implementation but is in fact over an RPC connection.
var db Database
switch raw.(type) {
case gRPCClient:
db = raw.(gRPCClient)
default:
return nil, errors.New("unsupported client type")
}

// Wrap RPC implementation in DatabasePluginClient
return &DatabasePluginClient{
client: client,
Database: db,
}, nil
}
78 changes: 78 additions & 0 deletions sdk/database/newdbplugin/plugin_factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package newdbplugin

import (
"context"
"fmt"

"github.com/hashicorp/errwrap"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/pluginutil"
)

// PluginFactory is used to build plugin database types. It wraps the database
// object in a logging and metrics middleware.
func PluginFactory(ctx context.Context, pluginName string, sys pluginutil.LookRunnerUtil, logger log.Logger) (Database, error) {
// Look for plugin in the plugin catalog
pluginRunner, err := sys.LookupPlugin(ctx, pluginName, consts.PluginTypeDatabase)
if err != nil {
return nil, err
}

namedLogger := logger.Named(pluginName)

var transport string
var db Database
if pluginRunner.Builtin {
// Plugin is builtin so we can retrieve an instance of the interface
// from the pluginRunner. Then cast it to a Database.
dbRaw, err := pluginRunner.BuiltinFactory()
if err != nil {
return nil, errwrap.Wrapf("error initializing plugin: {{err}}", err)
}

var ok bool
db, ok = dbRaw.(Database)
if !ok {
return nil, fmt.Errorf("unsupported database type: %q", pluginName)
}

transport = "builtin"

} else {
// create a DatabasePluginClient instance
db, err = NewPluginClient(ctx, sys, pluginRunner, namedLogger, false)
if err != nil {
return nil, err
}

// Switch on the underlying database client type to get the transport
// method.
switch db.(*DatabasePluginClient).Database.(type) {
case *gRPCClient:
transport = "gRPC"
}

}

typeStr, err := db.Type()
if err != nil {
return nil, errwrap.Wrapf("error getting plugin type: {{err}}", err)
}

// Wrap with metrics middleware
db = &databaseMetricsMiddleware{
next: db,
typeStr: typeStr,
}

// Wrap with tracing middleware
if namedLogger.IsTrace() {
db = &databaseTracingMiddleware{
next: db,
logger: namedLogger.With("transport", transport),
}
}

return db, nil
}
42 changes: 42 additions & 0 deletions sdk/database/newdbplugin/plugin_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package newdbplugin

import (
"crypto/tls"
"fmt"

"github.com/hashicorp/go-plugin"
"github.com/hashicorp/vault/sdk/helper/pluginutil"
)

// Serve is called from within a plugin and wraps the provided
// Database implementation in a databasePluginRPCServer object and starts a
// RPC server.
func Serve(db Database, tlsProvider func() (*tls.Config, error)) {
plugin.Serve(ServeConfig(db, tlsProvider))
}

func ServeConfig(db Database, tlsProvider func() (*tls.Config, error)) *plugin.ServeConfig {
err := pluginutil.OptionallyEnableMlock()
if err != nil {
fmt.Println(err)
return nil
}

// pluginSets is the map of plugins we can dispense.
pluginSets := map[int]plugin.PluginSet{
5: plugin.PluginSet{
"database": &GRPCDatabasePlugin{
Impl: db,
},
},
}

conf := &plugin.ServeConfig{
HandshakeConfig: handshakeConfig,
VersionedPlugins: pluginSets,
TLSProvider: tlsProvider,
GRPCServer: plugin.DefaultGRPCServer,
}

return conf
}