Bump reva deps (#8412)

* bump dependencies

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* bump reva and add config options

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

---------

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
This commit is contained in:
Jörn Friedrich Dreyer
2024-02-21 10:20:36 +01:00
committed by GitHub
parent c92ebf4b46
commit 5ed57cc09a
490 changed files with 19128 additions and 11161 deletions
+287 -103
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin
import (
@@ -23,6 +26,9 @@ import (
"time"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin/internal/cmdrunner"
"github.com/hashicorp/go-plugin/internal/grpcmux"
"github.com/hashicorp/go-plugin/runner"
"google.golang.org/grpc"
)
@@ -41,7 +47,7 @@ var managedClientsLock sync.Mutex
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")
ErrProcessNotFound = cmdrunner.ErrProcessNotFound
// ErrChecksumsDoNotMatch is returned when binary's checksum doesn't match
// the one provided in the SecureConfig.
@@ -58,8 +64,18 @@ var (
// ErrSecureConfigAndReattach is returned when both Reattach and
// SecureConfig are set.
ErrSecureConfigAndReattach = errors.New("only one of Reattach or SecureConfig can be set")
// ErrGRPCBrokerMuxNotSupported is returned when the client requests
// multiplexing over the gRPC broker, but the plugin does not support the
// feature. In most cases, this should be resolvable by updating and
// rebuilding the plugin, or restarting the plugin with
// ClientConfig.GRPCBrokerMultiplex set to false.
ErrGRPCBrokerMuxNotSupported = errors.New("client requested gRPC broker multiplexing but plugin does not support the feature")
)
// defaultPluginLogBufferSize is the default size of the buffer used to read from stderr for plugin log lines.
const defaultPluginLogBufferSize = 64 * 1024
// Client handles the lifecycle of a plugin application. It launches
// plugins, connects to them, dispenses interface implementations, and handles
// killing the process.
@@ -76,7 +92,7 @@ type Client struct {
exited bool
l sync.Mutex
address net.Addr
process *os.Process
runner runner.AttachedRunner
client ClientProtocol
protocol Protocol
logger hclog.Logger
@@ -95,6 +111,11 @@ type Client struct {
// processKilled is used for testing only, to flag when the process was
// forcefully killed.
processKilled bool
unixSocketCfg UnixSocketConfig
grpcMuxerOnce sync.Once
grpcMuxer *grpcmux.GRPCClientMuxer
}
// NegotiatedVersion returns the protocol version negotiated with the server.
@@ -103,6 +124,19 @@ func (c *Client) NegotiatedVersion() int {
return c.negotiatedVersion
}
// ID returns a unique ID for the running plugin. By default this is the process
// ID (pid), but it could take other forms if RunnerFunc was provided.
func (c *Client) ID() string {
c.l.Lock()
defer c.l.Unlock()
if c.runner != nil {
return c.runner.ID()
}
return ""
}
// ClientConfig is the configuration used to initialize a new
// plugin client. After being used to initialize a plugin client,
// that configuration must not be modified again.
@@ -130,6 +164,13 @@ type ClientConfig struct {
Cmd *exec.Cmd
Reattach *ReattachConfig
// RunnerFunc allows consumers to provide their own implementation of
// runner.Runner and control the context within which a plugin is executed.
// The cmd argument will have been copied from the config and populated with
// environment variables that a go-plugin server expects to read such as
// AutoMTLS certs and the magic cookie key.
RunnerFunc func(l hclog.Logger, cmd *exec.Cmd, tmpDir string) (runner.Runner, error)
// SecureConfig is configuration for verifying the integrity of the
// executable. It can not be used with Reattach.
SecureConfig *SecureConfig
@@ -182,6 +223,10 @@ type ClientConfig struct {
// it will default to hclog's default logger.
Logger hclog.Logger
// PluginLogBufferSize is the buffer size(bytes) to read from stderr for plugin log lines.
// If this is 0, then the default of 64KB is used.
PluginLogBufferSize int
// AutoMTLS has the client and server automatically negotiate mTLS for
// transport authentication. This ensures that only the original client will
// be allowed to connect to the server, and all other connections will be
@@ -209,6 +254,44 @@ type ClientConfig struct {
// to create gRPC connections. This only affects plugins using the gRPC
// protocol.
GRPCDialOptions []grpc.DialOption
// GRPCBrokerMultiplex turns on multiplexing for the gRPC broker. The gRPC
// broker will multiplex all brokered gRPC servers over the plugin's original
// listener socket instead of making a new listener for each server. The
// go-plugin library currently only includes a Go implementation for the
// server (i.e. plugin) side of gRPC broker multiplexing.
//
// Does not support reattaching.
//
// Multiplexed gRPC streams MUST be established sequentially, i.e. after
// calling AcceptAndServe from one side, wait for the other side to Dial
// before calling AcceptAndServe again.
GRPCBrokerMultiplex bool
// SkipHostEnv allows plugins to run without inheriting the parent process'
// environment variables.
SkipHostEnv bool
// UnixSocketConfig configures additional options for any Unix sockets
// that are created. Not normally required. Not supported on Windows.
UnixSocketConfig *UnixSocketConfig
}
type UnixSocketConfig struct {
// If set, go-plugin will change the owner of any Unix sockets created to
// this group, and set them as group-writable. Can be a name or gid. The
// client process must be a member of this group or chown will fail.
Group string
// TempDir specifies the base directory to use when creating a plugin-specific
// temporary directory. It is expected to already exist and be writable. If
// not set, defaults to the directory chosen by os.MkdirTemp.
TempDir string
// The directory to create Unix sockets in. Internally created and managed
// by go-plugin and deleted when the plugin is killed. Will be created
// inside TempDir if specified.
socketDir string
}
// ReattachConfig is used to configure a client to reattach to an
@@ -220,6 +303,11 @@ type ReattachConfig struct {
Addr net.Addr
Pid int
// ReattachFunc allows consumers to provide their own implementation of
// runner.AttachedRunner and attach to something other than a plain process.
// At least one of Pid or ReattachFunc must be set.
ReattachFunc runner.ReattachFunc
// Test is set to true if this is reattaching to to a plugin in "test mode"
// (see ServeConfig.Test). In this mode, client.Kill will NOT kill the
// process and instead will rely on the plugin to terminate itself. This
@@ -295,11 +383,11 @@ func CleanupClients() {
wg.Wait()
}
// Creates a new plugin client which manages the lifecycle of an external
// NewClient creates a new plugin client which manages the lifecycle of an external
// plugin and gets the address for the RPC connection.
//
// The client must be cleaned up at some point by calling Kill(). If
// the client is a managed client (created with NewManagedClient) you
// the client is a managed client (created with ClientConfig.Managed) you
// can just call CleanupClients at the end of your program and they will
// be properly cleaned.
func NewClient(config *ClientConfig) (c *Client) {
@@ -317,10 +405,10 @@ func NewClient(config *ClientConfig) (c *Client) {
}
if config.SyncStdout == nil {
config.SyncStdout = ioutil.Discard
config.SyncStdout = io.Discard
}
if config.SyncStderr == nil {
config.SyncStderr = ioutil.Discard
config.SyncStderr = io.Discard
}
if config.AllowedProtocols == nil {
@@ -335,6 +423,10 @@ func NewClient(config *ClientConfig) (c *Client) {
})
}
if config.PluginLogBufferSize == 0 {
config.PluginLogBufferSize = defaultPluginLogBufferSize
}
c = &Client{
config: config,
logger: config.Logger,
@@ -407,12 +499,13 @@ func (c *Client) killed() bool {
func (c *Client) Kill() {
// Grab a lock to read some private fields.
c.l.Lock()
process := c.process
runner := c.runner
addr := c.address
hostSocketDir := c.unixSocketCfg.socketDir
c.l.Unlock()
// If there is no process, there is nothing to kill.
if process == nil {
// If there is no runner or ID, there is nothing to kill.
if runner == nil || runner.ID() == "" {
return
}
@@ -420,10 +513,14 @@ func (c *Client) Kill() {
// Wait for the all client goroutines to finish.
c.clientWaitGroup.Wait()
if hostSocketDir != "" {
os.RemoveAll(hostSocketDir)
}
// Make sure there is no reference to the old process after it has been
// killed.
c.l.Lock()
c.process = nil
c.runner = nil
c.l.Unlock()
}()
@@ -466,14 +563,16 @@ func (c *Client) Kill() {
// If graceful exiting failed, just kill it
c.logger.Warn("plugin failed to exit gracefully")
process.Kill()
if err := runner.Kill(context.Background()); err != nil {
c.logger.Debug("error killing plugin", "error", err)
}
c.l.Lock()
c.processKilled = true
c.l.Unlock()
}
// Starts the underlying subprocess, communicating with it to negotiate
// Start the underlying subprocess, communicating with it to negotiate
// a port for RPC connections, and returning the address to connect via RPC.
//
// This method is safe to call multiple times. Subsequent calls have no effect.
@@ -491,16 +590,27 @@ func (c *Client) Start() (addr net.Addr, err error) {
// this in a {} for scoping reasons, and hopeful that the escape
// analysis will pop the stack here.
{
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")
var mutuallyExclusiveOptions int
if c.config.Cmd != nil {
mutuallyExclusiveOptions += 1
}
if c.config.Reattach != nil {
mutuallyExclusiveOptions += 1
}
if c.config.RunnerFunc != nil {
mutuallyExclusiveOptions += 1
}
if mutuallyExclusiveOptions != 1 {
return nil, fmt.Errorf("exactly one of Cmd, or Reattach, or RunnerFunc must be set")
}
if secureSet && attachSet {
if c.config.SecureConfig != nil && c.config.Reattach != nil {
return nil, ErrSecureConfigAndReattach
}
if c.config.GRPCBrokerMultiplex && c.config.Reattach != nil {
return nil, fmt.Errorf("gRPC broker multiplexing is not supported with Reattach config")
}
}
if c.config.Reattach != nil {
@@ -532,24 +642,24 @@ func (c *Client) Start() (addr net.Addr, err error) {
fmt.Sprintf("PLUGIN_MAX_PORT=%d", c.config.MaxPort),
fmt.Sprintf("PLUGIN_PROTOCOL_VERSIONS=%s", strings.Join(versionStrings, ",")),
}
if c.config.GRPCBrokerMultiplex {
env = append(env, fmt.Sprintf("%s=true", envMultiplexGRPC))
}
cmd := c.config.Cmd
cmd.Env = append(cmd.Env, os.Environ()...)
if cmd == nil {
// It's only possible to get here if RunnerFunc is non-nil, but we'll
// still use cmd as a spec to populate metadata for the external
// implementation to consume.
cmd = exec.Command("")
}
if !c.config.SkipHostEnv {
cmd.Env = append(cmd.Env, os.Environ()...)
}
cmd.Env = append(cmd.Env, env...)
cmd.Stdin = os.Stdin
cmdStdout, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
cmdStderr, err := cmd.StderrPipe()
if err != nil {
return nil, err
}
if c.config.SecureConfig == nil {
c.logger.Warn("plugin configured with a nil SecureConfig")
} else {
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 {
@@ -582,26 +692,62 @@ func (c *Client) Start() (addr net.Addr, err error) {
}
}
c.logger.Debug("starting plugin", "path", cmd.Path, "args", cmd.Args)
err = cmd.Start()
if err != nil {
return
if c.config.UnixSocketConfig != nil {
c.unixSocketCfg = *c.config.UnixSocketConfig
}
// Set the process
c.process = cmd.Process
c.logger.Debug("plugin started", "path", cmd.Path, "pid", c.process.Pid)
if c.unixSocketCfg.Group != "" {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", EnvUnixSocketGroup, c.unixSocketCfg.Group))
}
var runner runner.Runner
switch {
case c.config.RunnerFunc != nil:
c.unixSocketCfg.socketDir, err = os.MkdirTemp(c.unixSocketCfg.TempDir, "plugin-dir")
if err != nil {
return nil, err
}
// os.MkdirTemp creates folders with 0o700, so if we have a group
// configured we need to make it group-writable.
if c.unixSocketCfg.Group != "" {
err = setGroupWritable(c.unixSocketCfg.socketDir, c.unixSocketCfg.Group, 0o770)
if err != nil {
return nil, err
}
}
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", EnvUnixSocketDir, c.unixSocketCfg.socketDir))
c.logger.Trace("created temporary directory for unix sockets", "dir", c.unixSocketCfg.socketDir)
runner, err = c.config.RunnerFunc(c.logger, cmd, c.unixSocketCfg.socketDir)
if err != nil {
return nil, err
}
default:
runner, err = cmdrunner.NewCmdRunner(c.logger, cmd)
if err != nil {
return nil, err
}
}
c.runner = runner
startCtx, startCtxCancel := context.WithTimeout(context.Background(), c.config.StartTimeout)
defer startCtxCancel()
err = runner.Start(startCtx)
if err != nil {
return nil, err
}
// Make sure the command is properly cleaned up if there is an error
defer func() {
r := recover()
rErr := recover()
if err != nil || r != nil {
cmd.Process.Kill()
if err != nil || rErr != nil {
runner.Kill(context.Background())
}
if r != nil {
panic(r)
if rErr != nil {
panic(rErr)
}
}()
@@ -612,7 +758,7 @@ func (c *Client) Start() (addr net.Addr, err error) {
c.clientWaitGroup.Add(1)
c.stderrWaitGroup.Add(1)
// logStderr calls Done()
go c.logStderr(cmdStderr)
go c.logStderr(runner.Name(), runner.Stderr())
c.clientWaitGroup.Add(1)
go func() {
@@ -621,29 +767,17 @@ func (c *Client) Start() (addr net.Addr, err error) {
defer c.clientWaitGroup.Done()
// get the cmd info early, since the process information will be removed
// in Kill.
pid := c.process.Pid
path := cmd.Path
// wait to finish reading from stderr since the stderr pipe reader
// will be closed by the subsequent call to cmd.Wait().
c.stderrWaitGroup.Wait()
// Wait for the command to end.
err := cmd.Wait()
msgArgs := []interface{}{
"path", path,
"pid", pid,
}
err := runner.Wait(context.Background())
if err != nil {
msgArgs = append(msgArgs,
[]interface{}{"error", err.Error()}...)
c.logger.Error("plugin process exited", msgArgs...)
c.logger.Error("plugin process exited", "plugin", runner.Name(), "id", runner.ID(), "error", err.Error())
} else {
// Log and make sure to flush the logs right away
c.logger.Info("plugin process exited", msgArgs...)
c.logger.Info("plugin process exited", "plugin", runner.Name(), "id", runner.ID())
}
os.Stderr.Sync()
@@ -662,10 +796,13 @@ func (c *Client) Start() (addr net.Addr, err error) {
defer c.clientWaitGroup.Done()
defer close(linesCh)
scanner := bufio.NewScanner(cmdStdout)
scanner := bufio.NewScanner(runner.Stdout())
for scanner.Scan() {
linesCh <- scanner.Text()
}
if scanner.Err() != nil {
c.logger.Error("error encountered while scanning stdout", "error", scanner.Err())
}
}()
// Make sure after we exit we read the lines from stdout forever
@@ -685,22 +822,27 @@ func (c *Client) Start() (addr net.Addr, err error) {
timeout := time.After(c.config.StartTimeout)
// Start looking for the address
c.logger.Debug("waiting for RPC address", "path", cmd.Path)
c.logger.Debug("waiting for RPC address", "plugin", runner.Name())
select {
case <-timeout:
err = errors.New("timeout while waiting for plugin to start")
case <-c.doneCtx.Done():
err = errors.New("plugin exited before we could connect")
case line := <-linesCh:
case line, ok := <-linesCh:
// Trim the line and split by "|" in order to get the parts of
// the output.
line = strings.TrimSpace(line)
parts := strings.SplitN(line, "|", 6)
parts := strings.Split(line, "|")
if len(parts) < 4 {
err = fmt.Errorf(
"Unrecognized remote plugin message: %s\n\n"+
"This usually means that the plugin is either invalid or simply\n"+
"needs to be recompiled to support the latest protocol.", line)
errText := fmt.Sprintf("Unrecognized remote plugin message: %s", line)
if !ok {
errText += "\n" + "Failed to read any lines from plugin's stdout"
}
additionalNotes := runner.Diagnose(context.Background())
if additionalNotes != "" {
errText += "\n" + additionalNotes
}
err = errors.New(errText)
return
}
@@ -735,13 +877,18 @@ func (c *Client) Start() (addr net.Addr, err error) {
c.negotiatedVersion = version
c.logger.Debug("using plugin", "version", version)
switch parts[2] {
network, address, err := runner.PluginToHost(parts[2], parts[3])
if err != nil {
return addr, err
}
switch network {
case "tcp":
addr, err = net.ResolveTCPAddr("tcp", parts[3])
addr, err = net.ResolveTCPAddr("tcp", address)
case "unix":
addr, err = net.ResolveUnixAddr("unix", parts[3])
addr, err = net.ResolveUnixAddr("unix", address)
default:
err = fmt.Errorf("Unknown address type: %s", parts[3])
err = fmt.Errorf("Unknown address type: %s", address)
}
// If we have a server type, then record that. We default to net/rpc
@@ -773,6 +920,18 @@ func (c *Client) Start() (addr net.Addr, err error) {
return nil, fmt.Errorf("error parsing server cert: %s", err)
}
}
if c.config.GRPCBrokerMultiplex && c.protocol == ProtocolGRPC {
if len(parts) <= 6 {
return nil, fmt.Errorf("%w; for Go plugins, you will need to update the "+
"github.com/hashicorp/go-plugin dependency and recompile", ErrGRPCBrokerMuxNotSupported)
}
if muxSupported, err := strconv.ParseBool(parts[6]); err != nil {
return nil, fmt.Errorf("error parsing %q as a boolean for gRPC broker multiplexing support", parts[6])
} else if !muxSupported {
return nil, ErrGRPCBrokerMuxNotSupported
}
}
}
c.address = addr
@@ -802,39 +961,30 @@ func (c *Client) loadServerCert(cert string) error {
}
func (c *Client) reattach() (net.Addr, error) {
// Verify the process still exists. If not, then it is an error
p, err := os.FindProcess(c.config.Reattach.Pid)
if err != nil {
// On Unix systems, FindProcess never returns an error.
// On Windows, for non-existent pids it returns:
// os.SyscallError - 'OpenProcess: the paremter is incorrect'
return nil, ErrProcessNotFound
reattachFunc := c.config.Reattach.ReattachFunc
// For backwards compatibility default to cmdrunner.ReattachFunc
if reattachFunc == nil {
reattachFunc = cmdrunner.ReattachFunc(c.config.Reattach.Pid, c.config.Reattach.Addr)
}
// Attempt to connect to the addr since on Unix systems FindProcess
// doesn't actually return an error if it can't find the process.
conn, err := net.Dial(
c.config.Reattach.Addr.Network(),
c.config.Reattach.Addr.String())
r, err := reattachFunc()
if err != nil {
p.Kill()
return nil, ErrProcessNotFound
return nil, err
}
conn.Close()
// Create a context for when we kill
c.doneCtx, c.ctxCancel = context.WithCancel(context.Background())
c.clientWaitGroup.Add(1)
// Goroutine to mark exit status
go func(pid int) {
go func(r runner.AttachedRunner) {
defer c.clientWaitGroup.Done()
// ensure the context is cancelled when we're done
defer c.ctxCancel()
// Wait for the process to die
pidWait(pid)
r.Wait(context.Background())
// Log so we can see it
c.logger.Debug("reattached plugin process exited")
@@ -843,7 +993,7 @@ func (c *Client) reattach() (net.Addr, error) {
c.l.Lock()
defer c.l.Unlock()
c.exited = true
}(p.Pid)
}(r)
// Set the address and protocol
c.address = c.config.Reattach.Addr
@@ -855,13 +1005,12 @@ func (c *Client) reattach() (net.Addr, error) {
if c.config.Reattach.Test {
c.negotiatedVersion = c.config.Reattach.ProtocolVersion
}
// If we're in test mode, we do NOT set the process. This avoids the
// process being killed (the only purpose we have for c.process), since
// in test mode the process is responsible for exiting on its own.
if !c.config.Reattach.Test {
c.process = p
} else {
// If we're in test mode, we do NOT set the runner. This avoids the
// runner being killed (the only purpose we have for setting c.runner
// when reattaching), since in test mode the process is responsible for
// exiting on its own.
c.runner = r
}
return c.address, nil
@@ -900,6 +1049,9 @@ func (c *Client) checkProtoVersion(protoVersion string) (int, PluginSet, error)
//
// If this returns nil then the process hasn't been started yet. Please
// call Start or Client before calling this.
//
// Clients who specified a RunnerFunc will need to populate their own
// ReattachFunc in the returned ReattachConfig before it can be used.
func (c *Client) ReattachConfig() *ReattachConfig {
c.l.Lock()
defer c.l.Unlock()
@@ -917,11 +1069,16 @@ func (c *Client) ReattachConfig() *ReattachConfig {
return c.config.Reattach
}
return &ReattachConfig{
reattach := &ReattachConfig{
Protocol: c.protocol,
Addr: c.address,
Pid: c.config.Cmd.Process.Pid,
}
if c.config.Cmd != nil && c.config.Cmd.Process != nil {
reattach.Pid = c.config.Cmd.Process.Pid
}
return reattach
}
// Protocol returns the protocol of server on the remote end. This will
@@ -957,11 +1114,24 @@ func netAddrDialer(addr net.Addr) func(string, time.Duration) (net.Conn, error)
// dialer is compatible with grpc.WithDialer and creates the connection
// to the plugin.
func (c *Client) dialer(_ string, timeout time.Duration) (net.Conn, error) {
conn, err := netAddrDialer(c.address)("", timeout)
muxer, err := c.getGRPCMuxer(c.address)
if err != nil {
return nil, err
}
var conn net.Conn
if muxer.Enabled() {
conn, err = muxer.Dial()
if err != nil {
return nil, err
}
} else {
conn, err = netAddrDialer(c.address)("", timeout)
if err != nil {
return nil, err
}
}
// 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 {
@@ -971,14 +1141,28 @@ func (c *Client) dialer(_ string, timeout time.Duration) (net.Conn, error) {
return conn, nil
}
var stdErrBufferSize = 64 * 1024
func (c *Client) getGRPCMuxer(addr net.Addr) (*grpcmux.GRPCClientMuxer, error) {
if c.protocol != ProtocolGRPC || !c.config.GRPCBrokerMultiplex {
return nil, nil
}
func (c *Client) logStderr(r io.Reader) {
var err error
c.grpcMuxerOnce.Do(func() {
c.grpcMuxer, err = grpcmux.NewGRPCClientMuxer(c.logger, addr)
})
if err != nil {
return nil, err
}
return c.grpcMuxer, nil
}
func (c *Client) logStderr(name string, r io.Reader) {
defer c.clientWaitGroup.Done()
defer c.stderrWaitGroup.Done()
l := c.logger.Named(filepath.Base(c.config.Cmd.Path))
l := c.logger.Named(filepath.Base(name))
reader := bufio.NewReaderSize(r, stdErrBufferSize)
reader := bufio.NewReaderSize(r, c.config.PluginLogBufferSize)
// continuation indicates the previous line was a prefix
continuation := false