Skip to content

Commit 341dadf

Browse files
committed
Cli: Support --cap-add, --cap-drop and --privileged on services
Signed-off-by: Olli Janatuinen <[email protected]>
1 parent ebdd7e1 commit 341dadf

5 files changed

Lines changed: 143 additions & 0 deletions

File tree

cli/command/service/create.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,13 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command {
6565
flags.Var(&opts.sysctls, flagSysCtl, "Sysctl options")
6666
flags.SetAnnotation(flagSysCtl, "version", []string{"1.40"})
6767

68+
flags.Var(newListOptsVar(), flagCapAdd, "Add Linux capabilities")
69+
flags.SetAnnotation(flagCapAdd, "version", []string{"1.41"})
70+
flags.Var(newListOptsVar(), flagCapDrop, "Drop Linux capabilities")
71+
flags.SetAnnotation(flagCapDrop, "version", []string{"1.41"})
72+
flags.Bool(flagPrivileged, false, "Give extended privileges to this container")
73+
flags.SetAnnotation(flagPrivileged, "version", []string{"1.41"})
74+
6875
flags.Var(cliopts.NewListOptsRef(&opts.resources.resGenericResources, ValidateSingleGenericResource), "generic-resource", "User defined resources")
6976
flags.SetAnnotation(flagHostAdd, "version", []string{"1.32"})
7077

cli/command/service/opts.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/docker/docker/api/types/swarm"
1515
"github.com/docker/docker/api/types/versions"
1616
"github.com/docker/docker/client"
17+
"github.com/docker/docker/oci/caps"
1718
"github.com/docker/swarmkit/api"
1819
"github.com/docker/swarmkit/api/defaults"
1920
gogotypes "github.com/gogo/protobuf/types"
@@ -490,6 +491,7 @@ type serviceOptions struct {
490491
hostname string
491492
env opts.ListOpts
492493
envFile opts.ListOpts
494+
capabilities opts.ListOpts
493495
workdir string
494496
user string
495497
groups opts.ListOpts
@@ -539,6 +541,7 @@ func newServiceOptions() *serviceOptions {
539541
containerLabels: opts.NewListOpts(opts.ValidateLabel),
540542
env: opts.NewListOpts(opts.ValidateEnv),
541543
envFile: opts.NewListOpts(nil),
544+
capabilities: opts.NewListOpts(nil),
542545
groups: opts.NewListOpts(nil),
543546
logDriver: newLogDriverOptions(),
544547
dns: opts.NewListOpts(opts.ValidateIPAddress),
@@ -579,6 +582,44 @@ func (options *serviceOptions) ToStopGracePeriod(flags *pflag.FlagSet) *time.Dur
579582
return nil
580583
}
581584

585+
func (options *serviceOptions) ToCapabilities(flags *pflag.FlagSet) ([]string, error) {
586+
if flags.Changed(flagCapAdd) || flags.Changed(flagCapDrop) || flags.Changed(flagPrivileged) {
587+
privileged, err := strconv.ParseBool(flags.Lookup(flagPrivileged).Value.String())
588+
if err != nil {
589+
return nil, err
590+
}
591+
capAdd := flags.Lookup(flagCapAdd).Value.(*opts.ListOpts).GetAll()
592+
capDrop := flags.Lookup(flagCapDrop).Value.(*opts.ListOpts).GetAll()
593+
594+
defaultCapabilities := []string{
595+
"CAP_CHOWN",
596+
"CAP_DAC_OVERRIDE",
597+
"CAP_FSETID",
598+
"CAP_FOWNER",
599+
"CAP_MKNOD",
600+
"CAP_NET_RAW",
601+
"CAP_SETGID",
602+
"CAP_SETUID",
603+
"CAP_SETFCAP",
604+
"CAP_SETPCAP",
605+
"CAP_NET_BIND_SERVICE",
606+
"CAP_SYS_CHROOT",
607+
"CAP_KILL",
608+
"CAP_AUDIT_WRITE",
609+
}
610+
capabilities := []string{}
611+
if privileged || len(capAdd) > 0 || len(capDrop) > 0 {
612+
capabilities, err := caps.TweakCapabilities(defaultCapabilities, capAdd, capDrop, nil, privileged)
613+
if err != nil {
614+
return nil, err
615+
}
616+
return capabilities, nil
617+
}
618+
return capabilities, nil
619+
}
620+
return nil, nil
621+
}
622+
582623
func (options *serviceOptions) ToService(ctx context.Context, apiClient client.NetworkAPIClient, flags *pflag.FlagSet) (swarm.ServiceSpec, error) {
583624
var service swarm.ServiceSpec
584625

@@ -628,6 +669,11 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N
628669
return service, err
629670
}
630671

672+
capabilities, err := options.ToCapabilities(flags)
673+
if err != nil {
674+
return service, err
675+
}
676+
631677
service = swarm.ServiceSpec{
632678
Annotations: swarm.Annotations{
633679
Name: options.name,
@@ -659,6 +705,7 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N
659705
Healthcheck: healthConfig,
660706
Isolation: container.Isolation(options.isolation),
661707
Sysctls: opts.ConvertKVStringsToMap(options.sysctls.GetAll()),
708+
Capabilities: capabilities,
662709
},
663710
Networks: networks,
664711
Resources: resources,
@@ -841,6 +888,8 @@ const (
841888
flagPlacementPref = "placement-pref"
842889
flagPlacementPrefAdd = "placement-pref-add"
843890
flagPlacementPrefRemove = "placement-pref-rm"
891+
flagCapAdd = "cap-add"
892+
flagCapDrop = "cap-drop"
844893
flagConstraint = "constraint"
845894
flagConstraintRemove = "constraint-rm"
846895
flagConstraintAdd = "constraint-add"
@@ -886,6 +935,7 @@ const (
886935
flagNetwork = "network"
887936
flagNetworkAdd = "network-add"
888937
flagNetworkRemove = "network-rm"
938+
flagPrivileged = "privileged"
889939
flagPublish = "publish"
890940
flagPublishRemove = "publish-rm"
891941
flagPublishAdd = "publish-add"

cli/command/service/opts_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/docker/docker/api/types"
1111
"github.com/docker/docker/api/types/container"
1212
"github.com/docker/docker/api/types/swarm"
13+
"github.com/docker/docker/oci/caps"
1314
"gotest.tools/assert"
1415
is "gotest.tools/assert/cmp"
1516
)
@@ -300,3 +301,48 @@ func TestToServiceSysCtls(t *testing.T) {
300301
assert.NilError(t, err)
301302
assert.Check(t, is.DeepEqual(service.TaskTemplate.ContainerSpec.Sysctls, expected))
302303
}
304+
305+
func TestToServiceCapAddAndCapDrop(t *testing.T) {
306+
o := newServiceOptions()
307+
o.mode = "replicated"
308+
309+
flags := newCreateCommand(nil).Flags()
310+
flags.Set("cap-add", "SYS_NICE")
311+
flags.Set("cap-add", "CAP_NET_ADMIN")
312+
flags.Set("cap-drop", "CHOWN")
313+
flags.Set("cap-drop", "DAC_OVERRIDE")
314+
flags.Set("cap-drop", "CAP_FSETID")
315+
flags.Set("cap-drop", "CAP_FOWNER")
316+
317+
expected := []string{
318+
"CAP_MKNOD",
319+
"CAP_NET_RAW",
320+
"CAP_SETGID",
321+
"CAP_SETUID",
322+
"CAP_SETFCAP",
323+
"CAP_SETPCAP",
324+
"CAP_NET_BIND_SERVICE",
325+
"CAP_SYS_CHROOT",
326+
"CAP_KILL",
327+
"CAP_AUDIT_WRITE",
328+
"CAP_SYS_NICE",
329+
"CAP_NET_ADMIN",
330+
}
331+
332+
service, err := o.ToService(context.Background(), &fakeClient{}, flags)
333+
assert.NilError(t, err)
334+
assert.Check(t, is.DeepEqual(service.TaskTemplate.ContainerSpec.Capabilities, expected))
335+
}
336+
337+
func TestToServicePrivileged(t *testing.T) {
338+
o := newServiceOptions()
339+
o.mode = "replicated"
340+
341+
flags := newCreateCommand(nil).Flags()
342+
flags.Set("privileged", "true")
343+
344+
expected := caps.GetAllCapabilities()
345+
service, err := o.ToService(context.Background(), &fakeClient{}, flags)
346+
assert.NilError(t, err)
347+
assert.Check(t, is.DeepEqual(service.TaskTemplate.ContainerSpec.Capabilities, expected))
348+
}

cli/command/service/update.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/docker/docker/api/types/swarm"
1717
"github.com/docker/docker/api/types/versions"
1818
"github.com/docker/docker/client"
19+
"github.com/docker/docker/oci/caps"
1920
"github.com/docker/swarmkit/api/defaults"
2021
"github.com/pkg/errors"
2122
"github.com/spf13/cobra"
@@ -101,6 +102,11 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
101102
flags.Var(newListOptsVar(), flagSysCtlRemove, "Remove a Sysctl option")
102103
flags.SetAnnotation(flagSysCtlRemove, "version", []string{"1.40"})
103104

105+
flags.Var(newListOptsVar(), flagCapAdd, "Add Linux capabilities")
106+
flags.SetAnnotation(flagCapAdd, "version", []string{"1.41"})
107+
flags.Var(newListOptsVar(), flagCapDrop, "Drop Linux capabilities")
108+
flags.SetAnnotation(flagCapDrop, "version", []string{"1.41"})
109+
104110
// Add needs parsing, Remove only needs the key
105111
flags.Var(newListOptsVar(), flagGenericResourcesRemove, "Remove a Generic resource")
106112
flags.SetAnnotation(flagHostAdd, "version", []string{"1.32"})
@@ -336,6 +342,9 @@ func updateService(ctx context.Context, apiClient client.NetworkAPIClient, flags
336342
if err := updateMounts(flags, &cspec.Mounts); err != nil {
337343
return err
338344
}
345+
if err := updateCapabilities(flags, &task.ContainerSpec.Capabilities); err != nil {
346+
return err
347+
}
339348

340349
updateSysCtls(flags, &task.ContainerSpec.Sysctls)
341350

@@ -713,6 +722,22 @@ func updateEnvironment(flags *pflag.FlagSet, field *[]string) {
713722
*field = removeItems(*field, toRemove, envKey)
714723
}
715724

725+
func updateCapabilities(flags *pflag.FlagSet, capabilities *[]string) error {
726+
if flags.Changed(flagCapAdd) || flags.Changed(flagCapDrop) {
727+
capAdd := flags.Lookup(flagCapAdd).Value.(*opts.ListOpts).GetAll()
728+
capDrop := flags.Lookup(flagCapDrop).Value.(*opts.ListOpts).GetAll()
729+
730+
if len(capAdd) > 0 || len(capDrop) > 0 {
731+
var err error
732+
*capabilities, err = caps.TweakCapabilities(*capabilities, capAdd, capDrop, nil, false)
733+
if err != nil {
734+
return err
735+
}
736+
}
737+
}
738+
return nil
739+
}
740+
716741
func getUpdatedSecrets(apiClient client.SecretAPIClient, flags *pflag.FlagSet, secrets []*swarm.SecretReference) ([]*swarm.SecretReference, error) {
717742
newSecrets := []*swarm.SecretReference{}
718743

cli/command/service/update_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,3 +1248,18 @@ func TestUpdateCredSpec(t *testing.T) {
12481248
})
12491249
}
12501250
}
1251+
1252+
func TestUpdateCapabilities(t *testing.T) {
1253+
flags := newUpdateCommand(nil).Flags()
1254+
flags.Set("cap-add", "CAP_SYS_NICE")
1255+
flags.Set("cap-drop", "MKNOD")
1256+
1257+
containerSpec := &swarm.ContainerSpec{
1258+
Capabilities: []string{"CAP_MKNOD", "CAP_NET_RAW"},
1259+
}
1260+
1261+
updateCapabilities(flags, &containerSpec.Capabilities)
1262+
assert.Assert(t, is.Len(containerSpec.Capabilities, 2))
1263+
assert.Check(t, is.Equal("CAP_NET_RAW", containerSpec.Capabilities[0]))
1264+
assert.Check(t, is.Equal("CAP_SYS_NICE", containerSpec.Capabilities[1]))
1265+
}

0 commit comments

Comments
 (0)