2018-01-18 21:49:20 +00:00
|
|
|
package plugin
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
2018-07-20 18:11:00 +00:00
|
|
|
"math"
|
2018-03-07 14:09:37 +00:00
|
|
|
"sync/atomic"
|
2018-01-18 21:49:20 +00:00
|
|
|
|
2018-04-03 00:46:59 +00:00
|
|
|
log "github.com/hashicorp/go-hclog"
|
Add plugin version to GRPC interface (#17088)
Add plugin version to GRPC interface
Added a version interface in the sdk/logical so that it can be shared between all plugin types, and then wired it up to RunningVersion in the mounts, auth list, and database systems.
I've tested that this works with auth, database, and secrets plugin types, with the following logic to populate RunningVersion:
If a plugin has a PluginVersion() method implemented, then that is used
If not, and the plugin is built into the Vault binary, then the go.mod version is used
Otherwise, the it will be the empty string.
My apologies for the length of this PR.
* Placeholder backend should be external
We use a placeholder backend (previously a framework.Backend) before a
GRPC plugin is lazy-loaded. This makes us later think the plugin is a
builtin plugin.
So we added a `placeholderBackend` type that overrides the
`IsExternal()` method so that later we know that the plugin is external,
and don't give it a default builtin version.
2022-09-15 23:37:59 +00:00
|
|
|
"github.com/hashicorp/go-plugin"
|
2019-04-12 21:54:35 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/helper/pluginutil"
|
|
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
|
|
"github.com/hashicorp/vault/sdk/plugin/pb"
|
Add plugin version to GRPC interface (#17088)
Add plugin version to GRPC interface
Added a version interface in the sdk/logical so that it can be shared between all plugin types, and then wired it up to RunningVersion in the mounts, auth list, and database systems.
I've tested that this works with auth, database, and secrets plugin types, with the following logic to populate RunningVersion:
If a plugin has a PluginVersion() method implemented, then that is used
If not, and the plugin is built into the Vault binary, then the go.mod version is used
Otherwise, the it will be the empty string.
My apologies for the length of this PR.
* Placeholder backend should be external
We use a placeholder backend (previously a framework.Backend) before a
GRPC plugin is lazy-loaded. This makes us later think the plugin is a
builtin plugin.
So we added a `placeholderBackend` type that overrides the
`IsExternal()` method so that later we know that the plugin is external,
and don't give it a default builtin version.
2022-09-15 23:37:59 +00:00
|
|
|
"google.golang.org/grpc"
|
|
|
|
"google.golang.org/grpc/codes"
|
|
|
|
"google.golang.org/grpc/status"
|
2018-01-18 21:49:20 +00:00
|
|
|
)
|
|
|
|
|
2021-04-08 16:43:39 +00:00
|
|
|
var (
|
|
|
|
ErrPluginShutdown = errors.New("plugin is shut down")
|
|
|
|
ErrClientInMetadataMode = errors.New("plugin client can not perform action while in metadata mode")
|
|
|
|
)
|
2018-01-18 21:49:20 +00:00
|
|
|
|
2018-01-19 06:44:44 +00:00
|
|
|
// Validate backendGRPCPluginClient satisfies the logical.Backend interface
|
|
|
|
var _ logical.Backend = &backendGRPCPluginClient{}
|
|
|
|
|
2018-01-18 21:49:20 +00:00
|
|
|
// backendPluginClient implements logical.Backend and is the
|
|
|
|
// go-plugin client.
|
|
|
|
type backendGRPCPluginClient struct {
|
Add plugin version to GRPC interface (#17088)
Add plugin version to GRPC interface
Added a version interface in the sdk/logical so that it can be shared between all plugin types, and then wired it up to RunningVersion in the mounts, auth list, and database systems.
I've tested that this works with auth, database, and secrets plugin types, with the following logic to populate RunningVersion:
If a plugin has a PluginVersion() method implemented, then that is used
If not, and the plugin is built into the Vault binary, then the go.mod version is used
Otherwise, the it will be the empty string.
My apologies for the length of this PR.
* Placeholder backend should be external
We use a placeholder backend (previously a framework.Backend) before a
GRPC plugin is lazy-loaded. This makes us later think the plugin is a
builtin plugin.
So we added a `placeholderBackend` type that overrides the
`IsExternal()` method so that later we know that the plugin is external,
and don't give it a default builtin version.
2022-09-15 23:37:59 +00:00
|
|
|
broker *plugin.GRPCBroker
|
|
|
|
client pb.BackendClient
|
|
|
|
versionClient logical.PluginVersionClient
|
|
|
|
metadataMode bool
|
2018-01-18 21:49:20 +00:00
|
|
|
|
|
|
|
system logical.SystemView
|
|
|
|
logger log.Logger
|
|
|
|
|
2018-03-07 14:09:37 +00:00
|
|
|
// This is used to signal to the Cleanup function that it can proceed
|
|
|
|
// because we have a defined server
|
|
|
|
cleanupCh chan struct{}
|
|
|
|
|
2018-01-18 21:49:20 +00:00
|
|
|
// server is the grpc server used for serving storage and sysview requests.
|
2018-03-07 14:09:37 +00:00
|
|
|
server *atomic.Value
|
|
|
|
|
2018-01-18 21:49:20 +00:00
|
|
|
// clientConn is the underlying grpc connection to the server, we store it
|
|
|
|
// so it can be cleaned up.
|
|
|
|
clientConn *grpc.ClientConn
|
|
|
|
doneCtx context.Context
|
|
|
|
}
|
|
|
|
|
2019-07-05 23:55:40 +00:00
|
|
|
func (b *backendGRPCPluginClient) Initialize(ctx context.Context, _ *logical.InitializationRequest) error {
|
|
|
|
if b.metadataMode {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
quitCh := pluginutil.CtxCancelIfCanceled(cancel, b.doneCtx)
|
|
|
|
defer close(quitCh)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
reply, err := b.client.Initialize(ctx, &pb.InitializeArgs{}, largeMsgGRPCCallOpts...)
|
|
|
|
if err != nil {
|
|
|
|
if b.doneCtx.Err() != nil {
|
|
|
|
return ErrPluginShutdown
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the plugin doesn't have Initialize implemented we should not fail
|
2019-07-19 01:10:15 +00:00
|
|
|
// the initialize call; otherwise this could halt startup of vault.
|
2019-07-05 23:55:40 +00:00
|
|
|
grpcStatus, ok := status.FromError(err)
|
|
|
|
if ok && grpcStatus.Code() == codes.Unimplemented {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if reply.Err != nil {
|
|
|
|
return pb.ProtoErrToErr(reply.Err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-01-18 21:49:20 +00:00
|
|
|
func (b *backendGRPCPluginClient) HandleRequest(ctx context.Context, req *logical.Request) (*logical.Response, error) {
|
|
|
|
if b.metadataMode {
|
|
|
|
return nil, ErrClientInMetadataMode
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
quitCh := pluginutil.CtxCancelIfCanceled(cancel, b.doneCtx)
|
|
|
|
defer close(quitCh)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
protoReq, err := pb.LogicalRequestToProtoRequest(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
reply, err := b.client.HandleRequest(ctx, &pb.HandleRequestArgs{
|
|
|
|
Request: protoReq,
|
2018-02-06 18:52:35 +00:00
|
|
|
}, largeMsgGRPCCallOpts...)
|
2018-01-18 21:49:20 +00:00
|
|
|
if err != nil {
|
|
|
|
if b.doneCtx.Err() != nil {
|
|
|
|
return nil, ErrPluginShutdown
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
resp, err := pb.ProtoResponseToLogicalResponse(reply.Response)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if reply.Err != nil {
|
|
|
|
return resp, pb.ProtoErrToErr(reply.Err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *backendGRPCPluginClient) SpecialPaths() *logical.Paths {
|
|
|
|
reply, err := b.client.SpecialPaths(b.doneCtx, &pb.Empty{})
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-02-09 00:51:26 +00:00
|
|
|
if reply.Paths == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-01-18 21:49:20 +00:00
|
|
|
return &logical.Paths{
|
|
|
|
Root: reply.Paths.Root,
|
|
|
|
Unauthenticated: reply.Paths.Unauthenticated,
|
|
|
|
LocalStorage: reply.Paths.LocalStorage,
|
|
|
|
SealWrapStorage: reply.Paths.SealWrapStorage,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// System returns vault's system view. The backend client stores the view during
|
|
|
|
// Setup, so there is no need to shim the system just to get it back.
|
|
|
|
func (b *backendGRPCPluginClient) System() logical.SystemView {
|
|
|
|
return b.system
|
|
|
|
}
|
|
|
|
|
|
|
|
// Logger returns vault's logger. The backend client stores the logger during
|
|
|
|
// Setup, so there is no need to shim the logger just to get it back.
|
|
|
|
func (b *backendGRPCPluginClient) Logger() log.Logger {
|
|
|
|
return b.logger
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *backendGRPCPluginClient) HandleExistenceCheck(ctx context.Context, req *logical.Request) (bool, bool, error) {
|
|
|
|
if b.metadataMode {
|
|
|
|
return false, false, ErrClientInMetadataMode
|
|
|
|
}
|
|
|
|
|
|
|
|
protoReq, err := pb.LogicalRequestToProtoRequest(req)
|
|
|
|
if err != nil {
|
|
|
|
return false, false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
quitCh := pluginutil.CtxCancelIfCanceled(cancel, b.doneCtx)
|
|
|
|
defer close(quitCh)
|
|
|
|
defer cancel()
|
|
|
|
reply, err := b.client.HandleExistenceCheck(ctx, &pb.HandleExistenceCheckArgs{
|
|
|
|
Request: protoReq,
|
2018-02-06 18:52:35 +00:00
|
|
|
}, largeMsgGRPCCallOpts...)
|
2018-01-18 21:49:20 +00:00
|
|
|
if err != nil {
|
|
|
|
if b.doneCtx.Err() != nil {
|
|
|
|
return false, false, ErrPluginShutdown
|
|
|
|
}
|
|
|
|
return false, false, err
|
|
|
|
}
|
|
|
|
if reply.Err != nil {
|
|
|
|
return false, false, pb.ProtoErrToErr(reply.Err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return reply.CheckFound, reply.Exists, nil
|
|
|
|
}
|
|
|
|
|
2018-01-19 06:44:44 +00:00
|
|
|
func (b *backendGRPCPluginClient) Cleanup(ctx context.Context) {
|
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
quitCh := pluginutil.CtxCancelIfCanceled(cancel, b.doneCtx)
|
|
|
|
defer close(quitCh)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
b.client.Cleanup(ctx, &pb.Empty{})
|
2018-03-07 14:09:37 +00:00
|
|
|
|
|
|
|
// This will block until Setup has run the function to create a new server
|
|
|
|
// in b.server. If we stop here before it has a chance to actually start
|
2019-03-19 13:32:45 +00:00
|
|
|
// listening, when it starts listening it will immediately error out and
|
2018-03-07 14:09:37 +00:00
|
|
|
// exit, which is fine. Overall this ensures that we do not miss stopping
|
|
|
|
// the server if it ends up being created after Cleanup is called.
|
|
|
|
<-b.cleanupCh
|
|
|
|
server := b.server.Load()
|
|
|
|
if server != nil {
|
|
|
|
server.(*grpc.Server).GracefulStop()
|
2018-01-18 21:49:20 +00:00
|
|
|
}
|
|
|
|
b.clientConn.Close()
|
|
|
|
}
|
|
|
|
|
2018-01-19 06:44:44 +00:00
|
|
|
func (b *backendGRPCPluginClient) InvalidateKey(ctx context.Context, key string) {
|
2018-01-18 21:49:20 +00:00
|
|
|
if b.metadataMode {
|
|
|
|
return
|
|
|
|
}
|
2018-01-19 06:44:44 +00:00
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
quitCh := pluginutil.CtxCancelIfCanceled(cancel, b.doneCtx)
|
|
|
|
defer close(quitCh)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
b.client.InvalidateKey(ctx, &pb.InvalidateKeyArgs{
|
2018-01-18 21:49:20 +00:00
|
|
|
Key: key,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-01-19 06:44:44 +00:00
|
|
|
func (b *backendGRPCPluginClient) Setup(ctx context.Context, config *logical.BackendConfig) error {
|
2018-01-18 21:49:20 +00:00
|
|
|
// Shim logical.Storage
|
|
|
|
storageImpl := config.StorageView
|
|
|
|
if b.metadataMode {
|
|
|
|
storageImpl = &NOOPStorage{}
|
|
|
|
}
|
|
|
|
storage := &GRPCStorageServer{
|
|
|
|
impl: storageImpl,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Shim logical.SystemView
|
|
|
|
sysViewImpl := config.System
|
|
|
|
if b.metadataMode {
|
|
|
|
sysViewImpl = &logical.StaticSystemView{}
|
|
|
|
}
|
|
|
|
sysView := &gRPCSystemViewServer{
|
|
|
|
impl: sysViewImpl,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Register the server in this closure.
|
|
|
|
serverFunc := func(opts []grpc.ServerOption) *grpc.Server {
|
2018-07-20 18:11:00 +00:00
|
|
|
opts = append(opts, grpc.MaxRecvMsgSize(math.MaxInt32))
|
|
|
|
opts = append(opts, grpc.MaxSendMsgSize(math.MaxInt32))
|
|
|
|
|
2018-01-18 21:49:20 +00:00
|
|
|
s := grpc.NewServer(opts...)
|
|
|
|
pb.RegisterSystemViewServer(s, sysView)
|
|
|
|
pb.RegisterStorageServer(s, storage)
|
2018-03-07 14:09:37 +00:00
|
|
|
b.server.Store(s)
|
|
|
|
close(b.cleanupCh)
|
2018-01-18 21:49:20 +00:00
|
|
|
return s
|
|
|
|
}
|
|
|
|
brokerID := b.broker.NextId()
|
|
|
|
go b.broker.AcceptAndServe(brokerID, serverFunc)
|
|
|
|
|
|
|
|
args := &pb.SetupArgs{
|
2018-03-21 19:04:27 +00:00
|
|
|
BrokerID: brokerID,
|
|
|
|
Config: config.Config,
|
|
|
|
BackendUUID: config.BackendUUID,
|
2018-01-18 21:49:20 +00:00
|
|
|
}
|
|
|
|
|
2018-01-19 06:44:44 +00:00
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
quitCh := pluginutil.CtxCancelIfCanceled(cancel, b.doneCtx)
|
|
|
|
defer close(quitCh)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
reply, err := b.client.Setup(ctx, args)
|
2018-01-18 21:49:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if reply.Err != "" {
|
|
|
|
return errors.New(reply.Err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set system and logger for getter methods
|
|
|
|
b.system = config.System
|
|
|
|
b.logger = config.Logger
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *backendGRPCPluginClient) Type() logical.BackendType {
|
|
|
|
reply, err := b.client.Type(b.doneCtx, &pb.Empty{})
|
|
|
|
if err != nil {
|
|
|
|
return logical.TypeUnknown
|
|
|
|
}
|
|
|
|
|
|
|
|
return logical.BackendType(reply.Type)
|
|
|
|
}
|
Add plugin version to GRPC interface (#17088)
Add plugin version to GRPC interface
Added a version interface in the sdk/logical so that it can be shared between all plugin types, and then wired it up to RunningVersion in the mounts, auth list, and database systems.
I've tested that this works with auth, database, and secrets plugin types, with the following logic to populate RunningVersion:
If a plugin has a PluginVersion() method implemented, then that is used
If not, and the plugin is built into the Vault binary, then the go.mod version is used
Otherwise, the it will be the empty string.
My apologies for the length of this PR.
* Placeholder backend should be external
We use a placeholder backend (previously a framework.Backend) before a
GRPC plugin is lazy-loaded. This makes us later think the plugin is a
builtin plugin.
So we added a `placeholderBackend` type that overrides the
`IsExternal()` method so that later we know that the plugin is external,
and don't give it a default builtin version.
2022-09-15 23:37:59 +00:00
|
|
|
|
|
|
|
func (b *backendGRPCPluginClient) PluginVersion() logical.PluginVersion {
|
|
|
|
reply, err := b.versionClient.Version(b.doneCtx, &logical.Empty{})
|
|
|
|
if err != nil {
|
|
|
|
if stErr, ok := status.FromError(err); ok {
|
|
|
|
if stErr.Code() == codes.Unimplemented {
|
|
|
|
return logical.EmptyPluginVersion
|
|
|
|
}
|
|
|
|
}
|
|
|
|
b.Logger().Warn("Unknown error getting plugin version", "err", err)
|
|
|
|
return logical.EmptyPluginVersion
|
|
|
|
}
|
|
|
|
return logical.PluginVersion{
|
|
|
|
Version: reply.GetPluginVersion(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *backendGRPCPluginClient) IsExternal() bool {
|
|
|
|
return true
|
|
|
|
}
|