diff --git a/client/driver/docker.go b/client/driver/docker.go index d2b3827c0..6fd43dd6b 100644 --- a/client/driver/docker.go +++ b/client/driver/docker.go @@ -202,6 +202,8 @@ type DockerDriverConfig struct { MacAddress string `mapstructure:"mac_address"` // Pin mac address to container SecurityOpt []string `mapstructure:"security_opt"` // Flags to pass directly to security-opt Devices []DockerDevice `mapstructure:"devices"` // To allow mounting USB or other serial control devices + CapAdd []string `mapstructure:"cap_add"` // Flags to pass directly to cap-add + CapDrop []string `mapstructure:"cap_drop"` // Flags to pass directly to cap-drop } func sliceMergeUlimit(ulimitsRaw map[string]string) ([]docker.ULimit, error) { @@ -304,6 +306,8 @@ func NewDockerDriverConfig(task *structs.Task, env *env.TaskEnv) (*DockerDriverC dconf.ExtraHosts = env.ParseAndReplace(dconf.ExtraHosts) dconf.MacAddress = env.ReplaceEnv(dconf.MacAddress) dconf.SecurityOpt = env.ParseAndReplace(dconf.SecurityOpt) + dconf.CapAdd = env.ParseAndReplace(dconf.CapAdd) + dconf.CapDrop = env.ParseAndReplace(dconf.CapDrop) for _, m := range dconf.SysctlRaw { for k, v := range m { @@ -644,6 +648,12 @@ func (d *DockerDriver) Validate(config map[string]interface{}) error { "devices": { Type: fields.TypeArray, }, + "cap_add": { + Type: fields.TypeArray, + }, + "cap_drop": { + Type: fields.TypeArray, + }, }, } @@ -1115,6 +1125,9 @@ func (d *DockerDriver) createContainerConfig(ctx *ExecContext, task *structs.Tas } hostConfig.Privileged = driverConfig.Privileged + hostConfig.CapAdd = driverConfig.CapAdd + hostConfig.CapDrop = driverConfig.CapDrop + // set SHM size if driverConfig.ShmSize != 0 { hostConfig.ShmSize = driverConfig.ShmSize diff --git a/client/driver/docker_test.go b/client/driver/docker_test.go index e9051087a..44a6f31ee 100644 --- a/client/driver/docker_test.go +++ b/client/driver/docker_test.go @@ -1048,6 +1048,37 @@ func TestDockerDriver_SecurityOpt(t *testing.T) { } } +func TestDockerDriver_Capabilities(t *testing.T) { + if !tu.IsTravis() { + t.Parallel() + } + if !testutil.DockerIsConnected(t) { + t.Skip("Docker not connected") + } + + task, _, _ := dockerTask(t) + task.Config["cap_add"] = []string{"ALL"} + task.Config["cap_drop"] = []string{"MKNOD", "NET_ADMIN"} + + client, handle, cleanup := dockerSetup(t, task) + defer cleanup() + + waitForExist(t, client, handle) + + container, err := client.InspectContainer(handle.ContainerID()) + if err != nil { + t.Fatalf("err: %v", err) + } + + if !reflect.DeepEqual(task.Config["cap_add"], container.HostConfig.CapAdd) { + t.Errorf("CapAdd doesn't match.\nExpected:\n%s\nGot:\n%s\n", task.Config["cap_add"], container.HostConfig.CapAdd) + } + + if !reflect.DeepEqual(task.Config["cap_drop"], container.HostConfig.CapDrop) { + t.Errorf("CapDrop doesn't match.\nExpected:\n%s\nGot:\n%s\n", task.Config["cap_drop"], container.HostConfig.CapDrop) + } +} + func TestDockerDriver_DNS(t *testing.T) { if !tu.IsTravis() { t.Parallel() diff --git a/website/source/docs/drivers/docker.html.md b/website/source/docs/drivers/docker.html.md index 5000f2a7a..dbd1eada8 100644 --- a/website/source/docs/drivers/docker.html.md +++ b/website/source/docs/drivers/docker.html.md @@ -324,6 +324,32 @@ The `docker` driver supports the following configuration in the job spec. Only } ``` +* `cap_add` - (Optional) A list of string flags to pass directly to + [`--cap-add`](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities). + For example: + + + ```hcl + config { + cap_add = [ + "SYS_TIME", + ] + } + ``` + +* `cap_drop` - (Optional) A list of string flags to pass directly to + [`--cap-drop`](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities). + For example: + + + ```hcl + config { + cap_drop = [ + "MKNOD", + ] + } + ``` + ### Container Name Nomad creates a container after pulling an image. Containers are named