drivers/docker: reuse capabilities plumbing in docker driver
This changeset does not introduce any functional change for the docker driver, but rather cleans up the implementation around computing configured capabilities by re-using code written for the exec/java task drivers.
This commit is contained in:
parent
2361a91938
commit
87c96eed11
|
@ -10,7 +10,6 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -23,10 +22,9 @@ import (
|
|||
plugin "github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/nomad/client/taskenv"
|
||||
"github.com/hashicorp/nomad/drivers/docker/docklog"
|
||||
"github.com/hashicorp/nomad/drivers/shared/capabilities"
|
||||
"github.com/hashicorp/nomad/drivers/shared/eventer"
|
||||
"github.com/hashicorp/nomad/drivers/shared/executor"
|
||||
"github.com/hashicorp/nomad/drivers/shared/resolvconf"
|
||||
"github.com/hashicorp/nomad/helper"
|
||||
nstructs "github.com/hashicorp/nomad/nomad/structs"
|
||||
"github.com/hashicorp/nomad/plugins/base"
|
||||
"github.com/hashicorp/nomad/plugins/drivers"
|
||||
|
@ -913,8 +911,9 @@ func (d *Driver) createContainerConfig(task *drivers.TaskConfig, driverConfig *T
|
|||
hostConfig.Privileged = driverConfig.Privileged
|
||||
|
||||
// set add/drop capabilities
|
||||
hostConfig.CapAdd, hostConfig.CapDrop, err = d.getCaps(driverConfig)
|
||||
if err != nil {
|
||||
if hostConfig.CapAdd, hostConfig.CapDrop, err = capabilities.Delta(
|
||||
capabilities.DockerDefaults(), d.config.AllowCaps, driverConfig.CapAdd, driverConfig.CapDrop,
|
||||
); err != nil {
|
||||
return c, err
|
||||
}
|
||||
|
||||
|
@ -1184,119 +1183,6 @@ func (d *Driver) createContainerConfig(task *drivers.TaskConfig, driverConfig *T
|
|||
}, nil
|
||||
}
|
||||
|
||||
// getCaps computes the capabilities to supply to the --add-cap and --drop-cap
|
||||
// options to the docker driver, which override the default capabilities enabled
|
||||
// by docker itself.
|
||||
func (d *Driver) getCaps(taskConfig *TaskConfig) ([]string, []string, error) {
|
||||
|
||||
// capabilities allowable by client docker plugin configuration
|
||||
allowCaps := expandAllowCaps(d.config.AllowCaps)
|
||||
|
||||
// capabilities the task docker config is asking for based on the default
|
||||
// capabilities allowable by nomad
|
||||
desiredCaps, err := tweakCapabilities(nomadDefaultCaps(), taskConfig.CapAdd, taskConfig.CapDrop)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// capabilities the task is requesting that are NOT allowed by the docker plugin
|
||||
if missing := missingCaps(allowCaps, desiredCaps); len(missing) > 0 {
|
||||
return nil, nil, fmt.Errorf("Docker driver does not have the following caps allow-listed on this Nomad agent: %s", missing)
|
||||
}
|
||||
|
||||
// capabilities that should be dropped relative to the docker default capabilities
|
||||
dropCaps := capDrops(taskConfig.CapDrop, allowCaps)
|
||||
|
||||
return taskConfig.CapAdd, dropCaps, nil
|
||||
}
|
||||
|
||||
// capDrops will compute the total dropped capabilities set
|
||||
//
|
||||
// {task cap_drop} U ({docker defaults} \ {driver allow caps})
|
||||
func capDrops(dropCaps []string, allowCaps []string) []string {
|
||||
dropSet := make(map[string]struct{})
|
||||
|
||||
for _, c := range normalizeCaps(dropCaps) {
|
||||
dropSet[c] = struct{}{}
|
||||
}
|
||||
|
||||
// if dropCaps includes ALL, no need to iterate every capability
|
||||
if _, exists := dropSet["ALL"]; exists {
|
||||
return []string{"ALL"}
|
||||
}
|
||||
|
||||
dockerDefaults := helper.SliceStringToSet(normalizeCaps(dockerDefaultCaps()))
|
||||
allowedCaps := helper.SliceStringToSet(normalizeCaps(allowCaps))
|
||||
|
||||
// find the docker default caps not in allowed caps
|
||||
for dCap := range dockerDefaults {
|
||||
if _, exists := allowedCaps[dCap]; !exists {
|
||||
dropSet[dCap] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
drops := make([]string, 0, len(dropSet))
|
||||
for c := range dropSet {
|
||||
drops = append(drops, c)
|
||||
}
|
||||
sort.Strings(drops)
|
||||
return drops
|
||||
}
|
||||
|
||||
// expandAllowCaps returns the normalized set of allowable capabilities set
|
||||
// for the docker plugin configuration.
|
||||
func expandAllowCaps(allowCaps []string) []string {
|
||||
if len(allowCaps) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
set := make(map[string]struct{}, len(allowCaps))
|
||||
|
||||
for _, rawCap := range allowCaps {
|
||||
capability := strings.ToUpper(rawCap)
|
||||
if capability == "ALL" {
|
||||
for _, defCap := range normalizeCaps(executor.SupportedCaps(true)) {
|
||||
set[defCap] = struct{}{}
|
||||
}
|
||||
} else {
|
||||
set[capability] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
result := make([]string, 0, len(set))
|
||||
for capability := range set {
|
||||
result = append(result, capability)
|
||||
}
|
||||
sort.Strings(result)
|
||||
return result
|
||||
}
|
||||
|
||||
// missingCaps returns the set of elements in desired that are not present in
|
||||
// allowed. The elements in desired are first upper-cased before comparison.
|
||||
// The elements in allowed are assumed to be upper-cased.
|
||||
func missingCaps(allowed, desired []string) []string {
|
||||
_, missing := helper.SliceStringIsSubset(allowed, normalizeCaps(desired))
|
||||
sort.Strings(missing)
|
||||
return missing
|
||||
}
|
||||
|
||||
// normalizeCaps returns a copy of caps with duplicate elements removed and all
|
||||
// elements upper-cased.
|
||||
func normalizeCaps(caps []string) []string {
|
||||
set := make(map[string]struct{}, len(caps))
|
||||
for _, c := range caps {
|
||||
normal := strings.TrimPrefix(strings.ToUpper(c), "CAP_")
|
||||
set[strings.ToUpper(normal)] = struct{}{}
|
||||
}
|
||||
|
||||
result := make([]string, 0, len(set))
|
||||
for c := range set {
|
||||
result = append(result, c)
|
||||
}
|
||||
sort.Strings(result)
|
||||
return result
|
||||
}
|
||||
|
||||
func (d *Driver) toDockerMount(m *DockerMount, task *drivers.TaskConfig) (*docker.HostMount, error) {
|
||||
hm, err := m.toDockerHostMount()
|
||||
if err != nil {
|
||||
|
|
|
@ -19,7 +19,6 @@ import (
|
|||
hclog "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/nomad/client/taskenv"
|
||||
"github.com/hashicorp/nomad/client/testutil"
|
||||
"github.com/hashicorp/nomad/drivers/shared/executor"
|
||||
"github.com/hashicorp/nomad/helper/freeport"
|
||||
"github.com/hashicorp/nomad/helper/pluginutils/hclspecutils"
|
||||
"github.com/hashicorp/nomad/helper/pluginutils/hclutils"
|
||||
|
@ -1387,44 +1386,44 @@ func TestDockerDriver_Capabilities(t *testing.T) {
|
|||
{
|
||||
Name: "default-allowlist-add-allowed",
|
||||
CapAdd: []string{"fowner", "mknod"},
|
||||
CapDrop: []string{"ALL"},
|
||||
CapDrop: []string{"all"},
|
||||
},
|
||||
{
|
||||
Name: "default-allowlist-add-forbidden",
|
||||
CapAdd: []string{"net_admin"},
|
||||
StartError: "NET_ADMIN",
|
||||
StartError: "net_admin",
|
||||
},
|
||||
{
|
||||
Name: "default-allowlist-drop-existing",
|
||||
CapDrop: []string{"FOWNER", "MKNOD", "NET_RAW"},
|
||||
CapDrop: []string{"fowner", "mknod", "net_raw"},
|
||||
},
|
||||
{
|
||||
Name: "restrictive-allowlist-drop-all",
|
||||
CapDrop: []string{"ALL"},
|
||||
Allowlist: "FOWNER,MKNOD",
|
||||
CapDrop: []string{"all"},
|
||||
Allowlist: "fowner,mknod",
|
||||
},
|
||||
{
|
||||
Name: "restrictive-allowlist-add-allowed",
|
||||
CapAdd: []string{"fowner", "mknod"},
|
||||
CapDrop: []string{"ALL"},
|
||||
Allowlist: "fowner,mknod",
|
||||
CapDrop: []string{"all"},
|
||||
Allowlist: "mknod,fowner",
|
||||
},
|
||||
{
|
||||
Name: "restrictive-allowlist-add-forbidden",
|
||||
CapAdd: []string{"net_admin", "mknod"},
|
||||
CapDrop: []string{"ALL"},
|
||||
CapDrop: []string{"all"},
|
||||
Allowlist: "fowner,mknod",
|
||||
StartError: "NET_ADMIN",
|
||||
StartError: "net_admin",
|
||||
},
|
||||
{
|
||||
Name: "permissive-allowlist",
|
||||
CapAdd: []string{"net_admin", "mknod"},
|
||||
Allowlist: "ALL",
|
||||
CapAdd: []string{"mknod", "net_admin"},
|
||||
Allowlist: "all",
|
||||
},
|
||||
{
|
||||
Name: "permissive-allowlist-add-all",
|
||||
CapAdd: []string{"all"},
|
||||
Allowlist: "ALL",
|
||||
Allowlist: "all",
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -3064,169 +3063,3 @@ func TestDockerDriver_StopSignal(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerCaps_normalizeCaps(t *testing.T) {
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
result := normalizeCaps(nil)
|
||||
require.Len(t, result, 0)
|
||||
})
|
||||
|
||||
t.Run("mixed", func(t *testing.T) {
|
||||
result := normalizeCaps([]string{
|
||||
"DAC_OVERRIDE", "sys_chroot", "kill", "KILL",
|
||||
})
|
||||
require.Equal(t, []string{
|
||||
"DAC_OVERRIDE", "KILL", "SYS_CHROOT",
|
||||
}, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDockerCaps_missingCaps(t *testing.T) {
|
||||
allowed := []string{
|
||||
"DAC_OVERRIDE", "SYS_CHROOT", "KILL", "CHOWN",
|
||||
}
|
||||
|
||||
t.Run("none missing", func(t *testing.T) {
|
||||
result := missingCaps(allowed, []string{
|
||||
"SYS_CHROOT", "chown", "KILL",
|
||||
})
|
||||
require.Equal(t, []string(nil), result)
|
||||
})
|
||||
|
||||
t.Run("some missing", func(t *testing.T) {
|
||||
result := missingCaps(allowed, []string{
|
||||
"chown", "audit_write", "SETPCAP", "dac_override",
|
||||
})
|
||||
require.Equal(t, []string{"AUDIT_WRITE", "SETPCAP"}, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDockerCaps_expandAllowCaps(t *testing.T) {
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
result := expandAllowCaps(nil)
|
||||
require.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("manual", func(t *testing.T) {
|
||||
result := expandAllowCaps([]string{
|
||||
"DAC_OVERRIDE", "SYS_CHROOT", "KILL", "CHOWN",
|
||||
})
|
||||
require.Equal(t, []string{
|
||||
"CHOWN", "DAC_OVERRIDE", "KILL", "SYS_CHROOT",
|
||||
}, result)
|
||||
})
|
||||
|
||||
t.Run("all", func(t *testing.T) {
|
||||
result := expandAllowCaps([]string{"all"})
|
||||
exp := normalizeCaps(executor.SupportedCaps(true))
|
||||
sort.Strings(exp)
|
||||
require.Equal(t, exp, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDockerCaps_capDrops(t *testing.T) {
|
||||
// docker default caps is always the same, task configured drop_caps and
|
||||
// plugin config allow_caps may be altered
|
||||
|
||||
// This is the 90% use case, where NET_RAW is dropped, as Nomad's default
|
||||
// capability allow-list is a subset of the docker default cap list.
|
||||
t.Run("defaults", func(t *testing.T) {
|
||||
result := capDrops(nil, nomadDefaultCaps())
|
||||
require.Equal(t, []string{"NET_RAW"}, result)
|
||||
})
|
||||
|
||||
// Users want to use ICMP (ping).
|
||||
t.Run("enable net_raw", func(t *testing.T) {
|
||||
result := capDrops(nil, append(nomadDefaultCaps(), "net_raw"))
|
||||
require.Empty(t, result)
|
||||
})
|
||||
|
||||
// The plugin is reduced in ability.
|
||||
t.Run("enable minimal", func(t *testing.T) {
|
||||
allow := []string{"setgid", "setuid", "chown", "kill"}
|
||||
exp := []string{"AUDIT_WRITE", "DAC_OVERRIDE", "FOWNER", "FSETID", "MKNOD", "NET_BIND_SERVICE", "NET_RAW", "SETFCAP", "SETPCAP", "SYS_CHROOT"}
|
||||
result := capDrops(nil, allow)
|
||||
require.Equal(t, exp, result)
|
||||
})
|
||||
|
||||
// The task drops abilities.
|
||||
t.Run("task drops", func(t *testing.T) {
|
||||
drops := []string{"audit_write", "fowner", "kill", "chown"}
|
||||
exp := []string{"AUDIT_WRITE", "CHOWN", "FOWNER", "KILL", "NET_RAW"}
|
||||
result := capDrops(drops, nomadDefaultCaps())
|
||||
require.Equal(t, exp, result)
|
||||
})
|
||||
|
||||
// Drop all mixed with others.
|
||||
t.Run("task drops mix", func(t *testing.T) {
|
||||
drops := []string{"audit_write", "all", "chown"}
|
||||
exp := []string{"ALL"} // minimized
|
||||
result := capDrops(drops, nomadDefaultCaps())
|
||||
require.Equal(t, exp, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDockerCaps_getCaps(t *testing.T) {
|
||||
testutil.ExecCompatible(t) // tests require linux
|
||||
|
||||
t.Run("defaults", func(t *testing.T) {
|
||||
d := Driver{config: &DriverConfig{
|
||||
AllowCaps: nomadDefaultCaps(),
|
||||
}}
|
||||
add, drop, err := d.getCaps(&TaskConfig{
|
||||
CapAdd: nil, CapDrop: nil,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, add)
|
||||
require.Equal(t, []string{"NET_RAW"}, drop)
|
||||
})
|
||||
|
||||
t.Run("enable net_raw", func(t *testing.T) {
|
||||
d := Driver{config: &DriverConfig{
|
||||
AllowCaps: append(nomadDefaultCaps(), "net_raw"),
|
||||
}}
|
||||
add, drop, err := d.getCaps(&TaskConfig{
|
||||
CapAdd: nil, CapDrop: nil,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, add)
|
||||
require.Empty(t, drop)
|
||||
})
|
||||
|
||||
t.Run("block sys_time", func(t *testing.T) {
|
||||
d := Driver{config: &DriverConfig{
|
||||
AllowCaps: nomadDefaultCaps(),
|
||||
}}
|
||||
_, _, err := d.getCaps(&TaskConfig{
|
||||
CapAdd: []string{"SYS_TIME"},
|
||||
CapDrop: nil,
|
||||
})
|
||||
require.EqualError(t, err, `Docker driver does not have the following caps allow-listed on this Nomad agent: [SYS_TIME]`)
|
||||
})
|
||||
|
||||
t.Run("enable sys_time", func(t *testing.T) {
|
||||
d := Driver{config: &DriverConfig{
|
||||
AllowCaps: append(nomadDefaultCaps(), "sys_time"),
|
||||
}}
|
||||
add, drop, err := d.getCaps(&TaskConfig{
|
||||
CapAdd: []string{"SYS_TIME"},
|
||||
CapDrop: nil,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []string{"SYS_TIME"}, add)
|
||||
require.Equal(t, []string{"NET_RAW"}, drop)
|
||||
})
|
||||
|
||||
t.Run("task drops chown", func(t *testing.T) {
|
||||
d := Driver{config: &DriverConfig{
|
||||
AllowCaps: nomadDefaultCaps(),
|
||||
}}
|
||||
add, drop, err := d.getCaps(&TaskConfig{
|
||||
CapAdd: nil,
|
||||
CapDrop: []string{"chown"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, add)
|
||||
require.Equal(t, []string{"CHOWN", "NET_RAW"}, drop)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -473,7 +473,9 @@ func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drive
|
|||
cfg.Mounts = append(cfg.Mounts, dnsMount)
|
||||
}
|
||||
|
||||
caps, err := capabilities.Calculate(d.config.AllowCaps, driverConfig.CapAdd, driverConfig.CapDrop)
|
||||
caps, err := capabilities.Calculate(
|
||||
capabilities.NomadDefaults(), d.config.AllowCaps, driverConfig.CapAdd, driverConfig.CapDrop,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
|
@ -485,7 +485,9 @@ func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drive
|
|||
cfg.Mounts = append(cfg.Mounts, dnsMount)
|
||||
}
|
||||
|
||||
caps, err := capabilities.Calculate(d.config.AllowCaps, driverConfig.CapAdd, driverConfig.CapDrop)
|
||||
caps, err := capabilities.Calculate(
|
||||
capabilities.NomadDefaults(), d.config.AllowCaps, driverConfig.CapAdd, driverConfig.CapDrop,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
|
@ -39,6 +39,10 @@ func DockerDefaults() *Set {
|
|||
|
||||
// Supported returns the set of capabilities supported by the operating system.
|
||||
//
|
||||
// This set will expand over time as new capabilities are introduced to the kernel
|
||||
// and the capability library is updated (which tends to happen to keep up with
|
||||
// run-container libraries).
|
||||
//
|
||||
// Defers to a library generated from
|
||||
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/capability.h
|
||||
func Supported() *Set {
|
||||
|
@ -114,32 +118,92 @@ func LegacySupported() *Set {
|
|||
})
|
||||
}
|
||||
|
||||
// Calculate the reduced set of linux capabilities to enable for driver, taking
|
||||
// into account the capabilities allowed by the driver and the capabilities
|
||||
// explicitly requested / removed by the task configuration.
|
||||
// Calculate the resulting set of linux capabilities to enable for a task, taking
|
||||
// into account:
|
||||
// - default capability basis
|
||||
// - driver allowable capabilities
|
||||
// - task capability drops
|
||||
// - task capability adds
|
||||
//
|
||||
// capAdd if set indicates the minimal set of capabilities that should be enabled.
|
||||
// capDrop if set indicates capabilities that should be dropped from the driver defaults
|
||||
// Nomad establishes a standard set of enabled capabilities allowed by the task
|
||||
// driver if allow_caps is not set. This is the same set that the task will be
|
||||
// enabled with by default if allow_caps does not further reduce permissions,
|
||||
// in which case the task capabilities will also be reduced accordingly.
|
||||
//
|
||||
// If the task requests a capability not allowed by the driver, an error is
|
||||
// returned.
|
||||
func Calculate(allowCaps, capAdd, capDrop []string) ([]string, error) {
|
||||
driverAllowed := New(allowCaps)
|
||||
// The task will drop any capabilities specified in cap_drop, and add back
|
||||
// capabilities specified in cap_add. The task will not be allowed to add capabilities
|
||||
// not set in the the allow_caps setting (which by default is the same as the basis).
|
||||
//
|
||||
// cap_add takes precedence over cap_drop, enabling the common pattern of dropping
|
||||
// all capabilities, then adding back the desired smaller set. e.g.
|
||||
// cap_drop = ["all"]
|
||||
// cap_add = ["chown", "kill"]
|
||||
//
|
||||
// Note that the resulting capability names are upper-cased and prefixed with
|
||||
// "CAP_", which is the expected input for the exec/java driver implementation.
|
||||
func Calculate(basis *Set, allowCaps, capAdd, capDrop []string) ([]string, error) {
|
||||
allow := New(allowCaps)
|
||||
adds := New(capAdd)
|
||||
|
||||
// determine caps the task wants that are not allowed
|
||||
taskCaps := New(capAdd)
|
||||
missing := driverAllowed.Difference(taskCaps)
|
||||
missing := allow.Difference(adds)
|
||||
if !missing.Empty() {
|
||||
return nil, fmt.Errorf("driver does not allow the following capabilities: %s", missing)
|
||||
}
|
||||
|
||||
// if task did not specify allowed caps, use nomad defaults minus task drops
|
||||
if len(capAdd) == 0 {
|
||||
driverAllowed.Remove(capDrop)
|
||||
return driverAllowed.Slice(true), nil
|
||||
// the realized enabled capabilities starts with what is allowed both by driver
|
||||
// config AND is a member of the basis (i.e. nomad defaults)
|
||||
result := basis.Intersect(allow)
|
||||
|
||||
// then remove capabilities the task explicitly drops
|
||||
result.Remove(capDrop)
|
||||
|
||||
// then add back capabilities the task explicitly adds
|
||||
return result.Union(adds).Slice(true), nil
|
||||
}
|
||||
|
||||
// Delta calculates the set of capabilities that must be added and dropped relative
|
||||
// to a basis to achieve a desired result. The use case is that the docker driver
|
||||
// assumes a default set (DockerDefault), and we must calculate what to pass into
|
||||
// --cap-add and --cap-drop on container creation given the inputs of the docker
|
||||
// plugin config for allow_caps, and the docker task configuration for cap_add and
|
||||
// cap_drop. Note that the user provided cap_add and cap_drop settings are always
|
||||
// included, even if they are redundant with the basis (maintaining existing
|
||||
// behavior, working with existing tests).
|
||||
//
|
||||
// Note that the resulting capability names are lower-cased and not prefixed with
|
||||
// "CAP_", which is the existing style used with the docker driver implementation.
|
||||
func Delta(basis *Set, allowCaps, capAdd, capDrop []string) ([]string, []string, error) {
|
||||
all := func(caps []string) bool {
|
||||
for _, c := range caps {
|
||||
if normalize(c) == "all" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// otherwise task did specify allowed caps, enable exactly those
|
||||
taskAdd := New(capAdd)
|
||||
return taskAdd.Slice(true), nil
|
||||
// set of caps allowed by driver
|
||||
allow := New(allowCaps)
|
||||
|
||||
// determine caps the task wants that are not allowed
|
||||
missing := allow.Difference(New(capAdd))
|
||||
if !missing.Empty() {
|
||||
return nil, nil, fmt.Errorf("driver does not allow the following capabilities: %s", missing)
|
||||
}
|
||||
|
||||
// add what the task is asking for
|
||||
add := New(capAdd).Slice(false)
|
||||
if all(capAdd) {
|
||||
add = []string{"all"}
|
||||
}
|
||||
|
||||
// drop what the task removes plus whatever is in the basis that is not
|
||||
// in the driver allow configuration
|
||||
drop := New(allowCaps).Difference(basis).Union(New(capDrop)).Slice(false)
|
||||
if all(capDrop) {
|
||||
drop = []string{"all"}
|
||||
}
|
||||
|
||||
return add, drop, nil
|
||||
}
|
||||
|
|
|
@ -45,27 +45,59 @@ func TestCaps_Calculate(t *testing.T) {
|
|||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "allow all",
|
||||
name: "allow all no mods",
|
||||
allowCaps: []string{"all"},
|
||||
capAdd: nil,
|
||||
capDrop: nil,
|
||||
exp: Supported().Slice(true),
|
||||
exp: NomadDefaults().Slice(true),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "allow selection",
|
||||
name: "allow selection no mods",
|
||||
allowCaps: []string{"cap_net_raw", "chown", "SYS_TIME"},
|
||||
capAdd: nil,
|
||||
capDrop: nil,
|
||||
exp: []string{"CAP_CHOWN"},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "allow selection and add them",
|
||||
allowCaps: []string{"cap_net_raw", "chown", "SYS_TIME"},
|
||||
capAdd: []string{"net_raw", "sys_time"},
|
||||
capDrop: nil,
|
||||
exp: []string{"CAP_CHOWN", "CAP_NET_RAW", "CAP_SYS_TIME"},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "add allowed",
|
||||
name: "allow defaults and add redundant",
|
||||
allowCaps: NomadDefaults().Slice(false),
|
||||
capAdd: []string{"chown", "KILL"},
|
||||
capDrop: nil,
|
||||
exp: []string{"CAP_CHOWN", "CAP_KILL"},
|
||||
exp: NomadDefaults().Slice(true),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "allow defaults and add all",
|
||||
allowCaps: NomadDefaults().Slice(false),
|
||||
capAdd: []string{"all"},
|
||||
capDrop: nil,
|
||||
exp: nil,
|
||||
err: errors.New("driver does not allow the following capabilities: audit_control, audit_read, block_suspend, bpf, dac_read_search, ipc_lock, ipc_owner, lease, linux_immutable, mac_admin, mac_override, net_admin, net_broadcast, net_raw, perfmon, sys_admin, sys_boot, sys_module, sys_nice, sys_pacct, sys_ptrace, sys_rawio, sys_resource, sys_time, sys_tty_config, syslog, wake_alarm"),
|
||||
},
|
||||
{
|
||||
name: "allow defaults and drop all",
|
||||
allowCaps: NomadDefaults().Slice(false),
|
||||
capAdd: nil,
|
||||
capDrop: []string{"all"},
|
||||
exp: []string{},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "allow defaults and drop all and add back some",
|
||||
allowCaps: NomadDefaults().Slice(false),
|
||||
capAdd: []string{"chown", "fowner"},
|
||||
capDrop: []string{"all"},
|
||||
exp: []string{"CAP_CHOWN", "CAP_FOWNER"},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
|
@ -92,11 +124,145 @@ func TestCaps_Calculate(t *testing.T) {
|
|||
exp: []string{},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "drop all and add back",
|
||||
allowCaps: NomadDefaults().Slice(false),
|
||||
capAdd: []string{"chown", "mknod"},
|
||||
capDrop: []string{"all"},
|
||||
exp: []string{"CAP_CHOWN", "CAP_MKNOD"},
|
||||
err: nil,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
caps, err := Calculate(tc.allowCaps, tc.capAdd, tc.capDrop)
|
||||
caps, err := Calculate(NomadDefaults(), tc.allowCaps, tc.capAdd, tc.capDrop)
|
||||
require.Equal(t, tc.err, err)
|
||||
require.Equal(t, tc.exp, caps)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCaps_Delta(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
|
||||
// input
|
||||
allowCaps []string // driver config
|
||||
capAdd []string // task config
|
||||
capDrop []string // task config
|
||||
|
||||
// output
|
||||
expAdd []string
|
||||
expDrop []string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "the default setting",
|
||||
allowCaps: NomadDefaults().Slice(false),
|
||||
capAdd: nil,
|
||||
capDrop: nil,
|
||||
expAdd: []string{},
|
||||
expDrop: []string{"net_raw"},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "allow all no mods",
|
||||
allowCaps: []string{"all"},
|
||||
capAdd: nil,
|
||||
capDrop: nil,
|
||||
expAdd: []string{},
|
||||
expDrop: []string{},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "allow non-default no mods",
|
||||
allowCaps: []string{"cap_net_raw", "chown", "SYS_TIME"},
|
||||
capAdd: nil,
|
||||
capDrop: nil,
|
||||
expAdd: []string{},
|
||||
expDrop: []string{
|
||||
"audit_write", "dac_override", "fowner", "fsetid",
|
||||
"kill", "mknod", "net_bind_service", "setfcap",
|
||||
"setgid", "setpcap", "setuid", "sys_chroot"},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "allow default add from default",
|
||||
allowCaps: NomadDefaults().Slice(false),
|
||||
capAdd: []string{"chown", "KILL"},
|
||||
capDrop: nil,
|
||||
expAdd: []string{"chown", "kill"},
|
||||
expDrop: []string{"net_raw"},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "allow default add disallowed",
|
||||
allowCaps: NomadDefaults().Slice(false),
|
||||
capAdd: []string{"chown", "net_raw"},
|
||||
capDrop: nil,
|
||||
expAdd: nil,
|
||||
expDrop: nil,
|
||||
err: errors.New("driver does not allow the following capabilities: net_raw"),
|
||||
},
|
||||
{
|
||||
name: "allow default drop from default",
|
||||
allowCaps: NomadDefaults().Slice(false),
|
||||
capAdd: nil,
|
||||
capDrop: []string{"chown", "fowner", "CAP_KILL", "SYS_CHROOT", "mknod", "dac_override"},
|
||||
expAdd: []string{},
|
||||
expDrop: []string{"chown", "dac_override", "fowner", "kill", "mknod", "net_raw", "sys_chroot"},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "allow default drop all",
|
||||
allowCaps: NomadDefaults().Slice(false),
|
||||
capAdd: nil,
|
||||
capDrop: []string{"all"},
|
||||
expAdd: []string{},
|
||||
expDrop: []string{"all"},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "task drop all and add back",
|
||||
allowCaps: NomadDefaults().Slice(false),
|
||||
capAdd: []string{"chown", "fowner"},
|
||||
capDrop: []string{"all"},
|
||||
expAdd: []string{"chown", "fowner"},
|
||||
expDrop: []string{"all"},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "add atop allow all",
|
||||
allowCaps: []string{"all"},
|
||||
capAdd: []string{"chown", "fowner"},
|
||||
capDrop: nil,
|
||||
expAdd: []string{"chown", "fowner"},
|
||||
expDrop: []string{},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "add all atop all",
|
||||
allowCaps: []string{"all"},
|
||||
capAdd: []string{"all"},
|
||||
capDrop: nil,
|
||||
expAdd: []string{"all"},
|
||||
expDrop: []string{},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "add all atop defaults",
|
||||
allowCaps: NomadDefaults().Slice(false),
|
||||
capAdd: []string{"all"},
|
||||
capDrop: nil,
|
||||
expAdd: nil,
|
||||
expDrop: nil,
|
||||
err: errors.New("driver does not allow the following capabilities: audit_control, audit_read, block_suspend, bpf, dac_read_search, ipc_lock, ipc_owner, lease, linux_immutable, mac_admin, mac_override, net_admin, net_broadcast, net_raw, perfmon, sys_admin, sys_boot, sys_module, sys_nice, sys_pacct, sys_ptrace, sys_rawio, sys_resource, sys_time, sys_tty_config, syslog, wake_alarm"),
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
add, drop, err := Delta(DockerDefaults(), tc.allowCaps, tc.capAdd, tc.capDrop)
|
||||
require.Equal(t, tc.err, err)
|
||||
require.Equal(t, tc.expAdd, add)
|
||||
require.Equal(t, tc.expDrop, drop)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ var null = nothing{}
|
|||
// operations, taking care of name normalization, and sentinel value expansions.
|
||||
//
|
||||
// Linux capabilities can be expressed in multiple ways when working with docker
|
||||
// and/or libcontainer, along with Nomad.
|
||||
// and/or executor, along with Nomad configuration.
|
||||
//
|
||||
// Capability names may be upper or lower case, and may or may not be prefixed
|
||||
// with "CAP_" or "cap_". On top of that, Nomad interprets the special name "all"
|
||||
|
@ -61,6 +61,18 @@ func (s *Set) Remove(caps []string) {
|
|||
}
|
||||
}
|
||||
|
||||
// Union returns of Set of elements of both s and b.
|
||||
func (s *Set) Union(b *Set) *Set {
|
||||
data := make(map[string]nothing)
|
||||
for c := range s.data {
|
||||
data[c] = null
|
||||
}
|
||||
for c := range b.data {
|
||||
data[c] = null
|
||||
}
|
||||
return &Set{data: data}
|
||||
}
|
||||
|
||||
// Difference returns the Set of elements of b not in s.
|
||||
func (s *Set) Difference(b *Set) *Set {
|
||||
data := make(map[string]nothing)
|
||||
|
@ -72,6 +84,17 @@ func (s *Set) Difference(b *Set) *Set {
|
|||
return &Set{data: data}
|
||||
}
|
||||
|
||||
// Intersect returns the Set of elements in both s and b.
|
||||
func (s *Set) Intersect(b *Set) *Set {
|
||||
data := make(map[string]nothing)
|
||||
for c := range s.data {
|
||||
if _, exists := b.data[c]; exists {
|
||||
data[c] = null
|
||||
}
|
||||
}
|
||||
return &Set{data: data}
|
||||
}
|
||||
|
||||
// Empty return true if no capabilities exist in s.
|
||||
func (s *Set) Empty() bool {
|
||||
return len(s.data) == 0
|
||||
|
@ -84,7 +107,7 @@ func (s *Set) String() string {
|
|||
|
||||
// Slice returns a sorted slice of capabilities in s.
|
||||
//
|
||||
// big - indicates whether to uppercase and prefix capabilities with CAP_
|
||||
// upper - indicates whether to uppercase and prefix capabilities with CAP_
|
||||
func (s *Set) Slice(upper bool) []string {
|
||||
caps := make([]string, 0, len(s.data))
|
||||
for c := range s.data {
|
||||
|
|
|
@ -160,3 +160,55 @@ func TestSet_Difference(t *testing.T) {
|
|||
require.Equal(t, "x, y", result.String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestSet_Intersect(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
a := New(nil)
|
||||
b := New([]string{"a", "b"})
|
||||
|
||||
result := a.Intersect(b)
|
||||
require.True(t, result.Empty())
|
||||
|
||||
result2 := b.Intersect(a)
|
||||
require.True(t, result2.Empty())
|
||||
})
|
||||
|
||||
t.Run("intersect", func(t *testing.T) {
|
||||
a := New([]string{"A", "b", "C", "d", "e", "f", "G"})
|
||||
b := New([]string{"Z", "B", "E", "f", "y"})
|
||||
|
||||
result := a.Intersect(b)
|
||||
require.Equal(t, "b, e, f", result.String())
|
||||
|
||||
result2 := b.Intersect(a)
|
||||
require.Equal(t, "b, e, f", result2.String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestSet_Union(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
a := New(nil)
|
||||
b := New([]string{"a", "b"})
|
||||
|
||||
result := a.Union(b)
|
||||
require.Equal(t, "a, b", result.String())
|
||||
|
||||
result2 := b.Union(a)
|
||||
require.Equal(t, "a, b", result2.String())
|
||||
})
|
||||
|
||||
t.Run("union", func(t *testing.T) {
|
||||
a := New([]string{"A", "b", "C", "d", "e", "f", "G"})
|
||||
b := New([]string{"Z", "B", "E", "f", "y"})
|
||||
|
||||
result := a.Union(b)
|
||||
require.Equal(t, "a, b, c, d, e, f, g, y, z", result.String())
|
||||
|
||||
result2 := b.Union(a)
|
||||
require.Equal(t, "a, b, c, d, e, f, g, y, z", result2.String())
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue