diff --git a/go.mod b/go.mod index edd9bf2..22aa9fd 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/hashicorp/go-plugin +module github.com/james-barrow/go-plugin go 1.24 @@ -13,6 +13,7 @@ require ( ) require ( + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/bufbuild/protocompile v0.14.1 // indirect github.com/fatih/color v1.13.0 // indirect github.com/mattn/go-colorable v0.1.12 // indirect diff --git a/go.sum b/go.sum index dc44317..0d96df9 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/server.go b/server.go index 0f4b8e5..490522f 100644 --- a/server.go +++ b/server.go @@ -8,7 +8,6 @@ import ( "crypto/tls" "crypto/x509" "encoding/base64" - "errors" "fmt" "io" "net" @@ -525,13 +524,15 @@ func Serve(opts *ServeConfig) { } } -func serverListener(unixSocketCfg UnixSocketConfig) (net.Listener, error) { - if runtime.GOOS == "windows" { - return serverListener_tcp() - } +//func serverListener(unixSocketCfg UnixSocketConfig) (net.Listener, error) { +//if runtime.GOOS == "windows" { +// return serverListener_tcp() +//} - return serverListener_unix(unixSocketCfg) -} +// return serverListener_unix(unixSocketCfg) +//} + +/* func serverListener_tcp() (net.Listener, error) { envMinPort := os.Getenv("PLUGIN_MIN_PORT") @@ -565,17 +566,60 @@ func serverListener_tcp() (net.Listener, error) { } for port := minPort; port <= maxPort; port++ { - address := fmt.Sprintf("127.0.0.1:%d", port) - listener, err := net.Listen("tcp", address) + //address := fmt.Sprintf("127.0.0.1:%d", port) + //listener, err := net.Listen("tcp", address) + //listener, err := secureListen(c.pipeName) + if err == nil { - return listener, nil + return nil, nil } } return nil, errors.New("couldn't bind plugin TCP listener") } -func serverListener_unix(unixSocketCfg UnixSocketConfig) (net.Listener, error) { +*/ + +func serverListener(unixSocketCfg UnixSocketConfig) (net.Listener, error) { + switch runtime.GOOS { + case "windows": + // Windows: use a named pipe — no temp file needed + name := fmt.Sprintf("go-plugin-%d", os.Getpid()) + return secureListenWindows(name) + + default: + // Unix: create a temp file to get a unique path, then replace with socket + tf, err := os.CreateTemp(unixSocketCfg.socketDir, "plugin") + if err != nil { + return nil, err + } + path := tf.Name() + if err := tf.Close(); err != nil { + return nil, err + } + if err := os.Remove(path); err != nil { + return nil, err + } + + l, err := secureListenUnix(path) + if err != nil { + return nil, err + } + + // set group permissions if configured + if unixSocketCfg.Group != "" { + if err = setGroupWritable(path, unixSocketCfg.Group, 0o660); err != nil { + return nil, err + } + } + + // wrap so socket file is deleted on close + return newDeleteFileListener(l, path), nil + } +} + +/* +func serverListener(unixSocketCfg UnixSocketConfig) (net.Listener, error) { tf, err := os.CreateTemp(unixSocketCfg.socketDir, "plugin") if err != nil { return nil, err @@ -591,7 +635,9 @@ func serverListener_unix(unixSocketCfg UnixSocketConfig) (net.Listener, error) { return nil, err } - l, err := net.Listen("unix", path) + l, err := secureListen(path) + + //l, err := net.Listen("unix", path) if err != nil { return nil, err } @@ -609,6 +655,7 @@ func serverListener_unix(unixSocketCfg UnixSocketConfig) (net.Listener, error) { // is removed on close. return newDeleteFileListener(l, path), nil } +*/ func setGroupWritable(path, groupString string, mode os.FileMode) error { groupID, err := strconv.Atoi(groupString) diff --git a/server_unix_test.go b/server_unix_test.go index 6a98448..6f94627 100644 --- a/server_unix_test.go +++ b/server_unix_test.go @@ -25,7 +25,7 @@ func TestUnixSocketGroupPermissions(t *testing.T) { "as name": {group.Name}, } { t.Run(name, func(t *testing.T) { - ln, err := serverListener_unix(UnixSocketConfig{Group: tc.group}) + ln, err := serverListener(UnixSocketConfig{Group: tc.group}) if err != nil { t.Fatal(err) } diff --git a/transport.go b/transport.go new file mode 100644 index 0000000..555f859 --- /dev/null +++ b/transport.go @@ -0,0 +1,32 @@ +// transport.go +package plugin + +import ( + "fmt" + "net" + "os" + "runtime" + "sync" + "syscall" +) + +var umaskMu sync.Mutex + +func secureListen(path string) (net.Listener, error) { + switch runtime.GOOS { + case "windows": + name := fmt.Sprintf("go-plugin-%d", os.Getpid()) + return secureListenWindows(name) + default: + return secureListenUnix(path) + } +} + +func secureListenUnix(path string) (net.Listener, error) { + umaskMu.Lock() + oldUmask := syscall.Umask(0177) + l, err := net.Listen("unix", path) + syscall.Umask(oldUmask) + umaskMu.Unlock() + return l, err +} diff --git a/transport_other.go b/transport_other.go new file mode 100644 index 0000000..ebc2c2b --- /dev/null +++ b/transport_other.go @@ -0,0 +1,14 @@ +// transport_other.go +//go:build !windows + +package plugin + +import "net" + +func secureListenWindows(name string) (net.Listener, error) { + return nil, nil // never called on non-Windows +} + +func pipeName() string { + return "" // never called on non-Windows +} diff --git a/transport_windows.go b/transport_windows.go new file mode 100644 index 0000000..0bc43db --- /dev/null +++ b/transport_windows.go @@ -0,0 +1,27 @@ +// transport_windows.go +//go:build windows + +package plugin + +import ( + "fmt" + "net" + "os" + + winio "github.com/Microsoft/go-winio" +) + +func secureListenWindows(name string) (net.Listener, error) { + pipe := fmt.Sprintf(`\\.\pipe\%s`, name) + return winio.ListenPipe(pipe, &winio.PipeConfig{ + SecurityDescriptor: "D:P(A;;GA;;;OW)", // owner full access only + MessageMode: false, + InputBufferSize: 65536, + OutputBufferSize: 65536, + }) +} + +// pipeName returns the named pipe path for the client to connect to +func pipeName() string { + return fmt.Sprintf(`\\.\pipe\go-plugin-%d`, os.Getpid()) +}