forked from hashicorp/vault
/
plugin.go
176 lines (151 loc) · 4.94 KB
/
plugin.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package plugin
import (
"context"
"crypto/ecdsa"
"crypto/rsa"
"encoding/gob"
"errors"
"fmt"
"time"
"sync"
"github.com/hashicorp/errwrap"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
"github.com/hashicorp/vault/helper/pluginutil"
"github.com/hashicorp/vault/logical"
)
// init registers basic structs with gob which will be used to transport complex
// types through the plugin server and client.
func init() {
// Common basic structs
gob.Register([]interface{}{})
gob.Register(map[string]interface{}{})
gob.Register(map[string]string{})
gob.Register(map[string]int{})
// Register these types since we have to serialize and de-serialize
// tls.ConnectionState over the wire as part of logical.Request.Connection.
gob.Register(rsa.PublicKey{})
gob.Register(ecdsa.PublicKey{})
gob.Register(time.Duration(0))
// Custom common error types for requests. If you add something here, you must
// also add it to the switch statement in `wrapError`!
gob.Register(&plugin.BasicError{})
gob.Register(logical.CodedError(0, ""))
gob.Register(&logical.StatusBadRequest{})
}
// BackendPluginClient is a wrapper around backendPluginClient
// that also contains its plugin.Client instance. It's primarily
// used to cleanly kill the client on Cleanup()
type BackendPluginClient struct {
client *plugin.Client
sync.Mutex
logical.Backend
}
// Cleanup calls the RPC client's Cleanup() func and also calls
// the go-plugin's client Kill() func
func (b *BackendPluginClient) Cleanup(ctx context.Context) {
b.Backend.Cleanup(ctx)
b.client.Kill()
}
// NewBackend will return an instance of an RPC-based client implementation of the backend for
// external plugins, or a concrete implementation of the backend if it is a builtin backend.
// The backend is returned as a logical.Backend interface. The isMetadataMode param determines whether
// the plugin should run in metadata mode.
func NewBackend(ctx context.Context, pluginName string, sys pluginutil.LookRunnerUtil, logger log.Logger, isMetadataMode bool) (logical.Backend, error) {
// Look for plugin in the plugin catalog
pluginRunner, err := sys.LookupPlugin(ctx, pluginName)
if err != nil {
return nil, err
}
var backend logical.Backend
if pluginRunner.Builtin {
// Plugin is builtin so we can retrieve an instance of the interface
// from the pluginRunner. Then cast it to logical.Backend.
backendRaw, err := pluginRunner.BuiltinFactory()
if err != nil {
return nil, errwrap.Wrapf("error getting plugin type: {{err}}", err)
}
var ok bool
backend, ok = backendRaw.(logical.Backend)
if !ok {
return nil, fmt.Errorf("unsupported backend type: %q", pluginName)
}
} else {
// create a backendPluginClient instance
backend, err = newPluginClient(ctx, sys, pluginRunner, logger, isMetadataMode)
if err != nil {
return nil, err
}
}
return backend, nil
}
func newPluginClient(ctx context.Context, sys pluginutil.RunnerUtil, pluginRunner *pluginutil.PluginRunner, logger log.Logger, isMetadataMode bool) (logical.Backend, error) {
// pluginMap is the map of plugins we can dispense.
pluginMap := map[string]plugin.Plugin{
"backend": &BackendPlugin{
metadataMode: isMetadataMode,
},
}
namedLogger := logger.Named(pluginRunner.Name)
var client *plugin.Client
var err error
if isMetadataMode {
client, err = pluginRunner.RunMetadataMode(ctx, sys, pluginMap, handshakeConfig, []string{}, namedLogger)
} else {
client, err = pluginRunner.Run(ctx, sys, pluginMap, handshakeConfig, []string{}, namedLogger)
}
if err != nil {
return nil, err
}
// Connect via RPC
rpcClient, err := client.Client()
if err != nil {
return nil, err
}
// Request the plugin
raw, err := rpcClient.Dispense("backend")
if err != nil {
return nil, err
}
var backend logical.Backend
var transport string
// We should have a logical backend type now. This feels like a normal interface
// implementation but is in fact over an RPC connection.
switch raw.(type) {
case *backendPluginClient:
backend = raw.(*backendPluginClient)
transport = "netRPC"
case *backendGRPCPluginClient:
backend = raw.(*backendGRPCPluginClient)
transport = "gRPC"
default:
return nil, errors.New("unsupported plugin client type")
}
// Wrap the backend in a tracing middleware
if namedLogger.IsTrace() {
backend = &backendTracingMiddleware{
logger: namedLogger.With("transport", transport),
next: backend,
}
}
return &BackendPluginClient{
client: client,
Backend: backend,
}, nil
}
// wrapError takes a generic error type and makes it usable with the plugin
// interface. Only errors which have exported fields and have been registered
// with gob can be unwrapped and transported. This checks error types and, if
// none match, wrap the error in a plugin.BasicError.
func wrapError(err error) error {
if err == nil {
return nil
}
switch err.(type) {
case *plugin.BasicError,
logical.HTTPCodedError,
*logical.StatusBadRequest:
return err
}
return plugin.NewBasicError(err)
}