Skip to content

Commit 84bf277

Browse files
Implement getUserInfo function to read environment variables (#24)
* move env calls out of packages * userinfo * Implement getUserInfo function to read environment variables Implemented the getUserInfo function in cli/cli.go to read environment variables and populate the UserInfo struct with appropriate user information for both sudo and non-sudo scenarios. Features: - Detects sudo usage via SUDO_USER environment variable - Retrieves original user information when running under sudo - Parses SUDO_UID and SUDO_GID for proper user credentials - Handles XDG_CONFIG_HOME for config directory determination - Fallback to current user when not running under sudo - Comprehensive error handling with graceful fallbacks Cleanup: - Removed all commented environment variable code from: - namespace/linux.go (sudo user/uid/gid handling) - namespace/macos.go (sudo user/uid handling) - tls/tls.go (commented getConfigDir function) - Centralized all environment variable reading in CLI package All tests pass and code compiles successfully. Co-authored-by: f0ssel <[email protected]> * move code --------- Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com> Co-authored-by: f0ssel <[email protected]>
1 parent 6571ed1 commit 84bf277

File tree

7 files changed

+181
-185
lines changed

7 files changed

+181
-185
lines changed

cli/cli.go

Lines changed: 107 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@ import (
66
"log/slog"
77
"os"
88
"os/signal"
9+
"os/user"
10+
"path/filepath"
11+
"strconv"
912
"strings"
1013
"syscall"
1114

1215
"github.com/coder/jail"
1316
"github.com/coder/jail/audit"
17+
"github.com/coder/jail/namespace"
1418
"github.com/coder/jail/rules"
1519
"github.com/coder/jail/tls"
1620
"github.com/coder/serpent"
@@ -64,35 +68,12 @@ Examples:
6468
}
6569
}
6670

