github.com/hernad/nomad@v1.6.112/helper/users/lookup.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package users 5 6 import ( 7 "fmt" 8 "net" 9 "os" 10 "os/user" 11 "strconv" 12 "sync" 13 14 "github.com/hashicorp/go-hclog" 15 "github.com/hashicorp/go-multierror" 16 ) 17 18 var globalCache = newCache() 19 20 // Lookup returns the user.User entry associated with the given username. 21 // 22 // Values are cached up to 1 hour, or 1 minute for failure cases. 23 func Lookup(username string) (*user.User, error) { 24 return globalCache.GetUser(username) 25 } 26 27 // lock is used to serialize all user lookup at the process level, because 28 // some NSS implementations are not concurrency safe 29 var lock sync.Mutex 30 31 // internalLookupUser username while holding a global process lock. 32 func internalLookupUser(username string) (*user.User, error) { 33 lock.Lock() 34 defer lock.Unlock() 35 return user.Lookup(username) 36 } 37 38 // Current returns the current user, acquired while holding a global process 39 // lock. 40 func Current() (*user.User, error) { 41 lock.Lock() 42 defer lock.Unlock() 43 return user.Current() 44 } 45 46 // UIDforUser returns the UID for the specified username or returns an error. 47 // 48 // Will always fail on Windows and Plan 9. 49 func UIDforUser(username string) (int, error) { 50 u, err := Lookup(username) 51 if err != nil { 52 return 0, err 53 } 54 55 uid, err := strconv.Atoi(u.Uid) 56 if err != nil { 57 return 0, fmt.Errorf("error parsing uid: %w", err) 58 } 59 60 return uid, nil 61 } 62 63 // WriteFileFor is like os.WriteFile except if possible it chowns the file to 64 // the specified user (possibly from Task.User) and sets the permissions to 65 // 0o600. 66 // 67 // If chowning fails (either due to OS or Nomad being unprivileged), the file 68 // will be left world readable (0o666). 69 // 70 // On failure a multierror with both the original and fallback errors will be 71 // returned. 72 // 73 // See SocketFileFor if writing a unix socket file. 74 func WriteFileFor(path string, contents []byte, username string) error { 75 // Don't even bother trying to chown to an empty username 76 var origErr error 77 if username != "" { 78 origErr := writeFileFor(path, contents, username) 79 if origErr == nil { 80 // Success! 81 return nil 82 } 83 } 84 85 // Fallback to world readable 86 if err := os.WriteFile(path, contents, 0o666); err != nil { 87 if origErr != nil { 88 // Return both errors 89 return &multierror.Error{ 90 Errors: []error{origErr, err}, 91 } 92 } else { 93 return err 94 } 95 } 96 97 return nil 98 } 99 100 func writeFileFor(path string, contents []byte, username string) error { 101 uid, err := UIDforUser(username) 102 if err != nil { 103 return err 104 } 105 106 if err := os.WriteFile(path, contents, 0o600); err != nil { 107 return err 108 } 109 110 if err := os.Chown(path, uid, -1); err != nil { 111 // Delete the file so that the fallback method properly resets 112 // permissions. 113 _ = os.Remove(path) 114 return err 115 } 116 117 return nil 118 } 119 120 // SocketFileFor creates a unix domain socket file on the specified path and, 121 // if possible, makes it usable by only the specified user. Failing that it 122 // will leave the socket open to all users. Non-fatal errors are logged. 123 // 124 // See WriteFileFor if writing a regular file. 125 func SocketFileFor(logger hclog.Logger, path, username string) (net.Listener, error) { 126 if err := os.RemoveAll(path); err != nil { 127 logger.Warn("error removing socket", "path", path, "error", err) 128 } 129 130 udsln, err := net.Listen("unix", path) 131 if err != nil { 132 return nil, err 133 } 134 135 if username != "" { 136 // Try to set perms on socket file to least privileges. 137 if err := setSocketOwner(path, username); err == nil { 138 // Success! Exit early 139 return udsln, nil 140 } 141 142 // This error is expected to always occur in some environments (Windows, 143 // non-root agents), so don't log above Trace. 144 logger.Trace("failed to set user on socket", "path", path, "user", username, "error", err) 145 } 146 147 // Opportunistic least privileges failed above, so make sure anyone can use 148 // the socket. 149 if err := os.Chmod(path, 0o666); err != nil { 150 logger.Warn("error setting socket permissions", "path", path, "error", err) 151 } 152 153 return udsln, nil 154 } 155 156 func setSocketOwner(path, username string) error { 157 uid, err := UIDforUser(username) 158 if err != nil { 159 return err 160 } 161 162 if err := os.Chown(path, uid, -1); err != nil { 163 return err 164 } 165 166 if err := os.Chmod(path, 0o600); err != nil { 167 // Awkward situation that is hopefully impossible to reach where we could 168 // chown the socket but not change its mode. 169 return err 170 } 171 172 return nil 173 }