2019-03-01 09:34:36 -05:00
// Package commandconn provides a net.Conn implementation that can be used for
// proxying (or emulating) stream via a custom command.
//
// For example, to provide an http.Client that can connect to a Docker daemon
// running in a Docker container ("DIND"):
//
2022-07-13 06:29:49 -04:00
// httpClient := &http.Client{
// Transport: &http.Transport{
// DialContext: func(ctx context.Context, _network, _addr string) (net.Conn, error) {
// return commandconn.New(ctx, "docker", "exec", "-it", containerID, "docker", "system", "dial-stdio")
// },
// },
// }
2019-03-01 09:34:36 -05:00
package commandconn
import (
"bytes"
"context"
"fmt"
"io"
"net"
"os"
"runtime"
"strings"
"sync"
2023-04-24 04:57:16 -04:00
"sync/atomic"
2019-03-01 09:34:36 -05:00
"syscall"
"time"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
Use golang.org/x/sys/execabs
On Windows, the os/exec.{Command,CommandContext,LookPath} functions
resolve command names that have neither path separators nor file extension
(e.g., "git") by first looking in the current working directory before
looking in the PATH environment variable.
Go maintainers intended to match cmd.exe's historical behavior.
However, this is pretty much never the intended behavior and as an abundance of precaution
this patch prevents that when executing commands.
Example of commands that docker.exe may execute: `git`, `docker-buildx` (or other cli plugin), `docker-credential-wincred`, `docker`.
Note that this was prompted by the [Go 1.15.7 security fixes](https://blog.golang.org/path-security), but unlike in `go.exe`,
the windows path lookups in docker are not in a code path allowing remote code execution, thus there is no security impact on docker.
Signed-off-by: Tibor Vass <tibor@docker.com>
2021-01-25 14:18:54 -05:00
exec "golang.org/x/sys/execabs"
2019-03-01 09:34:36 -05:00
)
// New returns net.Conn
2023-03-30 10:45:07 -04:00
func New ( _ context . Context , cmd string , args ... string ) ( net . Conn , error ) {
2019-03-01 09:34:36 -05:00
var (
c commandConn
err error
)
2022-12-04 22:09:17 -05:00
c . cmd = exec . Command ( cmd , args ... )
2019-03-01 09:34:36 -05:00
// we assume that args never contains sensitive information
logrus . Debugf ( "commandconn: starting %s with %v" , cmd , args )
c . cmd . Env = os . Environ ( )
2019-03-05 01:56:50 -05:00
c . cmd . SysProcAttr = & syscall . SysProcAttr { }
2019-03-01 09:34:36 -05:00
setPdeathsig ( c . cmd )
2019-03-05 01:56:50 -05:00
createSession ( c . cmd )
2019-03-01 09:34:36 -05:00
c . stdin , err = c . cmd . StdinPipe ( )
if err != nil {
return nil , err
}
c . stdout , err = c . cmd . StdoutPipe ( )
if err != nil {
return nil , err
}
c . cmd . Stderr = & stderrWriter {
stderrMu : & c . stderrMu ,
stderr : & c . stderr ,
debugPrefix : fmt . Sprintf ( "commandconn (%s):" , cmd ) ,
}
c . localAddr = dummyAddr { network : "dummy" , s : "dummy-0" }
c . remoteAddr = dummyAddr { network : "dummy" , s : "dummy-1" }
return & c , c . cmd . Start ( )
}
// commandConn implements net.Conn
type commandConn struct {
2023-04-24 04:57:16 -04:00
cmdMutex sync . Mutex // for cmd, cmdWaitErr
cmd * exec . Cmd
cmdWaitErr error
cmdExited atomic . Bool
stdin io . WriteCloser
stdout io . ReadCloser
stderrMu sync . Mutex // for stderr
stderr bytes . Buffer
stdinClosed atomic . Bool
stdoutClosed atomic . Bool
closing atomic . Bool
localAddr net . Addr
remoteAddr net . Addr
2019-03-01 09:34:36 -05:00
}
2023-04-24 04:57:16 -04:00
// kill terminates the process. On Windows it kills the process directly,
// whereas on other platforms, a SIGTERM is sent, before forcefully terminating
// the process after 3 seconds.
func ( c * commandConn ) kill ( ) {
if c . cmdExited . Load ( ) {
return
2019-03-01 09:34:36 -05:00
}
2023-04-24 04:57:16 -04:00
c . cmdMutex . Lock ( )
2019-03-01 09:34:36 -05:00
var werr error
if runtime . GOOS != "windows" {
werrCh := make ( chan error )
2023-04-24 04:57:16 -04:00
go func ( ) { werrCh <- c . cmd . Wait ( ) } ( )
_ = c . cmd . Process . Signal ( syscall . SIGTERM )
2019-03-01 09:34:36 -05:00
select {
case werr = <- werrCh :
case <- time . After ( 3 * time . Second ) :
2023-04-24 04:57:16 -04:00
_ = c . cmd . Process . Kill ( )
2019-03-01 09:34:36 -05:00
werr = <- werrCh
}
} else {
2023-04-24 04:57:16 -04:00
_ = c . cmd . Process . Kill ( )
werr = c . cmd . Wait ( )
2019-03-01 09:34:36 -05:00
}
2023-04-24 04:57:16 -04:00
c . cmdWaitErr = werr
c . cmdMutex . Unlock ( )
c . cmdExited . Store ( true )
2019-03-01 09:34:36 -05:00
}
2023-04-24 04:57:16 -04:00
// handleEOF handles io.EOF errors while reading or writing from the underlying
// command pipes.
//
// When we've received an EOF we expect that the command will
// be terminated soon. As such, we call Wait() on the command
// and return EOF or the error depending on whether the command
// exited with an error.
//
// If Wait() does not return within 10s, an error is returned
func ( c * commandConn ) handleEOF ( err error ) error {
if err != io . EOF {
return err
2019-03-01 09:34:36 -05:00
}
c . cmdMutex . Lock ( )
2023-04-24 04:57:16 -04:00
defer c . cmdMutex . Unlock ( )
var werr error
if c . cmdExited . Load ( ) {
2019-03-01 09:34:36 -05:00
werr = c . cmdWaitErr
} else {
werrCh := make ( chan error )
go func ( ) { werrCh <- c . cmd . Wait ( ) } ( )
select {
case werr = <- werrCh :
c . cmdWaitErr = werr
2023-04-24 04:57:16 -04:00
c . cmdExited . Store ( true )
2019-03-01 09:34:36 -05:00
case <- time . After ( 10 * time . Second ) :
c . stderrMu . Lock ( )
stderr := c . stderr . String ( )
c . stderrMu . Unlock ( )
2023-04-24 04:57:16 -04:00
return errors . Errorf ( "command %v did not exit after %v: stderr=%q" , c . cmd . Args , err , stderr )
2019-03-01 09:34:36 -05:00
}
}
2023-04-24 04:57:16 -04:00
2019-03-01 09:34:36 -05:00
if werr == nil {
2023-04-24 04:57:16 -04:00
return err
2019-03-01 09:34:36 -05:00
}
c . stderrMu . Lock ( )
stderr := c . stderr . String ( )
c . stderrMu . Unlock ( )
return errors . Errorf ( "command %v has exited with %v, please make sure the URL is valid, and Docker 18.09 or later is installed on the remote host: stderr=%s" , c . cmd . Args , werr , stderr )
}
func ignorableCloseError ( err error ) bool {
2023-04-24 04:57:16 -04:00
return strings . Contains ( err . Error ( ) , os . ErrClosed . Error ( ) )
}
func ( c * commandConn ) Read ( p [ ] byte ) ( int , error ) {
n , err := c . stdout . Read ( p )
// check after the call to Read, since
// it is blocking, and while waiting on it
// Close might get called
if c . closing . Load ( ) {
// If we're currently closing the connection
2023-06-30 10:03:19 -04:00
// we don't want to call onEOF
return n , err
2019-03-01 09:34:36 -05:00
}
2023-04-24 04:57:16 -04:00
return n , c . handleEOF ( err )
}
func ( c * commandConn ) Write ( p [ ] byte ) ( int , error ) {
n , err := c . stdin . Write ( p )
// check after the call to Write, since
// it is blocking, and while waiting on it
// Close might get called
if c . closing . Load ( ) {
// If we're currently closing the connection
2023-06-30 10:03:19 -04:00
// we don't want to call onEOF
return n , err
2019-03-01 09:34:36 -05:00
}
2023-04-24 04:57:16 -04:00
return n , c . handleEOF ( err )
2019-03-01 09:34:36 -05:00
}
2023-04-24 04:57:16 -04:00
// CloseRead allows commandConn to implement halfCloser
2019-03-01 09:34:36 -05:00
func ( c * commandConn ) CloseRead ( ) error {
// NOTE: maybe already closed here
if err := c . stdout . Close ( ) ; err != nil && ! ignorableCloseError ( err ) {
2023-04-24 04:57:16 -04:00
return err
2019-03-01 09:34:36 -05:00
}
2023-04-24 04:57:16 -04:00
c . stdoutClosed . Store ( true )
2019-03-01 09:34:36 -05:00
2023-04-24 04:57:16 -04:00
if c . stdinClosed . Load ( ) {
c . kill ( )
2019-03-01 09:34:36 -05:00
}
2023-04-24 04:57:16 -04:00
return nil
2019-03-01 09:34:36 -05:00
}
2023-04-24 04:57:16 -04:00
// CloseWrite allows commandConn to implement halfCloser
2019-03-01 09:34:36 -05:00
func ( c * commandConn ) CloseWrite ( ) error {
// NOTE: maybe already closed here
if err := c . stdin . Close ( ) ; err != nil && ! ignorableCloseError ( err ) {
2023-04-24 04:57:16 -04:00
return err
2019-03-01 09:34:36 -05:00
}
2023-04-24 04:57:16 -04:00
c . stdinClosed . Store ( true )
2019-03-01 09:34:36 -05:00
2023-04-24 04:57:16 -04:00
if c . stdoutClosed . Load ( ) {
c . kill ( )
2019-03-01 09:34:36 -05:00
}
2023-04-24 04:57:16 -04:00
return nil
2019-03-01 09:34:36 -05:00
}
2023-04-24 04:57:16 -04:00
// Close is the net.Conn func that gets called
// by the transport when a dial is cancelled
// due to it's context timing out. Any blocked
// Read or Write calls will be unblocked and
// return errors. It will block until the underlying
// command has terminated.
2019-03-01 09:34:36 -05:00
func ( c * commandConn ) Close ( ) error {
2023-04-24 04:57:16 -04:00
c . closing . Store ( true )
defer c . closing . Store ( false )
if err := c . CloseRead ( ) ; err != nil {
2019-03-01 09:34:36 -05:00
logrus . Warnf ( "commandConn.Close: CloseRead: %v" , err )
2023-04-24 04:57:16 -04:00
return err
2019-03-01 09:34:36 -05:00
}
2023-04-24 04:57:16 -04:00
if err := c . CloseWrite ( ) ; err != nil {
2019-03-01 09:34:36 -05:00
logrus . Warnf ( "commandConn.Close: CloseWrite: %v" , err )
2023-04-24 04:57:16 -04:00
return err
2019-03-01 09:34:36 -05:00
}
2023-04-24 04:57:16 -04:00
return nil
2019-03-01 09:34:36 -05:00
}
func ( c * commandConn ) LocalAddr ( ) net . Addr {
return c . localAddr
}
2022-09-29 11:21:51 -04:00
2019-03-01 09:34:36 -05:00
func ( c * commandConn ) RemoteAddr ( ) net . Addr {
return c . remoteAddr
}
2022-09-29 11:21:51 -04:00
2019-03-01 09:34:36 -05:00
func ( c * commandConn ) SetDeadline ( t time . Time ) error {
logrus . Debugf ( "unimplemented call: SetDeadline(%v)" , t )
return nil
}
2022-09-29 11:21:51 -04:00
2019-03-01 09:34:36 -05:00
func ( c * commandConn ) SetReadDeadline ( t time . Time ) error {
logrus . Debugf ( "unimplemented call: SetReadDeadline(%v)" , t )
return nil
}
2022-09-29 11:21:51 -04:00
2019-03-01 09:34:36 -05:00
func ( c * commandConn ) SetWriteDeadline ( t time . Time ) error {
logrus . Debugf ( "unimplemented call: SetWriteDeadline(%v)" , t )
return nil
}
type dummyAddr struct {
network string
s string
}
func ( d dummyAddr ) Network ( ) string {
return d . network
}
func ( d dummyAddr ) String ( ) string {
return d . s
}
type stderrWriter struct {
stderrMu * sync . Mutex
stderr * bytes . Buffer
debugPrefix string
}
func ( w * stderrWriter ) Write ( p [ ] byte ) ( int , error ) {
logrus . Debugf ( "%s%s" , w . debugPrefix , string ( p ) )
w . stderrMu . Lock ( )
if w . stderr . Len ( ) > 4096 {
w . stderr . Reset ( )
}
n , err := w . stderr . Write ( p )
w . stderrMu . Unlock ( )
return n , err
}