8000 Merge pull request #38380 from olljanat/capabilities-support · moby/moby@5801c04 · GitHub
[go: up one dir, main page]

Skip to content

Commit 5801c04

Browse files
authored
Merge pull request #38380 from olljanat/capabilities-support
Add support for exact list of capabilities + capAdd / capDrop refactor
2 parents 0cde75e + 80d7bfd commit 5801c04

File tree

12 files changed

+270
-73
lines changed

12 files changed

+270
-73
lines changed

api/server/router/container/container_routes.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,11 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
473473
hostConfig.KernelMemoryTCP = 0
474474
}
475475

476+
// Ignore Capabilities because it was added in API 1.40.
477+
if hostConfig != nil && versions.LessThan(version, "1.40") {
478+
hostConfig.Capabilities = nil
479+
}
480+
476481
ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
477482
Name: name,
478483
Config: config,

api/swagger.yaml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -645,14 +645,22 @@ definitions:
645645
$ref: "#/definitions/Mount"
646646

647647
# Applicable to UNIX platforms
648+
Capabilities:
649+
type: "array"
650+
description: |
651+
A list of kernel capabilities to be available for container (this overrides the default set).
652+
653+
Conflicts with options 'CapAdd' and 'CapDrop'"
654+
items:
655+
type: "string"
648656
CapAdd:
649657
type: "array"
650-
description: "A list of kernel capabilities to add to the container."
658+
description: "A list of kernel capabilities to add to the container. Conflicts with option 'Capabilities'"
651659
items:
652660
type: "string"
653661
CapDrop:
654662
type: "array"
655-
description: "A list of kernel capabilities to drop from the container."
663+
description: "A list of kernel capabilities to drop from the container. Conflicts with option 'Capabilities'"
656664
items:
657665
type: "string"
658666
Dns:

api/types/container/host_config.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -370,9 +370,10 @@ type HostConfig struct {
370370
// Applicable to UNIX platforms
371371
CapAdd strslice.StrSlice // List of kernel capabilities to add to the container
372372
CapDrop strslice.StrSlice // List of kernel capabilities to remove from the container
373-
DNS []string `json:"Dns"` // List of DNS server to lookup
374-
DNSOptions []string `json:"DnsOptions"` // List of DNSOption to look for
375-
DNSSearch []string `json:"DnsSearch"` // List of DNSSearch to look for
373+
Capabilities []string `json:"Capabilities"` // List of kernel capabilities to be available for container (this overrides the default set)
374+
DNS []string `json:"Dns"` // List of DNS server to lookup
375+
DNSOptions []string `json:"DnsOptions"` // List of DNSOption to look for
376+
DNSSearch []string `json:"DnsSearch"` // List of DNSSearch to look for
376377
ExtraHosts []string // List of extra hosts
377378
GroupAdd []string // List of additional groups that the container process will run as
378379
IpcMode IpcMode // IPC namespace to use for the container

daemon/container.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/docker/docker/daemon/network"
1616
"github.com/docker/docker/errdefs"
1717
"github.com/docker/docker/image"
18+
"github.com/docker/docker/oci/caps"
1819
"github.com/docker/docker/opts"
1920
"github.com/docker/docker/pkg/signal"
2021
"github.com/docker/docker/pkg/system"
@@ -295,12 +296,35 @@ func validateHostConfig(hostConfig *containertypes.HostConfig, platform string)
295296
if err := validateRestartPolicy(hostConfig.RestartPolicy); err != nil {
296297
return err
297298
}
299+
if err := validateCapabilities(hostConfig); err != nil {
300+
return err
301+
}
298302
if !hostConfig.Isolation.IsValid() {
299303
return errors.Errorf("invalid isolation '%s' on %s", hostConfig.Isolation, runtime.GOOS)
300304
}
301305
return nil
302306
}
303307

