Skip to content

Commit

Permalink
Service cap-add/cap-drop: add special "RESET" value
Browse files Browse the repository at this point in the history
This implements a special "RESET" value that can be used to reset the
list of capabilities to add/drop when updating a service.

Given the following service;

| CapDrop        | CapAdd        |
| -------------- | ------------- |
| CAP_SOME_CAP   |               |

When updating the service, and applying `--cap-drop RESET`, the "drop" list
is reset to its default:

| CapDrop        | CapAdd        |
| -------------- | ------------- |
|                |               |

When updating the service, and applying `--cap-drop RESET`, combined with
`--cap-add CAP_SOME_CAP` and `--cap-drop CAP_SOME_OTHER_CAP`:

| CapDrop        | CapAdd        |
| -------------- | ------------- |
| CAP_FOO_CAP    | CAP_SOME_CAP  |

Signed-off-by: Sebastiaan van Stijn <[email protected]>
  • Loading branch information
thaJeztah committed Aug 26, 2020
1 parent 839ec17 commit 31cd380
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 23 deletions.
29 changes: 20 additions & 9 deletions cli/command/service/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -1408,31 +1408,39 @@ func updateCredSpecConfig(flags *pflag.FlagSet, containerSpec *swarm.ContainerSp
// | | CAP_SOME_CAP |
//
func updateCapabilities(flags *pflag.FlagSet, containerSpec *swarm.ContainerSpec) {
var toAdd, toDrop map[string]struct{}
var (
toAdd, toDrop map[string]struct{}

capDrop = opts.CapabilitiesMap(containerSpec.CapabilityDrop)
capAdd = opts.CapabilitiesMap(containerSpec.CapabilityAdd)
)
if flags.Changed(flagCapAdd) {
toAdd = opts.CapabilitiesMap(flags.Lookup(flagCapAdd).Value.(*opts.ListOpts).GetAll())
}
if flags.Changed(flagCapDrop) {
toDrop = opts.CapabilitiesMap(flags.Lookup(flagCapDrop).Value.(*opts.ListOpts).GetAll())
}
if _, ok := toAdd[opts.ResetCapabilities]; ok {
delete(toAdd, opts.ResetCapabilities)
capAdd = make(map[string]struct{})
}
if _, ok := toDrop[opts.ResetCapabilities]; ok {
delete(toDrop, opts.ResetCapabilities)
capDrop = make(map[string]struct{})
}
if _, ok := toAdd[opts.AllCapabilities]; ok {
// Special case: "adding" capabilities takes precedence over "dropping",
// therefore adding "ALL" capabilities resets all dropped capabilities.
containerSpec.CapabilityDrop = nil
containerSpec.CapabilityAdd = []string{opts.AllCapabilities}
return
}
if flags.Changed(flagCapDrop) {
toDrop = opts.CapabilitiesMap(flags.Lookup(flagCapDrop).Value.(*opts.ListOpts).GetAll())
}
for c := range toAdd {
// If a capability is both added and dropped on update, then "adding"
// takes precedence.
delete(toDrop, c)
}

var (
capDrop = opts.CapabilitiesMap(containerSpec.CapabilityDrop)
capAdd = opts.CapabilitiesMap(containerSpec.CapabilityAdd)
)

if flags.Changed(flagCapDrop) {
// First remove the capabilities to "drop" from the service's exiting
// list of capabilities to "add".
Expand Down Expand Up @@ -1494,6 +1502,9 @@ func updateCapabilities(flags *pflag.FlagSet, containerSpec *swarm.ContainerSpec
}

func asSortedSlice(vals map[string]struct{}) []string {
if _, ok := vals[opts.AllCapabilities]; ok {
return []string{opts.AllCapabilities}
}
var o []string
for c := range vals {
o = append(o, c)
Expand Down
20 changes: 20 additions & 0 deletions cli/command/service/update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1468,6 +1468,26 @@ func TestUpdateCaps(t *testing.T) {
expectedAdd: []string{"CAP_AAA", "CAP_BBB", "CAP_CCC", "CAP_DDD"},
expectedDrop: []string{"CAP_WWW", "CAP_XXX", "CAP_YYY", "CAP_ZZZ"},
},
{
name: "Reset capabilities",
flagAdd: []string{"RESET"},
flagDrop: []string{"RESET"},
spec: &swarm.ContainerSpec{
CapabilityAdd: []string{"CAP_AAA", "CAP_BBB", "CAP_CCC", "CAP_DDD"},
CapabilityDrop: []string{"CAP_WWW", "CAP_XXX", "CAP_YYY", "CAP_ZZZ"},
},
},
{
name: "Reset capabilities, and update after",
flagAdd: []string{"RESET", "CAP_ADD_ONE", "CAP_FOO"},
flagDrop: []string{"RESET", "CAP_DROP_ONE", "CAP_FOO"},
spec: &swarm.ContainerSpec{
CapabilityAdd: []string{"CAP_AAA", "CAP_BBB", "CAP_CCC", "CAP_DDD"},
CapabilityDrop: []string{"CAP_WWW", "CAP_XXX", "CAP_YYY", "CAP_ZZZ"},
},
expectedAdd: []string{"CAP_ADD_ONE", "CAP_FOO"},
expectedDrop: []string{"CAP_DROP_ONE"},
},
}

for _, tc := range tests {
Expand Down
27 changes: 13 additions & 14 deletions opts/capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ import (
"strings"
)

// AllCapabilities is a special value to add or drop all capabilities
const AllCapabilities = "ALL"
const (
// AllCapabilities is a special value to add or drop all capabilities
AllCapabilities = "ALL"

// ResetCapabilities is a special value to reset capabilities when updating.
// This value should only be used when updating, not used on "create".
ResetCapabilities = "RESET"
)

// NormalizeCapability normalizes a capability by upper-casing, trimming white space
// and adding a CAP_ prefix (if not yet present). This function also accepts the
Expand All @@ -17,27 +23,20 @@ const AllCapabilities = "ALL"
// handled by the daemon.
func NormalizeCapability(cap string) string {
cap = strings.ToUpper(strings.TrimSpace(cap))
if cap != AllCapabilities && !strings.HasPrefix(cap, "CAP_") {
if cap == AllCapabilities || cap == ResetCapabilities {
return cap
}
if !strings.HasPrefix(cap, "CAP_") {
cap = "CAP_" + cap
}
return cap
}

// CapabilitiesMap normalizes the given capabilities and converts them to a map.
// If the list contains the special "ALL" capabilities option, only that option
// is included.
func CapabilitiesMap(caps []string) map[string]struct{} {
normalized := make(map[string]struct{})
for _, c := range caps {
c = NormalizeCapability(c)
if c == AllCapabilities {
// List contains "ALL" capabilities. No need to look further; return
// a map with just the "ALL" capabilities set instead.
normalized = map[string]struct{}{
AllCapabilities: {},
}
break
}
if _, ok := normalized[c]; !ok {
normalized[c] = struct{}{}
}
Expand All @@ -62,7 +61,7 @@ func EffectiveCapAddCapDrop(add, drop []string) (capAdd, capDrop []string) {
if _, ok := addCaps[AllCapabilities]; ok {
// Special case: "adding" capabilities takes precedence over "dropping",
// therefore adding "ALL" capabilities resets all dropped capabilities.
return append(capAdd, AllCapabilities), capDrop
return []string{AllCapabilities}, []string{}
}

for c := range addCaps {
Expand Down

0 comments on commit 31cd380

Please sign in to comment.