]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/go-plugin/server.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / go-plugin / server.go
CommitLineData
bae9f6d2
JC
1package plugin
2
3import (
15c0b25d 4 "crypto/tls"
107c1cdb 5 "crypto/x509"
15c0b25d 6 "encoding/base64"
bae9f6d2
JC
7 "errors"
8 "fmt"
9 "io/ioutil"
10 "log"
11 "net"
12 "os"
13 "os/signal"
14 "runtime"
107c1cdb 15 "sort"
bae9f6d2 16 "strconv"
107c1cdb 17 "strings"
bae9f6d2 18 "sync/atomic"
15c0b25d
AP
19
20 "github.com/hashicorp/go-hclog"
21
22 "google.golang.org/grpc"
bae9f6d2
JC
23)
24
25// CoreProtocolVersion is the ProtocolVersion of the plugin system itself.
26// We will increment this whenever we change any protocol behavior. This
27// will invalidate any prior plugins but will at least allow us to iterate
28// on the core in a safe way. We will do our best to do this very
29// infrequently.
30const CoreProtocolVersion = 1
31
32// HandshakeConfig is the configuration used by client and servers to
33// handshake before starting a plugin connection. This is embedded by
34// both ServeConfig and ClientConfig.
35//
36// In practice, the plugin host creates a HandshakeConfig that is exported
37// and plugins then can easily consume it.
38type HandshakeConfig struct {
39 // ProtocolVersion is the version that clients must match on to
40 // agree they can communicate. This should match the ProtocolVersion
41 // set on ClientConfig when using a plugin.
107c1cdb
ND
42 // This field is not required if VersionedPlugins are being used in the
43 // Client or Server configurations.
bae9f6d2
JC
44 ProtocolVersion uint
45
46 // MagicCookieKey and value are used as a very basic verification
47 // that a plugin is intended to be launched. This is not a security
48 // measure, just a UX feature. If the magic cookie doesn't match,
49 // we show human-friendly output.
50 MagicCookieKey string
51 MagicCookieValue string
52}
53
107c1cdb
ND
54// PluginSet is a set of plugins provided to be registered in the plugin
55// server.
56type PluginSet map[string]Plugin
57
bae9f6d2
JC
58// ServeConfig configures what sorts of plugins are served.
59type ServeConfig struct {
60 // HandshakeConfig is the configuration that must match clients.
61 HandshakeConfig
62
15c0b25d
AP
63 // TLSProvider is a function that returns a configured tls.Config.
64 TLSProvider func() (*tls.Config, error)
65
bae9f6d2 66 // Plugins are the plugins that are served.
107c1cdb
ND
67 // The implied version of this PluginSet is the Handshake.ProtocolVersion.
68 Plugins PluginSet
69
70 // VersionedPlugins is a map of PluginSets for specific protocol versions.
71 // These can be used to negotiate a compatible version between client and
72 // server. If this is set, Handshake.ProtocolVersion is not required.
73 VersionedPlugins map[int]PluginSet
15c0b25d
AP
74
75 // GRPCServer should be non-nil to enable serving the plugins over
76 // gRPC. This is a function to create the server when needed with the
77 // given server options. The server options populated by go-plugin will
78 // be for TLS if set. You may modify the input slice.
79 //
80 // Note that the grpc.Server will automatically be registered with
81 // the gRPC health checking service. This is not optional since go-plugin
82 // relies on this to implement Ping().
83 GRPCServer func([]grpc.ServerOption) *grpc.Server
84
85 // Logger is used to pass a logger into the server. If none is provided the
86 // server will create a default logger.
87 Logger hclog.Logger
88}
89
107c1cdb
ND
90// protocolVersion determines the protocol version and plugin set to be used by
91// the server. In the event that there is no suitable version, the last version
92// in the config is returned leaving the client to report the incompatibility.
93func protocolVersion(opts *ServeConfig) (int, Protocol, PluginSet) {
94 protoVersion := int(opts.ProtocolVersion)
95 pluginSet := opts.Plugins
96 protoType := ProtocolNetRPC
97 // Check if the client sent a list of acceptable versions
98 var clientVersions []int
99 if vs := os.Getenv("PLUGIN_PROTOCOL_VERSIONS"); vs != "" {
100 for _, s := range strings.Split(vs, ",") {
101 v, err := strconv.Atoi(s)
102 if err != nil {
103 fmt.Fprintf(os.Stderr, "server sent invalid plugin version %q", s)
104 continue
105 }
106 clientVersions = append(clientVersions, v)
107 }
108 }
109
110 // We want to iterate in reverse order, to ensure we match the newest
111 // compatible plugin version.
112 sort.Sort(sort.Reverse(sort.IntSlice(clientVersions)))
113
114 // set the old un-versioned fields as if they were versioned plugins
115 if opts.VersionedPlugins == nil {
116 opts.VersionedPlugins = make(map[int]PluginSet)
117 }
118
119 if pluginSet != nil {
120 opts.VersionedPlugins[protoVersion] = pluginSet
121 }
122
123 // Sort the version to make sure we match the latest first
124 var versions []int
125 for v := range opts.VersionedPlugins {
126 versions = append(versions, v)
127 }
128
129 sort.Sort(sort.Reverse(sort.IntSlice(versions)))
130
131 // See if we have multiple versions of Plugins to choose from
132 for _, version := range versions {
133 // Record each version, since we guarantee that this returns valid
134 // values even if they are not a protocol match.
135 protoVersion = version
136 pluginSet = opts.VersionedPlugins[version]
137
138 // If we have a configured gRPC server we should select a protocol
139 if opts.GRPCServer != nil {
140 // All plugins in a set must use the same transport, so check the first
141 // for the protocol type
142 for _, p := range pluginSet {
143 switch p.(type) {
144 case GRPCPlugin:
145 protoType = ProtocolGRPC
146 default:
147 protoType = ProtocolNetRPC
148 }
149 break
150 }
151 }
152
153 for _, clientVersion := range clientVersions {
154 if clientVersion == protoVersion {
155 return protoVersion, protoType, pluginSet
156 }
157 }
15c0b25d
AP
158 }
159
107c1cdb
ND
160 // Return the lowest version as the fallback.
161 // Since we iterated over all the versions in reverse order above, these
162 // values are from the lowest version number plugins (which may be from
163 // a combination of the Handshake.ProtocolVersion and ServeConfig.Plugins
164 // fields). This allows serving the oldest version of our plugins to a
165 // legacy client that did not send a PLUGIN_PROTOCOL_VERSIONS list.
166 return protoVersion, protoType, pluginSet
bae9f6d2
JC
167}
168
169// Serve serves the plugins given by ServeConfig.
170//
171// Serve doesn't return until the plugin is done being executed. Any
15c0b25d 172// errors will be outputted to os.Stderr.
bae9f6d2
JC
173//
174// This is the method that plugins should call in their main() functions.
175func Serve(opts *ServeConfig) {
176 // Validate the handshake config
177 if opts.MagicCookieKey == "" || opts.MagicCookieValue == "" {
178 fmt.Fprintf(os.Stderr,
179 "Misconfigured ServeConfig given to serve this plugin: no magic cookie\n"+
180 "key or value was set. Please notify the plugin author and report\n"+
181 "this as a bug.\n")
182 os.Exit(1)
183 }
184
185 // First check the cookie
186 if os.Getenv(opts.MagicCookieKey) != opts.MagicCookieValue {
187 fmt.Fprintf(os.Stderr,
188 "This binary is a plugin. These are not meant to be executed directly.\n"+
189 "Please execute the program that consumes these plugins, which will\n"+
190 "load any plugins automatically\n")
191 os.Exit(1)
192 }
193
107c1cdb
ND
194 // negotiate the version and plugins
195 // start with default version in the handshake config
196 protoVersion, protoType, pluginSet := protocolVersion(opts)
197
bae9f6d2
JC
198 // Logging goes to the original stderr
199 log.SetOutput(os.Stderr)
200
15c0b25d
AP
201 logger := opts.Logger
202 if logger == nil {
203 // internal logger to os.Stderr
204 logger = hclog.New(&hclog.LoggerOptions{
205 Level: hclog.Trace,
206 Output: os.Stderr,
207 JSONFormat: true,
208 })
209 }
210
bae9f6d2
JC
211 // Create our new stdout, stderr files. These will override our built-in
212 // stdout/stderr so that it works across the stream boundary.
213 stdout_r, stdout_w, err := os.Pipe()
214 if err != nil {
215 fmt.Fprintf(os.Stderr, "Error preparing plugin: %s\n", err)
216 os.Exit(1)
217 }
218 stderr_r, stderr_w, err := os.Pipe()
219 if err != nil {
220 fmt.Fprintf(os.Stderr, "Error preparing plugin: %s\n", err)
221 os.Exit(1)
222 }
223
224 // Register a listener so we can accept a connection
225 listener, err := serverListener()
226 if err != nil {
15c0b25d 227 logger.Error("plugin init error", "error", err)
bae9f6d2
JC
228 return
229 }
15c0b25d
AP
230
231 // Close the listener on return. We wrap this in a func() on purpose
232 // because the "listener" reference may change to TLS.
233 defer func() {
234 listener.Close()
235 }()
236
237 var tlsConfig *tls.Config
238 if opts.TLSProvider != nil {
239 tlsConfig, err = opts.TLSProvider()
240 if err != nil {
241 logger.Error("plugin tls init", "error", err)
242 return
243 }
244 }
bae9f6d2 245
107c1cdb
ND
246 var serverCert string
247 clientCert := os.Getenv("PLUGIN_CLIENT_CERT")
248 // If the client is configured using AutoMTLS, the certificate will be here,
249 // and we need to generate our own in response.
250 if tlsConfig == nil && clientCert != "" {
251 logger.Info("configuring server automatic mTLS")
252 clientCertPool := x509.NewCertPool()
253 if !clientCertPool.AppendCertsFromPEM([]byte(clientCert)) {
254 logger.Error("client cert provided but failed to parse", "cert", clientCert)
255 }
256
257 certPEM, keyPEM, err := generateCert()
258 if err != nil {
259 logger.Error("failed to generate client certificate", "error", err)
260 panic(err)
261 }
262
263 cert, err := tls.X509KeyPair(certPEM, keyPEM)
264 if err != nil {
265 logger.Error("failed to parse client certificate", "error", err)
266 panic(err)
267 }
268
269 tlsConfig = &tls.Config{
270 Certificates: []tls.Certificate{cert},
271 ClientAuth: tls.RequireAndVerifyClientCert,
272 ClientCAs: clientCertPool,
273 MinVersion: tls.VersionTLS12,
274 }
275
276 // We send back the raw leaf cert data for the client rather than the
277 // PEM, since the protocol can't handle newlines.
278 serverCert = base64.RawStdEncoding.EncodeToString(cert.Certificate[0])
279 }
280
bae9f6d2
JC
281 // Create the channel to tell us when we're done
282 doneCh := make(chan struct{})
283
15c0b25d
AP
284 // Build the server type
285 var server ServerProtocol
107c1cdb 286 switch protoType {
15c0b25d
AP
287 case ProtocolNetRPC:
288 // If we have a TLS configuration then we wrap the listener
289 // ourselves and do it at that level.
290 if tlsConfig != nil {
291 listener = tls.NewListener(listener, tlsConfig)
292 }
293
294 // Create the RPC server to dispense
295 server = &RPCServer{
107c1cdb 296 Plugins: pluginSet,
15c0b25d
AP
297 Stdout: stdout_r,
298 Stderr: stderr_r,
299 DoneCh: doneCh,
300 }
301
302 case ProtocolGRPC:
303 // Create the gRPC server
304 server = &GRPCServer{
107c1cdb 305 Plugins: pluginSet,
15c0b25d
AP
306 Server: opts.GRPCServer,
307 TLS: tlsConfig,
308 Stdout: stdout_r,
309 Stderr: stderr_r,
310 DoneCh: doneCh,
107c1cdb 311 logger: logger,
15c0b25d
AP
312 }
313
314 default:
107c1cdb 315 panic("unknown server protocol: " + protoType)
bae9f6d2
JC
316 }
317
15c0b25d
AP
318 // Initialize the servers
319 if err := server.Init(); err != nil {
320 logger.Error("protocol init", "error", err)
321 return
322 }
323
15c0b25d
AP
324 logger.Debug("plugin address", "network", listener.Addr().Network(), "address", listener.Addr().String())
325
107c1cdb
ND
326 // Output the address and service name to stdout so that the client can bring it up.
327 fmt.Printf("%d|%d|%s|%s|%s|%s\n",
bae9f6d2 328 CoreProtocolVersion,
107c1cdb 329 protoVersion,
bae9f6d2 330 listener.Addr().Network(),
15c0b25d 331 listener.Addr().String(),
107c1cdb
ND
332 protoType,
333 serverCert)
bae9f6d2
JC
334 os.Stdout.Sync()
335
336 // Eat the interrupts
337 ch := make(chan os.Signal, 1)
338 signal.Notify(ch, os.Interrupt)
339 go func() {
340 var count int32 = 0
341 for {
342 <-ch
343 newCount := atomic.AddInt32(&count, 1)
15c0b25d 344 logger.Debug("plugin received interrupt signal, ignoring", "count", newCount)
bae9f6d2
JC
345 }
346 }()
347
348 // Set our new out, err
349 os.Stdout = stdout_w
350 os.Stderr = stderr_w
351
15c0b25d
AP
352 // Accept connections and wait for completion
353 go server.Serve(listener)
bae9f6d2
JC
354 <-doneCh
355}
356
357func serverListener() (net.Listener, error) {
358 if runtime.GOOS == "windows" {
359 return serverListener_tcp()
360 }
361
362 return serverListener_unix()
363}
364
365func serverListener_tcp() (net.Listener, error) {
366 minPort, err := strconv.ParseInt(os.Getenv("PLUGIN_MIN_PORT"), 10, 32)
367 if err != nil {
368 return nil, err
369 }
370
371 maxPort, err := strconv.ParseInt(os.Getenv("PLUGIN_MAX_PORT"), 10, 32)
372 if err != nil {
373 return nil, err
374 }
375
376 for port := minPort; port <= maxPort; port++ {
377 address := fmt.Sprintf("127.0.0.1:%d", port)
378 listener, err := net.Listen("tcp", address)
379 if err == nil {
380 return listener, nil
381 }
382 }
383
384 return nil, errors.New("Couldn't bind plugin TCP listener")
385}
386
387func serverListener_unix() (net.Listener, error) {
388 tf, err := ioutil.TempFile("", "plugin")
389 if err != nil {
390 return nil, err
391 }
392 path := tf.Name()
393
394 // Close the file and remove it because it has to not exist for
395 // the domain socket.
396 if err := tf.Close(); err != nil {
397 return nil, err
398 }
399 if err := os.Remove(path); err != nil {
400 return nil, err
401 }
402
403 l, err := net.Listen("unix", path)
404 if err != nil {
405 return nil, err
406 }
407
408 // Wrap the listener in rmListener so that the Unix domain socket file
409 // is removed on close.
410 return &rmListener{
411 Listener: l,
412 Path: path,
413 }, nil
414}
415
416// rmListener is an implementation of net.Listener that forwards most
417// calls to the listener but also removes a file as part of the close. We
418// use this to cleanup the unix domain socket on close.
419type rmListener struct {
420 net.Listener
421 Path string
422}
423
424func (l *rmListener) Close() error {
425 // Close the listener itself
426 if err := l.Listener.Close(); err != nil {
427 return err
428 }
429
430 // Remove the file
431 return os.Remove(l.Path)
432}