summaryrefslogtreecommitdiff
path: root/vendor/github.com/hashicorp/terraform/vendor/github.com/hashicorp/go-plugin/client.go
diff options
context:
space:
mode:
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.go267
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 {