exec: Fix incorrect HOME and USER env variables for tasks that have user set (#25859)

Co-authored-by: Tim Gross <tgross@hashicorp.com>
This commit is contained in:
Piotr Kazmierczak
2025-05-16 15:02:45 +02:00
committed by GitHub
parent fd16f80b5a
commit 0fa0624576
7 changed files with 112 additions and 51 deletions

3
.changelog/25859.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:bug
exec: Adjust USER and HOME env vars when user value is set
```

View File

@@ -62,6 +62,16 @@ func RequireLinux(t *testing.T) {
}
}
// RequireCILinux skips tests unless:
// - running on Linux
// - running on GHA
func RequireCILinux(t *testing.T) {
u, _ := user.Current()
if runtime.GOOS != "linux" || u.Username != "runner" {
t.Skip("Test requires Linux and Github CI runner")
}
}
// RequireNotWindows skips tests whenever:
// - running on Windows
func RequireNotWindows(t *testing.T) {

View File

@@ -35,8 +35,6 @@ func withNetworkIsolation(f func() error, _ *drivers.NetworkIsolationSpec) error
return f()
}
func setCmdUser(*exec.Cmd, string) error { return nil }
func (e *UniversalExecutor) ListProcesses() set.Collection[int] {
return procstats.ListByPid(e.childCmd.Process.Pid)
}

View File

@@ -30,6 +30,7 @@ import (
cstructs "github.com/hashicorp/nomad/client/structs"
"github.com/hashicorp/nomad/drivers/shared/capabilities"
"github.com/hashicorp/nomad/drivers/shared/executor/procstats"
"github.com/hashicorp/nomad/helper/users"
"github.com/hashicorp/nomad/helper/uuid"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/hashicorp/nomad/plugins/drivers"
@@ -230,6 +231,14 @@ func (l *LibcontainerExecutor) Launch(command *ExecCommand) (*ProcessState, erro
if command.User != "" {
process.User = command.User
// Override HOME and USER environment variables
u, err := users.Lookup(command.User)
if err != nil {
return nil, err
}
process.Env = append(process.Env, fmt.Sprintf("USER=%s", u.Username))
process.Env = append(process.Env, fmt.Sprintf("HOME=%s", u.HomeDir))
}
l.userProc = process

View File

@@ -830,6 +830,40 @@ func TestExecutor_WorkDir(t *testing.T) {
must.Eq(t, output, workDir)
}
// TestExecutor_UserEnv tests that the USER environment variable is set
// correctly if user is set. We're not testing HOME because that could get
// tricky on GHA runners.
func TestExecutor_UserEnv(t *testing.T) {
t.Parallel()
testutil.RequireCILinux(t)
testutil.ExecCompatible(t)
testExecCmd := testExecutorCommandWithChroot(t)
execCmd, allocDir := testExecCmd.command, testExecCmd.allocDir
execCmd.Cmd = "/bin/bash"
execCmd.Args = []string{"-c", "echo $USER"}
execCmd.User = "runner"
execCmd.ResourceLimits = true
defer allocDir.Destroy()
executor := NewExecutorWithIsolation(testlog.HCLogger(t), compute)
defer executor.Shutdown("SIGKILL", 0)
ps, err := executor.Launch(execCmd)
must.NoError(t, err)
must.NonZero(t, ps.Pid)
state, err := executor.Wait(context.Background())
must.NoError(t, err)
must.Zero(t, state.ExitCode)
_, ok := executor.(*LibcontainerExecutor)
must.True(t, ok)
output := strings.TrimSpace(testExecCmd.stdout.String())
must.Eq(t, output, "runner")
}
func TestExecCommand_getCgroupOr_off(t *testing.T) {
ci.Parallel(t)

View File

@@ -16,7 +16,6 @@ import (
"github.com/hashicorp/nomad/client/lib/cgroupslib"
"github.com/hashicorp/nomad/client/lib/nsutil"
"github.com/hashicorp/nomad/drivers/shared/executor/procstats"
"github.com/hashicorp/nomad/helper/users"
"github.com/hashicorp/nomad/plugins/drivers"
"github.com/opencontainers/runc/libcontainer/cgroups"
"golang.org/x/sys/unix"
@@ -28,54 +27,6 @@ const (
memoryNoLimit = -1
)
// setCmdUser takes a user id as a string and looks up the user, and sets the command
// to execute as that user.
func setCmdUser(cmd *exec.Cmd, userid string) error {
u, err := users.Lookup(userid)
if err != nil {
return fmt.Errorf("failed to identify user %v: %v", userid, err)
}
// Get the groups the user is a part of
gidStrings, err := u.GroupIds()
if err != nil {
return fmt.Errorf("unable to lookup user's group membership: %v", err)
}
gids := make([]uint32, len(gidStrings))
for _, gidString := range gidStrings {
u, err := strconv.ParseUint(gidString, 10, 32)
if err != nil {
return fmt.Errorf("unable to convert user's group to uint32 %s: %v", gidString, err)
}
gids = append(gids, uint32(u))
}
// Convert the uid and gid
uid, err := strconv.ParseUint(u.Uid, 10, 32)
if err != nil {
return fmt.Errorf("unable to convert userid to uint32: %s", err)
}
gid, err := strconv.ParseUint(u.Gid, 10, 32)
if err != nil {
return fmt.Errorf("unable to convert groupid to uint32: %s", err)
}
// Set the command to run as that user and group.
if cmd.SysProcAttr == nil {
cmd.SysProcAttr = &syscall.SysProcAttr{}
}
if cmd.SysProcAttr.Credential == nil {
cmd.SysProcAttr.Credential = &syscall.Credential{}
}
cmd.SysProcAttr.Credential.Uid = uint32(uid)
cmd.SysProcAttr.Credential.Gid = uint32(gid)
cmd.SysProcAttr.Credential.Groups = gids
return nil
}
// setSubCmdCgroup sets the cgroup for non-Task child processes of the
// executor.Executor (since in cg2 it lives outside the task's cgroup)
func (e *UniversalExecutor) setSubCmdCgroup(cmd *exec.Cmd, cgroup string) (func(), error) {

View File

@@ -8,7 +8,11 @@ package executor
import (
"fmt"
"os"
"os/exec"
"strconv"
"syscall"
"github.com/hashicorp/nomad/helper/users"
)
// configure new process group for child process
@@ -51,3 +55,55 @@ func (e *UniversalExecutor) shutdownProcess(sig os.Signal, proc *os.Process) err
return nil
}
// setCmdUser takes a user id as a string and looks up the user, and sets the command
// to execute as that user.
func setCmdUser(cmd *exec.Cmd, userid string) error {
u, err := users.Lookup(userid)
if err != nil {
return fmt.Errorf("failed to identify user %v: %v", userid, err)
}
// Get the groups the user is a part of
gidStrings, err := u.GroupIds()
if err != nil {
return fmt.Errorf("unable to lookup user's group membership: %v", err)
}
gids := make([]uint32, len(gidStrings))
for _, gidString := range gidStrings {
u, err := strconv.ParseUint(gidString, 10, 32)
if err != nil {
return fmt.Errorf("unable to convert user's group to uint32 %s: %v", gidString, err)
}
gids = append(gids, uint32(u))
}
// Convert the uid and gid
uid, err := strconv.ParseUint(u.Uid, 10, 32)
if err != nil {
return fmt.Errorf("unable to convert userid to uint32: %w", err)
}
gid, err := strconv.ParseUint(u.Gid, 10, 32)
if err != nil {
return fmt.Errorf("unable to convert groupid to uint32: %s", err)
}
// Set the command to run as that user and group.
if cmd.SysProcAttr == nil {
cmd.SysProcAttr = &syscall.SysProcAttr{}
}
if cmd.SysProcAttr.Credential == nil {
cmd.SysProcAttr.Credential = &syscall.Credential{}
}
cmd.SysProcAttr.Credential.Uid = uint32(uid)
cmd.SysProcAttr.Credential.Gid = uint32(gid)
cmd.SysProcAttr.Credential.Groups = gids
// Override HOME and USER environment variables
cmd.Env = append(cmd.Env, fmt.Sprintf("USER=%s", u.Username))
cmd.Env = append(cmd.Env, fmt.Sprintf("HOME=%s", u.HomeDir))
return nil
}