github.com/hashicorp/go-plugin@v1.6.0/client.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package plugin 5 6 import ( 7 "bufio" 8 "context" 9 "crypto/subtle" 10 "crypto/tls" 11 "crypto/x509" 12 "encoding/base64" 13 "errors" 14 "fmt" 15 "hash" 16 "io" 17 "io/ioutil" 18 "net" 19 "os" 20 "os/exec" 21 "path/filepath" 22 "strconv" 23 "strings" 24 "sync" 25 "sync/atomic" 26 "time" 27 28 "github.com/hashicorp/go-hclog" 29 "github.com/hashicorp/go-plugin/internal/cmdrunner" 30 "github.com/hashicorp/go-plugin/internal/grpcmux" 31 "github.com/hashicorp/go-plugin/runner" 32 "google.golang.org/grpc" 33 ) 34 35 // If this is 1, then we've called CleanupClients. This can be used 36 // by plugin RPC implementations to change error behavior since you 37 // can expected network connection errors at this point. This should be 38 // read by using sync/atomic. 39 var Killed uint32 = 0 40 41 // This is a slice of the "managed" clients which are cleaned up when 42 // calling Cleanup 43 var managedClients = make([]*Client, 0, 5) 44 var managedClientsLock sync.Mutex 45 46 // Error types 47 var ( 48 // ErrProcessNotFound is returned when a client is instantiated to 49 // reattach to an existing process and it isn't found. 50 ErrProcessNotFound = cmdrunner.ErrProcessNotFound 51 52 // ErrChecksumsDoNotMatch is returned when binary's checksum doesn't match 53 // the one provided in the SecureConfig. 54 ErrChecksumsDoNotMatch = errors.New("checksums did not match") 55 56 // ErrSecureNoChecksum is returned when an empty checksum is provided to the 57 // SecureConfig. 58 ErrSecureConfigNoChecksum = errors.New("no checksum provided") 59 60 // ErrSecureNoHash is returned when a nil Hash object is provided to the 61 // SecureConfig. 62 ErrSecureConfigNoHash = errors.New("no hash implementation provided") 63 64 // ErrSecureConfigAndReattach is returned when both Reattach and 65 // SecureConfig are set. 66 ErrSecureConfigAndReattach = errors.New("only one of Reattach or SecureConfig can be set") 67 68 // ErrGRPCBrokerMuxNotSupported is returned when the client requests 69 // multiplexing over the gRPC broker, but the plugin does not support the 70 // feature. In most cases, this should be resolvable by updating and 71 // rebuilding the plugin, or restarting the plugin with 72 // ClientConfig.GRPCBrokerMultiplex set to false. 73 ErrGRPCBrokerMuxNotSupported = errors.New("client requested gRPC broker multiplexing but plugin does not support the feature") 74 ) 75 76 // defaultPluginLogBufferSize is the default size of the buffer used to read from stderr for plugin log lines. 77 const defaultPluginLogBufferSize = 64 * 1024 78 79 // Client handles the lifecycle of a plugin application. It launches 80 // plugins, connects to them, dispenses interface implementations, and handles 81 // killing the process. 82 // 83 // Plugin hosts should use one Client for each plugin executable. To 84 // dispense a plugin type, use the `Client.Client` function, and then 85 // cal `Dispense`. This awkward API is mostly historical but is used to split 86 // the client that deals with subprocess management and the client that 87 // does RPC management. 88 // 89 // See NewClient and ClientConfig for using a Client. 90 type Client struct { 91 config *ClientConfig 92 exited bool 93 l sync.Mutex 94 address net.Addr 95 runner runner.AttachedRunner 96 client ClientProtocol 97 protocol Protocol 98 logger hclog.Logger 99 doneCtx context.Context 100 ctxCancel context.CancelFunc 101 negotiatedVersion int 102 103 // clientWaitGroup is used to manage the lifecycle of the plugin management 104 // goroutines. 105 clientWaitGroup sync.WaitGroup 106 107 // stderrWaitGroup is used to prevent the command's Wait() function from 108 // being called before we've finished reading from the stderr pipe. 109 stderrWaitGroup sync.WaitGroup 110 111 // processKilled is used for testing only, to flag when the process was 112 // forcefully killed. 113 processKilled bool 114 115 unixSocketCfg UnixSocketConfig 116 117 grpcMuxerOnce sync.Once 118 grpcMuxer *grpcmux.GRPCClientMuxer 119 } 120 121 // NegotiatedVersion returns the protocol version negotiated with the server. 122 // This is only valid after Start() is called. 123 func (c *Client) NegotiatedVersion() int { 124 return c.negotiatedVersion 125 } 126 127 // ID returns a unique ID for the running plugin. By default this is the process 128 // ID (pid), but it could take other forms if RunnerFunc was provided. 129 func (c *Client) ID() string { 130 c.l.Lock() 131 defer c.l.Unlock() 132 133 if c.runner != nil { 134 return c.runner.ID() 135 } 136 137 return "" 138 } 139 140 // ClientConfig is the configuration used to initialize a new 141 // plugin client. After being used to initialize a plugin client, 142 // that configuration must not be modified again. 143 type ClientConfig struct { 144 // HandshakeConfig is the configuration that must match servers. 145 HandshakeConfig 146 147 // Plugins are the plugins that can be consumed. 148 // The implied version of this PluginSet is the Handshake.ProtocolVersion. 149 Plugins PluginSet 150 151 // VersionedPlugins is a map of PluginSets for specific protocol versions. 152 // These can be used to negotiate a compatible version between client and 153 // server. If this is set, Handshake.ProtocolVersion is not required. 154 VersionedPlugins map[int]PluginSet 155 156 // One of the following must be set, but not both. 157 // 158 // Cmd is the unstarted subprocess for starting the plugin. If this is 159 // set, then the Client starts the plugin process on its own and connects 160 // to it. 161 // 162 // Reattach is configuration for reattaching to an existing plugin process 163 // that is already running. This isn't common. 164 Cmd *exec.Cmd 165 Reattach *ReattachConfig 166 167 // RunnerFunc allows consumers to provide their own implementation of 168 // runner.Runner and control the context within which a plugin is executed. 169 // The cmd argument will have been copied from the config and populated with 170 // environment variables that a go-plugin server expects to read such as 171 // AutoMTLS certs and the magic cookie key. 172 RunnerFunc func(l hclog.Logger, cmd *exec.Cmd, tmpDir string) (runner.Runner, error) 173 174 // SecureConfig is configuration for verifying the integrity of the 175 // executable. It can not be used with Reattach. 176 SecureConfig *SecureConfig 177 178 // TLSConfig is used to enable TLS on the RPC client. 179 TLSConfig *tls.Config 180 181 // Managed represents if the client should be managed by the 182 // plugin package or not. If true, then by calling CleanupClients, 183 // it will automatically be cleaned up. Otherwise, the client 184 // user is fully responsible for making sure to Kill all plugin 185 // clients. By default the client is _not_ managed. 186 Managed bool 187 188 // The minimum and maximum port to use for communicating with 189 // the subprocess. If not set, this defaults to 10,000 and 25,000 190 // respectively. 191 MinPort, MaxPort uint 192 193 // StartTimeout is the timeout to wait for the plugin to say it 194 // has started successfully. 195 StartTimeout time.Duration 196 197 // If non-nil, then the stderr of the client will be written to here 198 // (as well as the log). This is the original os.Stderr of the subprocess. 199 // This isn't the output of synced stderr. 200 Stderr io.Writer 201 202 // SyncStdout, SyncStderr can be set to override the 203 // respective os.Std* values in the plugin. Care should be taken to 204 // avoid races here. If these are nil, then this will be set to 205 // ioutil.Discard. 206 SyncStdout io.Writer 207 SyncStderr io.Writer 208 209 // AllowedProtocols is a list of allowed protocols. If this isn't set, 210 // then only netrpc is allowed. This is so that older go-plugin systems 211 // can show friendly errors if they see a plugin with an unknown 212 // protocol. 213 // 214 // By setting this, you can cause an error immediately on plugin start 215 // if an unsupported protocol is used with a good error message. 216 // 217 // If this isn't set at all (nil value), then only net/rpc is accepted. 218 // This is done for legacy reasons. You must explicitly opt-in to 219 // new protocols. 220 AllowedProtocols []Protocol 221 222 // Logger is the logger that the client will used. If none is provided, 223 // it will default to hclog's default logger. 224 Logger hclog.Logger 225 226 // PluginLogBufferSize is the buffer size(bytes) to read from stderr for plugin log lines. 227 // If this is 0, then the default of 64KB is used. 228 PluginLogBufferSize int 229 230 // AutoMTLS has the client and server automatically negotiate mTLS for 231 // transport authentication. This ensures that only the original client will 232 // be allowed to connect to the server, and all other connections will be 233 // rejected. The client will also refuse to connect to any server that isn't 234 // the original instance started by the client. 235 // 236 // In this mode of operation, the client generates a one-time use tls 237 // certificate, sends the public x.509 certificate to the new server, and 238 // the server generates a one-time use tls certificate, and sends the public 239 // x.509 certificate back to the client. These are used to authenticate all 240 // rpc connections between the client and server. 241 // 242 // Setting AutoMTLS to true implies that the server must support the 243 // protocol, and correctly negotiate the tls certificates, or a connection 244 // failure will result. 245 // 246 // The client should not set TLSConfig, nor should the server set a 247 // TLSProvider, because AutoMTLS implies that a new certificate and tls 248 // configuration will be generated at startup. 249 // 250 // You cannot Reattach to a server with this option enabled. 251 AutoMTLS bool 252 253 // GRPCDialOptions allows plugin users to pass custom grpc.DialOption 254 // to create gRPC connections. This only affects plugins using the gRPC 255 // protocol. 256 GRPCDialOptions []grpc.DialOption 257 258 // GRPCBrokerMultiplex turns on multiplexing for the gRPC broker. The gRPC 259 // broker will multiplex all brokered gRPC servers over the plugin's original 260 // listener socket instead of making a new listener for each server. The 261 // go-plugin library currently only includes a Go implementation for the 262 // server (i.e. plugin) side of gRPC broker multiplexing. 263 // 264 // Does not support reattaching. 265 // 266 // Multiplexed gRPC streams MUST be established sequentially, i.e. after 267 // calling AcceptAndServe from one side, wait for the other side to Dial 268 // before calling AcceptAndServe again. 269 GRPCBrokerMultiplex bool 270 271 // SkipHostEnv allows plugins to run without inheriting the parent process' 272 // environment variables. 273 SkipHostEnv bool 274 275 // UnixSocketConfig configures additional options for any Unix sockets 276 // that are created. Not normally required. Not supported on Windows. 277 UnixSocketConfig *UnixSocketConfig 278 } 279 280 type UnixSocketConfig struct { 281 // If set, go-plugin will change the owner of any Unix sockets created to 282 // this group, and set them as group-writable. Can be a name or gid. The 283 // client process must be a member of this group or chown will fail. 284 Group string 285 286 // TempDir specifies the base directory to use when creating a plugin-specific 287 // temporary directory. It is expected to already exist and be writable. If 288 // not set, defaults to the directory chosen by os.MkdirTemp. 289 TempDir string 290 291 // The directory to create Unix sockets in. Internally created and managed 292 // by go-plugin and deleted when the plugin is killed. Will be created 293 // inside TempDir if specified. 294 socketDir string 295 } 296 297 // ReattachConfig is used to configure a client to reattach to an 298 // already-running plugin process. You can retrieve this information by 299 // calling ReattachConfig on Client. 300 type ReattachConfig struct { 301 Protocol Protocol 302 ProtocolVersion int 303 Addr net.Addr 304 Pid int 305 306 // ReattachFunc allows consumers to provide their own implementation of 307 // runner.AttachedRunner and attach to something other than a plain process. 308 // At least one of Pid or ReattachFunc must be set. 309 ReattachFunc runner.ReattachFunc 310 311 // Test is set to true if this is reattaching to to a plugin in "test mode" 312 // (see ServeConfig.Test). In this mode, client.Kill will NOT kill the 313 // process and instead will rely on the plugin to terminate itself. This 314 // should not be used in non-test environments. 315 Test bool 316 } 317 318 // SecureConfig is used to configure a client to verify the integrity of an 319 // executable before running. It does this by verifying the checksum is 320 // expected. Hash is used to specify the hashing method to use when checksumming 321 // the file. The configuration is verified by the client by calling the 322 // SecureConfig.Check() function. 323 // 324 // The host process should ensure the checksum was provided by a trusted and 325 // authoritative source. The binary should be installed in such a way that it 326 // can not be modified by an unauthorized user between the time of this check 327 // and the time of execution. 328 type SecureConfig struct { 329 Checksum []byte 330 Hash hash.Hash 331 } 332 333 // Check takes the filepath to an executable and returns true if the checksum of 334 // the file matches the checksum provided in the SecureConfig. 335 func (s *SecureConfig) Check(filePath string) (bool, error) { 336 if len(s.Checksum) == 0 { 337 return false, ErrSecureConfigNoChecksum 338 } 339 340 if s.Hash == nil { 341 return false, ErrSecureConfigNoHash 342 } 343 344 file, err := os.Open(filePath) 345 if err != nil { 346 return false, err 347 } 348 defer file.Close() 349 350 _, err = io.Copy(s.Hash, file) 351 if err != nil { 352 return false, err 353 } 354 355 sum := s.Hash.Sum(nil) 356 357 return subtle.ConstantTimeCompare(sum, s.Checksum) == 1, nil 358 } 359 360 // This makes sure all the managed subprocesses are killed and properly 361 // logged. This should be called before the parent process running the 362 // plugins exits. 363 // 364 // This must only be called _once_. 365 func CleanupClients() { 366 // Set the killed to true so that we don't get unexpected panics 367 atomic.StoreUint32(&Killed, 1) 368 369 // Kill all the managed clients in parallel and use a WaitGroup 370 // to wait for them all to finish up. 371 var wg sync.WaitGroup 372 managedClientsLock.Lock() 373 for _, client := range managedClients { 374 wg.Add(1) 375 376 go func(client *Client) { 377 client.Kill() 378 wg.Done() 379 }(client) 380 } 381 managedClientsLock.Unlock() 382 383 wg.Wait() 384 } 385 386 // NewClient creates a new plugin client which manages the lifecycle of an external 387 // plugin and gets the address for the RPC connection. 388 // 389 // The client must be cleaned up at some point by calling Kill(). If 390 // the client is a managed client (created with ClientConfig.Managed) you 391 // can just call CleanupClients at the end of your program and they will 392 // be properly cleaned. 393 func NewClient(config *ClientConfig) (c *Client) { 394 if config.MinPort == 0 && config.MaxPort == 0 { 395 config.MinPort = 10000 396 config.MaxPort = 25000 397 } 398 399 if config.StartTimeout == 0 { 400 config.StartTimeout = 1 * time.Minute 401 } 402 403 if config.Stderr == nil { 404 config.Stderr = ioutil.Discard 405 } 406 407 if config.SyncStdout == nil { 408 config.SyncStdout = io.Discard 409 } 410 if config.SyncStderr == nil { 411 config.SyncStderr = io.Discard 412 } 413 414 if config.AllowedProtocols == nil { 415 config.AllowedProtocols = []Protocol{ProtocolNetRPC} 416 } 417 418 if config.Logger == nil { 419 config.Logger = hclog.New(&hclog.LoggerOptions{ 420 Output: hclog.DefaultOutput, 421 Level: hclog.Trace, 422 Name: "plugin", 423 }) 424 } 425 426 if config.PluginLogBufferSize == 0 { 427 config.PluginLogBufferSize = defaultPluginLogBufferSize 428 } 429 430 c = &Client{ 431 config: config, 432 logger: config.Logger, 433 } 434 if config.Managed { 435 managedClientsLock.Lock() 436 managedClients = append(managedClients, c) 437 managedClientsLock.Unlock() 438 } 439 440 return 441 } 442 443 // Client returns the protocol client for this connection. 444 // 445 // Subsequent calls to this will return the same client. 446 func (c *Client) Client() (ClientProtocol, error) { 447 _, err := c.Start() 448 if err != nil { 449 return nil, err 450 } 451 452 c.l.Lock() 453 defer c.l.Unlock() 454 455 if c.client != nil { 456 return c.client, nil 457 } 458 459 switch c.protocol { 460 case ProtocolNetRPC: 461 c.client, err = newRPCClient(c) 462 463 case ProtocolGRPC: 464 c.client, err = newGRPCClient(c.doneCtx, c) 465 466 default: 467 return nil, fmt.Errorf("unknown server protocol: %s", c.protocol) 468 } 469 470 if err != nil { 471 c.client = nil 472 return nil, err 473 } 474 475 return c.client, nil 476 } 477 478 // Tells whether or not the underlying process has exited. 479 func (c *Client) Exited() bool { 480 c.l.Lock() 481 defer c.l.Unlock() 482 return c.exited 483 } 484 485 // killed is used in tests to check if a process failed to exit gracefully, and 486 // needed to be killed. 487 func (c *Client) killed() bool { 488 c.l.Lock() 489 defer c.l.Unlock() 490 return c.processKilled 491 } 492 493 // End the executing subprocess (if it is running) and perform any cleanup 494 // tasks necessary such as capturing any remaining logs and so on. 495 // 496 // This method blocks until the process successfully exits. 497 // 498 // This method can safely be called multiple times. 499 func (c *Client) Kill() { 500 // Grab a lock to read some private fields. 501 c.l.Lock() 502 runner := c.runner 503 addr := c.address 504 hostSocketDir := c.unixSocketCfg.socketDir 505 c.l.Unlock() 506 507 // If there is no runner or ID, there is nothing to kill. 508 if runner == nil || runner.ID() == "" { 509 return 510 } 511 512 defer func() { 513 // Wait for the all client goroutines to finish. 514 c.clientWaitGroup.Wait() 515 516 if hostSocketDir != "" { 517 os.RemoveAll(hostSocketDir) 518 } 519 520 // Make sure there is no reference to the old process after it has been 521 // killed. 522 c.l.Lock() 523 c.runner = nil 524 c.l.Unlock() 525 }() 526 527 // We need to check for address here. It is possible that the plugin 528 // started (process != nil) but has no address (addr == nil) if the 529 // plugin failed at startup. If we do have an address, we need to close 530 // the plugin net connections. 531 graceful := false 532 if addr != nil { 533 // Close the client to cleanly exit the process. 534 client, err := c.Client() 535 if err == nil { 536 err = client.Close() 537 538 // If there is no error, then we attempt to wait for a graceful 539 // exit. If there was an error, we assume that graceful cleanup 540 // won't happen and just force kill. 541 graceful = err == nil 542 if err != nil { 543 // If there was an error just log it. We're going to force 544 // kill in a moment anyways. 545 c.logger.Warn("error closing client during Kill", "err", err) 546 } 547 } else { 548 c.logger.Error("client", "error", err) 549 } 550 } 551 552 // If we're attempting a graceful exit, then we wait for a short period 553 // of time to allow that to happen. To wait for this we just wait on the 554 // doneCh which would be closed if the process exits. 555 if graceful { 556 select { 557 case <-c.doneCtx.Done(): 558 c.logger.Debug("plugin exited") 559 return 560 case <-time.After(2 * time.Second): 561 } 562 } 563 564 // If graceful exiting failed, just kill it 565 c.logger.Warn("plugin failed to exit gracefully") 566 if err := runner.Kill(context.Background()); err != nil { 567 c.logger.Debug("error killing plugin", "error", err) 568 } 569 570 c.l.Lock() 571 c.processKilled = true 572 c.l.Unlock() 573 } 574 575 // Start the underlying subprocess, communicating with it to negotiate 576 // a port for RPC connections, and returning the address to connect via RPC. 577 // 578 // This method is safe to call multiple times. Subsequent calls have no effect. 579 // Once a client has been started once, it cannot be started again, even if 580 // it was killed. 581 func (c *Client) Start() (addr net.Addr, err error) { 582 c.l.Lock() 583 defer c.l.Unlock() 584 585 if c.address != nil { 586 return c.address, nil 587 } 588 589 // If one of cmd or reattach isn't set, then it is an error. We wrap 590 // this in a {} for scoping reasons, and hopeful that the escape 591 // analysis will pop the stack here. 592 { 593 var mutuallyExclusiveOptions int 594 if c.config.Cmd != nil { 595 mutuallyExclusiveOptions += 1 596 } 597 if c.config.Reattach != nil { 598 mutuallyExclusiveOptions += 1 599 } 600 if c.config.RunnerFunc != nil { 601 mutuallyExclusiveOptions += 1 602 } 603 if mutuallyExclusiveOptions != 1 { 604 return nil, fmt.Errorf("exactly one of Cmd, or Reattach, or RunnerFunc must be set") 605 } 606 607 if c.config.SecureConfig != nil && c.config.Reattach != nil { 608 return nil, ErrSecureConfigAndReattach 609 } 610 611 if c.config.GRPCBrokerMultiplex && c.config.Reattach != nil { 612 return nil, fmt.Errorf("gRPC broker multiplexing is not supported with Reattach config") 613 } 614 } 615 616 if c.config.Reattach != nil { 617 return c.reattach() 618 } 619 620 if c.config.VersionedPlugins == nil { 621 c.config.VersionedPlugins = make(map[int]PluginSet) 622 } 623 624 // handle all plugins as versioned, using the handshake config as the default. 625 version := int(c.config.ProtocolVersion) 626 627 // Make sure we're not overwriting a real version 0. If ProtocolVersion was 628 // non-zero, then we have to just assume the user made sure that 629 // VersionedPlugins doesn't conflict. 630 if _, ok := c.config.VersionedPlugins[version]; !ok && c.config.Plugins != nil { 631 c.config.VersionedPlugins[version] = c.config.Plugins 632 } 633 634 var versionStrings []string 635 for v := range c.config.VersionedPlugins { 636 versionStrings = append(versionStrings, strconv.Itoa(v)) 637 } 638 639 env := []string{ 640 fmt.Sprintf("%s=%s", c.config.MagicCookieKey, c.config.MagicCookieValue), 641 fmt.Sprintf("PLUGIN_MIN_PORT=%d", c.config.MinPort), 642 fmt.Sprintf("PLUGIN_MAX_PORT=%d", c.config.MaxPort), 643 fmt.Sprintf("PLUGIN_PROTOCOL_VERSIONS=%s", strings.Join(versionStrings, ",")), 644 } 645 if c.config.GRPCBrokerMultiplex { 646 env = append(env, fmt.Sprintf("%s=true", envMultiplexGRPC)) 647 } 648 649 cmd := c.config.Cmd 650 if cmd == nil { 651 // It's only possible to get here if RunnerFunc is non-nil, but we'll 652 // still use cmd as a spec to populate metadata for the external 653 // implementation to consume. 654 cmd = exec.Command("") 655 } 656 if !c.config.SkipHostEnv { 657 cmd.Env = append(cmd.Env, os.Environ()...) 658 } 659 cmd.Env = append(cmd.Env, env...) 660 cmd.Stdin = os.Stdin 661 662 if c.config.SecureConfig != nil { 663 if ok, err := c.config.SecureConfig.Check(cmd.Path); err != nil { 664 return nil, fmt.Errorf("error verifying checksum: %s", err) 665 } else if !ok { 666 return nil, ErrChecksumsDoNotMatch 667 } 668 } 669 670 // Setup a temporary certificate for client/server mtls, and send the public 671 // certificate to the plugin. 672 if c.config.AutoMTLS { 673 c.logger.Info("configuring client automatic mTLS") 674 certPEM, keyPEM, err := generateCert() 675 if err != nil { 676 c.logger.Error("failed to generate client certificate", "error", err) 677 return nil, err 678 } 679 cert, err := tls.X509KeyPair(certPEM, keyPEM) 680 if err != nil { 681 c.logger.Error("failed to parse client certificate", "error", err) 682 return nil, err 683 } 684 685 cmd.Env = append(cmd.Env, fmt.Sprintf("PLUGIN_CLIENT_CERT=%s", certPEM)) 686 687 c.config.TLSConfig = &tls.Config{ 688 Certificates: []tls.Certificate{cert}, 689 ClientAuth: tls.RequireAndVerifyClientCert, 690 MinVersion: tls.VersionTLS12, 691 ServerName: "localhost", 692 } 693 } 694 695 if c.config.UnixSocketConfig != nil { 696 c.unixSocketCfg = *c.config.UnixSocketConfig 697 } 698 699 if c.unixSocketCfg.Group != "" { 700 cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", EnvUnixSocketGroup, c.unixSocketCfg.Group)) 701 } 702 703 var runner runner.Runner 704 switch { 705 case c.config.RunnerFunc != nil: 706 c.unixSocketCfg.socketDir, err = os.MkdirTemp(c.unixSocketCfg.TempDir, "plugin-dir") 707 if err != nil { 708 return nil, err 709 } 710 // os.MkdirTemp creates folders with 0o700, so if we have a group 711 // configured we need to make it group-writable. 712 if c.unixSocketCfg.Group != "" { 713 err = setGroupWritable(c.unixSocketCfg.socketDir, c.unixSocketCfg.Group, 0o770) 714 if err != nil { 715 return nil, err 716 } 717 } 718 cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", EnvUnixSocketDir, c.unixSocketCfg.socketDir)) 719 c.logger.Trace("created temporary directory for unix sockets", "dir", c.unixSocketCfg.socketDir) 720 721 runner, err = c.config.RunnerFunc(c.logger, cmd, c.unixSocketCfg.socketDir) 722 if err != nil { 723 return nil, err 724 } 725 default: 726 runner, err = cmdrunner.NewCmdRunner(c.logger, cmd) 727 if err != nil { 728 return nil, err 729 } 730 731 } 732 733 c.runner = runner 734 startCtx, startCtxCancel := context.WithTimeout(context.Background(), c.config.StartTimeout) 735 defer startCtxCancel() 736 err = runner.Start(startCtx) 737 if err != nil { 738 return nil, err 739 } 740 741 // Make sure the command is properly cleaned up if there is an error 742 defer func() { 743 rErr := recover() 744 745 if err != nil || rErr != nil { 746 runner.Kill(context.Background()) 747 } 748 749 if rErr != nil { 750 panic(rErr) 751 } 752 }() 753 754 // Create a context for when we kill 755 c.doneCtx, c.ctxCancel = context.WithCancel(context.Background()) 756 757 // Start goroutine that logs the stderr 758 c.clientWaitGroup.Add(1) 759 c.stderrWaitGroup.Add(1) 760 // logStderr calls Done() 761 go c.logStderr(runner.Name(), runner.Stderr()) 762 763 c.clientWaitGroup.Add(1) 764 go func() { 765 // ensure the context is cancelled when we're done 766 defer c.ctxCancel() 767 768 defer c.clientWaitGroup.Done() 769 770 // wait to finish reading from stderr since the stderr pipe reader 771 // will be closed by the subsequent call to cmd.Wait(). 772 c.stderrWaitGroup.Wait() 773 774 // Wait for the command to end. 775 err := runner.Wait(context.Background()) 776 if err != nil { 777 c.logger.Error("plugin process exited", "plugin", runner.Name(), "id", runner.ID(), "error", err.Error()) 778 } else { 779 // Log and make sure to flush the logs right away 780 c.logger.Info("plugin process exited", "plugin", runner.Name(), "id", runner.ID()) 781 } 782 783 os.Stderr.Sync() 784 785 // Set that we exited, which takes a lock 786 c.l.Lock() 787 defer c.l.Unlock() 788 c.exited = true 789 }() 790 791 // Start a goroutine that is going to be reading the lines 792 // out of stdout 793 linesCh := make(chan string) 794 c.clientWaitGroup.Add(1) 795 go func() { 796 defer c.clientWaitGroup.Done() 797 defer close(linesCh) 798 799 scanner := bufio.NewScanner(runner.Stdout()) 800 for scanner.Scan() { 801 linesCh <- scanner.Text() 802 } 803 if scanner.Err() != nil { 804 c.logger.Error("error encountered while scanning stdout", "error", scanner.Err()) 805 } 806 }() 807 808 // Make sure after we exit we read the lines from stdout forever 809 // so they don't block since it is a pipe. 810 // The scanner goroutine above will close this, but track it with a wait 811 // group for completeness. 812 c.clientWaitGroup.Add(1) 813 defer func() { 814 go func() { 815 defer c.clientWaitGroup.Done() 816 for range linesCh { 817 } 818 }() 819 }() 820 821 // Some channels for the next step 822 timeout := time.After(c.config.StartTimeout) 823 824 // Start looking for the address 825 c.logger.Debug("waiting for RPC address", "plugin", runner.Name()) 826 select { 827 case <-timeout: 828 err = errors.New("timeout while waiting for plugin to start") 829 case <-c.doneCtx.Done(): 830 err = errors.New("plugin exited before we could connect") 831 case line, ok := <-linesCh: 832 // Trim the line and split by "|" in order to get the parts of 833 // the output. 834 line = strings.TrimSpace(line) 835 parts := strings.Split(line, "|") 836 if len(parts) < 4 { 837 errText := fmt.Sprintf("Unrecognized remote plugin message: %s", line) 838 if !ok { 839 errText += "\n" + "Failed to read any lines from plugin's stdout" 840 } 841 additionalNotes := runner.Diagnose(context.Background()) 842 if additionalNotes != "" { 843 errText += "\n" + additionalNotes 844 } 845 err = errors.New(errText) 846 return 847 } 848 849 // Check the core protocol. Wrapped in a {} for scoping. 850 { 851 var coreProtocol int 852 coreProtocol, err = strconv.Atoi(parts[0]) 853 if err != nil { 854 err = fmt.Errorf("Error parsing core protocol version: %s", err) 855 return 856 } 857 858 if coreProtocol != CoreProtocolVersion { 859 err = fmt.Errorf("Incompatible core API version with plugin. "+ 860 "Plugin version: %s, Core version: %d\n\n"+ 861 "To fix this, the plugin usually only needs to be recompiled.\n"+ 862 "Please report this to the plugin author.", parts[0], CoreProtocolVersion) 863 return 864 } 865 } 866 867 // Test the API version 868 version, pluginSet, err := c.checkProtoVersion(parts[1]) 869 if err != nil { 870 return addr, err 871 } 872 873 // set the Plugins value to the compatible set, so the version 874 // doesn't need to be passed through to the ClientProtocol 875 // implementation. 876 c.config.Plugins = pluginSet 877 c.negotiatedVersion = version 878 c.logger.Debug("using plugin", "version", version) 879 880 network, address, err := runner.PluginToHost(parts[2], parts[3]) 881 if err != nil { 882 return addr, err 883 } 884 885 switch network { 886 case "tcp": 887 addr, err = net.ResolveTCPAddr("tcp", address) 888 case "unix": 889 addr, err = net.ResolveUnixAddr("unix", address) 890 default: 891 err = fmt.Errorf("Unknown address type: %s", address) 892 } 893 894 // If we have a server type, then record that. We default to net/rpc 895 // for backwards compatibility. 896 c.protocol = ProtocolNetRPC 897 if len(parts) >= 5 { 898 c.protocol = Protocol(parts[4]) 899 } 900 901 found := false 902 for _, p := range c.config.AllowedProtocols { 903 if p == c.protocol { 904 found = true 905 break 906 } 907 } 908 if !found { 909 err = fmt.Errorf("Unsupported plugin protocol %q. Supported: %v", 910 c.protocol, c.config.AllowedProtocols) 911 return addr, err 912 } 913 914 // See if we have a TLS certificate from the server. 915 // Checking if the length is > 50 rules out catching the unused "extra" 916 // data returned from some older implementations. 917 if len(parts) >= 6 && len(parts[5]) > 50 { 918 err := c.loadServerCert(parts[5]) 919 if err != nil { 920 return nil, fmt.Errorf("error parsing server cert: %s", err) 921 } 922 } 923 924 if c.config.GRPCBrokerMultiplex && c.protocol == ProtocolGRPC { 925 if len(parts) <= 6 { 926 return nil, fmt.Errorf("%w; for Go plugins, you will need to update the "+ 927 "github.com/hashicorp/go-plugin dependency and recompile", ErrGRPCBrokerMuxNotSupported) 928 } 929 if muxSupported, err := strconv.ParseBool(parts[6]); err != nil { 930 return nil, fmt.Errorf("error parsing %q as a boolean for gRPC broker multiplexing support", parts[6]) 931 } else if !muxSupported { 932 return nil, ErrGRPCBrokerMuxNotSupported 933 } 934 } 935 } 936 937 c.address = addr 938 return 939 } 940 941 // loadServerCert is used by AutoMTLS to read an x.509 cert returned by the 942 // server, and load it as the RootCA and ClientCA for the client TLSConfig. 943 func (c *Client) loadServerCert(cert string) error { 944 certPool := x509.NewCertPool() 945 946 asn1, err := base64.RawStdEncoding.DecodeString(cert) 947 if err != nil { 948 return err 949 } 950 951 x509Cert, err := x509.ParseCertificate([]byte(asn1)) 952 if err != nil { 953 return err 954 } 955 956 certPool.AddCert(x509Cert) 957 958 c.config.TLSConfig.RootCAs = certPool 959 c.config.TLSConfig.ClientCAs = certPool 960 return nil 961 } 962 963 func (c *Client) reattach() (net.Addr, error) { 964 reattachFunc := c.config.Reattach.ReattachFunc 965 // For backwards compatibility default to cmdrunner.ReattachFunc 966 if reattachFunc == nil { 967 reattachFunc = cmdrunner.ReattachFunc(c.config.Reattach.Pid, c.config.Reattach.Addr) 968 } 969 970 r, err := reattachFunc() 971 if err != nil { 972 return nil, err 973 } 974 975 // Create a context for when we kill 976 c.doneCtx, c.ctxCancel = context.WithCancel(context.Background()) 977 978 c.clientWaitGroup.Add(1) 979 // Goroutine to mark exit status 980 go func(r runner.AttachedRunner) { 981 defer c.clientWaitGroup.Done() 982 983 // ensure the context is cancelled when we're done 984 defer c.ctxCancel() 985 986 // Wait for the process to die 987 r.Wait(context.Background()) 988 989 // Log so we can see it 990 c.logger.Debug("reattached plugin process exited") 991 992 // Mark it 993 c.l.Lock() 994 defer c.l.Unlock() 995 c.exited = true 996 }(r) 997 998 // Set the address and protocol 999 c.address = c.config.Reattach.Addr 1000 c.protocol = c.config.Reattach.Protocol 1001 if c.protocol == "" { 1002 // Default the protocol to net/rpc for backwards compatibility 1003 c.protocol = ProtocolNetRPC 1004 } 1005 1006 if c.config.Reattach.Test { 1007 c.negotiatedVersion = c.config.Reattach.ProtocolVersion 1008 } else { 1009 // If we're in test mode, we do NOT set the runner. This avoids the 1010 // runner being killed (the only purpose we have for setting c.runner 1011 // when reattaching), since in test mode the process is responsible for 1012 // exiting on its own. 1013 c.runner = r 1014 } 1015 1016 return c.address, nil 1017 } 1018 1019 // checkProtoVersion returns the negotiated version and PluginSet. 1020 // This returns an error if the server returned an incompatible protocol 1021 // version, or an invalid handshake response. 1022 func (c *Client) checkProtoVersion(protoVersion string) (int, PluginSet, error) { 1023 serverVersion, err := strconv.Atoi(protoVersion) 1024 if err != nil { 1025 return 0, nil, fmt.Errorf("Error parsing protocol version %q: %s", protoVersion, err) 1026 } 1027 1028 // record these for the error message 1029 var clientVersions []int 1030 1031 // all versions, including the legacy ProtocolVersion have been added to 1032 // the versions set 1033 for version, plugins := range c.config.VersionedPlugins { 1034 clientVersions = append(clientVersions, version) 1035 1036 if serverVersion != version { 1037 continue 1038 } 1039 return version, plugins, nil 1040 } 1041 1042 return 0, nil, fmt.Errorf("Incompatible API version with plugin. "+ 1043 "Plugin version: %d, Client versions: %d", serverVersion, clientVersions) 1044 } 1045 1046 // ReattachConfig returns the information that must be provided to NewClient 1047 // to reattach to the plugin process that this client started. This is 1048 // useful for plugins that detach from their parent process. 1049 // 1050 // If this returns nil then the process hasn't been started yet. Please 1051 // call Start or Client before calling this. 1052 // 1053 // Clients who specified a RunnerFunc will need to populate their own 1054 // ReattachFunc in the returned ReattachConfig before it can be used. 1055 func (c *Client) ReattachConfig() *ReattachConfig { 1056 c.l.Lock() 1057 defer c.l.Unlock() 1058 1059 if c.address == nil { 1060 return nil 1061 } 1062 1063 if c.config.Cmd != nil && c.config.Cmd.Process == nil { 1064 return nil 1065 } 1066 1067 // If we connected via reattach, just return the information as-is 1068 if c.config.Reattach != nil { 1069 return c.config.Reattach 1070 } 1071 1072 reattach := &ReattachConfig{ 1073 Protocol: c.protocol, 1074 Addr: c.address, 1075 } 1076 1077 if c.config.Cmd != nil && c.config.Cmd.Process != nil { 1078 reattach.Pid = c.config.Cmd.Process.Pid 1079 } 1080 1081 return reattach 1082 } 1083 1084 // Protocol returns the protocol of server on the remote end. This will 1085 // start the plugin process if it isn't already started. Errors from 1086 // starting the plugin are surpressed and ProtocolInvalid is returned. It 1087 // is recommended you call Start explicitly before calling Protocol to ensure 1088 // no errors occur. 1089 func (c *Client) Protocol() Protocol { 1090 _, err := c.Start() 1091 if err != nil { 1092 return ProtocolInvalid 1093 } 1094 1095 return c.protocol 1096 } 1097 1098 func netAddrDialer(addr net.Addr) func(string, time.Duration) (net.Conn, error) { 1099 return func(_ string, _ time.Duration) (net.Conn, error) { 1100 // Connect to the client 1101 conn, err := net.Dial(addr.Network(), addr.String()) 1102 if err != nil { 1103 return nil, err 1104 } 1105 if tcpConn, ok := conn.(*net.TCPConn); ok { 1106 // Make sure to set keep alive so that the connection doesn't die 1107 tcpConn.SetKeepAlive(true) 1108 } 1109 1110 return conn, nil 1111 } 1112 } 1113 1114 // dialer is compatible with grpc.WithDialer and creates the connection 1115 // to the plugin. 1116 func (c *Client) dialer(_ string, timeout time.Duration) (net.Conn, error) { 1117 muxer, err := c.getGRPCMuxer(c.address) 1118 if err != nil { 1119 return nil, err 1120 } 1121 1122 var conn net.Conn 1123 if muxer.Enabled() { 1124 conn, err = muxer.Dial() 1125 if err != nil { 1126 return nil, err 1127 } 1128 } else { 1129 conn, err = netAddrDialer(c.address)("", timeout) 1130 if err != nil { 1131 return nil, err 1132 } 1133 } 1134 1135 // If we have a TLS config we wrap our connection. We only do this 1136 // for net/rpc since gRPC uses its own mechanism for TLS. 1137 if c.protocol == ProtocolNetRPC && c.config.TLSConfig != nil { 1138 conn = tls.Client(conn, c.config.TLSConfig) 1139 } 1140 1141 return conn, nil 1142 } 1143 1144 func (c *Client) getGRPCMuxer(addr net.Addr) (*grpcmux.GRPCClientMuxer, error) { 1145 if c.protocol != ProtocolGRPC || !c.config.GRPCBrokerMultiplex { 1146 return nil, nil 1147 } 1148 1149 var err error 1150 c.grpcMuxerOnce.Do(func() { 1151 c.grpcMuxer, err = grpcmux.NewGRPCClientMuxer(c.logger, addr) 1152 }) 1153 if err != nil { 1154 return nil, err 1155 } 1156 1157 return c.grpcMuxer, nil 1158 } 1159 1160 func (c *Client) logStderr(name string, r io.Reader) { 1161 defer c.clientWaitGroup.Done() 1162 defer c.stderrWaitGroup.Done() 1163 l := c.logger.Named(filepath.Base(name)) 1164 1165 reader := bufio.NewReaderSize(r, c.config.PluginLogBufferSize) 1166 // continuation indicates the previous line was a prefix 1167 continuation := false 1168 1169 for { 1170 line, isPrefix, err := reader.ReadLine() 1171 switch { 1172 case err == io.EOF: 1173 return 1174 case err != nil: 1175 l.Error("reading plugin stderr", "error", err) 1176 return 1177 } 1178 1179 c.config.Stderr.Write(line) 1180 1181 // The line was longer than our max token size, so it's likely 1182 // incomplete and won't unmarshal. 1183 if isPrefix || continuation { 1184 l.Debug(string(line)) 1185 1186 // if we're finishing a continued line, add the newline back in 1187 if !isPrefix { 1188 c.config.Stderr.Write([]byte{'\n'}) 1189 } 1190 1191 continuation = isPrefix 1192 continue 1193 } 1194 1195 c.config.Stderr.Write([]byte{'\n'}) 1196 1197 entry, err := parseJSON(line) 1198 // If output is not JSON format, print directly to Debug 1199 if err != nil { 1200 // Attempt to infer the desired log level from the commonly used 1201 // string prefixes 1202 switch line := string(line); { 1203 case strings.HasPrefix(line, "[TRACE]"): 1204 l.Trace(line) 1205 case strings.HasPrefix(line, "[DEBUG]"): 1206 l.Debug(line) 1207 case strings.HasPrefix(line, "[INFO]"): 1208 l.Info(line) 1209 case strings.HasPrefix(line, "[WARN]"): 1210 l.Warn(line) 1211 case strings.HasPrefix(line, "[ERROR]"): 1212 l.Error(line) 1213 default: 1214 l.Debug(line) 1215 } 1216 } else { 1217 out := flattenKVPairs(entry.KVPairs) 1218 1219 out = append(out, "timestamp", entry.Timestamp.Format(hclog.TimeFormat)) 1220 switch hclog.LevelFromString(entry.Level) { 1221 case hclog.Trace: 1222 l.Trace(entry.Message, out...) 1223 case hclog.Debug: 1224 l.Debug(entry.Message, out...) 1225 case hclog.Info: 1226 l.Info(entry.Message, out...) 1227 case hclog.Warn: 1228 l.Warn(entry.Message, out...) 1229 case hclog.Error: 1230 l.Error(entry.Message, out...) 1231 default: 1232 // if there was no log level, it's likely this is unexpected 1233 // json from something other than hclog, and we should output 1234 // it verbatim. 1235 l.Debug(string(line)) 1236 } 1237 } 1238 } 1239 }