mirror of
https://github.com/moby/moby.git
synced 2025-07-29 07:21:35 +03:00
Capabilities refactor
- Add support for exact list of capabilities, support only OCI model - Support OCI model on CapAdd and CapDrop but remain backward compatibility - Create variable locally instead of declaring it at the top - Use const for magic "ALL" value - Rename `cap` variable as it overlaps with `cap()` built-in - Normalize and validate capabilities before use - Move validation for conflicting options to validateHostConfig() - TweakCapabilities: simplify logic to calculate capabilities Signed-off-by: Olli Janatuinen <olli.janatuinen@gmail.com> Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
@ -473,6 +473,11 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
|
|||||||
hostConfig.KernelMemoryTCP = 0
|
hostConfig.KernelMemoryTCP = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ignore Capabilities because it was added in API 1.40.
|
||||||
|
if hostConfig != nil && versions.LessThan(version, "1.40") {
|
||||||
|
hostConfig.Capabilities = nil
|
||||||
|
}
|
||||||
|
|
||||||
ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
|
ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
|
||||||
Name: name,
|
Name: name,
|
||||||
Config: config,
|
Config: config,
|
||||||
|
@ -645,14 +645,22 @@ definitions:
|
|||||||
$ref: "#/definitions/Mount"
|
$ref: "#/definitions/Mount"
|
||||||
|
|
||||||
# Applicable to UNIX platforms
|
# Applicable to UNIX platforms
|
||||||
|
Capabilities:
|
||||||
|
type: "array"
|
||||||
|
description: |
|
||||||
|
A list of kernel capabilities to be available for container (this overrides the default set).
|
||||||
|
|
||||||
|
Conflicts with options 'CapAdd' and 'CapDrop'"
|
||||||
|
items:
|
||||||
|
type: "string"
|
||||||
CapAdd:
|
CapAdd:
|
||||||
type: "array"
|
type: "array"
|
||||||
description: "A list of kernel capabilities to add to the container."
|
description: "A list of kernel capabilities to add to the container. Conflicts with option 'Capabilities'"
|
||||||
items:
|
items:
|
||||||
type: "string"
|
type: "string"
|
||||||
CapDrop:
|
CapDrop:
|
||||||
type: "array"
|
type: "array"
|
||||||
description: "A list of kernel capabilities to drop from the container."
|
description: "A list of kernel capabilities to drop from the container. Conflicts with option 'Capabilities'"
|
||||||
items:
|
items:
|
||||||
type: "string"
|
type: "string"
|
||||||
Dns:
|
Dns:
|
||||||
|
@ -370,9 +370,10 @@ type HostConfig struct {
|
|||||||
// Applicable to UNIX platforms
|
// Applicable to UNIX platforms
|
||||||
CapAdd strslice.StrSlice // List of kernel capabilities to add to the container
|
CapAdd strslice.StrSlice // List of kernel capabilities to add to the container
|
||||||
CapDrop strslice.StrSlice // List of kernel capabilities to remove from the container
|
CapDrop strslice.StrSlice // List of kernel capabilities to remove from the container
|
||||||
DNS []string `json:"Dns"` // List of DNS server to lookup
|
Capabilities []string `json:"Capabilities"` // List of kernel capabilities to be available for container (this overrides the default set)
|
||||||
DNSOptions []string `json:"DnsOptions"` // List of DNSOption to look for
|
DNS []string `json:"Dns"` // List of DNS server to lookup
|
||||||
DNSSearch []string `json:"DnsSearch"` // List of DNSSearch to look for
|
DNSOptions []string `json:"DnsOptions"` // List of DNSOption to look for
|
||||||
|
DNSSearch []string `json:"DnsSearch"` // List of DNSSearch to look for
|
||||||
ExtraHosts []string // List of extra hosts
|
ExtraHosts []string // List of extra hosts
|
||||||
GroupAdd []string // List of additional groups that the container process will run as
|
GroupAdd []string // List of additional groups that the container process will run as
|
||||||
IpcMode IpcMode // IPC namespace to use for the container
|
IpcMode IpcMode // IPC namespace to use for the container
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/docker/docker/daemon/network"
|
"github.com/docker/docker/daemon/network"
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
"github.com/docker/docker/image"
|
"github.com/docker/docker/image"
|
||||||
|
"github.com/docker/docker/oci/caps"
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/docker/opts"
|
||||||
"github.com/docker/docker/pkg/signal"
|
"github.com/docker/docker/pkg/signal"
|
||||||
"github.com/docker/docker/pkg/system"
|
"github.com/docker/docker/pkg/system"
|
||||||
@ -295,12 +296,35 @@ func validateHostConfig(hostConfig *containertypes.HostConfig, platform string)
|
|||||||
if err := validateRestartPolicy(hostConfig.RestartPolicy); err != nil {
|
if err := validateRestartPolicy(hostConfig.RestartPolicy); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := validateCapabilities(hostConfig); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if !hostConfig.Isolation.IsValid() {
|
if !hostConfig.Isolation.IsValid() {
|
||||||
return errors.Errorf("invalid isolation '%s' on %s", hostConfig.Isolation, runtime.GOOS)
|
return errors.Errorf("invalid isolation '%s' on %s", hostConfig.Isolation, runtime.GOOS)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateCapabilities(hostConfig *containertypes.HostConfig) error {
|
||||||
|
if len(hostConfig.CapAdd) > 0 && hostConfig.Capabilities != nil {
|
||||||
|
return errdefs.InvalidParameter(errors.Errorf("conflicting options: Capabilities and CapAdd"))
|
||||||
|
}
|
||||||
|
if len(hostConfig.CapDrop) > 0 && hostConfig.Capabilities != nil {
|
||||||
|
return errdefs.InvalidParameter(errors.Errorf("conflicting options: Capabilities and CapDrop"))
|
||||||
|
}
|
||||||
|
if _, err := caps.NormalizeLegacyCapabilities(hostConfig.CapAdd); err != nil {
|
||||||
|
return errors.Wrap(err, "invalid CapAdd")
|
||||||
|
}
|
||||||
|
if _, err := caps.NormalizeLegacyCapabilities(hostConfig.CapDrop); err != nil {
|
||||||
|
return errors.Wrap(err, "invalid CapDrop")
|
||||||
|
}
|
||||||
|
if err := caps.ValidateCapabilities(hostConfig.Capabilities); err != nil {
|
||||||
|
return errors.Wrap(err, "invalid Capabilities")
|
||||||
|
}
|
||||||
|
// TODO consider returning warnings if "Privileged" is combined with Capabilities, CapAdd and/or CapDrop
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// validateHealthCheck validates the healthcheck params of Config
|
// validateHealthCheck validates the healthcheck params of Config
|
||||||
func validateHealthCheck(healthConfig *containertypes.HealthConfig) error {
|
func validateHealthCheck(healthConfig *containertypes.HealthConfig) error {
|
||||||
if healthConfig == nil {
|
if healthConfig == nil {
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/docker/docker/container"
|
"github.com/docker/docker/container"
|
||||||
daemonconfig "github.com/docker/docker/daemon/config"
|
daemonconfig "github.com/docker/docker/daemon/config"
|
||||||
"github.com/docker/docker/oci"
|
"github.com/docker/docker/oci"
|
||||||
|
"github.com/docker/docker/oci/caps"
|
||||||
"github.com/docker/docker/pkg/idtools"
|
"github.com/docker/docker/pkg/idtools"
|
||||||
"github.com/docker/docker/pkg/mount"
|
"github.com/docker/docker/pkg/mount"
|
||||||
volumemounts "github.com/docker/docker/volume/mounts"
|
volumemounts "github.com/docker/docker/volume/mounts"
|
||||||
@ -762,7 +763,11 @@ func (daemon *Daemon) createSpec(c *container.Container) (retSpec *specs.Spec, e
|
|||||||
if err := setNamespaces(daemon, &s, c); err != nil {
|
if err := setNamespaces(daemon, &s, c); err != nil {
|
||||||
return nil, fmt.Errorf("linux spec namespaces: %v", err)
|
return nil, fmt.Errorf("linux spec namespaces: %v", err)
|
||||||
}
|
}
|
||||||
if err := oci.SetCapabilities(&s, c.HostConfig.CapAdd, c.HostConfig.CapDrop, c.HostConfig.Privileged); err != nil {
|
capabilities, err := caps.TweakCapabilities(oci.DefaultCapabilities(), c.HostConfig.CapAdd, c.HostConfig.CapDrop, c.HostConfig.Capabilities, c.HostConfig.Privileged)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("linux spec capabilities: %v", err)
|
||||||
|
}
|
||||||
|
if err := oci.SetCapabilities(&s, capabilities); err != nil {
|
||||||
return nil, fmt.Errorf("linux spec capabilities: %v", err)
|
return nil, fmt.Errorf("linux spec capabilities: %v", err)
|
||||||
}
|
}
|
||||||
if err := setSeccomp(daemon, &s, c); err != nil {
|
if err := setSeccomp(daemon, &s, c); err != nil {
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
containertypes "github.com/docker/docker/api/types/container"
|
containertypes "github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/container"
|
"github.com/docker/docker/container"
|
||||||
"github.com/docker/docker/oci"
|
"github.com/docker/docker/oci"
|
||||||
|
"github.com/docker/docker/oci/caps"
|
||||||
"github.com/docker/docker/pkg/sysinfo"
|
"github.com/docker/docker/pkg/sysinfo"
|
||||||
"github.com/docker/docker/pkg/system"
|
"github.com/docker/docker/pkg/system"
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
@ -368,7 +369,11 @@ func (daemon *Daemon) createSpecLinuxFields(c *container.Container, s *specs.Spe
|
|||||||
}
|
}
|
||||||
s.Root.Path = "rootfs"
|
s.Root.Path = "rootfs"
|
||||||
s.Root.Readonly = c.HostConfig.ReadonlyRootfs
|
s.Root.Readonly = c.HostConfig.ReadonlyRootfs
|
||||||
if err := oci.SetCapabilities(s, c.HostConfig.CapAdd, c.HostConfig.CapDrop, c.HostConfig.Privileged); err != nil {
|
capabilities, err := caps.TweakCapabilities(oci.DefaultCapabilities(), c.HostConfig.CapAdd, c.HostConfig.CapDrop, c.HostConfig.Capabilities, c.HostConfig.Privileged)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("linux spec capabilities: %v", err)
|
||||||
|
}
|
||||||
|
if err := oci.SetCapabilities(s, capabilities); err != nil {
|
||||||
return fmt.Errorf("linux spec capabilities: %v", err)
|
return fmt.Errorf("linux spec capabilities: %v", err)
|
||||||
}
|
}
|
||||||
devPermissions, err := oci.AppendDevicePermissionsFromCgroupRules(nil, c.HostConfig.DeviceCgroupRules)
|
devPermissions, err := oci.AppendDevicePermissionsFromCgroupRules(nil, c.HostConfig.DeviceCgroupRules)
|
||||||
|
@ -37,6 +37,9 @@ keywords: "API, Docker, rcli, REST, documentation"
|
|||||||
* `GET /service/{id}` now returns `MaxReplicas` as part of the `Placement`.
|
* `GET /service/{id}` now returns `MaxReplicas` as part of the `Placement`.
|
||||||
* `POST /service/create` and `POST /services/(id or name)/update` now take the field `MaxReplicas`
|
* `POST /service/create` and `POST /services/(id or name)/update` now take the field `MaxReplicas`
|
||||||
as part of the service `Placement`, allowing to specify maximum replicas per node for the service.
|
as part of the service `Placement`, allowing to specify maximum replicas per node for the service.
|
||||||
|
* `GET /containers` now returns `Capabilities` field as part of the `HostConfig`.
|
||||||
|
* `GET /containers/{id}` now returns `Capabilities` field as part of the `HostConfig`.
|
||||||
|
* `POST /containers/create` now takes `Capabilities` field to set exact list kernel capabilities to be available for container (this overrides the default set).
|
||||||
|
|
||||||
## V1.39 API changes
|
## V1.39 API changes
|
||||||
|
|
||||||
|
@ -1377,6 +1377,8 @@ func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCmd(c *check.C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// regression #14318
|
// regression #14318
|
||||||
|
// for backward compatibility testing with and without CAP_ prefix
|
||||||
|
// and with upper and lowercase
|
||||||
func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCapAddDrop(c *check.C) {
|
func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCapAddDrop(c *check.C) {
|
||||||
// Windows doesn't support CapAdd/CapDrop
|
// Windows doesn't support CapAdd/CapDrop
|
||||||
testRequires(c, DaemonIsLinux)
|
testRequires(c, DaemonIsLinux)
|
||||||
@ -1384,7 +1386,7 @@ func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCapAddDrop(c *che
|
|||||||
Image string
|
Image string
|
||||||
CapAdd string
|
CapAdd string
|
||||||
CapDrop string
|
CapDrop string
|
||||||
}{"busybox", "NET_ADMIN", "SYS_ADMIN"}
|
}{"busybox", "NET_ADMIN", "cap_sys_admin"}
|
||||||
res, _, err := request.Post("/containers/create?name=capaddtest0", request.JSONBody(config))
|
res, _, err := request.Post("/containers/create?name=capaddtest0", request.JSONBody(config))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
c.Assert(res.StatusCode, checker.Equals, http.StatusCreated)
|
c.Assert(res.StatusCode, checker.Equals, http.StatusCreated)
|
||||||
@ -1393,8 +1395,8 @@ func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCapAddDrop(c *che
|
|||||||
Image: "busybox",
|
Image: "busybox",
|
||||||
}
|
}
|
||||||
hostConfig := containertypes.HostConfig{
|
hostConfig := containertypes.HostConfig{
|
||||||
CapAdd: []string{"NET_ADMIN", "SYS_ADMIN"},
|
CapAdd: []string{"net_admin", "SYS_ADMIN"},
|
||||||
CapDrop: []string{"SETGID"},
|
CapDrop: []string{"SETGID", "CAP_SETPCAP"},
|
||||||
}
|
}
|
||||||
|
|
||||||
cli, err := client.NewClientWithOpts(client.FromEnv)
|
cli, err := client.NewClientWithOpts(client.FromEnv)
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/api/types/versions"
|
"github.com/docker/docker/api/types/versions"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
ctr "github.com/docker/docker/integration/internal/container"
|
ctr "github.com/docker/docker/integration/internal/container"
|
||||||
"github.com/docker/docker/internal/test/request"
|
"github.com/docker/docker/internal/test/request"
|
||||||
"github.com/docker/docker/oci"
|
"github.com/docker/docker/oci"
|
||||||
@ -225,6 +226,131 @@ func TestCreateWithCustomMaskedPaths(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateWithCapabilities(t *testing.T) {
|
||||||
|
skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME: test should be able to run on LCOW")
|
||||||
|
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"), "Capabilities was added in API v1.40")
|
||||||
|
|
||||||
|
defer setupTest(t)()
|
||||||
|
ctx := context.Background()
|
||||||
|
clientNew := request.NewAPIClient(t)
|
||||||
|
clientOld := request.NewAPIClient(t, client.WithVersion("1.39"))
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
doc string
|
||||||
|
hostConfig container.HostConfig
|
||||||
|
expected []string
|
||||||
|
expectedError string
|
||||||
|
oldClient bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
doc: "no capabilities",
|
||||||
|
hostConfig: container.HostConfig{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "empty capabilities",
|
||||||
|
hostConfig: container.HostConfig{
|
||||||
|
Capabilities: []string{},
|
||||||
|
},
|
||||||
|
expected: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "valid capabilities",
|
||||||
|
hostConfig: container.HostConfig{
|
||||||
|
Capabilities: []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"},
|
||||||
|
},
|
||||||
|
expected: []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "invalid capabilities",
|
||||||
|
hostConfig: container.HostConfig{
|
||||||
|
Capabilities: []string{"NET_RAW"},
|
||||||
|
},
|
||||||
|
expectedError: `invalid Capabilities: unknown capability: "NET_RAW"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "duplicate capabilities",
|
||||||
|
hostConfig: container.HostConfig{
|
||||||
|
Capabilities: []string{"CAP_SYS_NICE", "CAP_SYS_NICE"},
|
||||||
|
},
|
||||||
|
expected: []string{"CAP_SYS_NICE", "CAP_SYS_NICE"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "capabilities API v1.39",
|
||||||
|
hostConfig: container.HostConfig{
|
||||||
|
Capabilities: []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"},
|
||||||
|
},
|
||||||
|
expected: nil,
|
||||||
|
oldClient: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "empty capadd",
|
||||||
|
hostConfig: container.HostConfig{
|
||||||
|
Capabilities: []string{"CAP_NET_ADMIN"},
|
||||||
|
CapAdd: []string{},
|
||||||
|
},
|
||||||
|
expected: []string{"CAP_NET_ADMIN"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "empty capdrop",
|
||||||
|
hostConfig: container.HostConfig{
|
||||||
|
Capabilities: []string{"CAP_NET_ADMIN"},
|
||||||
|
CapDrop: []string{},
|
||||||
|
},
|
||||||
|
expected: []string{"CAP_NET_ADMIN"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "capadd capdrop",
|
||||||
|
hostConfig: container.HostConfig{
|
||||||
|
CapAdd: []string{"SYS_NICE", "CAP_SYS_NICE"},
|
||||||
|
CapDrop: []string{"SYS_NICE", "CAP_SYS_NICE"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "conflict with capadd",
|
||||||
|
hostConfig: container.HostConfig{
|
||||||
|
Capabilities: []string{"CAP_NET_ADMIN"},
|
||||||
|
CapAdd: []string{"SYS_NICE"},
|
||||||
|
},
|
||||||
|
expectedError: `conflicting options: Capabilities and CapAdd`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "conflict with capdrop",
|
||||||
|
hostConfig: container.HostConfig{
|
||||||
|
Capabilities: []string{"CAP_NET_ADMIN"},
|
||||||
|
CapDrop: []string{"NET_RAW"},
|
||||||
|
},
|
||||||
|
expectedError: `conflicting options: Capabilities and CapDrop`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.doc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
client := clientNew
|
||||||
|
if tc.oldClient {
|
||||||
|
client = clientOld
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := client.ContainerCreate(context.Background(),
|
||||||
|
&container.Config{Image: "busybox"},
|
||||||
|
&tc.hostConfig,
|
||||||
|
&network.NetworkingConfig{},
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
if tc.expectedError == "" {
|
||||||
|
assert.NilError(t, err)
|
||||||
|
ci, err := client.ContainerInspect(ctx, c.ID)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Check(t, ci.HostConfig != nil)
|
||||||
|
assert.DeepEqual(t, tc.expected, ci.HostConfig.Capabilities)
|
||||||
|
} else {
|
||||||
|
assert.ErrorContains(t, err, tc.expectedError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCreateWithCustomReadonlyPaths(t *testing.T) {
|
func TestCreateWithCustomReadonlyPaths(t *testing.T) {
|
||||||
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
|
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker/errdefs"
|
||||||
"github.com/syndtr/gocapability/capability"
|
"github.com/syndtr/gocapability/capability"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -67,73 +68,102 @@ func GetAllCapabilities() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// inSlice tests whether a string is contained in a slice of strings or not.
|
// inSlice tests whether a string is contained in a slice of strings or not.
|
||||||
// Comparison is case insensitive
|
|
||||||
func inSlice(slice []string, s string) bool {
|
func inSlice(slice []string, s string) bool {
|
||||||
for _, ss := range slice {
|
for _, ss := range slice {
|
||||||
if strings.ToLower(s) == strings.ToLower(ss) {
|
if s == ss {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// TweakCapabilities can tweak capabilities by adding or dropping capabilities
|
const allCapabilities = "ALL"
|
||||||
// based on the basics capabilities.
|
|
||||||
func TweakCapabilities(basics, adds, drops []string) ([]string, error) {
|
|
||||||
var (
|
|
||||||
newCaps []string
|
|
||||||
allCaps = GetAllCapabilities()
|
|
||||||
)
|
|
||||||
|
|
||||||
// FIXME(tonistiigi): docker format is without CAP_ prefix, oci is with prefix
|
// NormalizeLegacyCapabilities normalizes, and validates CapAdd/CapDrop capabilities
|
||||||
// Currently they are mixed in here. We should do conversion in one place.
|
// by upper-casing them, and adding a CAP_ prefix (if not yet present).
|
||||||
|
//
|
||||||
|
// This function also accepts the "ALL" magic-value, that's used by CapAdd/CapDrop.
|
||||||
|
func NormalizeLegacyCapabilities(caps []string) ([]string, error) {
|
||||||
|
var normalized []string
|
||||||
|
|
||||||
// look for invalid cap in the drop list
|
valids := GetAllCapabilities()
|
||||||
for _, cap := range drops {
|
for _, c := range caps {
|
||||||
if strings.ToLower(cap) == "all" {
|
c = strings.ToUpper(c)
|
||||||
|
if c == allCapabilities {
|
||||||
|
normalized = append(normalized, c)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if !strings.HasPrefix(c, "CAP_") {
|
||||||
if !inSlice(allCaps, "CAP_"+cap) {
|
c = "CAP_" + c
|
||||||
return nil, fmt.Errorf("Unknown capability drop: %q", cap)
|
|
||||||
}
|
}
|
||||||
|
if !inSlice(valids, c) {
|
||||||
|
return nil, errdefs.InvalidParameter(fmt.Errorf("unknown capability: %q", c))
|
||||||
|
}
|
||||||
|
normalized = append(normalized, c)
|
||||||
}
|
}
|
||||||
|
return normalized, nil
|
||||||
// handle --cap-add=all
|
}
|
||||||
if inSlice(adds, "all") {
|
|
||||||
basics = allCaps
|
// ValidateCapabilities validates if caps only contains valid capabilities
|
||||||
}
|
func ValidateCapabilities(caps []string) error {
|
||||||
|
valids := GetAllCapabilities()
|
||||||
if !inSlice(drops, "all") {
|
for _, c := range caps {
|
||||||
for _, cap := range basics {
|
if !inSlice(valids, c) {
|
||||||
// skip `all` already handled above
|
return errdefs.InvalidParameter(fmt.Errorf("unknown capability: %q", c))
|
||||||
if strings.ToLower(cap) == "all" {
|
}
|
||||||
continue
|
}
|
||||||
}
|
return nil
|
||||||
|
}
|
||||||
// if we don't drop `all`, add back all the non-dropped caps
|
|
||||||
if !inSlice(drops, cap[4:]) {
|
// TweakCapabilities tweaks capabilities by adding, dropping, or overriding
|
||||||
newCaps = append(newCaps, strings.ToUpper(cap))
|
// capabilities in the basics capabilities list.
|
||||||
}
|
func TweakCapabilities(basics, adds, drops, capabilities []string, privileged bool) ([]string, error) {
|
||||||
}
|
switch {
|
||||||
}
|
case privileged:
|
||||||
|
// Privileged containers get all capabilities
|
||||||
for _, cap := range adds {
|
return GetAllCapabilities(), nil
|
||||||
// skip `all` already handled above
|
case capabilities != nil:
|
||||||
if strings.ToLower(cap) == "all" {
|
// Use custom set of capabilities
|
||||||
continue
|
if err := ValidateCapabilities(capabilities); err != nil {
|
||||||
}
|
return nil, err
|
||||||
|
}
|
||||||
cap = "CAP_" + cap
|
return capabilities, nil
|
||||||
|
case len(adds) == 0 && len(drops) == 0:
|
||||||
if !inSlice(allCaps, cap) {
|
// Nothing to tweak; we're done
|
||||||
return nil, fmt.Errorf("Unknown capability to add: %q", cap)
|
return basics, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// add cap if not already in the list
|
capDrop, err := NormalizeLegacyCapabilities(drops)
|
||||||
if !inSlice(newCaps, cap) {
|
if err != nil {
|
||||||
newCaps = append(newCaps, strings.ToUpper(cap))
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
capAdd, err := NormalizeLegacyCapabilities(adds)
|
||||||
return newCaps, nil
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var caps []string
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case inSlice(capAdd, allCapabilities):
|
||||||
|
// Add all capabilities except ones on capDrop
|
||||||
|
for _, c := range GetAllCapabilities() {
|
||||||
|
if !inSlice(capDrop, c) {
|
||||||
|
caps = append(caps, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case inSlice(capDrop, allCapabilities):
|
||||||
|
// "Drop" all capabilities; use what's in capAdd instead
|
||||||
|
caps = capAdd
|
||||||
|
default:
|
||||||
|
// First drop some capabilities
|
||||||
|
for _, c := range basics {
|
||||||
|
if !inSlice(capDrop, c) {
|
||||||
|
caps = append(caps, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Then add the list of capabilities from capAdd
|
||||||
|
caps = append(caps, capAdd...)
|
||||||
|
}
|
||||||
|
return caps, nil
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,8 @@ func iPtr(i int64) *int64 { return &i }
|
|||||||
func u32Ptr(i int64) *uint32 { u := uint32(i); return &u }
|
func u32Ptr(i int64) *uint32 { u := uint32(i); return &u }
|
||||||
func fmPtr(i int64) *os.FileMode { fm := os.FileMode(i); return &fm }
|
func fmPtr(i int64) *os.FileMode { fm := os.FileMode(i); return &fm }
|
||||||
|
|
||||||
func defaultCapabilities() []string {
|
// DefaultCapabilities returns a Linux kernel default capabilities
|
||||||
|
func DefaultCapabilities() []string {
|
||||||
return []string{
|
return []string{
|
||||||
"CAP_CHOWN",
|
"CAP_CHOWN",
|
||||||
"CAP_DAC_OVERRIDE",
|
"CAP_DAC_OVERRIDE",
|
||||||
@ -59,10 +60,10 @@ func DefaultLinuxSpec() specs.Spec {
|
|||||||
Version: specs.Version,
|
Version: specs.Version,
|
||||||
Process: &specs.Process{
|
Process: &specs.Process{
|
||||||
Capabilities: &specs.LinuxCapabilities{
|
Capabilities: &specs.LinuxCapabilities{
|
||||||
Bounding: defaultCapabilities(),
|
Bounding: DefaultCapabilities(),
|
||||||
Permitted: defaultCapabilities(),
|
Permitted: DefaultCapabilities(),
|
||||||
Inheritable: defaultCapabilities(),
|
Inheritable: DefaultCapabilities(),
|
||||||
Effective: defaultCapabilities(),
|
Effective: DefaultCapabilities(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Root: &specs.Root{},
|
Root: &specs.Root{},
|
||||||
|
15
oci/oci.go
15
oci/oci.go
@ -5,7 +5,6 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/docker/docker/oci/caps"
|
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -14,19 +13,7 @@ var deviceCgroupRuleRegex = regexp.MustCompile("^([acb]) ([0-9]+|\\*):([0-9]+|\\
|
|||||||
|
|
||||||
// SetCapabilities sets the provided capabilities on the spec
|
// SetCapabilities sets the provided capabilities on the spec
|
||||||
// All capabilities are added if privileged is true
|
// All capabilities are added if privileged is true
|
||||||
func SetCapabilities(s *specs.Spec, add, drop []string, privileged bool) error {
|
func SetCapabilities(s *specs.Spec, caplist []string) error {
|
||||||
var (
|
|
||||||
caplist []string
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if privileged {
|
|
||||||
caplist = caps.GetAllCapabilities()
|
|
||||||
} else {
|
|
||||||
caplist, err = caps.TweakCapabilities(s.Process.Capabilities.Bounding, add, drop)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.Process.Capabilities.Effective = caplist
|
s.Process.Capabilities.Effective = caplist
|
||||||
s.Process.Capabilities.Bounding = caplist
|
s.Process.Capabilities.Bounding = caplist
|
||||||
s.Process.Capabilities.Permitted = caplist
|
s.Process.Capabilities.Permitted = caplist
|
||||||
|
Reference in New Issue
Block a user