diff --git a/api/tasks.go b/api/tasks.go index 84765c71a..3c18269ea 100644 --- a/api/tasks.go +++ b/api/tasks.go @@ -84,6 +84,7 @@ type LogConfig struct { type Task struct { Name string Driver string + User string Config map[string]interface{} Constraints []*Constraint Env map[string]string diff --git a/client/driver/docker.go b/client/driver/docker.go index bc198f7fc..e154d8360 100644 --- a/client/driver/docker.go +++ b/client/driver/docker.go @@ -17,7 +17,6 @@ import ( docker "github.com/fsouza/go-dockerclient" "github.com/hashicorp/go-plugin" - "github.com/hashicorp/nomad/client/allocdir" "github.com/hashicorp/nomad/client/config" "github.com/hashicorp/nomad/client/driver/executor" @@ -224,6 +223,7 @@ func (d *DockerDriver) createContainer(ctx *ExecContext, task *structs.Task, config := &docker.Config{ Image: driverConfig.ImageName, Hostname: driverConfig.Hostname, + User: task.User, } hostConfig := &docker.HostConfig{ diff --git a/client/driver/docker_test.go b/client/driver/docker_test.go index 0410855d2..05f2606d9 100644 --- a/client/driver/docker_test.go +++ b/client/driver/docker_test.go @@ -9,6 +9,7 @@ import ( "path/filepath" "reflect" "runtime/debug" + "strings" "testing" "time" @@ -669,3 +670,45 @@ func TestDockerPortsMapping(t *testing.T) { } } } + +func TestDockerUser(t *testing.T) { + t.Parallel() + + task := &structs.Task{ + Name: "redis-demo", + User: "alice", + Config: map[string]interface{}{ + "image": "redis", + "command": "sleep", + "args": []string{"10000"}, + }, + Resources: &structs.Resources{ + MemoryMB: 256, + CPU: 512, + }, + LogConfig: &structs.LogConfig{ + MaxFiles: 10, + MaxFileSizeMB: 10, + }, + } + + if !dockerIsConnected(t) { + t.SkipNow() + } + + driverCtx, execCtx := testDriverContexts(task) + driver := NewDockerDriver(driverCtx) + defer execCtx.AllocDir.Destroy() + + // It should fail because the user "alice" does not exist on the given + // image. + handle, err := driver.Start(execCtx, task) + if err == nil { + handle.Kill() + t.Fatalf("Should've failed") + } + msg := "System error: Unable to find user alice" + if !strings.Contains(err.Error(), msg) { + t.Fatalf("Expecting '%v' in '%v'", msg, err) + } +} diff --git a/client/driver/exec.go b/client/driver/exec.go index 672f0e96c..64bda5e0e 100644 --- a/client/driver/exec.go +++ b/client/driver/exec.go @@ -74,6 +74,7 @@ func (d *ExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil { return nil, err } + // Get the command to be ran command := driverConfig.Command if err := validateCommand(command, "args"); err != nil { @@ -104,12 +105,13 @@ func (d *ExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, AllocDir: ctx.AllocDir, Task: task, } + ps, err := exec.LaunchCmd(&executor.ExecCommand{ Cmd: command, Args: driverConfig.Args, FSIsolation: true, ResourceLimits: true, - User: cstructs.DefaultUnpriviledgedUser, + User: getExecutorUser(task), }, executorCtx) if err != nil { pluginClient.Kill() diff --git a/client/driver/executor/executor.go b/client/driver/executor/executor.go index 904bd3448..0f495aa79 100644 --- a/client/driver/executor/executor.go +++ b/client/driver/executor/executor.go @@ -148,6 +148,7 @@ func (e *UniversalExecutor) LaunchCmd(command *ExecCommand, ctx *ExecutorContext // setting the user of the process if command.User != "" { + e.logger.Printf("[DEBUG] executor: running command as %s", command.User) if err := e.runAs(command.User); err != nil { return nil, err } diff --git a/client/driver/java.go b/client/driver/java.go index d4863df57..937bfffa4 100644 --- a/client/driver/java.go +++ b/client/driver/java.go @@ -169,7 +169,7 @@ func (d *JavaDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, Args: args, FSIsolation: true, ResourceLimits: true, - User: cstructs.DefaultUnpriviledgedUser, + User: getExecutorUser(task), }, executorCtx) if err != nil { pluginClient.Kill() diff --git a/client/driver/qemu.go b/client/driver/qemu.go index 60c3354d3..ef8b488da 100644 --- a/client/driver/qemu.go +++ b/client/driver/qemu.go @@ -195,7 +195,11 @@ func (d *QemuDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, AllocDir: ctx.AllocDir, Task: task, } - ps, err := exec.LaunchCmd(&executor.ExecCommand{Cmd: args[0], Args: args[1:]}, executorCtx) + ps, err := exec.LaunchCmd(&executor.ExecCommand{ + Cmd: args[0], + Args: args[1:], + User: task.User, + }, executorCtx) if err != nil { pluginClient.Kill() return nil, err diff --git a/client/driver/raw_exec.go b/client/driver/raw_exec.go index 2dc0aceeb..ba707bd90 100644 --- a/client/driver/raw_exec.go +++ b/client/driver/raw_exec.go @@ -100,7 +100,12 @@ func (d *RawExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandl AllocDir: ctx.AllocDir, Task: task, } - ps, err := exec.LaunchCmd(&executor.ExecCommand{Cmd: command, Args: driverConfig.Args}, executorCtx) + + ps, err := exec.LaunchCmd(&executor.ExecCommand{ + Cmd: command, + Args: driverConfig.Args, + User: task.User, + }, executorCtx) if err != nil { pluginClient.Kill() return nil, err diff --git a/client/driver/rkt.go b/client/driver/rkt.go index 76a1fa64e..1c44166c3 100644 --- a/client/driver/rkt.go +++ b/client/driver/rkt.go @@ -243,7 +243,11 @@ func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, e return nil, err } - ps, err := execIntf.LaunchCmd(&executor.ExecCommand{Cmd: absPath, Args: cmdArgs}, executorCtx) + ps, err := execIntf.LaunchCmd(&executor.ExecCommand{ + Cmd: absPath, + Args: cmdArgs, + User: task.User, + }, executorCtx) if err != nil { pluginClient.Kill() return nil, err diff --git a/client/driver/utils.go b/client/driver/utils.go index 1bafb8a0d..e0396defb 100644 --- a/client/driver/utils.go +++ b/client/driver/utils.go @@ -14,6 +14,8 @@ import ( "github.com/hashicorp/nomad/client/config" "github.com/hashicorp/nomad/client/driver/executor" "github.com/hashicorp/nomad/client/driver/logging" + cstructs "github.com/hashicorp/nomad/client/driver/structs" + "github.com/hashicorp/nomad/nomad/structs" ) // createExecutor launches an executor plugin and returns an instance of the @@ -146,3 +148,12 @@ func GetAbsolutePath(bin string) (string, error) { return filepath.EvalSymlinks(lp) } + +// getExecutorUser returns the user of the task, defaulting to +// cstructs.DefaultUnprivilegedUser if none was given. +func getExecutorUser(task *structs.Task) string { + if task.User == "" { + return cstructs.DefaultUnpriviledgedUser + } + return task.User +} diff --git a/jobspec/parse.go b/jobspec/parse.go index 1baf29cf6..c94950428 100644 --- a/jobspec/parse.go +++ b/jobspec/parse.go @@ -460,6 +460,7 @@ func parseTasks(jobName string, taskGroupName string, result *[]*structs.Task, l // Check for invalid keys valid := []string{ "driver", + "user", "env", "service", "config", diff --git a/jobspec/parse_test.go b/jobspec/parse_test.go index 4616db771..5e66f3d15 100644 --- a/jobspec/parse_test.go +++ b/jobspec/parse_test.go @@ -88,6 +88,7 @@ func TestParse(t *testing.T) { &structs.Task{ Name: "binstore", Driver: "docker", + User: "bob", Config: map[string]interface{}{ "image": "hashicorp/binstore", }, @@ -148,6 +149,7 @@ func TestParse(t *testing.T) { &structs.Task{ Name: "storagelocker", Driver: "java", + User: "", Config: map[string]interface{}{ "image": "hashicorp/storagelocker", }, diff --git a/jobspec/test-fixtures/basic.hcl b/jobspec/test-fixtures/basic.hcl index fddab3731..ccb20fe1b 100644 --- a/jobspec/test-fixtures/basic.hcl +++ b/jobspec/test-fixtures/basic.hcl @@ -39,6 +39,7 @@ job "binstore-storagelocker" { } task "binstore" { driver = "docker" + user = "bob" config { image = "hashicorp/binstore" } diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index a3fad3ee5..9d6215824 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -1583,6 +1583,10 @@ type Task struct { // Driver is used to control which driver is used Driver string + // User is used to determine which user will run the task. It defaults to + // the same user the Nomad client is being run as. + User string + // Config is provided to the driver to initialize Config map[string]interface{} diff --git a/website/source/docs/jobspec/index.html.md b/website/source/docs/jobspec/index.html.md index 7d277bbc2..4d078c9ef 100644 --- a/website/source/docs/jobspec/index.html.md +++ b/website/source/docs/jobspec/index.html.md @@ -228,6 +228,9 @@ The `task` object supports the following keys: task. See the [driver documentation](/docs/drivers/index.html) for what is available. Examples include `docker`, `qemu`, `java`, and `exec`. +* `user` - Set the user that will run the task. It defaults to the same user + the Nomad client is being run as. + * `constraint` - This can be provided multiple times to define additional constraints. See the constraint reference for more details. @@ -435,7 +438,7 @@ The `artifact` object maps supports the following keys: default to `local/`. * `options` - The `options` block allows setting parameters for `go-getter`. An - example is given below: + example is given below: ``` options { @@ -453,7 +456,7 @@ An example of downloading and unzipping an archive is as simple as: ``` artifact { - # The archive will be extracted before the task is run, making + # The archive will be extracted before the task is run, making # it easy to ship configurations with your binary. source = "https://example.com/my.zip"