diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/vendor/github.com/hashicorp/go-plugin/client.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/vendor/github.com/hashicorp/go-plugin/client.go | 267 |
1 files changed, 229 insertions, 38 deletions
diff --git a/vendor/github.com/hashicorp/terraform/vendor/github.com/hashicorp/go-plugin/client.go b/vendor/github.com/hashicorp/terraform/vendor/github.com/hashicorp/go-plugin/client.go index 9f8a0f27..b912826b 100644 --- a/vendor/github.com/hashicorp/terraform/vendor/github.com/hashicorp/go-plugin/client.go +++ b/vendor/github.com/hashicorp/terraform/vendor/github.com/hashicorp/go-plugin/client.go @@ -2,8 +2,11 @@ package plugin import ( "bufio" + "crypto/subtle" + "crypto/tls" "errors" "fmt" + "hash" "io" "io/ioutil" "log" @@ -17,6 +20,8 @@ import ( "sync/atomic" "time" "unicode" + + hclog "github.com/hashicorp/go-hclog" ) // If this is 1, then we've called CleanupClients. This can be used @@ -35,6 +40,22 @@ var ( // ErrProcessNotFound is returned when a client is instantiated to // reattach to an existing process and it isn't found. ErrProcessNotFound = errors.New("Reattachment process not found") + + // ErrChecksumsDoNotMatch is returned when binary's checksum doesn't match + // the one provided in the SecureConfig. + ErrChecksumsDoNotMatch = errors.New("checksums did not match") + + // ErrSecureNoChecksum is returned when an empty checksum is provided to the + // SecureConfig. + ErrSecureConfigNoChecksum = errors.New("no checksum provided") + + // ErrSecureNoHash is returned when a nil Hash object is provided to the + // SecureConfig. + ErrSecureConfigNoHash = errors.New("no hash implementation provided") + + // ErrSecureConfigAndReattach is returned when both Reattach and + // SecureConfig are set. + ErrSecureConfigAndReattach = errors.New("only one of Reattach or SecureConfig can be set") ) // Client handles the lifecycle of a plugin application. It launches @@ -55,7 +76,9 @@ type Client struct { l sync.Mutex address net.Addr process *os.Process - client *RPCClient + client ClientProtocol + protocol Protocol + logger hclog.Logger } // ClientConfig is the configuration used to initialize a new @@ -79,6 +102,13 @@ type ClientConfig struct { Cmd *exec.Cmd Reattach *ReattachConfig + // SecureConfig is configuration for verifying the integrity of the + // executable. It can not be used with Reattach. + SecureConfig *SecureConfig + + // TLSConfig is used to enable TLS on the RPC client. + TLSConfig *tls.Config + // Managed represents if the client should be managed by the // plugin package or not. If true, then by calling CleanupClients, // it will automatically be cleaned up. Otherwise, the client @@ -109,14 +139,74 @@ type ClientConfig struct { // sync any of these streams. SyncStdout io.Writer SyncStderr io.Writer + + // AllowedProtocols is a list of allowed protocols. If this isn't set, + // then only netrpc is allowed. This is so that older go-plugin systems + // can show friendly errors if they see a plugin with an unknown + // protocol. + // + // By setting this, you can cause an error immediately on plugin start + // if an unsupported protocol is used with a good error message. + // + // If this isn't set at all (nil value), then only net/rpc is accepted. + // This is done for legacy reasons. You must explicitly opt-in to + // new protocols. + AllowedProtocols []Protocol + + // Logger is the logger that the client will used. If none is provided, + // it will default to hclog's default logger. + Logger hclog.Logger } // ReattachConfig is used to configure a client to reattach to an // already-running plugin process. You can retrieve this information by // calling ReattachConfig on Client. type ReattachConfig struct { - Addr net.Addr - Pid int + Protocol Protocol + Addr net.Addr + Pid int +} + +// SecureConfig is used to configure a client to verify the integrity of an +// executable before running. It does this by verifying the checksum is +// expected. Hash is used to specify the hashing method to use when checksumming +// the file. The configuration is verified by the client by calling the +// SecureConfig.Check() function. +// +// The host process should ensure the checksum was provided by a trusted and +// authoritative source. The binary should be installed in such a way that it +// can not be modified by an unauthorized user between the time of this check +// and the time of execution. +type SecureConfig struct { + Checksum []byte + Hash hash.Hash +} + +// Check takes the filepath to an executable and returns true if the checksum of +// the file matches the checksum provided in the SecureConfig. +func (s *SecureConfig) Check(filePath string) (bool, error) { + if len(s.Checksum) == 0 { + return false, ErrSecureConfigNoChecksum + } + + if s.Hash == nil { + return false, ErrSecureConfigNoHash + } + + file, err := os.Open(filePath) + if err != nil { + return false, err + } + defer file.Close() + + _, err = io.Copy(s.Hash, file) + if err != nil { + return false, err + } + + sum := s.Hash.Sum(nil) + + return subtle.ConstantTimeCompare(sum, s.Checksum) == 1, nil } // This makes sure all the managed subprocesses are killed and properly @@ -174,7 +264,22 @@ func NewClient(config *ClientConfig) (c *Client) { config.SyncStderr = ioutil.Discard } - c = &Client{config: config} + if config.AllowedProtocols == nil { + config.AllowedProtocols = []Protocol{ProtocolNetRPC} + } + + if config.Logger == nil { + config.Logger = hclog.New(&hclog.LoggerOptions{ + Output: hclog.DefaultOutput, + Level: hclog.Trace, + Name: "plugin", + }) + } + + c = &Client{ + config: config, + logger: config.Logger, + } if config.Managed { managedClientsLock.Lock() managedClients = append(managedClients, c) @@ -184,11 +289,11 @@ func NewClient(config *ClientConfig) (c *Client) { return } -// Client returns an RPC client for the plugin. +// Client returns the protocol client for this connection. // -// Subsequent calls to this will return the same RPC client. -func (c *Client) Client() (*RPCClient, error) { - addr, err := c.Start() +// Subsequent calls to this will return the same client. +func (c *Client) Client() (ClientProtocol, error) { + _, err := c.Start() if err != nil { return nil, err } @@ -200,29 +305,18 @@ func (c *Client) Client() (*RPCClient, error) { return c.client, nil } - // Connect to the client - conn, err := net.Dial(addr.Network(), addr.String()) - if err != nil { - return nil, err - } - if tcpConn, ok := conn.(*net.TCPConn); ok { - // Make sure to set keep alive so that the connection doesn't die - tcpConn.SetKeepAlive(true) - } + switch c.protocol { + case ProtocolNetRPC: + c.client, err = newRPCClient(c) - // Create the actual RPC client - c.client, err = NewRPCClient(conn, c.config.Plugins) - if err != nil { - conn.Close() - return nil, err + case ProtocolGRPC: + c.client, err = newGRPCClient(c) + + default: + return nil, fmt.Errorf("unknown server protocol: %s", c.protocol) } - // Begin the stream syncing so that stdin, out, err work properly - err = c.client.SyncStreams( - c.config.SyncStdout, - c.config.SyncStderr) if err != nil { - c.client.Close() c.client = nil return nil, err } @@ -274,8 +368,7 @@ func (c *Client) Kill() { if err != nil { // If there was an error just log it. We're going to force // kill in a moment anyways. - log.Printf( - "[WARN] plugin: error closing client during Kill: %s", err) + c.logger.Warn("error closing client during Kill", "err", err) } } } @@ -318,9 +411,14 @@ func (c *Client) Start() (addr net.Addr, err error) { { cmdSet := c.config.Cmd != nil attachSet := c.config.Reattach != nil + secureSet := c.config.SecureConfig != nil if cmdSet == attachSet { return nil, fmt.Errorf("Only one of Cmd or Reattach must be set") } + + if secureSet && attachSet { + return nil, ErrSecureConfigAndReattach + } } // Create the logging channel for when we kill @@ -350,7 +448,7 @@ func (c *Client) Start() (addr net.Addr, err error) { pidWait(pid) // Log so we can see it - log.Printf("[DEBUG] plugin: reattached plugin process exited\n") + c.logger.Debug("reattached plugin process exited") // Mark it c.l.Lock() @@ -364,6 +462,11 @@ func (c *Client) Start() (addr net.Addr, err error) { // Set the address and process c.address = c.config.Reattach.Addr c.process = p + c.protocol = c.config.Reattach.Protocol + if c.protocol == "" { + // Default the protocol to net/rpc for backwards compatibility + c.protocol = ProtocolNetRPC + } return c.address, nil } @@ -384,7 +487,15 @@ func (c *Client) Start() (addr net.Addr, err error) { cmd.Stderr = stderr_w cmd.Stdout = stdout_w - log.Printf("[DEBUG] plugin: starting plugin: %s %#v", cmd.Path, cmd.Args) + if c.config.SecureConfig != nil { + if ok, err := c.config.SecureConfig.Check(cmd.Path); err != nil { + return nil, fmt.Errorf("error verifying checksum: %s", err) + } else if !ok { + return nil, ErrChecksumsDoNotMatch + } + } + + c.logger.Debug("starting plugin", "path", cmd.Path, "args", cmd.Args) err = cmd.Start() if err != nil { return @@ -418,7 +529,7 @@ func (c *Client) Start() (addr net.Addr, err error) { cmd.Wait() // Log and make sure to flush the logs write away - log.Printf("[DEBUG] plugin: %s: plugin process exited\n", cmd.Path) + c.logger.Debug("plugin process exited", "path", cmd.Path) os.Stderr.Sync() // Mark that we exited @@ -465,7 +576,7 @@ func (c *Client) Start() (addr net.Addr, err error) { timeout := time.After(c.config.StartTimeout) // Start looking for the address - log.Printf("[DEBUG] plugin: waiting for RPC address for: %s", cmd.Path) + c.logger.Debug("waiting for RPC address", "path", cmd.Path) select { case <-timeout: err = errors.New("timeout while waiting for plugin to start") @@ -475,7 +586,7 @@ func (c *Client) Start() (addr net.Addr, err error) { // Trim the line and split by "|" in order to get the parts of // the output. line := strings.TrimSpace(string(lineBytes)) - parts := strings.SplitN(line, "|", 4) + parts := strings.SplitN(line, "|", 6) if len(parts) < 4 { err = fmt.Errorf( "Unrecognized remote plugin message: %s\n\n"+ @@ -525,6 +636,27 @@ func (c *Client) Start() (addr net.Addr, err error) { default: err = fmt.Errorf("Unknown address type: %s", parts[3]) } + + // If we have a server type, then record that. We default to net/rpc + // for backwards compatibility. + c.protocol = ProtocolNetRPC + if len(parts) >= 5 { + c.protocol = Protocol(parts[4]) + } + + found := false + for _, p := range c.config.AllowedProtocols { + if p == c.protocol { + found = true + break + } + } + if !found { + err = fmt.Errorf("Unsupported plugin protocol %q. Supported: %v", + c.protocol, c.config.AllowedProtocols) + return + } + } c.address = addr @@ -555,9 +687,46 @@ func (c *Client) ReattachConfig() *ReattachConfig { } return &ReattachConfig{ - Addr: c.address, - Pid: c.config.Cmd.Process.Pid, + Protocol: c.protocol, + Addr: c.address, + Pid: c.config.Cmd.Process.Pid, + } +} + +// Protocol returns the protocol of server on the remote end. This will +// start the plugin process if it isn't already started. Errors from +// starting the plugin are surpressed and ProtocolInvalid is returned. It +// is recommended you call Start explicitly before calling Protocol to ensure +// no errors occur. +func (c *Client) Protocol() Protocol { + _, err := c.Start() + if err != nil { + return ProtocolInvalid + } + + return c.protocol +} + +// dialer is compatible with grpc.WithDialer and creates the connection +// to the plugin. +func (c *Client) dialer(_ string, timeout time.Duration) (net.Conn, error) { + // Connect to the client + conn, err := net.Dial(c.address.Network(), c.address.String()) + if err != nil { + return nil, err + } + if tcpConn, ok := conn.(*net.TCPConn); ok { + // Make sure to set keep alive so that the connection doesn't die + tcpConn.SetKeepAlive(true) } + + // If we have a TLS config we wrap our connection. We only do this + // for net/rpc since gRPC uses its own mechanism for TLS. + if c.protocol == ProtocolNetRPC && c.config.TLSConfig != nil { + conn = tls.Client(conn, c.config.TLSConfig) + } + + return conn, nil } func (c *Client) logStderr(r io.Reader) { @@ -566,9 +735,31 @@ func (c *Client) logStderr(r io.Reader) { line, err := bufR.ReadString('\n') if line != "" { c.config.Stderr.Write([]byte(line)) - line = strings.TrimRightFunc(line, unicode.IsSpace) - log.Printf("[DEBUG] plugin: %s: %s", filepath.Base(c.config.Cmd.Path), line) + + l := c.logger.Named(filepath.Base(c.config.Cmd.Path)) + + entry, err := parseJSON(line) + // If output is not JSON format, print directly to Debug + if err != nil { + l.Debug(line) + } else { + out := flattenKVPairs(entry.KVPairs) + + l = l.With("timestamp", entry.Timestamp.Format(hclog.TimeFormat)) + switch hclog.LevelFromString(entry.Level) { + case hclog.Trace: + l.Trace(entry.Message, out...) + case hclog.Debug: + l.Debug(entry.Message, out...) + case hclog.Info: + l.Info(entry.Message, out...) + case hclog.Warn: + l.Warn(entry.Message, out...) + case hclog.Error: + l.Error(entry.Message, out...) + } + } } if err == io.EOF { |