Signed-off-by: Olli Janatuinen <olli.janatuinen@gmail.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
| ... | ... |
@@ -473,6 +473,11 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo |
| 473 | 473 |
hostConfig.KernelMemoryTCP = 0 |
| 474 | 474 |
} |
| 475 | 475 |
|
| 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 |
+ |
|
| 476 | 481 |
ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
|
| 477 | 482 |
Name: name, |
| 478 | 483 |
Config: config, |
| ... | ... |
@@ -645,14 +645,22 @@ definitions: |
| 645 | 645 |
$ref: "#/definitions/Mount" |
| 646 | 646 |
|
| 647 | 647 |
# 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" |
|
| 648 | 656 |
CapAdd: |
| 649 | 657 |
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'" |
|
| 651 | 659 |
items: |
| 652 | 660 |
type: "string" |
| 653 | 661 |
CapDrop: |
| 654 | 662 |
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'" |
|
| 656 | 664 |
items: |
| 657 | 665 |
type: "string" |
| 658 | 666 |
Dns: |
| ... | ... |
@@ -370,9 +370,10 @@ type HostConfig struct {
|
| 370 | 370 |
// Applicable to UNIX platforms |
| 371 | 371 |
CapAdd strslice.StrSlice // List of kernel capabilities to add to the container |
| 372 | 372 |
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 |
|
| 376 | 377 |
ExtraHosts []string // List of extra hosts |
| 377 | 378 |
GroupAdd []string // List of additional groups that the container process will run as |
| 378 | 379 |
IpcMode IpcMode // IPC namespace to use for the container |
| ... | ... |
@@ -15,6 +15,7 @@ import ( |
| 15 | 15 |
"github.com/docker/docker/daemon/network" |
| 16 | 16 |
"github.com/docker/docker/errdefs" |
| 17 | 17 |
"github.com/docker/docker/image" |
| 18 |
+ "github.com/docker/docker/oci/caps" |
|
| 18 | 19 |
"github.com/docker/docker/opts" |
| 19 | 20 |
"github.com/docker/docker/pkg/signal" |
| 20 | 21 |
"github.com/docker/docker/pkg/system" |
| ... | ... |
@@ -295,12 +296,35 @@ func validateHostConfig(hostConfig *containertypes.HostConfig, platform string) |
| 295 | 295 |
if err := validateRestartPolicy(hostConfig.RestartPolicy); err != nil {
|
| 296 | 296 |
return err |
| 297 | 297 |
} |
| 298 |
+ if err := validateCapabilities(hostConfig); err != nil {
|
|
| 299 |
+ return err |
|
| 300 |
+ } |
|
| 298 | 301 |
if !hostConfig.Isolation.IsValid() {
|
| 299 | 302 |
return errors.Errorf("invalid isolation '%s' on %s", hostConfig.Isolation, runtime.GOOS)
|
| 300 | 303 |
} |
| 301 | 304 |
return nil |
| 302 | 305 |
} |
| 303 | 306 |
|
| 307 |
+func validateCapabilities(hostConfig *containertypes.HostConfig) error {
|
|
| 308 |
+ if len(hostConfig.CapAdd) > 0 && hostConfig.Capabilities != nil {
|
|
| 309 |
+ return errdefs.InvalidParameter(errors.Errorf("conflicting options: Capabilities and CapAdd"))
|
|
| 310 |
+ } |
|
| 311 |
+ if len(hostConfig.CapDrop) > 0 && hostConfig.Capabilities != nil {
|
|
| 312 |
+ return errdefs.InvalidParameter(errors.Errorf("conflicting options: Capabilities and CapDrop"))
|
|
| 313 |
+ } |
|
| 314 |
+ if _, err := caps.NormalizeLegacyCapabilities(hostConfig.CapAdd); err != nil {
|
|
| 315 |
+ return errors.Wrap(err, "invalid CapAdd") |
|
| 316 |
+ } |
|
| 317 |
+ if _, err := caps.NormalizeLegacyCapabilities(hostConfig.CapDrop); err != nil {
|
|
| 318 |
+ return errors.Wrap(err, "invalid CapDrop") |
|
| 319 |
+ } |
|
| 320 |
+ if err := caps.ValidateCapabilities(hostConfig.Capabilities); err != nil {
|
|
| 321 |
+ return errors.Wrap(err, "invalid Capabilities") |
|
| 322 |
+ } |
|
| 323 |
+ // TODO consider returning warnings if "Privileged" is combined with Capabilities, CapAdd and/or CapDrop |
|
| 324 |
+ return nil |
|
| 325 |
+} |
|
| 326 |
+ |
|
| 304 | 327 |
// validateHealthCheck validates the healthcheck params of Config |
| 305 | 328 |
func validateHealthCheck(healthConfig *containertypes.HealthConfig) error {
|
| 306 | 329 |
if healthConfig == nil {
|
| ... | ... |
@@ -14,6 +14,7 @@ import ( |
| 14 | 14 |
"github.com/docker/docker/container" |
| 15 | 15 |
daemonconfig "github.com/docker/docker/daemon/config" |
| 16 | 16 |
"github.com/docker/docker/oci" |
| 17 |
+ "github.com/docker/docker/oci/caps" |
|
| 17 | 18 |
"github.com/docker/docker/pkg/idtools" |
| 18 | 19 |
"github.com/docker/docker/pkg/mount" |
| 19 | 20 |
volumemounts "github.com/docker/docker/volume/mounts" |
| ... | ... |
@@ -762,7 +763,11 @@ func (daemon *Daemon) createSpec(c *container.Container) (retSpec *specs.Spec, e |
| 762 | 762 |
if err := setNamespaces(daemon, &s, c); err != nil {
|
| 763 | 763 |
return nil, fmt.Errorf("linux spec namespaces: %v", err)
|
| 764 | 764 |
} |
| 765 |
- if err := oci.SetCapabilities(&s, c.HostConfig.CapAdd, c.HostConfig.CapDrop, c.HostConfig.Privileged); err != nil {
|
|
| 765 |
+ capabilities, err := caps.TweakCapabilities(oci.DefaultCapabilities(), c.HostConfig.CapAdd, c.HostConfig.CapDrop, c.HostConfig.Capabilities, c.HostConfig.Privileged) |
|
| 766 |
+ if err != nil {
|
|
| 767 |
+ return nil, fmt.Errorf("linux spec capabilities: %v", err)
|
|
| 768 |
+ } |
|
| 769 |
+ if err := oci.SetCapabilities(&s, capabilities); err != nil {
|
|
| 766 | 770 |
return nil, fmt.Errorf("linux spec capabilities: %v", err)
|
| 767 | 771 |
} |
| 768 | 772 |
if err := setSeccomp(daemon, &s, c); err != nil {
|
| ... | ... |
@@ -10,6 +10,7 @@ import ( |
| 10 | 10 |
containertypes "github.com/docker/docker/api/types/container" |
| 11 | 11 |
"github.com/docker/docker/container" |
| 12 | 12 |
"github.com/docker/docker/oci" |
| 13 |
+ "github.com/docker/docker/oci/caps" |
|
| 13 | 14 |
"github.com/docker/docker/pkg/sysinfo" |
| 14 | 15 |
"github.com/docker/docker/pkg/system" |
| 15 | 16 |
"github.com/opencontainers/runtime-spec/specs-go" |
| ... | ... |
@@ -368,7 +369,11 @@ func (daemon *Daemon) createSpecLinuxFields(c *container.Container, s *specs.Spe |
| 368 | 368 |
} |
| 369 | 369 |
s.Root.Path = "rootfs" |
| 370 | 370 |
s.Root.Readonly = c.HostConfig.ReadonlyRootfs |
| 371 |
- if err := oci.SetCapabilities(s, c.HostConfig.CapAdd, c.HostConfig.CapDrop, c.HostConfig.Privileged); err != nil {
|
|
| 371 |
+ capabilities, err := caps.TweakCapabilities(oci.DefaultCapabilities(), c.HostConfig.CapAdd, c.HostConfig.CapDrop, c.HostConfig.Capabilities, c.HostConfig.Privileged) |
|
| 372 |
+ if err != nil {
|
|
| 373 |
+ return fmt.Errorf("linux spec capabilities: %v", err)
|
|
| 374 |
+ } |
|
| 375 |
+ if err := oci.SetCapabilities(s, capabilities); err != nil {
|
|
| 372 | 376 |
return fmt.Errorf("linux spec capabilities: %v", err)
|
| 373 | 377 |
} |
| 374 | 378 |
devPermissions, err := oci.AppendDevicePermissionsFromCgroupRules(nil, c.HostConfig.DeviceCgroupRules) |
| ... | ... |
@@ -37,6 +37,9 @@ keywords: "API, Docker, rcli, REST, documentation" |
| 37 | 37 |
* `GET /service/{id}` now returns `MaxReplicas` as part of the `Placement`.
|
| 38 | 38 |
* `POST /service/create` and `POST /services/(id or name)/update` now take the field `MaxReplicas` |
| 39 | 39 |
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). |
|
| 40 | 43 |
|
| 41 | 44 |
## V1.39 API changes |
| 42 | 45 |
|
| ... | ... |
@@ -1377,6 +1377,8 @@ func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCmd(c *check.C) {
|
| 1377 | 1377 |
} |
| 1378 | 1378 |
|
| 1379 | 1379 |
// regression #14318 |
| 1380 |
+// for backward compatibility testing with and without CAP_ prefix |
|
| 1381 |
+// and with upper and lowercase |
|
| 1380 | 1382 |
func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCapAddDrop(c *check.C) {
|
| 1381 | 1383 |
// Windows doesn't support CapAdd/CapDrop |
| 1382 | 1384 |
testRequires(c, DaemonIsLinux) |
| ... | ... |
@@ -1384,7 +1386,7 @@ func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCapAddDrop(c *che |
| 1384 | 1384 |
Image string |
| 1385 | 1385 |
CapAdd string |
| 1386 | 1386 |
CapDrop string |
| 1387 |
- }{"busybox", "NET_ADMIN", "SYS_ADMIN"}
|
|
| 1387 |
+ }{"busybox", "NET_ADMIN", "cap_sys_admin"}
|
|
| 1388 | 1388 |
res, _, err := request.Post("/containers/create?name=capaddtest0", request.JSONBody(config))
|
| 1389 | 1389 |
c.Assert(err, checker.IsNil) |
| 1390 | 1390 |
c.Assert(res.StatusCode, checker.Equals, http.StatusCreated) |
| ... | ... |
@@ -1393,8 +1395,8 @@ func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCapAddDrop(c *che |
| 1393 | 1393 |
Image: "busybox", |
| 1394 | 1394 |
} |
| 1395 | 1395 |
hostConfig := containertypes.HostConfig{
|
| 1396 |
- CapAdd: []string{"NET_ADMIN", "SYS_ADMIN"},
|
|
| 1397 |
- CapDrop: []string{"SETGID"},
|
|
| 1396 |
+ CapAdd: []string{"net_admin", "SYS_ADMIN"},
|
|
| 1397 |
+ CapDrop: []string{"SETGID", "CAP_SETPCAP"},
|
|
| 1398 | 1398 |
} |
| 1399 | 1399 |
|
| 1400 | 1400 |
cli, err := client.NewClientWithOpts(client.FromEnv) |
| ... | ... |
@@ -13,6 +13,7 @@ import ( |
| 13 | 13 |
"github.com/docker/docker/api/types/container" |
| 14 | 14 |
"github.com/docker/docker/api/types/network" |
| 15 | 15 |
"github.com/docker/docker/api/types/versions" |
| 16 |
+ "github.com/docker/docker/client" |
|
| 16 | 17 |
ctr "github.com/docker/docker/integration/internal/container" |
| 17 | 18 |
"github.com/docker/docker/internal/test/request" |
| 18 | 19 |
"github.com/docker/docker/oci" |
| ... | ... |
@@ -225,6 +226,131 @@ func TestCreateWithCustomMaskedPaths(t *testing.T) {
|
| 225 | 225 |
} |
| 226 | 226 |
} |
| 227 | 227 |
|
| 228 |
+func TestCreateWithCapabilities(t *testing.T) {
|
|
| 229 |
+ skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME: test should be able to run on LCOW") |
|
| 230 |
+ skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"), "Capabilities was added in API v1.40") |
|
| 231 |
+ |
|
| 232 |
+ defer setupTest(t)() |
|
| 233 |
+ ctx := context.Background() |
|
| 234 |
+ clientNew := request.NewAPIClient(t) |
|
| 235 |
+ clientOld := request.NewAPIClient(t, client.WithVersion("1.39"))
|
|
| 236 |
+ |
|
| 237 |
+ testCases := []struct {
|
|
| 238 |
+ doc string |
|
| 239 |
+ hostConfig container.HostConfig |
|
| 240 |
+ expected []string |
|
| 241 |
+ expectedError string |
|
| 242 |
+ oldClient bool |
|
| 243 |
+ }{
|
|
| 244 |
+ {
|
|
| 245 |
+ doc: "no capabilities", |
|
| 246 |
+ hostConfig: container.HostConfig{},
|
|
| 247 |
+ }, |
|
| 248 |
+ {
|
|
| 249 |
+ doc: "empty capabilities", |
|
| 250 |
+ hostConfig: container.HostConfig{
|
|
| 251 |
+ Capabilities: []string{},
|
|
| 252 |
+ }, |
|
| 253 |
+ expected: []string{},
|
|
| 254 |
+ }, |
|
| 255 |
+ {
|
|
| 256 |
+ doc: "valid capabilities", |
|
| 257 |
+ hostConfig: container.HostConfig{
|
|
| 258 |
+ Capabilities: []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"},
|
|
| 259 |
+ }, |
|
| 260 |
+ expected: []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"},
|
|
| 261 |
+ }, |
|
| 262 |
+ {
|
|
| 263 |
+ doc: "invalid capabilities", |
|
| 264 |
+ hostConfig: container.HostConfig{
|
|
| 265 |
+ Capabilities: []string{"NET_RAW"},
|
|
| 266 |
+ }, |
|
| 267 |
+ expectedError: `invalid Capabilities: unknown capability: "NET_RAW"`, |
|
| 268 |
+ }, |
|
| 269 |
+ {
|
|
| 270 |
+ doc: "duplicate capabilities", |
|
| 271 |
+ hostConfig: container.HostConfig{
|
|
| 272 |
+ Capabilities: []string{"CAP_SYS_NICE", "CAP_SYS_NICE"},
|
|
| 273 |
+ }, |
|
| 274 |
+ expected: []string{"CAP_SYS_NICE", "CAP_SYS_NICE"},
|
|
| 275 |
+ }, |
|
| 276 |
+ {
|
|
| 277 |
+ doc: "capabilities API v1.39", |
|
| 278 |
+ hostConfig: container.HostConfig{
|
|
| 279 |
+ Capabilities: []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"},
|
|
| 280 |
+ }, |
|
| 281 |
+ expected: nil, |
|
| 282 |
+ oldClient: true, |
|
| 283 |
+ }, |
|
| 284 |
+ {
|
|
| 285 |
+ doc: "empty capadd", |
|
| 286 |
+ hostConfig: container.HostConfig{
|
|
| 287 |
+ Capabilities: []string{"CAP_NET_ADMIN"},
|
|
| 288 |
+ CapAdd: []string{},
|
|
| 289 |
+ }, |
|
| 290 |
+ expected: []string{"CAP_NET_ADMIN"},
|
|
| 291 |
+ }, |
|
| 292 |
+ {
|
|
| 293 |
+ doc: "empty capdrop", |
|
| 294 |
+ hostConfig: container.HostConfig{
|
|
| 295 |
+ Capabilities: []string{"CAP_NET_ADMIN"},
|
|
| 296 |
+ CapDrop: []string{},
|
|
| 297 |
+ }, |
|
| 298 |
+ expected: []string{"CAP_NET_ADMIN"},
|
|
| 299 |
+ }, |
|
| 300 |
+ {
|
|
| 301 |
+ doc: "capadd capdrop", |
|
| 302 |
+ hostConfig: container.HostConfig{
|
|
| 303 |
+ CapAdd: []string{"SYS_NICE", "CAP_SYS_NICE"},
|
|
| 304 |
+ CapDrop: []string{"SYS_NICE", "CAP_SYS_NICE"},
|
|
| 305 |
+ }, |
|
| 306 |
+ }, |
|
| 307 |
+ {
|
|
| 308 |
+ doc: "conflict with capadd", |
|
| 309 |
+ hostConfig: container.HostConfig{
|
|
| 310 |
+ Capabilities: []string{"CAP_NET_ADMIN"},
|
|
| 311 |
+ CapAdd: []string{"SYS_NICE"},
|
|
| 312 |
+ }, |
|
| 313 |
+ expectedError: `conflicting options: Capabilities and CapAdd`, |
|
| 314 |
+ }, |
|
| 315 |
+ {
|
|
| 316 |
+ doc: "conflict with capdrop", |
|
| 317 |
+ hostConfig: container.HostConfig{
|
|
| 318 |
+ Capabilities: []string{"CAP_NET_ADMIN"},
|
|
| 319 |
+ CapDrop: []string{"NET_RAW"},
|
|
| 320 |
+ }, |
|
| 321 |
+ expectedError: `conflicting options: Capabilities and CapDrop`, |
|
| 322 |
+ }, |
|
| 323 |
+ } |
|
| 324 |
+ |
|
| 325 |
+ for _, tc := range testCases {
|
|
| 326 |
+ tc := tc |
|
| 327 |
+ t.Run(tc.doc, func(t *testing.T) {
|
|
| 328 |
+ t.Parallel() |
|
| 329 |
+ client := clientNew |
|
| 330 |
+ if tc.oldClient {
|
|
| 331 |
+ client = clientOld |
|
| 332 |
+ } |
|
| 333 |
+ |
|
| 334 |
+ c, err := client.ContainerCreate(context.Background(), |
|
| 335 |
+ &container.Config{Image: "busybox"},
|
|
| 336 |
+ &tc.hostConfig, |
|
| 337 |
+ &network.NetworkingConfig{},
|
|
| 338 |
+ "", |
|
| 339 |
+ ) |
|
| 340 |
+ if tc.expectedError == "" {
|
|
| 341 |
+ assert.NilError(t, err) |
|
| 342 |
+ ci, err := client.ContainerInspect(ctx, c.ID) |
|
| 343 |
+ assert.NilError(t, err) |
|
| 344 |
+ assert.Check(t, ci.HostConfig != nil) |
|
| 345 |
+ assert.DeepEqual(t, tc.expected, ci.HostConfig.Capabilities) |
|
| 346 |
+ } else {
|
|
| 347 |
+ assert.ErrorContains(t, err, tc.expectedError) |
|
| 348 |
+ } |
|
| 349 |
+ }) |
|
| 350 |
+ } |
|
| 351 |
+} |
|
| 352 |
+ |
|
| 228 | 353 |
func TestCreateWithCustomReadonlyPaths(t *testing.T) {
|
| 229 | 354 |
skip.If(t, testEnv.DaemonInfo.OSType != "linux") |
| 230 | 355 |
|
| ... | ... |
@@ -4,6 +4,7 @@ import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
"strings" |
| 6 | 6 |
|
| 7 |
+ "github.com/docker/docker/errdefs" |
|
| 7 | 8 |
"github.com/syndtr/gocapability/capability" |
| 8 | 9 |
) |
| 9 | 10 |
|
| ... | ... |
@@ -67,73 +68,102 @@ func GetAllCapabilities() []string {
|
| 67 | 67 |
} |
| 68 | 68 |
|
| 69 | 69 |
// inSlice tests whether a string is contained in a slice of strings or not. |
| 70 |
-// Comparison is case insensitive |
|
| 71 | 70 |
func inSlice(slice []string, s string) bool {
|
| 72 | 71 |
for _, ss := range slice {
|
| 73 |
- if strings.ToLower(s) == strings.ToLower(ss) {
|
|
| 72 |
+ if s == ss {
|
|
| 74 | 73 |
return true |
| 75 | 74 |
} |
| 76 | 75 |
} |
| 77 | 76 |
return false |
| 78 | 77 |
} |
| 79 | 78 |
|
| 80 |
-// TweakCapabilities can tweak capabilities by adding or dropping capabilities |
|
| 81 |
-// based on the basics capabilities. |
|
| 82 |
-func TweakCapabilities(basics, adds, drops []string) ([]string, error) {
|
|
| 83 |
- var ( |
|
| 84 |
- newCaps []string |
|
| 85 |
- allCaps = GetAllCapabilities() |
|
| 86 |
- ) |
|
| 79 |
+const allCapabilities = "ALL" |
|
| 87 | 80 |
|
| 88 |
- // FIXME(tonistiigi): docker format is without CAP_ prefix, oci is with prefix |
|
| 89 |
- // Currently they are mixed in here. We should do conversion in one place. |
|
| 81 |
+// NormalizeLegacyCapabilities normalizes, and validates CapAdd/CapDrop capabilities |
|
| 82 |
+// by upper-casing them, and adding a CAP_ prefix (if not yet present). |
|
| 83 |
+// |
|
| 84 |
+// This function also accepts the "ALL" magic-value, that's used by CapAdd/CapDrop. |
|
| 85 |
+func NormalizeLegacyCapabilities(caps []string) ([]string, error) {
|
|
| 86 |
+ var normalized []string |
|
| 90 | 87 |
|
| 91 |
- // look for invalid cap in the drop list |
|
| 92 |
- for _, cap := range drops {
|
|
| 93 |
- if strings.ToLower(cap) == "all" {
|
|
| 88 |
+ valids := GetAllCapabilities() |
|
| 89 |
+ for _, c := range caps {
|
|
| 90 |
+ c = strings.ToUpper(c) |
|
| 91 |
+ if c == allCapabilities {
|
|
| 92 |
+ normalized = append(normalized, c) |
|
| 94 | 93 |
continue |
| 95 | 94 |
} |
| 96 |
- |
|
| 97 |
- if !inSlice(allCaps, "CAP_"+cap) {
|
|
| 98 |
- return nil, fmt.Errorf("Unknown capability drop: %q", cap)
|
|
| 95 |
+ if !strings.HasPrefix(c, "CAP_") {
|
|
| 96 |
+ c = "CAP_" + c |
|
| 97 |
+ } |
|
| 98 |
+ if !inSlice(valids, c) {
|
|
| 99 |
+ return nil, errdefs.InvalidParameter(fmt.Errorf("unknown capability: %q", c))
|
|
| 99 | 100 |
} |
| 101 |
+ normalized = append(normalized, c) |
|
| 100 | 102 |
} |
| 103 |
+ return normalized, nil |
|
| 104 |
+} |
|
| 101 | 105 |
|
| 102 |
- // handle --cap-add=all |
|
| 103 |
- if inSlice(adds, "all") {
|
|
| 104 |
- basics = allCaps |
|
| 106 |
+// ValidateCapabilities validates if caps only contains valid capabilities |
|
| 107 |
+func ValidateCapabilities(caps []string) error {
|
|
| 108 |
+ valids := GetAllCapabilities() |
|
| 109 |
+ for _, c := range caps {
|
|
| 110 |
+ if !inSlice(valids, c) {
|
|
| 111 |
+ return errdefs.InvalidParameter(fmt.Errorf("unknown capability: %q", c))
|
|
| 112 |
+ } |
|
| 105 | 113 |
} |
| 114 |
+ return nil |
|
| 115 |
+} |
|
| 106 | 116 |
|
| 107 |
- if !inSlice(drops, "all") {
|
|
| 108 |
- for _, cap := range basics {
|
|
| 109 |
- // skip `all` already handled above |
|
| 110 |
- if strings.ToLower(cap) == "all" {
|
|
| 111 |
- continue |
|
| 112 |
- } |
|
| 113 |
- |
|
| 114 |
- // if we don't drop `all`, add back all the non-dropped caps |
|
| 115 |
- if !inSlice(drops, cap[4:]) {
|
|
| 116 |
- newCaps = append(newCaps, strings.ToUpper(cap)) |
|
| 117 |
- } |
|
| 117 |
+// TweakCapabilities tweaks capabilities by adding, dropping, or overriding |
|
| 118 |
+// capabilities in the basics capabilities list. |
|
| 119 |
+func TweakCapabilities(basics, adds, drops, capabilities []string, privileged bool) ([]string, error) {
|
|
| 120 |
+ switch {
|
|
| 121 |
+ case privileged: |
|
| 122 |
+ // Privileged containers get all capabilities |
|
| 123 |
+ return GetAllCapabilities(), nil |
|
| 124 |
+ case capabilities != nil: |
|
| 125 |
+ // Use custom set of capabilities |
|
| 126 |
+ if err := ValidateCapabilities(capabilities); err != nil {
|
|
| 127 |
+ return nil, err |
|
| 118 | 128 |
} |
| 129 |
+ return capabilities, nil |
|
| 130 |
+ case len(adds) == 0 && len(drops) == 0: |
|
| 131 |
+ // Nothing to tweak; we're done |
|
| 132 |
+ return basics, nil |
|
| 119 | 133 |
} |
| 120 | 134 |
|
| 121 |
- for _, cap := range adds {
|
|
| 122 |
- // skip `all` already handled above |
|
| 123 |
- if strings.ToLower(cap) == "all" {
|
|
| 124 |
- continue |
|
| 125 |
- } |
|
| 135 |
+ capDrop, err := NormalizeLegacyCapabilities(drops) |
|
| 136 |
+ if err != nil {
|
|
| 137 |
+ return nil, err |
|
| 138 |
+ } |
|
| 139 |
+ capAdd, err := NormalizeLegacyCapabilities(adds) |
|
| 140 |
+ if err != nil {
|
|
| 141 |
+ return nil, err |
|
| 142 |
+ } |
|
| 126 | 143 |
|
| 127 |
- cap = "CAP_" + cap |
|
| 144 |
+ var caps []string |
|
| 128 | 145 |
|
| 129 |
- if !inSlice(allCaps, cap) {
|
|
| 130 |
- return nil, fmt.Errorf("Unknown capability to add: %q", cap)
|
|
| 146 |
+ switch {
|
|
| 147 |
+ case inSlice(capAdd, allCapabilities): |
|
| 148 |
+ // Add all capabilities except ones on capDrop |
|
| 149 |
+ for _, c := range GetAllCapabilities() {
|
|
| 150 |
+ if !inSlice(capDrop, c) {
|
|
| 151 |
+ caps = append(caps, c) |
|
| 152 |
+ } |
|
| 131 | 153 |
} |
| 132 |
- |
|
| 133 |
- // add cap if not already in the list |
|
| 134 |
- if !inSlice(newCaps, cap) {
|
|
| 135 |
- newCaps = append(newCaps, strings.ToUpper(cap)) |
|
| 154 |
+ case inSlice(capDrop, allCapabilities): |
|
| 155 |
+ // "Drop" all capabilities; use what's in capAdd instead |
|
| 156 |
+ caps = capAdd |
|
| 157 |
+ default: |
|
| 158 |
+ // First drop some capabilities |
|
| 159 |
+ for _, c := range basics {
|
|
| 160 |
+ if !inSlice(capDrop, c) {
|
|
| 161 |
+ caps = append(caps, c) |
|
| 162 |
+ } |
|
| 136 | 163 |
} |
| 164 |
+ // Then add the list of capabilities from capAdd |
|
| 165 |
+ caps = append(caps, capAdd...) |
|
| 137 | 166 |
} |
| 138 |
- return newCaps, nil |
|
| 167 |
+ return caps, nil |
|
| 139 | 168 |
} |
| ... | ... |
@@ -11,7 +11,8 @@ func iPtr(i int64) *int64 { return &i }
|
| 11 | 11 |
func u32Ptr(i int64) *uint32 { u := uint32(i); return &u }
|
| 12 | 12 |
func fmPtr(i int64) *os.FileMode { fm := os.FileMode(i); return &fm }
|
| 13 | 13 |
|
| 14 |
-func defaultCapabilities() []string {
|
|
| 14 |
+// DefaultCapabilities returns a Linux kernel default capabilities |
|
| 15 |
+func DefaultCapabilities() []string {
|
|
| 15 | 16 |
return []string{
|
| 16 | 17 |
"CAP_CHOWN", |
| 17 | 18 |
"CAP_DAC_OVERRIDE", |
| ... | ... |
@@ -59,10 +60,10 @@ func DefaultLinuxSpec() specs.Spec {
|
| 59 | 59 |
Version: specs.Version, |
| 60 | 60 |
Process: &specs.Process{
|
| 61 | 61 |
Capabilities: &specs.LinuxCapabilities{
|
| 62 |
- Bounding: defaultCapabilities(), |
|
| 63 |
- Permitted: defaultCapabilities(), |
|
| 64 |
- Inheritable: defaultCapabilities(), |
|
| 65 |
- Effective: defaultCapabilities(), |
|
| 62 |
+ Bounding: DefaultCapabilities(), |
|
| 63 |
+ Permitted: DefaultCapabilities(), |
|
| 64 |
+ Inheritable: DefaultCapabilities(), |
|
| 65 |
+ Effective: DefaultCapabilities(), |
|
| 66 | 66 |
}, |
| 67 | 67 |
}, |
| 68 | 68 |
Root: &specs.Root{},
|
| ... | ... |
@@ -5,7 +5,6 @@ import ( |
| 5 | 5 |
"regexp" |
| 6 | 6 |
"strconv" |
| 7 | 7 |
|
| 8 |
- "github.com/docker/docker/oci/caps" |
|
| 9 | 8 |
specs "github.com/opencontainers/runtime-spec/specs-go" |
| 10 | 9 |
) |
| 11 | 10 |
|
| ... | ... |
@@ -14,19 +13,7 @@ var deviceCgroupRuleRegex = regexp.MustCompile("^([acb]) ([0-9]+|\\*):([0-9]+|\\
|
| 14 | 14 |
|
| 15 | 15 |
// SetCapabilities sets the provided capabilities on the spec |
| 16 | 16 |
// All capabilities are added if privileged is true |
| 17 |
-func SetCapabilities(s *specs.Spec, add, drop []string, privileged bool) error {
|
|
| 18 |
- var ( |
|
| 19 |
- caplist []string |
|
| 20 |
- err error |
|
| 21 |
- ) |
|
| 22 |
- if privileged {
|
|
| 23 |
- caplist = caps.GetAllCapabilities() |
|
| 24 |
- } else {
|
|
| 25 |
- caplist, err = caps.TweakCapabilities(s.Process.Capabilities.Bounding, add, drop) |
|
| 26 |
- if err != nil {
|
|
| 27 |
- return err |
|
| 28 |
- } |
|
| 29 |
- } |
|
| 17 |
+func SetCapabilities(s *specs.Spec, caplist []string) error {
|
|
| 30 | 18 |
s.Process.Capabilities.Effective = caplist |
| 31 | 19 |
s.Process.Capabilities.Bounding = caplist |
| 32 | 20 |
s.Process.Capabilities.Permitted = caplist |