mirror of
https://github.com/linka-cloud/grpc.git
synced 2025-12-18 08:53:13 +00:00
feat(server/client): add windows pipe, pipe peer credentials support
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
This commit is contained in:
@@ -2,15 +2,36 @@ package peercreds
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/soheilhy/cmux"
|
||||
"github.com/tailscale/peercred"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
var ErrUnsupportedConnType = peercred.ErrUnsupportedConnType
|
||||
|
||||
var _ credentials.TransportCredentials = (*peerCreds)(nil)
|
||||
|
||||
// Creds are the peer credentials.
|
||||
type Creds struct {
|
||||
pid int
|
||||
uid string
|
||||
}
|
||||
|
||||
func (c *Creds) PID() (pid int, ok bool) {
|
||||
return c.pid, c.pid != 0
|
||||
}
|
||||
|
||||
// UserID returns the userid (or Windows SID) that owns the other side
|
||||
// of the connection, if known. (ok is false if not known)
|
||||
// The returned string is suitable to passing to os/user.LookupId.
|
||||
func (c *Creds) UserID() (uid string, ok bool) {
|
||||
return c.uid, c.uid != ""
|
||||
}
|
||||
|
||||
var common = credentials.CommonAuthInfo{SecurityLevel: credentials.PrivacyAndIntegrity}
|
||||
|
||||
func New() credentials.TransportCredentials {
|
||||
@@ -27,12 +48,12 @@ type peerCreds struct {
|
||||
// AuthInfo we’ll attach to the gRPC peer
|
||||
type AuthInfo struct {
|
||||
credentials.CommonAuthInfo
|
||||
Creds *peercred.Creds
|
||||
Creds Creds
|
||||
}
|
||||
|
||||
func (AuthInfo) AuthType() string { return "peercred" }
|
||||
|
||||
func (t *peerCreds) ClientHandshake(_ context.Context, _ string, conn net.Conn) (net.Conn, credentials.AuthInfo, error) {
|
||||
func (t *peerCreds) ClientHandshake(ctx context.Context, authority string, conn net.Conn) (net.Conn, credentials.AuthInfo, error) {
|
||||
return t.handshakeConn(conn)
|
||||
}
|
||||
|
||||
@@ -54,15 +75,30 @@ func (t *peerCreds) OverrideServerName(name string) error {
|
||||
}
|
||||
|
||||
func (t *peerCreds) handshakeConn(conn net.Conn) (net.Conn, credentials.AuthInfo, error) {
|
||||
if conn.RemoteAddr().Network() != "unix" {
|
||||
return nil, nil, errors.New("peercred only works with unix domain sockets")
|
||||
if conn.RemoteAddr().Network() != "unix" && conn.RemoteAddr().Network() != "pipe" {
|
||||
return nil, nil, errors.New("peercred only works with unix domain sockets or Windows named pipes")
|
||||
}
|
||||
creds, err := peercred.Get(conn)
|
||||
inner := conn
|
||||
unwrap:
|
||||
for {
|
||||
switch c := inner.(type) {
|
||||
case *cmux.MuxConn:
|
||||
inner = c.Conn
|
||||
case *tls.Conn:
|
||||
inner = c.NetConn()
|
||||
default:
|
||||
break unwrap
|
||||
}
|
||||
}
|
||||
creds, err := Get(inner)
|
||||
if err != nil {
|
||||
if errors.Is(err, peercred.ErrNotImplemented) {
|
||||
return nil, nil, errors.New("peercred not implemented on this OS")
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
return conn, AuthInfo{Creds: creds, CommonAuthInfo: common}, nil
|
||||
var c Creds
|
||||
c.uid, _ = creds.UserID()
|
||||
c.pid, _ = creds.PID()
|
||||
return conn, AuthInfo{Creds: c, CommonAuthInfo: common}, nil
|
||||
}
|
||||
|
||||
20
creds/peercreds/peercreds_unix.go
Normal file
20
creds/peercreds/peercreds_unix.go
Normal file
@@ -0,0 +1,20 @@
|
||||
//go:build !windows
|
||||
|
||||
package peercreds
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/tailscale/peercred"
|
||||
)
|
||||
|
||||
func Get(conn net.Conn) (*Creds, error) {
|
||||
creds, err := peercred.Get(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var c Creds
|
||||
c.uid, _ = creds.UserID()
|
||||
c.pid, _ = creds.PID()
|
||||
return &c, nil
|
||||
}
|
||||
134
creds/peercreds/peercreds_windows.go
Normal file
134
creds/peercreds/peercreds_windows.go
Normal file
@@ -0,0 +1,134 @@
|
||||
//go:build windows
|
||||
|
||||
package peercreds
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// Get returns peer creds for the client connected to this server-side pipe
|
||||
// connection. The conn must be a net.Conn returned from go-winio's ListenPipe.
|
||||
func Get(conn net.Conn) (*Creds, error) {
|
||||
if conn == nil {
|
||||
return nil, ErrUnsupportedConnType
|
||||
}
|
||||
|
||||
h, err := winioPipeHandle(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get client PID for this pipe instance.
|
||||
var pid uint32
|
||||
if err := windows.GetNamedPipeClientProcessId(h, &pid); err != nil {
|
||||
return nil, fmt.Errorf("GetNamedPipeClientProcessId: %w", err)
|
||||
}
|
||||
if pid == 0 {
|
||||
return nil, fmt.Errorf("GetNamedPipeClientProcessId returned pid=0")
|
||||
}
|
||||
|
||||
// Open the client process with query rights.
|
||||
const processQueryLimitedInfo = windows.PROCESS_QUERY_LIMITED_INFORMATION
|
||||
ph, err := windows.OpenProcess(processQueryLimitedInfo, false, pid)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("OpenProcess(%d): %w", pid, err)
|
||||
}
|
||||
defer windows.CloseHandle(ph)
|
||||
|
||||
// Open the process token.
|
||||
var token windows.Token
|
||||
if err := windows.OpenProcessToken(ph, windows.TOKEN_QUERY, &token); err != nil {
|
||||
return nil, fmt.Errorf("OpenProcessToken: %w", err)
|
||||
}
|
||||
defer token.Close()
|
||||
|
||||
// Get the token's user SID.
|
||||
tu, err := token.GetTokenUser()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetTokenUser: %w", err)
|
||||
}
|
||||
|
||||
return &Creds{
|
||||
uid: tu.User.Sid.String(),
|
||||
pid: int(pid),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// winioPipeHandle digs the underlying syscall HANDLE out of a go-winio
|
||||
// pipe connection using reflect + unsafe. This depends on the current
|
||||
// internal layout of github.com/Microsoft/go-winio:
|
||||
//
|
||||
// type win32Pipe struct {
|
||||
// *win32File
|
||||
// path string
|
||||
// }
|
||||
//
|
||||
// type win32MessageBytePipe struct {
|
||||
// win32Pipe
|
||||
// writeClosed bool
|
||||
// readEOF bool
|
||||
// }
|
||||
//
|
||||
// type win32File struct {
|
||||
// handle syscall.Handle
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// See pipe.go + file.go in go-winio. :contentReference[oaicite:1]{index=1}
|
||||
func winioPipeHandle(conn net.Conn) (windows.Handle, error) {
|
||||
v := reflect.ValueOf(conn)
|
||||
if !v.IsValid() {
|
||||
return 0, ErrUnsupportedConnType
|
||||
}
|
||||
|
||||
// Peel off interface & pointer layers: net.Conn is an interface and the
|
||||
// concrete type is *win32Pipe or *win32MessageBytePipe.
|
||||
for v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr {
|
||||
if v.IsNil() {
|
||||
return 0, ErrUnsupportedConnType
|
||||
}
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
if v.Kind() != reflect.Struct {
|
||||
return 0, ErrUnsupportedConnType
|
||||
}
|
||||
|
||||
var wfField reflect.Value
|
||||
|
||||
// Case 1: *win32Pipe { *win32File; path string }
|
||||
if f := v.FieldByName("win32File"); f.IsValid() && f.Kind() == reflect.Ptr {
|
||||
wfField = f
|
||||
} else if v.NumField() > 0 {
|
||||
// Case 2: *win32MessageBytePipe { win32Pipe; ... }
|
||||
embedded := v.Field(0)
|
||||
if embedded.IsValid() && embedded.Kind() == reflect.Struct {
|
||||
if f2 := embedded.FieldByName("win32File"); f2.IsValid() && f2.Kind() == reflect.Ptr {
|
||||
wfField = f2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !wfField.IsValid() || wfField.IsNil() {
|
||||
return 0, ErrUnsupportedConnType
|
||||
}
|
||||
|
||||
// wfField is a *win32File. Its first field is "handle syscall.Handle".
|
||||
// We only need the first field, so we define a 1-field header type with
|
||||
// compatible layout and reinterpret the pointer.
|
||||
type win32FileHeader struct {
|
||||
Handle windows.Handle // same underlying type as syscall.Handle
|
||||
}
|
||||
|
||||
ptr := unsafe.Pointer(wfField.Pointer())
|
||||
h := (*win32FileHeader)(ptr).Handle
|
||||
if h == 0 {
|
||||
return 0, fmt.Errorf("winio pipe handle is 0")
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
Reference in New Issue
Block a user