67-
// setupLogging creates a slog logger with the specified level
68-
func setupLogging(logLevel string) *slog.Logger {
69-
var level slog.Level
70-
switch strings.ToLower(logLevel) {
71-
case "error":
72-
level = slog.LevelError
73-
case "warn":
74-
level = slog.LevelWarn
75-
case "info":
76-
level = slog.LevelInfo
77-
case "debug":
78-
level = slog.LevelDebug
79-
default:
80-
level = slog.LevelWarn // Default to warn if invalid level
81-
}
82-
83-
// Create a standard slog logger with the appropriate level
84-
handler := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
85-
Level: level,
86-
})
87-
88-
return slog.New(handler)
89-
}
90-
9171
// Run executes the jail command with the given configuration and arguments
9272
func Run(ctx context.Context, config Config, args []string) error {
9373
ctx, cancel := context.WithCancel(ctx)
9474
defer cancel()
9575
logger := setupLogging(config.LogLevel)
76+
userInfo := getUserInfo()
9677

9778
// Get command arguments
9879
if len(args) == 0 {
@@ -118,7 +99,10 @@ func Run(ctx context.Context, config Config, args []string) error {
11899
auditor := audit.NewLoggingAuditor(logger)
119100

120101
// Create certificate manager
121-
certManager, err := tls.NewCertificateManager(logger)
102+
certManager, err := tls.NewCertificateManager(tls.Config{
103+
Logger: logger,
104+
ConfigDir: userInfo.ConfigDir,
105+
})
122106
if err != nil {
123107
logger.Error("Failed to create certificate manager", "error", err)
124108
return fmt.Errorf("failed to create certificate manager: %v", err)
@@ -173,3 +157,101 @@ func Run(ctx context.Context, config Config, args []string) error {
173157

174158
return nil
175159
}
160+
161+
func getUserInfo() namespace.UserInfo {
162+
// get the user info of the original user even if we are running under sudo
163+
sudoUser := os.Getenv("SUDO_USER")
164+
165+
// If running under sudo, get original user information
166+
if sudoUser != "" {
167+
user, err := user.Lookup(sudoUser)
168+
if err != nil {
169+
// Fallback to current user if lookup fails
170+
return getCurrentUserInfo()
171+
}
172+
173+
// Parse SUDO_UID and SUDO_GID
174+
uid := 0
175+
gid := 0
176+
177+
if sudoUID := os.Getenv("SUDO_UID"); sudoUID != "" {
178+
if parsedUID, err := strconv.Atoi(sudoUID); err == nil {
179+
uid = parsedUID
180+
}
181+
}
182+
183+
if sudoGID := os.Getenv("SUDO_GID"); sudoGID != "" {
184+
if parsedGID, err := strconv.Atoi(sudoGID); err == nil {
185+
gid = parsedGID
186+
}
187+
}
188+
189+
configDir := getConfigDir(user.HomeDir)
190+
191+
return namespace.UserInfo{
192+
Username: sudoUser,
193+
Uid: uid,
194+
Gid: gid,
195+
HomeDir: user.HomeDir,
196+
ConfigDir: configDir,
197+
}
198+
}
199+
200+
// Not running under sudo, use current user
201+
return getCurrentUserInfo()
202+
}
203+
204+
// setupLogging creates a slog logger with the specified level
205+
func setupLogging(logLevel string) *slog.Logger {
206+
var level slog.Level
207+
switch strings.ToLower(logLevel) {
208+
case "error":
209+
level = slog.LevelError
210+
case "warn":
211+
level = slog.LevelWarn
212+
case "info":
213+
level = slog.LevelInfo
214+
case "debug":
215+
level = slog.LevelDebug
216+
default:
217+
level = slog.LevelWarn // Default to warn if invalid level
218+
}
219+
220+
// Create a standard slog logger with the appropriate level
221+
handler := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
222+
Level: level,
223+
})
224+
225+
return slog.New(handler)
226+
}
227+
228+
// getCurrentUserInfo gets information for the current user
229+
func getCurrentUserInfo() namespace.UserInfo {
230+
currentUser, err := user.Current()
231+
if err != nil {
232+
// Fallback with empty values if we can't get user info
233+
return namespace.UserInfo{}
234+
}
235+
236+
uid, _ := strconv.Atoi(currentUser.Uid)
237+
gid, _ := strconv.Atoi(currentUser.Gid)
238+
239+
configDir := getConfigDir(currentUser.HomeDir)
240+
241+
return namespace.UserInfo{
242+
Username: currentUser.Username,
243+
Uid: uid,
244+
Gid: gid,
245+
HomeDir: currentUser.HomeDir,
246+
ConfigDir: configDir,
247+
}
248+
}
249+
250+
// getConfigDir determines the config directory based on XDG_CONFIG_HOME or fallback
251+
func getConfigDir(homeDir string) string {
252+
// Use XDG_CONFIG_HOME if set, otherwise fallback to ~/.config/coder_jail
253+
if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" {
254+
return filepath.Join(xdgConfigHome, "coder_jail")
255+
}
256+
return filepath.Join(homeDir, ".config", "coder_jail")
257+
}

jail

13.8 MB
Binary file not shown.

namespace/linux.go

Lines changed: 13 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import (
77
"log/slog"
88
"os"
99
"os/exec"
10-
"os/user"
11-
"strconv"
1210
"strings"
1311
"syscall"
1412
"time"
@@ -23,6 +21,10 @@ type Linux struct {
2321
procAttr *syscall.SysProcAttr
2422
httpProxyPort int
2523
httpsProxyPort int
24+
user string
25+
homeDir string
26+
uid int
27+
gid int
2628
}
2729

2830
// NewLinux creates a new Linux network jail instance
@@ -84,42 +86,17 @@ func (l *Linux) Start() error {
8486
}
8587
}
8688

87-
// When running under sudo, restore essential user environment variables
88-
sudoUser := os.Getenv("SUDO_USER")
89-
if sudoUser != "" {
90-
user, err := user.Lookup(sudoUser)
91-
if err == nil {
92-
// Set HOME to original user's home directory
93-
l.preparedEnv["HOME"] = user.HomeDir
94-
// Set USER to original username
95-
l.preparedEnv["USER"] = sudoUser
96-
// Set LOGNAME to original username (some tools check this instead of USER)
97-
l.preparedEnv["LOGNAME"] = sudoUser
98-
l.logger.Debug("Restored user environment", "home", user.HomeDir, "user", sudoUser)
99-
}
100-
}
89+
// Set HOME to original user's home directory
90+
l.preparedEnv["HOME"] = l.homeDir
91+
// Set USER to original username
92+
l.preparedEnv["USER"] = l.user
93+
// Set LOGNAME to original username (some tools check this instead of USER)
94+
l.preparedEnv["LOGNAME"] = l.user
10195

102-
// Prepare process credentials once during setup
103-
l.logger.Debug("Preparing process credentials")
104-
var gid, uid int
105-
sudoUID := os.Getenv("SUDO_UID")
106-
if sudoUID != "" {
107-
uid, err = strconv.Atoi(sudoUID)
108-
if err != nil {
109-
l.logger.Warn("Invalid SUDO_UID, subprocess will run as root", "sudo_uid", sudoUID, "error", err)
110-
}
111-
}
112-
sudoGID := os.Getenv("SUDO_GID")
113-
if sudoGID != "" {
114-
gid, err = strconv.Atoi(sudoGID)
115-
if err != nil {
116-
l.logger.Warn("Invalid SUDO_GID, subprocess will run as root", "sudo_gid", sudoGID, "error", err)
117-
}
118-
}
11996
l.procAttr = &syscall.SysProcAttr{
12097
Credential: &syscall.Credential{
121-
Uid: uint32(uid),
122-
Gid: uint32(gid),
98+
Uid: uint32(l.uid),
99+
Gid: uint32(l.gid),
123100
},
124101
}
125102

@@ -303,4 +280,4 @@ func (l *Linux) removeNamespace() error {
303280
return fmt.Errorf("failed to remove namespace: %v", err)
304281
}
305282
return nil
306-
}
283+
}

