package plugin import ( "context" "errors" "net/rpc" log "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-plugin" "github.com/hashicorp/vault/logical" ) var ( ErrClientInMetadataMode = errors.New("plugin client can not perform action while in metadata mode") ) // backendPluginClient implements logical.Backend and is the // go-plugin client. type backendPluginClient struct { broker *plugin.MuxBroker client *rpc.Client metadataMode bool system logical.SystemView logger log.Logger } // HandleRequestArgs is the args for HandleRequest method. type HandleRequestArgs struct { StorageID uint32 Request *logical.Request } // HandleRequestReply is the reply for HandleRequest method. type HandleRequestReply struct { Response *logical.Response Error error } // SpecialPathsReply is the reply for SpecialPaths method. type SpecialPathsReply struct { Paths *logical.Paths } // SystemReply is the reply for System method. type SystemReply struct { SystemView logical.SystemView Error error } // HandleExistenceCheckArgs is the args for HandleExistenceCheck method. type HandleExistenceCheckArgs struct { StorageID uint32 Request *logical.Request } // HandleExistenceCheckReply is the reply for HandleExistenceCheck method. type HandleExistenceCheckReply struct { CheckFound bool Exists bool Error error } // SetupArgs is the args for Setup method. type SetupArgs struct { StorageID uint32 LoggerID uint32 SysViewID uint32 Config map[string]string BackendUUID string } // SetupReply is the reply for Setup method. type SetupReply struct { Error error } // TypeReply is the reply for the Type method. type TypeReply struct { Type logical.BackendType } func (b *backendPluginClient) HandleRequest(ctx context.Context, req *logical.Request) (*logical.Response, error) { if b.metadataMode { return nil, ErrClientInMetadataMode } // Do not send the storage, since go-plugin cannot serialize // interfaces. The server will pick up the storage from the shim. req.Storage = nil args := &HandleRequestArgs{ Request: req, } var reply HandleRequestReply if req.Connection != nil { oldConnState := req.Connection.ConnState req.Connection.ConnState = nil defer func() { req.Connection.ConnState = oldConnState }() } err := b.client.Call("Plugin.HandleRequest", args, &reply) if err != nil { return nil, err } if reply.Error != nil { if reply.Error.Error() == logical.ErrUnsupportedOperation.Error() { return nil, logical.ErrUnsupportedOperation } return reply.Response, reply.Error } return reply.Response, nil } func (b *backendPluginClient) SpecialPaths() *logical.Paths { var reply SpecialPathsReply err := b.client.Call("Plugin.SpecialPaths", new(interface{}), &reply) if err != nil { return nil } return reply.Paths } // 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 *backendPluginClient) 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 *backendPluginClient) Logger() log.Logger { return b.logger } func (b *backendPluginClient) HandleExistenceCheck(ctx context.Context, req *logical.Request) (bool, bool, error) { if b.metadataMode { return false, false, ErrClientInMetadataMode } // Do not send the storage, since go-plugin cannot serialize // interfaces. The server will pick up the storage from the shim. req.Storage = nil args := &HandleExistenceCheckArgs{ Request: req, } var reply HandleExistenceCheckReply if req.Connection != nil { oldConnState := req.Connection.ConnState req.Connection.ConnState = nil defer func() { req.Connection.ConnState = oldConnState }() } err := b.client.Call("Plugin.HandleExistenceCheck", args, &reply) if err != nil { return false, false, err } if reply.Error != nil { // THINKING: Should be be a switch on all error types? if reply.Error.Error() == logical.ErrUnsupportedPath.Error() { return false, false, logical.ErrUnsupportedPath } return false, false, reply.Error } return reply.CheckFound, reply.Exists, nil } func (b *backendPluginClient) Cleanup(ctx context.Context) { b.client.Call("Plugin.Cleanup", new(interface{}), &struct{}{}) } func (b *backendPluginClient) Initialize(ctx context.Context) error { if b.metadataMode { return ErrClientInMetadataMode } err := b.client.Call("Plugin.Initialize", new(interface{}), &struct{}{}) return err } func (b *backendPluginClient) InvalidateKey(ctx context.Context, key string) { if b.metadataMode { return } b.client.Call("Plugin.InvalidateKey", key, &struct{}{}) } func (b *backendPluginClient) Setup(ctx context.Context, config *logical.BackendConfig) error { // Shim logical.Storage storageImpl := config.StorageView if b.metadataMode { storageImpl = &NOOPStorage{} } storageID := b.broker.NextId() go b.broker.AcceptAndServe(storageID, &StorageServer{ impl: storageImpl, }) // Shim logical.SystemView sysViewImpl := config.System if b.metadataMode { sysViewImpl = &logical.StaticSystemView{} } sysViewID := b.broker.NextId() go b.broker.AcceptAndServe(sysViewID, &SystemViewServer{ impl: sysViewImpl, }) args := &SetupArgs{ StorageID: storageID, SysViewID: sysViewID, Config: config.Config, BackendUUID: config.BackendUUID, } var reply SetupReply err := b.client.Call("Plugin.Setup", args, &reply) if err != nil { return err } if reply.Error != nil { return reply.Error } // Set system and logger for getter methods b.system = config.System b.logger = config.Logger return nil } func (b *backendPluginClient) Type() logical.BackendType { var reply TypeReply err := b.client.Call("Plugin.Type", new(interface{}), &reply) if err != nil { return logical.TypeUnknown } return logical.BackendType(reply.Type) }