github.com/hashicorp/vault/sdk@v0.13.0/plugin/plugin.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package plugin
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  
    11  	log "github.com/hashicorp/go-hclog"
    12  	"github.com/hashicorp/go-plugin"
    13  	"github.com/hashicorp/vault/sdk/helper/consts"
    14  	"github.com/hashicorp/vault/sdk/helper/pluginutil"
    15  	"github.com/hashicorp/vault/sdk/logical"
    16  )
    17  
    18  // BackendPluginClient is a wrapper around backendPluginClient
    19  // that also contains its plugin.Client instance. It's primarily
    20  // used to cleanly kill the client on Cleanup()
    21  type BackendPluginClient struct {
    22  	client *plugin.Client
    23  
    24  	logical.Backend
    25  }
    26  
    27  // Cleanup calls the RPC client's Cleanup() func and also calls
    28  // the go-plugin's client Kill() func
    29  func (b *BackendPluginClient) Cleanup(ctx context.Context) {
    30  	b.Backend.Cleanup(ctx)
    31  	b.client.Kill()
    32  }
    33  
    34  // NewBackendWithVersion will return an instance of an RPC-based client implementation of the backend for
    35  // external plugins, or a concrete implementation of the backend if it is a builtin backend.
    36  // The backend is returned as a logical.Backend interface. The isMetadataMode param determines whether
    37  // the plugin should run in metadata mode.
    38  func NewBackendWithVersion(ctx context.Context, pluginName string, pluginType consts.PluginType, sys pluginutil.LookRunnerUtil, conf *logical.BackendConfig, isMetadataMode bool, version string) (logical.Backend, error) {
    39  	// Look for plugin in the plugin catalog
    40  	pluginRunner, err := sys.LookupPluginVersion(ctx, pluginName, pluginType, version)
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  
    45  	var backend logical.Backend
    46  	if pluginRunner.Builtin {
    47  		// Plugin is builtin so we can retrieve an instance of the interface
    48  		// from the pluginRunner. Then cast it to logical.Factory.
    49  		rawFactory, err := pluginRunner.BuiltinFactory()
    50  		if err != nil {
    51  			return nil, fmt.Errorf("error getting plugin type: %q", err)
    52  		}
    53  
    54  		if factory, ok := rawFactory.(logical.Factory); !ok {
    55  			return nil, fmt.Errorf("unsupported backend type: %q", pluginName)
    56  		} else {
    57  			if backend, err = factory(ctx, conf); err != nil {
    58  				return nil, err
    59  			}
    60  		}
    61  	} else {
    62  		// create a backendPluginClient instance
    63  		backend, err = NewPluginClient(ctx, sys, pluginRunner, conf.Logger, isMetadataMode)
    64  		if err != nil {
    65  			return nil, err
    66  		}
    67  	}
    68  
    69  	return backend, nil
    70  }
    71  
    72  // NewBackend will return an instance of an RPC-based client implementation of the backend for
    73  // external plugins, or a concrete implementation of the backend if it is a builtin backend.
    74  // The backend is returned as a logical.Backend interface. The isMetadataMode param determines whether
    75  // the plugin should run in metadata mode.
    76  func NewBackend(ctx context.Context, pluginName string, pluginType consts.PluginType, sys pluginutil.LookRunnerUtil, conf *logical.BackendConfig, isMetadataMode bool) (logical.Backend, error) {
    77  	return NewBackendWithVersion(ctx, pluginName, pluginType, sys, conf, isMetadataMode, "")
    78  }
    79  
    80  func NewPluginClient(ctx context.Context, sys pluginutil.RunnerUtil, pluginRunner *pluginutil.PluginRunner, logger log.Logger, isMetadataMode bool) (logical.Backend, error) {
    81  	// pluginMap is the map of plugins we can dispense.
    82  	pluginSet := map[int]plugin.PluginSet{
    83  		// Version 3 used to supports both protocols. We want to keep it around
    84  		// since it's possible old plugins built against this version will still
    85  		// work with gRPC. There is currently no difference between version 3
    86  		// and version 4.
    87  		3: {
    88  			"backend": &GRPCBackendPlugin{
    89  				MetadataMode: isMetadataMode,
    90  			},
    91  		},
    92  		4: {
    93  			"backend": &GRPCBackendPlugin{
    94  				MetadataMode: isMetadataMode,
    95  			},
    96  		},
    97  	}
    98  
    99  	namedLogger := logger.Named(pluginRunner.Name)
   100  
   101  	var client *plugin.Client
   102  	var err error
   103  	if isMetadataMode {
   104  		client, err = pluginRunner.RunMetadataMode(ctx, sys, pluginSet, HandshakeConfig, []string{}, namedLogger)
   105  	} else {
   106  		client, err = pluginRunner.Run(ctx, sys, pluginSet, HandshakeConfig, []string{}, namedLogger)
   107  	}
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	// Connect via RPC
   113  	rpcClient, err := client.Client()
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  
   118  	// Request the plugin
   119  	raw, err := rpcClient.Dispense("backend")
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	var backend logical.Backend
   125  	var transport string
   126  	// We should have a logical backend type now. This feels like a normal interface
   127  	// implementation but is in fact over an RPC connection.
   128  	switch b := raw.(type) {
   129  	case *backendGRPCPluginClient:
   130  		backend = b
   131  		transport = "gRPC"
   132  	default:
   133  		return nil, errors.New("unsupported plugin client type")
   134  	}
   135  
   136  	// Wrap the backend in a tracing middleware
   137  	if namedLogger.IsTrace() {
   138  		backend = &BackendTracingMiddleware{
   139  			logger: namedLogger.With("transport", transport),
   140  			next:   backend,
   141  		}
   142  	}
   143  
   144  	return &BackendPluginClient{
   145  		client:  client,
   146  		Backend: backend,
   147  	}, nil
   148  }
   149  
   150  func (b *BackendPluginClient) PluginVersion() logical.PluginVersion {
   151  	if versioner, ok := b.Backend.(logical.PluginVersioner); ok {
   152  		return versioner.PluginVersion()
   153  	}
   154  	return logical.EmptyPluginVersion
   155  }
   156  
   157  var _ logical.PluginVersioner = (*BackendPluginClient)(nil)