Skip to content

Commit 75b2f42

Browse files
authored
Add DBv5 plugin serving & management functions (hashicorp#9745)
This mirrors what DBv4 is doing, but with the updated interface
1 parent 9afb87a commit 75b2f42

File tree

4 files changed

+242
-0
lines changed

4 files changed

+242
-0
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package newdbplugin
2+
3+
import (
4+
"context"
5+
6+
"github.com/hashicorp/go-plugin"
7+
"github.com/hashicorp/vault/sdk/database/newdbplugin/proto"
8+
"google.golang.org/grpc"
9+
)
10+
11+
// handshakeConfigs are used to just do a basic handshake between
12+
// a plugin and host. If the handshake fails, a user friendly error is shown.
13+
// This prevents users from executing bad plugins or executing a plugin
14+
// directory. It is a UX feature, not a security feature.
15+
var handshakeConfig = plugin.HandshakeConfig{
16+
ProtocolVersion: 5,
17+
MagicCookieKey: "VAULT_DATABASE_PLUGIN",
18+
MagicCookieValue: "926a0820-aea2-be28-51d6-83cdf00e8edb",
19+
}
20+
21+
type GRPCDatabasePlugin struct {
22+
Impl Database
23+
24+
// Embeding this will disable the netRPC protocol
25+
plugin.NetRPCUnsupportedPlugin
26+
}
27+
28+
var _ plugin.Plugin = &GRPCDatabasePlugin{}
29+
var _ plugin.GRPCPlugin = &GRPCDatabasePlugin{}
30+
31+
func (d GRPCDatabasePlugin) GRPCServer(_ *plugin.GRPCBroker, s *grpc.Server) error {
32+
proto.RegisterDatabaseServer(s, gRPCServer{impl: d.Impl})
33+
return nil
34+
}
35+
36+
func (GRPCDatabasePlugin) GRPCClient(doneCtx context.Context, _ *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) {
37+
client := gRPCClient{
38+
client: proto.NewDatabaseClient(c),
39+
doneCtx: doneCtx,
40+
}
41+
return client, nil
42+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package newdbplugin
2+
3+
import (
4+
"context"
5+
"errors"
6+
"sync"
7+
8+
log "github.com/hashicorp/go-hclog"
9+
plugin "github.com/hashicorp/go-plugin"
10+
"github.com/hashicorp/vault/sdk/helper/pluginutil"
11+
)
12+
13+
// DatabasePluginClient embeds a databasePluginRPCClient and wraps it's Close
14+
// method to also call Kill() on the plugin.Client.
15+
type DatabasePluginClient struct {
16+
client *plugin.Client
17+
sync.Mutex
18+
19+
Database
20+
}
21+
22+
// This wraps the Close call and ensures we both close the database connection
23+
// and kill the plugin.
24+
func (dc *DatabasePluginClient) Close() error {
25+
err := dc.Database.Close()
26+
dc.client.Kill()
27+
28+
return err
29+
}
30+
31+
// NewPluginClient returns a databaseRPCClient with a connection to a running
32+
// plugin. The client is wrapped in a DatabasePluginClient object to ensure the
33+
// plugin is killed on call of Close().
34+
func NewPluginClient(ctx context.Context, sys pluginutil.RunnerUtil, pluginRunner *pluginutil.PluginRunner, logger log.Logger, isMetadataMode bool) (Database, error) {
35+
// pluginSets is the map of plugins we can dispense.
36+
pluginSets := map[int]plugin.PluginSet{
37+
5: plugin.PluginSet{
38+
"database": new(GRPCDatabasePlugin),
39+
},
40+
}
41+
42+
var client *plugin.Client
43+
var err error
44+
if isMetadataMode {
45+
client, err = pluginRunner.RunMetadataMode(ctx, sys, pluginSets, handshakeConfig, []string{}, logger)
46+
} else {
47+
client, err = pluginRunner.Run(ctx, sys, pluginSets, handshakeConfig, []string{}, logger)
48+
}
49+
if err != nil {
50+
return nil, err
51+
}
52+
53+
// Connect via RPC
54+
rpcClient, err := client.Client()
55+
if err != nil {
56+
return nil, err
57+
}
58+
59+
// Request the plugin
60+
raw, err := rpcClient.Dispense("database")
61+
if err != nil {
62+
return nil, err
63+
}
64+
65+
// We should have a database type now. This feels like a normal interface
66+
// implementation but is in fact over an RPC connection.
67+
var db Database
68+
switch raw.(type) {
69+
case gRPCClient:
70+
db = raw.(gRPCClient)
71+
default:
72+
return nil, errors.New("unsupported client type")
73+
}
74+
75+
// Wrap RPC implementation in DatabasePluginClient
76+
return &DatabasePluginClient{
77+
client: client,
78+
Database: db,
79+
}, nil
80+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package newdbplugin
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/errwrap"
8+
log "github.com/hashicorp/go-hclog"
9+
"github.com/hashicorp/vault/sdk/helper/consts"
10+
"github.com/hashicorp/vault/sdk/helper/pluginutil"
11+
)
12+
13+
// PluginFactory is used to build plugin database types. It wraps the database
14+
// object in a logging and metrics middleware.
15+
func PluginFactory(ctx context.Context, pluginName string, sys pluginutil.LookRunnerUtil, logger log.Logger) (Database, error) {
16+
// Look for plugin in the plugin catalog
17+
pluginRunner, err := sys.LookupPlugin(ctx, pluginName, consts.PluginTypeDatabase)
18+
if err != nil {
19+
return nil, err
20+
}
21+
22+
namedLogger := logger.Named(pluginName)
23+
24+
var transport string
25+
var db Database
26+
if pluginRunner.Builtin {
27+
// Plugin is builtin so we can retrieve an instance of the interface
28+
// from the pluginRunner. Then cast it to a Database.
29+
dbRaw, err := pluginRunner.BuiltinFactory()
30+
if err != nil {
31+
return nil, errwrap.Wrapf("error initializing plugin: {{err}}", err)
32+
}
33+
34+
var ok bool
35+
db, ok = dbRaw.(Database)
36+
if !ok {
37+
return nil, fmt.Errorf("unsupported database type: %q", pluginName)
38+
}
39+
40+
transport = "builtin"
41+
42+
} else {
43+
// create a DatabasePluginClient instance
44+
db, err = NewPluginClient(ctx, sys, pluginRunner, namedLogger, false)
45+
if err != nil {
46+
return nil, err
47+
}
48+
49+
// Switch on the underlying database client type to get the transport
50+
// method.
51+
switch db.(*DatabasePluginClient).Database.(type) {
52+
case *gRPCClient:
53+
transport = "gRPC"
54+
}
55+
56+
}
57+
58+
typeStr, err := db.Type()
59+
if err != nil {
60+
return nil, errwrap.Wrapf("error getting plugin type: {{err}}", err)
61+
}
62+
63+
// Wrap with metrics middleware
64+
db = &databaseMetricsMiddleware{
65+
next: db,
66+
typeStr: typeStr,
67+
}
68+
69+
// Wrap with tracing middleware
70+
if namedLogger.IsTrace() {
71+
db = &databaseTracingMiddleware{
72+
next: db,
73+
logger: namedLogger.With("transport", transport),
74+
}
75+
}
76+
77+
return db, nil
78+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package newdbplugin
2+
3+
import (
4+
"crypto/tls"
5+
"fmt"
6+
7+
"github.com/hashicorp/go-plugin"
8+
"github.com/hashicorp/vault/sdk/helper/pluginutil"
9+
)
10+
11+
// Serve is called from within a plugin and wraps the provided
12+
// Database implementation in a databasePluginRPCServer object and starts a
13+
// RPC server.
14+
func Serve(db Database, tlsProvider func() (*tls.Config, error)) {
15+
plugin.Serve(ServeConfig(db, tlsProvider))
16+
}
17+
18+
func ServeConfig(db Database, tlsProvider func() (*tls.Config, error)) *plugin.ServeConfig {
19+
err := pluginutil.OptionallyEnableMlock()
20+
if err != nil {
21+
fmt.Println(err)
22+
return nil
23+
}
24+
25+
// pluginSets is the map of plugins we can dispense.
26+
pluginSets := map[int]plugin.PluginSet{
27+
5: plugin.PluginSet{
28+
"database": &GRPCDatabasePlugin{
29+
Impl: db,
30+
},
31+
},
32+
}
33+
34+
conf := &plugin.ServeConfig{
35+
HandshakeConfig: handshakeConfig,
36+
VersionedPlugins: pluginSets,
37+
TLSProvider: tlsProvider,
38+
GRPCServer: plugin.DefaultGRPCServer,
39+
}
40+
41+
return conf
42+
}

0 commit comments

Comments
 (0)