open-vault/sdk/helper/pluginutil/run_config.go
John-Michael Faircloth 1cf74e1179
feature: multiplexing support for database plugins (#14033)
* feat: DB plugin multiplexing (#13734)

* WIP: start from main and get a plugin runner from core

* move MultiplexedClient map to plugin catalog
- call sys.NewPluginClient from PluginFactory
- updates to getPluginClient
- thread through isMetadataMode

* use go-plugin ClientProtocol interface
- call sys.NewPluginClient from dbplugin.NewPluginClient

* move PluginSets to dbplugin package
- export dbplugin HandshakeConfig
- small refactor of PluginCatalog.getPluginClient

* add removeMultiplexedClient; clean up on Close()
- call client.Kill from plugin catalog
- set rpcClient when muxed client exists

* add ID to dbplugin.DatabasePluginClient struct

* only create one plugin process per plugin type

* update NewPluginClient to return connection ID to sdk
- wrap grpc.ClientConn so we can inject the ID into context
- get ID from context on grpc server

* add v6 multiplexing  protocol version

* WIP: backwards compat for db plugins

* Ensure locking on plugin catalog access

- Create public GetPluginClient method for plugin catalog
- rename postgres db plugin

* use the New constructor for db plugins

* grpc server: use write lock for Close and rlock for CRUD

* cleanup MultiplexedClients on Close

* remove TODO

* fix multiplexing regression with grpc server connection

* cleanup grpc server instances on close

* embed ClientProtocol in Multiplexer interface

* use PluginClientConfig arg to make NewPluginClient plugin type agnostic

* create a new plugin process for non-muxed plugins

* feat: plugin multiplexing: handle plugin client cleanup (#13896)

* use closure for plugin client cleanup

* log and return errors; add comments

* move rpcClient wrapping to core for ID injection

* refactor core plugin client and sdk

* remove unused ID method

* refactor and only wrap clientConn on multiplexed plugins

* rename structs and do not export types

* Slight refactor of system view interface

* Revert "Slight refactor of system view interface"

This reverts commit 73d420e5cd2f0415e000c5a9284ea72a58016dd6.

* Revert "Revert "Slight refactor of system view interface""

This reverts commit f75527008a1db06d04a23e04c3059674be8adb5f.

* only provide pluginRunner arg to the internal newPluginClient method

* embed ClientProtocol in pluginClient and name logger

* Add back MLock support

* remove enableMlock arg from setupPluginCatalog

* rename plugin util interface to PluginClient

Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>

* feature: multiplexing: fix unit tests (#14007)

* fix grpc_server tests and add coverage

* update run_config tests

* add happy path test case for grpc_server ID from context

* update test helpers

* feat: multiplexing: handle v5 plugin compiled with new sdk

* add mux supported flag and increase test coverage

* set multiplexingSupport field in plugin server

* remove multiplexingSupport field in sdk

* revert postgres to non-multiplexed

* add comments on grpc server fields

* use pointer receiver on grpc server methods

* add changelog

* use pointer for grpcserver instance

* Use a gRPC server to determine if a plugin should be multiplexed

* Apply suggestions from code review

Co-authored-by: Brian Kassouf <briankassouf@users.noreply.github.com>

* add lock to removePluginClient

* add multiplexingSupport field to externalPlugin struct

* do not send nil to grpc MultiplexingSupport

* check err before logging

* handle locking scenario for cleanupFunc

* allow ServeConfigMultiplex to dispense v5 plugin

* reposition structs, add err check and comments

* add comment on locking for cleanupExternalPlugin

Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
Co-authored-by: Brian Kassouf <briankassouf@users.noreply.github.com>
2022-02-17 08:50:33 -06:00

177 lines
3.9 KiB
Go

package pluginutil
import (
"context"
"crypto/sha256"
"crypto/tls"
"fmt"
"os/exec"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/version"
)
type PluginClientConfig struct {
Name string
PluginType consts.PluginType
PluginSets map[int]plugin.PluginSet
HandshakeConfig plugin.HandshakeConfig
Logger log.Logger
IsMetadataMode bool
AutoMTLS bool
MLock bool
}
type runConfig struct {
// Provided by PluginRunner
command string
args []string
sha256 []byte
// Initialized with what's in PluginRunner.Env, but can be added to
env []string
wrapper RunnerUtil
PluginClientConfig
}
func (rc runConfig) makeConfig(ctx context.Context) (*plugin.ClientConfig, error) {
cmd := exec.Command(rc.command, rc.args...)
cmd.Env = append(cmd.Env, rc.env...)
// Add the mlock setting to the ENV of the plugin
if rc.MLock || (rc.wrapper != nil && rc.wrapper.MlockEnabled()) {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", PluginMlockEnabled, "true"))
}
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", PluginVaultVersionEnv, version.GetVersion().Version))
if rc.IsMetadataMode {
rc.Logger = rc.Logger.With("metadata", "true")
}
metadataEnv := fmt.Sprintf("%s=%t", PluginMetadataModeEnv, rc.IsMetadataMode)
cmd.Env = append(cmd.Env, metadataEnv)
var clientTLSConfig *tls.Config
if !rc.AutoMTLS && !rc.IsMetadataMode {
// Get a CA TLS Certificate
certBytes, key, err := generateCert()
if err != nil {
return nil, err
}
// Use CA to sign a client cert and return a configured TLS config
clientTLSConfig, err = createClientTLSConfig(certBytes, key)
if err != nil {
return nil, err
}
// Use CA to sign a server cert and wrap the values in a response wrapped
// token.
wrapToken, err := wrapServerConfig(ctx, rc.wrapper, certBytes, key)
if err != nil {
return nil, err
}
// Add the response wrap token to the ENV of the plugin
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", PluginUnwrapTokenEnv, wrapToken))
}
secureConfig := &plugin.SecureConfig{
Checksum: rc.sha256,
Hash: sha256.New(),
}
clientConfig := &plugin.ClientConfig{
HandshakeConfig: rc.HandshakeConfig,
VersionedPlugins: rc.PluginSets,
Cmd: cmd,
SecureConfig: secureConfig,
TLSConfig: clientTLSConfig,
Logger: rc.Logger,
AllowedProtocols: []plugin.Protocol{
plugin.ProtocolNetRPC,
plugin.ProtocolGRPC,
},
AutoMTLS: rc.AutoMTLS,
}
return clientConfig, nil
}
func (rc runConfig) run(ctx context.Context) (*plugin.Client, error) {
clientConfig, err := rc.makeConfig(ctx)
if err != nil {
return nil, err
}
client := plugin.NewClient(clientConfig)
return client, nil
}
type RunOpt func(*runConfig)
func Env(env ...string) RunOpt {
return func(rc *runConfig) {
rc.env = append(rc.env, env...)
}
}
func Runner(wrapper RunnerUtil) RunOpt {
return func(rc *runConfig) {
rc.wrapper = wrapper
}
}
func PluginSets(pluginSets map[int]plugin.PluginSet) RunOpt {
return func(rc *runConfig) {
rc.PluginSets = pluginSets
}
}
func HandshakeConfig(hs plugin.HandshakeConfig) RunOpt {
return func(rc *runConfig) {
rc.HandshakeConfig = hs
}
}
func Logger(logger log.Logger) RunOpt {
return func(rc *runConfig) {
rc.Logger = logger
}
}
func MetadataMode(isMetadataMode bool) RunOpt {
return func(rc *runConfig) {
rc.IsMetadataMode = isMetadataMode
}
}
func AutoMTLS(autoMTLS bool) RunOpt {
return func(rc *runConfig) {
rc.AutoMTLS = autoMTLS
}
}
func MLock(mlock bool) RunOpt {
return func(rc *runConfig) {
rc.MLock = mlock
}
}
func (r *PluginRunner) RunConfig(ctx context.Context, opts ...RunOpt) (*plugin.Client, error) {
rc := runConfig{
command: r.Command,
args: r.Args,
sha256: r.Sha256,
env: r.Env,
}
for _, opt := range opts {
opt(&rc)
}
return rc.run(ctx)
}