github.com/hashicorp/go-plugin@v1.6.0/server.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package plugin 5 6 import ( 7 "context" 8 "crypto/tls" 9 "crypto/x509" 10 "encoding/base64" 11 "errors" 12 "fmt" 13 "io" 14 "net" 15 "os" 16 "os/signal" 17 "os/user" 18 "runtime" 19 "sort" 20 "strconv" 21 "strings" 22 23 hclog "github.com/hashicorp/go-hclog" 24 "github.com/hashicorp/go-plugin/internal/grpcmux" 25 "google.golang.org/grpc" 26 ) 27 28 // CoreProtocolVersion is the ProtocolVersion of the plugin system itself. 29 // We will increment this whenever we change any protocol behavior. This 30 // will invalidate any prior plugins but will at least allow us to iterate 31 // on the core in a safe way. We will do our best to do this very 32 // infrequently. 33 const CoreProtocolVersion = 1 34 35 // HandshakeConfig is the configuration used by client and servers to 36 // handshake before starting a plugin connection. This is embedded by 37 // both ServeConfig and ClientConfig. 38 // 39 // In practice, the plugin host creates a HandshakeConfig that is exported 40 // and plugins then can easily consume it. 41 type HandshakeConfig struct { 42 // ProtocolVersion is the version that clients must match on to 43 // agree they can communicate. This should match the ProtocolVersion 44 // set on ClientConfig when using a plugin. 45 // This field is not required if VersionedPlugins are being used in the 46 // Client or Server configurations. 47 ProtocolVersion uint 48 49 // MagicCookieKey and value are used as a very basic verification 50 // that a plugin is intended to be launched. This is not a security 51 // measure, just a UX feature. If the magic cookie doesn't match, 52 // we show human-friendly output. 53 MagicCookieKey string 54 MagicCookieValue string 55 } 56 57 // PluginSet is a set of plugins provided to be registered in the plugin 58 // server. 59 type PluginSet map[string]Plugin 60 61 // ServeConfig configures what sorts of plugins are served. 62 type ServeConfig struct { 63 // HandshakeConfig is the configuration that must match clients. 64 HandshakeConfig 65 66 // TLSProvider is a function that returns a configured tls.Config. 67 TLSProvider func() (*tls.Config, error) 68 69 // Plugins are the plugins that are served. 70 // The implied version of this PluginSet is the Handshake.ProtocolVersion. 71 Plugins PluginSet 72 73 // VersionedPlugins is a map of PluginSets for specific protocol versions. 74 // These can be used to negotiate a compatible version between client and 75 // server. If this is set, Handshake.ProtocolVersion is not required. 76 VersionedPlugins map[int]PluginSet 77 78 // GRPCServer should be non-nil to enable serving the plugins over 79 // gRPC. This is a function to create the server when needed with the 80 // given server options. The server options populated by go-plugin will 81 // be for TLS if set. You may modify the input slice. 82 // 83 // Note that the grpc.Server will automatically be registered with 84 // the gRPC health checking service. This is not optional since go-plugin 85 // relies on this to implement Ping(). 86 GRPCServer func([]grpc.ServerOption) *grpc.Server 87 88 // Logger is used to pass a logger into the server. If none is provided the 89 // server will create a default logger. 90 Logger hclog.Logger 91 92 // Test, if non-nil, will put plugin serving into "test mode". This is 93 // meant to be used as part of `go test` within a plugin's codebase to 94 // launch the plugin in-process and output a ReattachConfig. 95 // 96 // This changes the behavior of the server in a number of ways to 97 // accomodate the expectation of running in-process: 98 // 99 // * The handshake cookie is not validated. 100 // * Stdout/stderr will receive plugin reads and writes 101 // * Connection information will not be sent to stdout 102 // 103 Test *ServeTestConfig 104 } 105 106 // ServeTestConfig configures plugin serving for test mode. See ServeConfig.Test. 107 type ServeTestConfig struct { 108 // Context, if set, will force the plugin serving to end when cancelled. 109 // This is only a test configuration because the non-test configuration 110 // expects to take over the process and therefore end on an interrupt or 111 // kill signal. For tests, we need to kill the plugin serving routinely 112 // and this provides a way to do so. 113 // 114 // If you want to wait for the plugin process to close before moving on, 115 // you can wait on CloseCh. 116 Context context.Context 117 118 // If this channel is non-nil, we will send the ReattachConfig via 119 // this channel. This can be encoded (via JSON recommended) to the 120 // plugin client to attach to this plugin. 121 ReattachConfigCh chan<- *ReattachConfig 122 123 // CloseCh, if non-nil, will be closed when serving exits. This can be 124 // used along with Context to determine when the server is fully shut down. 125 // If this is not set, you can still use Context on its own, but note there 126 // may be a period of time between canceling the context and the plugin 127 // server being shut down. 128 CloseCh chan<- struct{} 129 130 // SyncStdio, if true, will enable the client side "SyncStdout/Stderr" 131 // functionality to work. This defaults to false because the implementation 132 // of making this work within test environments is particularly messy 133 // and SyncStdio functionality is fairly rare, so we default to the simple 134 // scenario. 135 SyncStdio bool 136 } 137 138 func unixSocketConfigFromEnv() UnixSocketConfig { 139 return UnixSocketConfig{ 140 Group: os.Getenv(EnvUnixSocketGroup), 141 socketDir: os.Getenv(EnvUnixSocketDir), 142 } 143 } 144 145 // protocolVersion determines the protocol version and plugin set to be used by 146 // the server. In the event that there is no suitable version, the last version 147 // in the config is returned leaving the client to report the incompatibility. 148 func protocolVersion(opts *ServeConfig) (int, Protocol, PluginSet) { 149 protoVersion := int(opts.ProtocolVersion) 150 pluginSet := opts.Plugins 151 protoType := ProtocolNetRPC 152 // Check if the client sent a list of acceptable versions 153 var clientVersions []int 154 if vs := os.Getenv("PLUGIN_PROTOCOL_VERSIONS"); vs != "" { 155 for _, s := range strings.Split(vs, ",") { 156 v, err := strconv.Atoi(s) 157 if err != nil { 158 fmt.Fprintf(os.Stderr, "server sent invalid plugin version %q", s) 159 continue 160 } 161 clientVersions = append(clientVersions, v) 162 } 163 } 164 165 // We want to iterate in reverse order, to ensure we match the newest 166 // compatible plugin version. 167 sort.Sort(sort.Reverse(sort.IntSlice(clientVersions))) 168 169 // set the old un-versioned fields as if they were versioned plugins 170 if opts.VersionedPlugins == nil { 171 opts.VersionedPlugins = make(map[int]PluginSet) 172 } 173 174 if pluginSet != nil { 175 opts.VersionedPlugins[protoVersion] = pluginSet 176 } 177 178 // Sort the version to make sure we match the latest first 179 var versions []int 180 for v := range opts.VersionedPlugins { 181 versions = append(versions, v) 182 } 183 184 sort.Sort(sort.Reverse(sort.IntSlice(versions))) 185 186 // See if we have multiple versions of Plugins to choose from 187 for _, version := range versions { 188 // Record each version, since we guarantee that this returns valid 189 // values even if they are not a protocol match. 190 protoVersion = version 191 pluginSet = opts.VersionedPlugins[version] 192 193 // If we have a configured gRPC server we should select a protocol 194 if opts.GRPCServer != nil { 195 // All plugins in a set must use the same transport, so check the first 196 // for the protocol type 197 for _, p := range pluginSet { 198 switch p.(type) { 199 case GRPCPlugin: 200 protoType = ProtocolGRPC 201 default: 202 protoType = ProtocolNetRPC 203 } 204 break 205 } 206 } 207 208 for _, clientVersion := range clientVersions { 209 if clientVersion == protoVersion { 210 return protoVersion, protoType, pluginSet 211 } 212 } 213 } 214 215 // Return the lowest version as the fallback. 216 // Since we iterated over all the versions in reverse order above, these 217 // values are from the lowest version number plugins (which may be from 218 // a combination of the Handshake.ProtocolVersion and ServeConfig.Plugins 219 // fields). This allows serving the oldest version of our plugins to a 220 // legacy client that did not send a PLUGIN_PROTOCOL_VERSIONS list. 221 return protoVersion, protoType, pluginSet 222 } 223 224 // Serve serves the plugins given by ServeConfig. 225 // 226 // Serve doesn't return until the plugin is done being executed. Any 227 // fixable errors will be output to os.Stderr and the process will 228 // exit with a status code of 1. Serve will panic for unexpected 229 // conditions where a user's fix is unknown. 230 // 231 // This is the method that plugins should call in their main() functions. 232 func Serve(opts *ServeConfig) { 233 exitCode := -1 234 // We use this to trigger an `os.Exit` so that we can execute our other 235 // deferred functions. In test mode, we just output the err to stderr 236 // and return. 237 defer func() { 238 if opts.Test == nil && exitCode >= 0 { 239 os.Exit(exitCode) 240 } 241 242 if opts.Test != nil && opts.Test.CloseCh != nil { 243 close(opts.Test.CloseCh) 244 } 245 }() 246 247 if opts.Test == nil { 248 // Validate the handshake config 249 if opts.MagicCookieKey == "" || opts.MagicCookieValue == "" { 250 fmt.Fprintf(os.Stderr, 251 "Misconfigured ServeConfig given to serve this plugin: no magic cookie\n"+ 252 "key or value was set. Please notify the plugin author and report\n"+ 253 "this as a bug.\n") 254 exitCode = 1 255 return 256 } 257 258 // First check the cookie 259 if os.Getenv(opts.MagicCookieKey) != opts.MagicCookieValue { 260 fmt.Fprintf(os.Stderr, 261 "This binary is a plugin. These are not meant to be executed directly.\n"+ 262 "Please execute the program that consumes these plugins, which will\n"+ 263 "load any plugins automatically\n") 264 exitCode = 1 265 return 266 } 267 } 268 269 // negotiate the version and plugins 270 // start with default version in the handshake config 271 protoVersion, protoType, pluginSet := protocolVersion(opts) 272 273 logger := opts.Logger 274 if logger == nil { 275 // internal logger to os.Stderr 276 logger = hclog.New(&hclog.LoggerOptions{ 277 Level: hclog.Trace, 278 Output: os.Stderr, 279 JSONFormat: true, 280 }) 281 } 282 283 // Register a listener so we can accept a connection 284 listener, err := serverListener(unixSocketConfigFromEnv()) 285 if err != nil { 286 logger.Error("plugin init error", "error", err) 287 return 288 } 289 290 // Close the listener on return. We wrap this in a func() on purpose 291 // because the "listener" reference may change to TLS. 292 defer func() { 293 listener.Close() 294 }() 295 296 var tlsConfig *tls.Config 297 if opts.TLSProvider != nil { 298 tlsConfig, err = opts.TLSProvider() 299 if err != nil { 300 logger.Error("plugin tls init", "error", err) 301 return 302 } 303 } 304 305 var serverCert string 306 clientCert := os.Getenv("PLUGIN_CLIENT_CERT") 307 // If the client is configured using AutoMTLS, the certificate will be here, 308 // and we need to generate our own in response. 309 if tlsConfig == nil && clientCert != "" { 310 logger.Info("configuring server automatic mTLS") 311 clientCertPool := x509.NewCertPool() 312 if !clientCertPool.AppendCertsFromPEM([]byte(clientCert)) { 313 logger.Error("client cert provided but failed to parse", "cert", clientCert) 314 } 315 316 certPEM, keyPEM, err := generateCert() 317 if err != nil { 318 logger.Error("failed to generate server certificate", "error", err) 319 panic(err) 320 } 321 322 cert, err := tls.X509KeyPair(certPEM, keyPEM) 323 if err != nil { 324 logger.Error("failed to parse server certificate", "error", err) 325 panic(err) 326 } 327 328 tlsConfig = &tls.Config{ 329 Certificates: []tls.Certificate{cert}, 330 ClientAuth: tls.RequireAndVerifyClientCert, 331 ClientCAs: clientCertPool, 332 MinVersion: tls.VersionTLS12, 333 RootCAs: clientCertPool, 334 ServerName: "localhost", 335 } 336 337 // We send back the raw leaf cert data for the client rather than the 338 // PEM, since the protocol can't handle newlines. 339 serverCert = base64.RawStdEncoding.EncodeToString(cert.Certificate[0]) 340 } 341 342 // Create the channel to tell us when we're done 343 doneCh := make(chan struct{}) 344 345 // Create our new stdout, stderr files. These will override our built-in 346 // stdout/stderr so that it works across the stream boundary. 347 var stdout_r, stderr_r io.Reader 348 stdout_r, stdout_w, err := os.Pipe() 349 if err != nil { 350 fmt.Fprintf(os.Stderr, "Error preparing plugin: %s\n", err) 351 os.Exit(1) 352 } 353 stderr_r, stderr_w, err := os.Pipe() 354 if err != nil { 355 fmt.Fprintf(os.Stderr, "Error preparing plugin: %s\n", err) 356 os.Exit(1) 357 } 358 359 // If we're in test mode, we tee off the reader and write the data 360 // as-is to our normal Stdout and Stderr so that they continue working 361 // while stdio works. This is because in test mode, we assume we're running 362 // in `go test` or some equivalent and we want output to go to standard 363 // locations. 364 if opts.Test != nil { 365 // TODO(mitchellh): This isn't super ideal because a TeeReader 366 // only works if the reader side is actively read. If we never 367 // connect via a plugin client, the output still gets swallowed. 368 stdout_r = io.TeeReader(stdout_r, os.Stdout) 369 stderr_r = io.TeeReader(stderr_r, os.Stderr) 370 } 371 372 // Build the server type 373 var server ServerProtocol 374 switch protoType { 375 case ProtocolNetRPC: 376 // If we have a TLS configuration then we wrap the listener 377 // ourselves and do it at that level. 378 if tlsConfig != nil { 379 listener = tls.NewListener(listener, tlsConfig) 380 } 381 382 // Create the RPC server to dispense 383 server = &RPCServer{ 384 Plugins: pluginSet, 385 Stdout: stdout_r, 386 Stderr: stderr_r, 387 DoneCh: doneCh, 388 } 389 390 case ProtocolGRPC: 391 var muxer *grpcmux.GRPCServerMuxer 392 if multiplex, _ := strconv.ParseBool(os.Getenv(envMultiplexGRPC)); multiplex { 393 muxer = grpcmux.NewGRPCServerMuxer(logger, listener) 394 listener = muxer 395 } 396 397 // Create the gRPC server 398 server = &GRPCServer{ 399 Plugins: pluginSet, 400 Server: opts.GRPCServer, 401 TLS: tlsConfig, 402 Stdout: stdout_r, 403 Stderr: stderr_r, 404 DoneCh: doneCh, 405 logger: logger, 406 muxer: muxer, 407 } 408 409 default: 410 panic("unknown server protocol: " + protoType) 411 } 412 413 // Initialize the servers 414 if err := server.Init(); err != nil { 415 logger.Error("protocol init", "error", err) 416 return 417 } 418 419 logger.Debug("plugin address", "network", listener.Addr().Network(), "address", listener.Addr().String()) 420 421 // Output the address and service name to stdout so that the client can 422 // bring it up. In test mode, we don't do this because clients will 423 // attach via a reattach config. 424 if opts.Test == nil { 425 const grpcBrokerMultiplexingSupported = true 426 protocolLine := fmt.Sprintf("%d|%d|%s|%s|%s|%s", 427 CoreProtocolVersion, 428 protoVersion, 429 listener.Addr().Network(), 430 listener.Addr().String(), 431 protoType, 432 serverCert) 433 434 // Old clients will error with new plugins if we blindly append the 435 // seventh segment for gRPC broker multiplexing support, because old 436 // client code uses strings.SplitN(line, "|", 6), which means a seventh 437 // segment will get appended to the sixth segment as "sixthpart|true". 438 // 439 // If the environment variable is set, we assume the client is new enough 440 // to handle a seventh segment, as it should now use 441 // strings.Split(line, "|") and always handle each segment individually. 442 if os.Getenv(envMultiplexGRPC) != "" { 443 protocolLine += fmt.Sprintf("|%v", grpcBrokerMultiplexingSupported) 444 } 445 fmt.Printf("%s\n", protocolLine) 446 os.Stdout.Sync() 447 } else if ch := opts.Test.ReattachConfigCh; ch != nil { 448 // Send back the reattach config that can be used. This isn't 449 // quite ready if they connect immediately but the client should 450 // retry a few times. 451 ch <- &ReattachConfig{ 452 Protocol: protoType, 453 ProtocolVersion: protoVersion, 454 Addr: listener.Addr(), 455 Pid: os.Getpid(), 456 Test: true, 457 } 458 } 459 460 // Eat the interrupts. In test mode we disable this so that go test 461 // can be cancelled properly. 462 if opts.Test == nil { 463 ch := make(chan os.Signal, 1) 464 signal.Notify(ch, os.Interrupt) 465 go func() { 466 count := 0 467 for { 468 <-ch 469 count++ 470 logger.Trace("plugin received interrupt signal, ignoring", "count", count) 471 } 472 }() 473 } 474 475 // Set our stdout, stderr to the stdio stream that clients can retrieve 476 // using ClientConfig.SyncStdout/err. We only do this for non-test mode 477 // or if the test mode explicitly requests it. 478 // 479 // In test mode, we use a multiwriter so that the data continues going 480 // to the normal stdout/stderr so output can show up in test logs. We 481 // also send to the stdio stream so that clients can continue working 482 // if they depend on that. 483 if opts.Test == nil || opts.Test.SyncStdio { 484 if opts.Test != nil { 485 // In test mode we need to maintain the original values so we can 486 // reset it. 487 defer func(out, err *os.File) { 488 os.Stdout = out 489 os.Stderr = err 490 }(os.Stdout, os.Stderr) 491 } 492 os.Stdout = stdout_w 493 os.Stderr = stderr_w 494 } 495 496 // Accept connections and wait for completion 497 go server.Serve(listener) 498 499 ctx := context.Background() 500 if opts.Test != nil && opts.Test.Context != nil { 501 ctx = opts.Test.Context 502 } 503 select { 504 case <-ctx.Done(): 505 // Cancellation. We can stop the server by closing the listener. 506 // This isn't graceful at all but this is currently only used by 507 // tests and its our only way to stop. 508 listener.Close() 509 510 // If this is a grpc server, then we also ask the server itself to 511 // end which will kill all connections. There isn't an easy way to do 512 // this for net/rpc currently but net/rpc is more and more unused. 513 if s, ok := server.(*GRPCServer); ok { 514 s.Stop() 515 } 516 517 // Wait for the server itself to shut down 518 <-doneCh 519 520 case <-doneCh: 521 // Note that given the documentation of Serve we should probably be 522 // setting exitCode = 0 and using os.Exit here. That's how it used to 523 // work before extracting this library. However, for years we've done 524 // this so we'll keep this functionality. 525 } 526 } 527 528 func serverListener(unixSocketCfg UnixSocketConfig) (net.Listener, error) { 529 if runtime.GOOS == "windows" { 530 return serverListener_tcp() 531 } 532 533 return serverListener_unix(unixSocketCfg) 534 } 535 536 func serverListener_tcp() (net.Listener, error) { 537 envMinPort := os.Getenv("PLUGIN_MIN_PORT") 538 envMaxPort := os.Getenv("PLUGIN_MAX_PORT") 539 540 var minPort, maxPort int64 541 var err error 542 543 switch { 544 case len(envMinPort) == 0: 545 minPort = 0 546 default: 547 minPort, err = strconv.ParseInt(envMinPort, 10, 32) 548 if err != nil { 549 return nil, fmt.Errorf("Couldn't get value from PLUGIN_MIN_PORT: %v", err) 550 } 551 } 552 553 switch { 554 case len(envMaxPort) == 0: 555 maxPort = 0 556 default: 557 maxPort, err = strconv.ParseInt(envMaxPort, 10, 32) 558 if err != nil { 559 return nil, fmt.Errorf("Couldn't get value from PLUGIN_MAX_PORT: %v", err) 560 } 561 } 562 563 if minPort > maxPort { 564 return nil, fmt.Errorf("PLUGIN_MIN_PORT value of %d is greater than PLUGIN_MAX_PORT value of %d", minPort, maxPort) 565 } 566 567 for port := minPort; port <= maxPort; port++ { 568 address := fmt.Sprintf("127.0.0.1:%d", port) 569 listener, err := net.Listen("tcp", address) 570 if err == nil { 571 return listener, nil 572 } 573 } 574 575 return nil, errors.New("Couldn't bind plugin TCP listener") 576 } 577 578 func serverListener_unix(unixSocketCfg UnixSocketConfig) (net.Listener, error) { 579 tf, err := os.CreateTemp(unixSocketCfg.socketDir, "plugin") 580 if err != nil { 581 return nil, err 582 } 583 path := tf.Name() 584 585 // Close the file and remove it because it has to not exist for 586 // the domain socket. 587 if err := tf.Close(); err != nil { 588 return nil, err 589 } 590 if err := os.Remove(path); err != nil { 591 return nil, err 592 } 593 594 l, err := net.Listen("unix", path) 595 if err != nil { 596 return nil, err 597 } 598 599 // By default, unix sockets are only writable by the owner. Set up a custom 600 // group owner and group write permissions if configured. 601 if unixSocketCfg.Group != "" { 602 err = setGroupWritable(path, unixSocketCfg.Group, 0o660) 603 if err != nil { 604 return nil, err 605 } 606 } 607 608 // Wrap the listener in rmListener so that the Unix domain socket file 609 // is removed on close. 610 return newDeleteFileListener(l, path), nil 611 } 612 613 func setGroupWritable(path, groupString string, mode os.FileMode) error { 614 groupID, err := strconv.Atoi(groupString) 615 if err != nil { 616 group, err := user.LookupGroup(groupString) 617 if err != nil { 618 return fmt.Errorf("failed to find gid from %q: %w", groupString, err) 619 } 620 groupID, err = strconv.Atoi(group.Gid) 621 if err != nil { 622 return fmt.Errorf("failed to parse %q group's gid as an integer: %w", groupString, err) 623 } 624 } 625 626 err = os.Chown(path, -1, groupID) 627 if err != nil { 628 return err 629 } 630 631 err = os.Chmod(path, mode) 632 if err != nil { 633 return err 634 } 635 636 return nil 637 } 638 639 // rmListener is an implementation of net.Listener that forwards most 640 // calls to the listener but also calls an additional close function. We 641 // use this to cleanup the unix domain socket on close, as well as clean 642 // up multiplexed listeners. 643 type rmListener struct { 644 net.Listener 645 close func() error 646 } 647 648 func newDeleteFileListener(ln net.Listener, path string) *rmListener { 649 return &rmListener{ 650 Listener: ln, 651 close: func() error { 652 return os.Remove(path) 653 }, 654 } 655 } 656 657 func (l *rmListener) Close() error { 658 // Close the listener itself 659 if err := l.Listener.Close(); err != nil { 660 return err 661 } 662 663 // Remove the file 664 return l.close() 665 }