Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add config field to specify chroot mapping for exec driver #1518

Merged
merged 6 commits into from
Aug 11, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions client/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ type Config struct {
// devices and IPs.
GloballyReservedPorts []int

// A mapping of directories on the host OS to attempt to embed inside each
// task's chroot.
ChrootEnv map[string]string

// Options provides arbitrary key-value configuration for nomad internals,
// like fingerprinters and drivers. The format is:
//
Expand Down
11 changes: 6 additions & 5 deletions client/driver/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,12 @@ func (d *ExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle,
return nil, err
}
executorCtx := &executor.ExecutorContext{
TaskEnv: d.taskEnv,
Driver: "exec",
AllocDir: ctx.AllocDir,
AllocID: ctx.AllocID,
Task: task,
TaskEnv: d.taskEnv,
Driver: "exec",
AllocDir: ctx.AllocDir,
AllocID: ctx.AllocID,
ChrootEnv: d.config.ChrootEnv,
Task: task,
}

ps, err := exec.LaunchCmd(&executor.ExecCommand{
Expand Down
4 changes: 4 additions & 0 deletions client/driver/executor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ type ExecutorContext struct {
// AllocID is the allocation id to which the task belongs
AllocID string

// A mapping of directories on the host OS to attempt to embed inside each
// task's chroot.
ChrootEnv map[string]string

// Driver is the name of the driver that invoked the executor
Driver string

Expand Down
7 changes: 6 additions & 1 deletion client/driver/executor/executor_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,12 @@ func (e *UniversalExecutor) configureChroot() error {
return err
}

if err := allocDir.Embed(e.ctx.Task.Name, chrootEnv); err != nil {
chroot := chrootEnv
if len(e.ctx.ChrootEnv) > 0 {
chroot = e.ctx.ChrootEnv
}

if err := allocDir.Embed(e.ctx.Task.Name, chroot); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a test for this as well? It would be nice if the test asserts that only the specified top level directories specified by the operator are present in the chroot.

return err
}

Expand Down
29 changes: 26 additions & 3 deletions client/driver/executor/executor_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,38 @@ import (
"strings"
"testing"

"github.com/hashicorp/nomad/client/driver/env"
cstructs "github.com/hashicorp/nomad/client/driver/structs"
"github.com/hashicorp/nomad/client/testutil"
"github.com/hashicorp/nomad/nomad/mock"
)

func testExecutorContextWithChroot(t *testing.T) *ExecutorContext {
taskEnv := env.NewTaskEnvironment(mock.Node())
task, allocDir := mockAllocDir(t)
ctx := &ExecutorContext{
TaskEnv: taskEnv,
Task: task,
AllocDir: allocDir,
ChrootEnv: map[string]string{
"/etc/ld.so.cache": "/etc/ld.so.cache",
"/etc/ld.so.conf": "/etc/ld.so.conf",
"/etc/ld.so.conf.d": "/etc/ld.so.conf.d",
"/lib": "/lib",
"/lib64": "/lib64",
"/usr/lib": "/usr/lib",
"/bin/ls": "/bin/ls",
"/foobar": "/does/not/exist",
},
}
return ctx
}

func TestExecutor_IsolationAndConstraints(t *testing.T) {
testutil.ExecCompatible(t)

execCmd := ExecCommand{Cmd: "/bin/echo", Args: []string{"hello world"}}
ctx := testExecutorContext(t)
execCmd := ExecCommand{Cmd: "/bin/ls", Args: []string{"-F", "/", "/etc/"}}
ctx := testExecutorContextWithChroot(t)
defer ctx.AllocDir.Destroy()

execCmd.FSIsolation = true
Expand Down Expand Up @@ -58,7 +81,7 @@ func TestExecutor_IsolationAndConstraints(t *testing.T) {
t.Fatalf("file %v hasn't been removed", memLimits)
}

expected := "hello world"
expected := "/:\nalloc/\nbin/\ndev/\netc/\nlib/\nlib64/\nlocal/\nproc/\ntmp/\nusr/\n\n/etc/:\nld.so.cache\nld.so.conf\nld.so.conf.d/"
file := filepath.Join(ctx.AllocDir.LogDir(), "web.stdout.0")
output, err := ioutil.ReadFile(file)
if err != nil {
Expand Down
11 changes: 6 additions & 5 deletions client/driver/java.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,12 @@ func (d *JavaDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle,
return nil, err
}
executorCtx := &executor.ExecutorContext{
TaskEnv: d.taskEnv,
Driver: "java",
AllocDir: ctx.AllocDir,
AllocID: ctx.AllocID,
Task: task,
TaskEnv: d.taskEnv,
Driver: "java",
AllocDir: ctx.AllocDir,
AllocID: ctx.AllocID,
ChrootEnv: d.config.ChrootEnv,
Task: task,
}

absPath, err := GetAbsolutePath("java")
Expand Down
1 change: 1 addition & 0 deletions command/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ func (a *Agent) clientConfig() (*clientconfig.Config, error) {
if a.config.Client.NetworkInterface != "" {
conf.NetworkInterface = a.config.Client.NetworkInterface
}
conf.ChrootEnv = a.config.Client.ChrootEnv
conf.Options = a.config.Client.Options
// Logging deprecation messages about consul related configuration in client
// options
Expand Down
4 changes: 4 additions & 0 deletions command/agent/config-test-fixtures/basic.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ client {
foo = "bar"
baz = "zip"
}
chroot_env {
"/opt/myapp/etc" = "/etc"
"/opt/myapp/bin" = "/bin"
}
network_interface = "eth0"
network_speed = 100
reserved {
Expand Down
12 changes: 12 additions & 0 deletions command/agent/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ type ClientConfig struct {
// Metadata associated with the node
Meta map[string]string `mapstructure:"meta"`

// A mapping of directories on the host OS to attempt to embed inside each
// task's chroot.
ChrootEnv map[string]string `mapstructure:"chroot_env"`

// Interface to use for network fingerprinting
NetworkInterface string `mapstructure:"network_interface"`

Expand Down Expand Up @@ -718,6 +722,14 @@ func (a *ClientConfig) Merge(b *ClientConfig) *ClientConfig {
result.Meta[k] = v
}

// Add the chroot_env map values
if result.ChrootEnv == nil {
result.ChrootEnv = make(map[string]string)
}
for k, v := range b.ChrootEnv {
result.ChrootEnv[k] = v
}

return &result
}

Expand Down
16 changes: 16 additions & 0 deletions command/agent/config_parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ func parseClient(result **ClientConfig, list *ast.ObjectList) error {
"node_class",
"options",
"meta",
"chroot_env",
"network_interface",
"network_speed",
"max_kill_timeout",
Expand All @@ -334,6 +335,7 @@ func parseClient(result **ClientConfig, list *ast.ObjectList) error {

delete(m, "options")
delete(m, "meta")
delete(m, "chroot_env")
delete(m, "reserved")
delete(m, "stats")

Expand Down Expand Up @@ -370,6 +372,20 @@ func parseClient(result **ClientConfig, list *ast.ObjectList) error {
}
}

// Parse out chroot_env fields. These are in HCL as a list so we need to
// iterate over them and merge them.
if chrootEnvO := listVal.Filter("chroot_env"); len(chrootEnvO.Items) > 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a test for this

for _, o := range chrootEnvO.Elem().Items {
var m map[string]interface{}
if err := hcl.DecodeObject(&m, o.Val); err != nil {
return err
}
if err := mapstructure.WeakDecode(m, &config.ChrootEnv); err != nil {
return err
}
}
}

// Parse reserved config
if o := listVal.Filter("reserved"); len(o.Items) > 0 {
if err := parseReserved(&config.Reserved, o); err != nil {
Expand Down
4 changes: 4 additions & 0 deletions command/agent/config_parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ func TestConfig_Parse(t *testing.T) {
"foo": "bar",
"baz": "zip",
},
ChrootEnv: map[string]string{
"/opt/myapp/etc": "/etc",
"/opt/myapp/bin": "/bin",
},
NetworkInterface: "eth0",
NetworkSpeed: 100,
MaxKillTimeout: "10s",
Expand Down
1 change: 1 addition & 0 deletions command/agent/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ func TestConfig_Merge(t *testing.T) {
"foo": "bar",
"baz": "zip",
},
ChrootEnv: map[string]string{},
ClientMaxPort: 20000,
ClientMinPort: 22000,
NetworkSpeed: 105,
Expand Down
28 changes: 28 additions & 0 deletions website/source/docs/agent/config.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,9 @@ configured on server nodes.
* <a id="options">`options`</a>: This is a key/value mapping of internal
configuration for clients, such as for driver configuration. Please see
[here](#options_map) for a description of available options.
* <a id="chroot_env">`chroot_env`</a>: This is a key/value mapping that
defines the chroot environment for jobs using the Exec and Java drivers.
Please see [here](#chroot_env_map) for an example and further information.
* <a id="network_interface">`network_interface`</a>: This is a string to force
network fingerprinting to use a specific network interface
* <a id="network_speed">`network_speed`</a>: This is an int that sets the
Expand Down Expand Up @@ -496,6 +499,31 @@ documentation [here](/docs/drivers/index.html)
If specified, fingerprinters not in the whitelist will be disabled. If the
whitelist is empty, all fingerprinters are used.

### <a id="chroot_env_map"></a>Client ChrootEnv Map

Drivers based on [Isolated Fork/Exec](/docs/drivers/exec.html) implement file
system isolation using chroot on Linux. The `chroot_env` map allows the chroot
environment to be configured using source paths on the host operating system.
The mapping format is: `source_path -> dest_path`.

The following example specifies a chroot which contains just enough to run the
`ls` utility, and not much else:

```
chroot_env {
"/bin/ls" = "/bin/ls"
"/etc/ld.so.cache" = "/etc/ld.so.cache"
"/etc/ld.so.conf" = "/etc/ld.so.conf"
"/etc/ld.so.conf.d" = "/etc/ld.so.conf.d"
"/lib" = "/lib"
"/lib64" = "/lib64"
}
```

When `chroot_env` is unspecified, the `exec` driver will use a default chroot
environment with the most commonly used parts of the operating system. See
`exec` documentation for the full list [here](/docs/drivers/exec.html#chroot).

## <a id="cli"></a>Command-line Options

A subset of the available Nomad agent configuration can optionally be passed in
Expand Down
5 changes: 4 additions & 1 deletion website/source/docs/drivers/exec.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,12 @@ the client and the configuration.
On Linux, Nomad will use cgroups, and a chroot to isolate the
resources of a process and as such the Nomad agent must be run as root.

### Chroot
### <a id="chroot"></a>Chroot
The chroot is populated with data in the following folders from the host
machine:

`["/bin", "/etc", "/lib", "/lib32", "/lib64", "/run/resolvconf", "/sbin",
"/usr"]`

This list is configurable through the agent client
[configuration file](/docs/agent/config.html#chroot_env).