Skip to content

Commit

Permalink
Add support cap add/drop to services.
Browse files Browse the repository at this point in the history
This allows clients to specify capabilities to add or drop form the
default capability.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
  • Loading branch information
cpuguy83 committed Jul 17, 2020
1 parent 8c7b14d commit 06a0d2d
Show file tree
Hide file tree
Showing 7 changed files with 449 additions and 144 deletions.
2 changes: 2 additions & 0 deletions agent/exec/dockerapi/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ func (c *containerConfig) hostConfig() *enginecontainer.HostConfig {
PortBindings: c.portBindings(),
Init: c.init(),
Isolation: c.isolation(),
CapAdd: c.spec().CapabilityAdd,
CapDrop: c.spec().CapabilityDrop,
}

// The format of extra hosts on swarmkit is specified in:
Expand Down
41 changes: 41 additions & 0 deletions agent/exec/dockerapi/container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

enginecontainer "github.com/docker/docker/api/types/container"
enginemount "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/swarmkit/api"
gogotypes "github.com/gogo/protobuf/types"
)
Expand Down Expand Up @@ -256,3 +257,43 @@ func TestIsolation(t *testing.T) {
t.Fatalf("expected %s, got %s", expected, actual)
}
}

func TestCapabilityAdd(t *testing.T) {
c := containerConfig{
task: &api.Task{
Spec: api.TaskSpec{
Runtime: &api.TaskSpec_Container{
Container: &api.ContainerSpec{
CapabilityAdd: []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"},
},
},
},
},
}

expected := strslice.StrSlice{"CAP_NET_RAW", "CAP_SYS_CHROOT"}
actual := c.hostConfig().CapAdd
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("expected %s, got %s", expected, actual)
}
}

func TestCapabilityDrop(t *testing.T) {
c := containerConfig{
task: &api.Task{
Spec: api.TaskSpec{
Runtime: &api.TaskSpec_Container{
Container: &api.ContainerSpec{
CapabilityDrop: []string{"CAP_KILL"},
},
},
},
},
}

expected := strslice.StrSlice{"CAP_KILL"}
actual := c.hostConfig().CapDrop
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("expected %s, got %s", expected, actual)
}
}
14 changes: 14 additions & 0 deletions api/api.pb.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5067,6 +5067,20 @@ file {
type_name: ".docker.swarmkit.v1.ContainerSpec.SysctlsEntry"
json_name: "sysctls"
}
field {
name: "capability_add"
number: 27
label: LABEL_REPEATED
type: TYPE_STRING
json_name: "capabilityAdd"
}
field {
name: "capability_drop"
number: 28
label: LABEL_REPEATED
type: TYPE_STRING
json_name: "capabilityDrop"
}
nested_type {
name: "LabelsEntry"
field {
Expand Down
416 changes: 272 additions & 144 deletions api/specs.pb.go

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions api/specs.proto
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,11 @@ message ContainerSpec {
//
// https://docs.docker.com/engine/reference/commandline/run/#configure-namespaced-kernel-parameters-sysctls-at-runtime
map<string, string> sysctls = 26;

// CapabilityAdd sets the list of capabilities to add to the default capability list
repeated string capability_add = 27;
// CapabilityAdd sets the list of capabilities to drop from the default capability list
repeated string capability_drop = 28;
}

// EndpointSpec defines the properties that can be configured to
Expand Down
106 changes: 106 additions & 0 deletions cmd/swarmctl/service/flagparser/capability.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package flagparser

import (
"github.com/docker/swarmkit/api"
"github.com/spf13/cobra"
)

// ParseAddCapability validates capabilities passed on the command line
func ParseAddCapability(cmd *cobra.Command, spec *api.ServiceSpec, flagName string) error {
flags := cmd.Flags()

if !flags.Changed(flagName) {
return nil
}

add, err := flags.GetStringSlice(flagName)
if err != nil {
return err
}

container := spec.Task.GetContainer()
if container == nil {
return nil
}

// Index adds so we don't have to double loop
addIndex := make(map[string]bool, len(add))
for _, v := range add {
addIndex[v] = true
}

// Check if any of the adds are in drop so we can remove them from the drop list.
var evict []int
for i, v := range container.CapabilityDrop {
if addIndex[v] {
evict = append(evict, i)
}
}
for n, i := range evict {
container.CapabilityDrop = append(container.CapabilityDrop[:i-n], container.CapabilityDrop[i-n+1:]...)
}

// De-dup the list to be added
for _, v := range container.CapabilityAdd {
if addIndex[v] {
delete(addIndex, v)
continue
}
}

for cap := range addIndex {
container.CapabilityAdd = append(container.CapabilityAdd, cap)
}

return nil
}

// ParseDropCapability validates capabilities passed on the command line
func ParseDropCapability(cmd *cobra.Command, spec *api.ServiceSpec, flagName string) error {
flags := cmd.Flags()

if !flags.Changed(flagName) {
return nil
}

drop, err := flags.GetStringSlice(flagName)
if err != nil {
return err
}

container := spec.Task.GetContainer()
if container == nil {
return nil
}

// Index removals so we don't have to double loop
dropIndex := make(map[string]bool, len(drop))
for _, v := range drop {
dropIndex[v] = true
}

// Check if any of the adds are in add so we can remove them from the add list.
var evict []int
for i, v := range container.CapabilityAdd {
if dropIndex[v] {
evict = append(evict, i)
}
}
for n, i := range evict {
container.CapabilityAdd = append(container.CapabilityAdd[:i-n], container.CapabilityAdd[i-n+1:]...)
}

// De-dup the list to be dropped
for _, v := range container.CapabilityDrop {
if dropIndex[v] {
delete(dropIndex, v)
continue
}
}

for cap := range dropIndex {
container.CapabilityDrop = append(container.CapabilityDrop, cap)
}

return nil
}
9 changes: 9 additions & 0 deletions cmd/swarmctl/service/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ var (
return err
}

if err := flagparser.ParseAddCapability(cmd, spec, "add-capability"); err != nil {
return err
}
if err := flagparser.ParseDropCapability(cmd, spec, "rm-capability"); err != nil {
return err
}

if reflect.DeepEqual(spec, &service.Spec) {
return errors.New("no changes detected")
}
Expand All @@ -77,6 +84,8 @@ func init() {
updateCmd.Flags().StringSlice("rm-secret", nil, "remove a secret from the service")
updateCmd.Flags().StringSlice("add-config", nil, "add a new config to the service")
updateCmd.Flags().StringSlice("rm-config", nil, "remove a config from the service")
updateCmd.Flags().StringSlice("add-capability", nil, "add a new capability to the service")
updateCmd.Flags().StringSlice("rm-capability", nil, "remove a capability from the service")
updateCmd.Flags().Bool("force", false, "force tasks to restart even if nothing has changed")
flagparser.AddServiceFlags(updateCmd.Flags())
}

0 comments on commit 06a0d2d

Please sign in to comment.