mirror of
https://github.com/bolkedebruin/rdpgw.git
synced 2026-05-13 19:59:59 +00:00
Restrict the rdpgw-auth socket to its own UID by default (#190)
The auth daemon's gRPC socket was world-writable and accepted any local UID that could connect to it. On a multi-tenant host any user on the box could speak the gRPC API and run an arbitrary username/ password through PAM -- effectively an unauthenticated PAM oracle. Create the socket with mode 0660 (Umask(0117)) and gate Accept on SO_PEERCRED: only the daemon's own UID is allowed by default, plus any operator-supplied --allow-uid / --allow-gid. Privilege-separated deployments (rdpgw and rdpgw-auth as different users) need to list the gateway's UID, or share a group; the existing path otherwise would have been permissive. The peer-credentials check is Linux-only; the non-Linux build keeps the listener as-is and logs a warning, since rdpgw-auth itself requires libpam and is effectively Linux-only in practice.
This commit is contained in:
89
cmd/auth/peercred_linux.go
Normal file
89
cmd/auth/peercred_linux.go
Normal file
@@ -0,0 +1,89 @@
|
||||
//go:build linux
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// gatedListener wraps a unix-socket net.Listener and accepts only
|
||||
// connections whose peer UID/GID is on the allow-list. The check is
|
||||
// applied at Accept(), before any application data is read, so the
|
||||
// gRPC server never sees an unauthorized caller.
|
||||
type gatedListener struct {
|
||||
net.Listener
|
||||
allowedUIDs map[uint32]struct{}
|
||||
allowedGIDs map[uint32]struct{}
|
||||
}
|
||||
|
||||
func newGatedListener(l net.Listener, uids []int, gids []int) net.Listener {
|
||||
uidSet := make(map[uint32]struct{}, len(uids))
|
||||
for _, u := range uids {
|
||||
uidSet[uint32(u)] = struct{}{}
|
||||
}
|
||||
gidSet := make(map[uint32]struct{}, len(gids))
|
||||
for _, g := range gids {
|
||||
gidSet[uint32(g)] = struct{}{}
|
||||
}
|
||||
return &gatedListener{Listener: l, allowedUIDs: uidSet, allowedGIDs: gidSet}
|
||||
}
|
||||
|
||||
func (l *gatedListener) Accept() (net.Conn, error) {
|
||||
for {
|
||||
conn, err := l.Listener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ucred, err := peerCred(conn)
|
||||
if err != nil {
|
||||
log.Printf("rejecting connection: cannot read peer credentials: %s", err)
|
||||
conn.Close()
|
||||
continue
|
||||
}
|
||||
if !l.allowed(ucred) {
|
||||
log.Printf("rejecting connection from uid=%d gid=%d pid=%d", ucred.Uid, ucred.Gid, ucred.Pid)
|
||||
conn.Close()
|
||||
continue
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (l *gatedListener) allowed(c *unix.Ucred) bool {
|
||||
if _, ok := l.allowedUIDs[c.Uid]; ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := l.allowedGIDs[c.Gid]; ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func peerCred(conn net.Conn) (*unix.Ucred, error) {
|
||||
uc, ok := conn.(*net.UnixConn)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("connection is not a *net.UnixConn (got %T)", conn)
|
||||
}
|
||||
raw, err := uc.SyscallConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
ucred *unix.Ucred
|
||||
credErr error
|
||||
)
|
||||
ctrlErr := raw.Control(func(fd uintptr) {
|
||||
ucred, credErr = unix.GetsockoptUcred(int(fd), unix.SOL_SOCKET, unix.SO_PEERCRED)
|
||||
})
|
||||
if ctrlErr != nil {
|
||||
return nil, ctrlErr
|
||||
}
|
||||
if credErr != nil {
|
||||
return nil, credErr
|
||||
}
|
||||
return ucred, nil
|
||||
}
|
||||
Reference in New Issue
Block a user