namespace/macos.go

Lines changed: 19 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"log/slog"
88
"os"
99
"os/exec"
10-
"os/user"
1110
"strconv"
1211
"strings"
1312
"syscall"
@@ -20,14 +19,15 @@ const (
2019

2120
// MacOSNetJail implements network jail using macOS PF (Packet Filter) and group-based isolation
2221
type MacOSNetJail struct {
23-
groupID int
22+
restrictedGid int
2423
pfRulesPath string
2524
mainRulesPath string
2625
logger *slog.Logger
2726
preparedEnv map[string]string
2827
procAttr *syscall.SysProcAttr
2928
httpProxyPort int
3029
httpsProxyPort int
30+
userInfo UserInfo
3131
}
3232

3333
// NewMacOS creates a new macOS network jail instance
@@ -49,6 +49,7 @@ func NewMacOS(config Config) (*MacOSNetJail, error) {
4949
preparedEnv: preparedEnv,
5050
httpProxyPort: config.HttpProxyPort,
5151
httpsProxyPort: config.HttpsProxyPort,
52+
userInfo: config.UserInfo,
5253
}, nil
5354
}
5455

@@ -83,47 +84,23 @@ func (m *MacOSNetJail) Start() error {
8384
}
8485
}
8586

86-
// When running under sudo, restore essential user environment variables
87-
sudoUser := os.Getenv("SUDO_USER")
88-
if sudoUser != "" {
89-
user, err := user.Lookup(sudoUser)
90-
if err == nil {
91-
// Set HOME to original user's home directory
92-
m.preparedEnv["HOME"] = user.HomeDir
93-
// Set USER to original username
94-
m.preparedEnv["USER"] = sudoUser
95-
// Set LOGNAME to original username (some tools check this instead of USER)
96-
m.preparedEnv["LOGNAME"] = sudoUser
97-
m.logger.Debug("Restored user environment", "home", user.HomeDir, "user", sudoUser)
98-
}
99-
}
87+
// Set HOME to original user's home directory
88+
m.preparedEnv["HOME"] = m.userInfo.HomeDir
89+
// Set USER to original username
90+
m.preparedEnv["USER"] = m.userInfo.Username
91+
// Set LOGNAME to original username (some tools check this instead of USER)
92+
m.preparedEnv["LOGNAME"] = m.userInfo.Username
10093

