From 7038c09bc91c7f65ff33afb777187ef9acca214c Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 16 Feb 2021 21:06:10 -0800 Subject: [PATCH] ipn/ipnserver: on darwin, let users who are admins use CLI without sudo Tangentially related to #987, #177, #594, #925, #505 Signed-off-by: Brad Fitzpatrick --- ipn/ipnserver/server.go | 60 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/ipn/ipnserver/server.go b/ipn/ipnserver/server.go index 1273513ae..f7937d39a 100644 --- a/ipn/ipnserver/server.go +++ b/ipn/ipnserver/server.go @@ -21,6 +21,7 @@ import ( "runtime" "strings" "sync" + "sync/atomic" "syscall" "time" @@ -311,16 +312,67 @@ func (s *server) serveConn(ctx context.Context, c net.Conn, logf logger.Logf) { } func isReadonlyConn(c net.Conn, logf logger.Logf) bool { + const ro = true + const rw = false creds, err := peercred.Get(c) if err != nil { - return true // conservatively + logf("connection from unknown peer; read-only") + return ro } uid, ok := creds.UserID() if !ok { - return true // conservatively + logf("connection from peer with unknown userid; read-only") + return ro } - logf("connection from userid %v", uid) - return uid != "0" + if uid == "0" { + logf("connection from userid %v; root has access", uid) + return rw + } + var adminGroupID string + switch runtime.GOOS { + case "darwin": + adminGroupID = darwinAdminGroupID() + default: + logf("connection from userid %v; read-only", uid) + return ro + } + if adminGroupID == "" { + logf("connection from userid %v; no system admin group found, read-only", uid) + return ro + } + u, err := user.LookupId(uid) + if err != nil { + logf("connection from userid %v; failed to look up user; read-only", uid) + return ro + } + gids, err := u.GroupIds() + if err != nil { + logf("connection from userid %v; failed to look up groups; read-only", uid) + return ro + } + for _, gid := range gids { + if gid == adminGroupID { + logf("connection from userid %v; is local admin, has access", uid) + return rw + } + } + logf("connection from userid %v; read-only", uid) + return ro +} + +var darwinAdminGroupIDCache atomic.Value // of string + +func darwinAdminGroupID() string { + s, _ := darwinAdminGroupIDCache.Load().(string) + if s != "" { + return s + } + g, err := user.LookupGroup("admin") + if err != nil { + return "" + } + darwinAdminGroupIDCache.Store(g.Gid) + return g.Gid } // inUseOtherUserError is the error type for when the server is in use