|
| 1 | +// User utilities to create UNIX users and drop root privileges |
| 2 | +package main |
| 3 | + |
| 4 | +import ( |
| 5 | + "fmt" |
| 6 | + log "github.com/sirupsen/logrus" |
| 7 | + "os" |
| 8 | + "os/user" |
| 9 | + "strconv" |
| 10 | + "strings" |
| 11 | + "syscall" |
| 12 | +) |
| 13 | + |
| 14 | +// AddUser adds a UNIX user (e.g., sbx_user1051) to the passwd and shadow files if not already present |
| 15 | +// The actual default values are based on inspecting the AWS Lambda runtime in us-east-1 |
| 16 | +// /etc/group is empty and /etc/gshadow is not accessible in AWS |
| 17 | +// The home directory does not exist in AWS Lambda |
| 18 | +func AddUser(user string, uid int, gid int) { |
| 19 | + // passwd file format: https://www.cyberciti.biz/faq/understanding-etcpasswd-file-format/ |
| 20 | + passwdFile := "/etc/passwd" |
| 21 | + passwdEntry := fmt.Sprintf("%[1]s:x:%[2]v:%[3]v::/home/%[1]s:/sbin/nologin", user, uid, gid) |
| 22 | + if !doesFileContainEntry(passwdFile, passwdEntry) { |
| 23 | + addEntry(passwdFile, passwdEntry) |
| 24 | + } |
| 25 | + // shadow file format: https://www.cyberciti.biz/faq/understanding-etcshadow-file/ |
| 26 | + shadowFile := "/etc/shadow" |
| 27 | + shadowEntry := fmt.Sprintf("%s:*:18313:0:99999:7:::", user) |
| 28 | + if !doesFileContainEntry(shadowFile, shadowEntry) { |
| 29 | + addEntry(shadowFile, shadowEntry) |
| 30 | + } |
| 31 | +} |
| 32 | + |
| 33 | +// doesFileContainEntry returns true if the entry string exists in the given file |
| 34 | +func doesFileContainEntry(file string, entry string) bool { |
| 35 | + data, err := os.ReadFile(file) |
| 36 | + if err != nil { |
| 37 | + log.Warnln("Could not read file:", file, err) |
| 38 | + return false |
| 39 | + } |
| 40 | + text := string(data) |
| 41 | + return strings.Contains(text, entry) |
| 42 | +} |
| 43 | + |
| 44 | +// addEntry appends an entry string to the given file |
| 45 | +func addEntry(file string, entry string) error { |
| 46 | + f, err := os.OpenFile(file, |
| 47 | + os.O_APPEND|os.O_WRONLY, 0644) |
| 48 | + if err != nil { |
| 49 | + log.Errorln("Error opening file:", file, err) |
| 50 | + return err |
| 51 | + } |
| 52 | + defer f.Close() |
| 53 | + if _, err := f.WriteString(entry); err != nil { |
| 54 | + log.Errorln("Error appending entry to file:", file, err) |
| 55 | + return err |
| 56 | + } |
| 57 | + return nil |
| 58 | +} |
| 59 | + |
| 60 | +// IsRootUser returns true if the current process is root and false otherwise. |
| 61 | +func IsRootUser() bool { |
| 62 | + return os.Getuid() == 0 |
| 63 | +} |
| 64 | + |
| 65 | +// UserLogger returns a context logger with user fields. |
| 66 | +func UserLogger() *log.Entry { |
| 67 | + // Skip user lookup at debug level |
| 68 | + if !log.IsLevelEnabled(log.DebugLevel) { |
| 69 | + return log.WithFields(log.Fields{}) |
| 70 | + } |
| 71 | + uid := os.Getuid() |
| 72 | + uidString := strconv.Itoa(uid) |
| 73 | + user, err := user.LookupId(uidString) |
| 74 | + if err != nil { |
| 75 | + log.Warnln("Could not look up user by uid:", uid, err) |
| 76 | + } |
| 77 | + return log.WithFields(log.Fields{ |
| 78 | + "username": user.Username, |
| 79 | + "uid": uid, |
| 80 | + "euid": os.Geteuid(), |
| 81 | + "gid": os.Getgid(), |
| 82 | + }) |
| 83 | +} |
| 84 | + |
| 85 | +// DropPrivileges switches to another UNIX user by dropping root privileges |
| 86 | +// Initially based on https://stackoverflow.com/a/75545491/6875981 |
| 87 | +func DropPrivileges(userToSwitchTo string) error { |
| 88 | + // Lookup user and group IDs for the user we want to switch to. |
| 89 | + userInfo, err := user.Lookup(userToSwitchTo) |
| 90 | + if err != nil { |
| 91 | + log.Errorln("Error looking up user:", userToSwitchTo, err) |
| 92 | + return err |
| 93 | + } |
| 94 | + // Convert group ID and user ID from string to int. |
| 95 | + gid, err := strconv.Atoi(userInfo.Gid) |
| 96 | + if err != nil { |
| 97 | + log.Errorln("Error converting gid:", userInfo.Gid, err) |
| 98 | + return err |
| 99 | + } |
| 100 | + uid, err := strconv.Atoi(userInfo.Uid) |
| 101 | + if err != nil { |
| 102 | + log.Errorln("Error converting uid:", userInfo.Uid, err) |
| 103 | + return err |
| 104 | + } |
| 105 | + |
| 106 | + // Limitation: Debugger gets stuck when stepping over these syscalls! |
| 107 | + // No breakpoints beyond this point are hit. |
| 108 | + // Set group ID (real and effective). |
| 109 | + if err = syscall.Setgid(gid); err != nil { |
| 110 | + log.Errorln("Failed to set group ID:", err) |
| 111 | + return err |
| 112 | + } |
| 113 | + // Set user ID (real and effective). |
| 114 | + if err = syscall.Setuid(uid); err != nil { |
| 115 | + log.Errorln("Failed to set user ID:", err) |
| 116 | + return err |
| 117 | + } |
| 118 | + return nil |
| 119 | +} |
0 commit comments