308+
func validateCapabilities(hostConfig *containertypes.HostConfig) error {
309+
if len(hostConfig.CapAdd) > 0 && hostConfig.Capabilities != nil {
310+
return errdefs.InvalidParameter(errors.Errorf("conflicting options: Capabilities and CapAdd"))
311+
}
312+
if len(hostConfig.CapDrop) > 0 && hostConfig.Capabilities != nil {
313+
return errdefs.InvalidParameter(errors.Errorf("conflicting options: Capabilities and CapDrop"))
314+
}
315+
if _, err := caps.NormalizeLegacyCapabilities(hostConfig.CapAdd); err != nil {
316+
return errors.Wrap(err, "invalid CapAdd")
317+
}
318+
if _, err := caps.NormalizeLegacyCapabilities(hostConfig.CapDrop); err != nil {
319+
return errors.Wrap(err, "invalid CapDrop")
320+
}
321+
if err := caps.ValidateCapabilities(hostConfig.Capabilities); err != nil {
322+
return errors.Wrap(err, "invalid Capabilities")
323+
}
324+
// TODO consider returning warnings if "Privileged" is combined with Capabilities, CapAdd and/or CapDrop
325+
return nil
326+
}
327+
304328
// validateHealthCheck validates the healthcheck params of Config
305329
func validateHealthCheck(healthConfig *containertypes.HealthConfig) error {
306330
if healthConfig == nil {

daemon/oci_linux.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/docker/docker/container"
1515
daemonconfig "github.com/docker/docker/daemon/config"
1616
"github.com/docker/docker/oci"
17+
"github.com/docker/docker/oci/caps"
1718
"github.com/docker/docker/pkg/idtools"
1819
"github.com/docker/docker/pkg/mount"
1920
volumemounts "github.com/docker/docker/volume/mounts"
@@ -762,7 +763,11 @@ func (daemon *Daemon) createSpec(c *container.Container) (retSpec *specs.Spec, e
762763
if err := setNamespaces(daemon, &s, c); err != nil {
763764
return nil, fmt.Errorf("linux spec namespaces: %v", err)
764765
}
765-
if err := oci.SetCapabilities(&s, c.HostConfig.CapAdd, c.HostConfig.CapDrop, c.HostConfig.Privileged); err != nil {
766+
capabilities, err := caps.TweakCapabilities(oci.DefaultCapabilities(), c.HostConfig.CapAdd, c.HostConfig.CapDrop, c.HostConfig.Capabilities, c.HostConfig.Privileged)
767+
if err != nil {
768+
return nil, fmt.Errorf("linux spec capabilities: %v", err)
769+
}
770+
if err := oci.SetCapabilities(&s, capabilities); err != nil {
766771
return nil, fmt.Errorf("linux spec capabilities: %v", err)
767772
}
768773
if err := setSeccomp(daemon, &s, c); err != nil {

daemon/oci_windows.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
containertypes "github.com/docker/docker/api/types/container"
1111
"github.com/docker/docker/container"
1212
"github.com/docker/docker/oci"
13+
"github.com/docker/docker/oci/caps"
1314
"github.com/docker/docker/pkg/sysinfo"
1415
"github.com/docker/docker/pkg/system"
1516
"github.com/opencontainers/runtime-spec/specs-go"
@@ -368,7 +369,11 @@ func (daemon *Daemon) createSpecLinuxFields(c *container.Container, s *specs.Spe
368369
}
369370
s.Root.Path = "rootfs"
370371
s.Root.Readonly = c.HostConfig.ReadonlyRootfs
371-
if err := oci.SetCapabilities(s, c.HostConfig.CapAdd, c.HostConfig.CapDrop, c.HostConfig.Privileged); err != nil {
372+
capabilities, err := caps.TweakCapabilities(oci.DefaultCapabilities(), c.HostConfig.CapAdd, c.HostConfig.CapDrop, c.HostConfig.Capabilities, c.HostConfig.Privileged)
373+
if err != nil {
374+
return fmt.Errorf("linux spec capabilities: %v", err)
375+
}
376+
if err := oci.SetCapabilities(s, capabilities); err != nil {
372377
return fmt.Errorf("linux spec capabilities: %v", err)
373378
}
374379
devPermissions, err := oci.AppendDevicePermissionsFromCgroupRules(nil, c.HostConfig.DeviceCgroupRules)

docs/api/version-history.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ keywords: "API, Docker, rcli, REST, documentation"
3737
* `GET /service/{id}` now returns `MaxReplicas` as part of the `Placement`.
3838
* `POST /service/create` and `POST /services/(id or name)/update` now take the field `MaxReplicas`
3939
as part of the service `Placement`, allowing to specify maximum replicas per node for the service.
40+
* `GET /containers` now returns `Capabilities` field as part of the `HostConfig`.
41+
* `GET /containers/{id}` now returns `Capabilities` field as part of the `HostConfig`.
42+
* `POST /containers/create` now takes `Capabilities` field to set exact list kernel capabilities to be available for container (this overrides the default set).
4043

4144
## V1.39 API changes
4245

integration-cli/docker_api_containers_test.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,14 +1377,16 @@ func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCmd(c *check.C) {
13771377
}
13781378

13791379
// regression #14318
1380+
// for backward compatibility testing with and without CAP_ prefix
1381+
// and with upper and lowercase
13801382
func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCapAddDrop(c *check.C) {
13811383
// Windows doesn't support CapAdd/CapDrop
13821384
testRequires(c, DaemonIsLinux)
13831385
config := struct {
13841386
Image string
13851387
CapAdd string
13861388
CapDrop string
1387-
}{"busybox", "NET_ADMIN", "SYS_ADMIN"}
1389+
}{"busybox", "NET_ADMIN", "cap_sys_admin"}
13881390
res, _, err := request.Post("/containers/create?name=capaddtest0", request.JSONBody(config))
13891391
c.Assert(err, checker.IsNil)
13901392
c.Assert(res.StatusCode, checker.Equals, http.StatusCreated)
@@ -1393,8 +1395,8 @@ func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCapAddDrop(c *che
13931395
Image: "busybox",
13941396
}
13951397
hostConfig := containertypes.HostConfig{
1396-
CapAdd: []string{"NET_ADMIN", "SYS_ADMIN"},
1397-
CapDrop: []string{"SETGID"},
1398+
CapAdd: []string{"net_admin", "SYS_ADMIN"},
1399+
CapDrop: []string{"SETGID", "CAP_SETPCAP"},
13981400
}
13991401

14001402
cli, err := client.NewClientWithOpts(client.FromEnv)

integration/container/create_test.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/docker/docker/api/types/container"
1414
"github.com/docker/docker/api/types/network"
1515
"github.com/docker/docker/api/types/versions"
16+
"github.com/docker/docker/client"
1617
ctr "github.com/docker/docker/integration/internal/container"
1718
"github.com/docker/docker/internal/test/request"
1819
"github.com/docker/docker/oci"
@@ -225,6 +226,131 @@ func TestCreateWithCustomMaskedPaths(t *testing.T) {
225226
}
226227
}
227228

229+
func TestCreateWithCapabilities(t *testing.T) {
230+
skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME: test should be able to run on LCOW")
231+
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"), "Capabilities was added in API v1.40")
232+
233+
defer setupTest(t)()
234+
ctx := context.Background()
235+
clientNew := request.NewAPIClient(t)
236+
clientOld := request.NewAPIClient(t, client.WithVersion("1.39"))
237+
238+
testCases := []struct {
239+
doc string
240+
hostConfig container.HostConfig
241+
expected []string
242+
expectedError string
243+
oldClient bool
244+
}{
245+
{
246+
doc: "no capabilities",
247+
hostConfig: container.HostConfig{},
248+
},
249+
{
250+
doc: "empty capabilities",
251+
hostConfig: container.HostConfig{
252+
Capabilities: []string{},
253+
},
254+
expected: []string{},
255+
},
256+
{
257+
doc: "valid capabilities",
258+
hostConfig: container.HostConfig{
259+
Capabilities: []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"},
260+
},
261+
expected: []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"},
262+
},
263+
{
264+
doc: "invalid capabilities",
265+
hostConfig: container.HostConfig{
266+
Capabilities: []string{"NET_RAW"},
267+
},
268+
expectedError: `invalid Capabilities: unknown capability: "NET_RAW"`,
269+
},
270+
{
271+
doc: "duplicate capabilities",
272+
hostConfig: container.HostConfig{
273+
Capabilities: []string{"CAP_SYS_NICE", "CAP_SYS_NICE"},
274+
},
275+
expected: []string{"CAP_SYS_NICE", "CAP_SYS_NICE"},
276+
},
277+
{
278+
doc: "capabilities API v1.39",
279+
hostConfig: container.HostConfig{
280+
Capabilities: []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"},
281+
},
282+
expected: nil,
283+
oldClient: true,
284+
},
285+
{
286+
doc: "empty capadd",
287+
hostConfig: container.HostConfig{
288+
Capabilities: []string{"CAP_NET_ADMIN"},
289+
CapAdd: []string{},
290+
},
291+
expected: []string{"CAP_NET_ADMIN"},
292+
},
293+
{
294+
doc: "empty capdrop",
295+
hostConfig: container.HostConfig{
296+
Capabilities: []string{"CAP_NET_ADMIN"},
297+
CapDrop: []string{},
298+
},
299+
expected: []string{"CAP_NET_ADMIN"},
300+
},
301+
{
302+
doc: "capadd capdrop",
303+
hostConfig: container.HostConfig{
304+
CapAdd: []string{"SYS_NICE", "CAP_SYS_NICE"},
305+
CapDrop: []string{"SYS_NICE", "CAP_SYS_NICE"},
306+
},
307+
},
308+
{
309+
doc: "conflict with capadd",
310+
hostConfig: container.HostConfig{
311+
Capabilities: []string{"CAP_NET_ADMIN"},
312+
CapAdd: []string{"SYS_NICE"},
313+
},
314+
expectedError: `conflicting options: Capabilities and CapAdd`,
315+
},
316+
{
317+
doc: "conflict with capdrop",
318+
hostConfig: container.HostConfig{
319+
Capabilities: []string{"CAP_NET_ADMIN"},
320+
CapDrop: []string{"NET_RAW"},
321+
},
322+
expectedError: `conflicting options: Capabilities and CapDrop`,
323+
},
324+
}
325+
326+
for _, tc := range testCases {
327+
tc := tc
328+
t.Run(tc.doc, func(t *testing.T) {
329+
t.Parallel()
330+
client := clientNew
331+
if tc.oldClient {
332+
client = clientOld
333+
}
334+
335+
c, err := client.ContainerCreate(context.Background(),
336+
&container.Config{Image: "busybox"},
337+
&tc.hostConfig,
338+
&network.NetworkingConfig{},
339+
"",
340+
)
341+
if tc.expectedError == "" {
342+
assert.NilError(t, err)
343+
ci, err := client.ContainerInspect(ctx, c.ID)
344+
assert.NilError(t, err)
345+
assert.Check(t, ci.HostConfig != nil)
346+
assert.DeepEqual(t, tc.expected, ci.HostConfig.Capabilities)
347+
} else {
348+
assert.ErrorContains(t, err, tc.expectedError)
349+
}
350+
})
351+
}
352+
}
353+
228354
func TestCreateWithCustomReadonlyPaths(t *testing.T) {
229355
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
230356

0 commit comments

Comments
 (0)
0