10194
// Prepare process credentials once during setup
10295
m.logger.Debug("Preparing process credentials")
96+
// Use original user ID but KEEP the jail group for network isolation
10397
procAttr := &syscall.SysProcAttr{
10498
Credential: &syscall.Credential{
105-
Gid: uint32(m.groupID),
99+
Uid: uint32(m.userInfo.Uid),
100+
Gid: uint32(m.restrictedGid),
106101
},
107102
}
108103

109-
// Drop privileges to original user if running under sudo
110-
sudoUID := os.Getenv("SUDO_UID")
111-
if sudoUID != "" {
112-
uid, err := strconv.Atoi(sudoUID)
113-
if err != nil {
114-
m.logger.Warn("Invalid SUDO_UID, subprocess will run as root", "sudo_uid", sudoUID, "error", err)
115-
} else {
116-
// Use original user ID but KEEP the jail group for network isolation
117-
procAttr = &syscall.SysProcAttr{
118-
Credential: &syscall.Credential{
119-
Uid: uint32(uid),
120-
Gid: uint32(m.groupID), // Keep jail group, not original user's group
121-
},
122-
}
123-
m.logger.Debug("Dropping privileges to original user with jail group", "uid", uid, "jail_gid", m.groupID)
124-
}
125-
}
126-
127104
// Store prepared process attributes for use in Command method
128105
m.procAttr = procAttr
129106

@@ -136,7 +113,7 @@ func (m *MacOSNetJail) Command(command []string) *exec.Cmd {
136113
m.logger.Debug("Command called", "command", command)
137114

138115
// Create command directly (no sg wrapper needed)
139-
m.logger.Debug("Creating command with group membership", "groupID", m.groupID)
116+
m.logger.Debug("Creating command with group membership", "groupID", m.restrictedGid)
140117
cmd := exec.Command(command[0], command[1:]...)
141118
m.logger.Debug("Full command args", "args", command)
142119

@@ -190,7 +167,7 @@ func (m *MacOSNetJail) ensureGroup() error {
190167
if err != nil {
191168
return fmt.Errorf("failed to parse GID: %v", err)
192169
}
193-
m.groupID = gid
170+
m.restrictedGid = gid
194171
return nil
195172
}
196173
}
@@ -219,7 +196,7 @@ func (m *MacOSNetJail) ensureGroup() error {
219196
if err != nil {
220197
return fmt.Errorf("failed to parse GID: %v", err)
221198
}
222-
m.groupID = gid
199+
m.restrictedGid = gid
223200
return nil
224201
}
225202
}
@@ -276,15 +253,15 @@ pass out on %s route-to (lo0 127.0.0.1) inet proto tcp from any to any group %d
276253
# Allow all loopback traffic
277254
pass on lo0 all
278255
`,
279-
m.groupID,
256+
m.restrictedGid,
280257
iface,
281258
m.httpsProxyPort, // Use HTTPS proxy port for all TCP traffic
282-
m.groupID,
259+
m.restrictedGid,
283260
iface,
284-
m.groupID,
261+
m.restrictedGid,
285262
)
286263

287-
m.logger.Debug("Comprehensive TCP jailing enabled for macOS", "group_id", m.groupID, "proxy_port", m.httpsProxyPort)
264+
m.logger.Debug("Comprehensive TCP jailing enabled for macOS", "group_id", m.restrictedGid, "proxy_port", m.httpsProxyPort)
288265
return rules, nil
289266
}
290267

namespace/name.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package namespace
2+
3+
import (
4+
"fmt"
5+
"time"
6+
)
7+
8+
const (
9+
prefix = "coder_jail"
10+
)
11+
12+
func newNamespaceName() string {
13+
return fmt.Sprintf("%s_%d", prefix, time.Now().UnixNano()%10000000)
14+
}

0 commit comments

Comments
 (0)