From c6ec4e081ec45bc7418c34cb448dd61427c2181b Mon Sep 17 00:00:00 2001 From: Albin Kerouanton Date: Wed, 29 Jul 2020 14:35:51 +0200 Subject: [PATCH] service: Add --cap-add & --cap-drop to service cmds Signed-off-by: Albin Kerouanton --- cli/command/service/formatter.go | 29 ++++++++++++ cli/command/service/opts.go | 12 +++++ cli/command/service/update.go | 39 ++++++++++++++++ cli/command/service/update_test.go | 71 ++++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+) diff --git a/cli/command/service/formatter.go b/cli/command/service/formatter.go index 6d30be9427..57d0d08766 100644 --- a/cli/command/service/formatter.go +++ b/cli/command/service/formatter.go @@ -97,6 +97,15 @@ ContainerSpec: {{- if .ContainerUser }} User: {{ .ContainerUser }} {{- end }} +{{- if .HasCapabilities }} +Capabilities: +{{- if .HasCapabilityAdd }} + Add: {{ .CapabilityAdd }} +{{- end }} +{{- if .HasCapabilityDrop }} + Drop: {{ .CapabilityDrop }} +{{- end }} +{{- end }} {{- if .ContainerSysCtls }} SysCtls: {{- range $k, $v := .ContainerSysCtls }} @@ -532,6 +541,26 @@ func (ctx *serviceInspectContext) Ports() []swarm.PortConfig { return ctx.Service.Endpoint.Ports } +func (ctx *serviceInspectContext) HasCapabilities() bool { + return len(ctx.Service.Spec.TaskTemplate.ContainerSpec.CapabilityAdd) > 0 || len(ctx.Service.Spec.TaskTemplate.ContainerSpec.CapabilityDrop) > 0 +} + +func (ctx *serviceInspectContext) HasCapabilityAdd() bool { + return len(ctx.Service.Spec.TaskTemplate.ContainerSpec.CapabilityAdd) > 0 +} + +func (ctx *serviceInspectContext) HasCapabilityDrop() bool { + return len(ctx.Service.Spec.TaskTemplate.ContainerSpec.CapabilityDrop) > 0 +} + +func (ctx *serviceInspectContext) CapabilityAdd() string { + return strings.Join(ctx.Service.Spec.TaskTemplate.ContainerSpec.CapabilityAdd, ", ") +} + +func (ctx *serviceInspectContext) CapabilityDrop() string { + return strings.Join(ctx.Service.Spec.TaskTemplate.ContainerSpec.CapabilityDrop, ", ") +} + const ( defaultServiceTableFormat = "table {{.ID}}\t{{.Name}}\t{{.Mode}}\t{{.Replicas}}\t{{.Image}}\t{{.Ports}}" diff --git a/cli/command/service/opts.go b/cli/command/service/opts.go index d473c5fcec..9fbd846556 100644 --- a/cli/command/service/opts.go +++ b/cli/command/service/opts.go @@ -506,6 +506,8 @@ type serviceOptions struct { dnsOption opts.ListOpts hosts opts.ListOpts sysctls opts.ListOpts + capAdd opts.ListOpts + capDrop opts.ListOpts resources resourceOptions stopGrace opts.DurationOpt @@ -549,6 +551,8 @@ func newServiceOptions() *serviceOptions { dnsSearch: opts.NewListOpts(opts.ValidateDNSSearch), hosts: opts.NewListOpts(opts.ValidateExtraHost), sysctls: opts.NewListOpts(nil), + capAdd: opts.NewListOpts(nil), + capDrop: opts.NewListOpts(nil), } } @@ -716,6 +720,8 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N Healthcheck: healthConfig, Isolation: container.Isolation(options.isolation), Sysctls: opts.ConvertKVStringsToMap(options.sysctls.GetAll()), + CapabilityAdd: options.capAdd.GetAll(), + CapabilityDrop: options.capDrop.GetAll(), }, Networks: networks, Resources: resources, @@ -818,6 +824,10 @@ func addServiceFlags(flags *pflag.FlagSet, opts *serviceOptions, defaultFlagValu flags.StringVar(&opts.hostname, flagHostname, "", "Container hostname") flags.SetAnnotation(flagHostname, "version", []string{"1.25"}) flags.Var(&opts.entrypoint, flagEntrypoint, "Overwrite the default ENTRYPOINT of the image") + flags.Var(&opts.capAdd, flagCapAdd, "Add Linux capabilities") + flags.SetAnnotation(flagCapAdd, "version", []string{"1.41"}) + flags.Var(&opts.capDrop, flagCapDrop, "Drop Linux capabilities") + flags.SetAnnotation(flagCapDrop, "version", []string{"1.41"}) flags.Var(&opts.resources.limitCPU, flagLimitCPU, "Limit CPUs") flags.Var(&opts.resources.limitMemBytes, flagLimitMemory, "Limit Memory") @@ -1001,6 +1011,8 @@ const ( flagConfigAdd = "config-add" flagConfigRemove = "config-rm" flagIsolation = "isolation" + flagCapAdd = "cap-add" + flagCapDrop = "cap-drop" ) func validateAPIVersion(c swarm.ServiceSpec, serverAPIVersion string) error { diff --git a/cli/command/service/update.go b/cli/command/service/update.go index 78230e2281..5a7dcd4331 100644 --- a/cli/command/service/update.go +++ b/cli/command/service/update.go @@ -505,6 +505,7 @@ func updateService(ctx context.Context, apiClient client.NetworkAPIClient, flags } updateString(flagStopSignal, &cspec.StopSignal) + updateCapabilities(flags, cspec) return nil } @@ -1349,3 +1350,41 @@ func updateCredSpecConfig(flags *pflag.FlagSet, containerSpec *swarm.ContainerSp containerSpec.Privileges.CredentialSpec = credSpec } } + +func updateCapabilities(flags *pflag.FlagSet, containerSpec *swarm.ContainerSpec) { + var addToRemove, dropToRemove map[string]struct{} + capAdd := containerSpec.CapabilityAdd + capDrop := containerSpec.CapabilityDrop + + // First add the capabilities passed to --cap-add to the list of requested caps + if flags.Changed(flagCapAdd) { + caps := flags.Lookup(flagCapAdd).Value.(*opts.ListOpts).GetAll() + capAdd = append(capAdd, caps...) + + dropToRemove = buildToRemoveSet(flags, flagCapAdd) + } + + // And add the capabilities passed to --cap-drop to the list of dropped caps + if flags.Changed(flagCapDrop) { + caps := flags.Lookup(flagCapDrop).Value.(*opts.ListOpts).GetAll() + capDrop = append(capDrop, caps...) + + addToRemove = buildToRemoveSet(flags, flagCapDrop) + } + + // Then take care of removing caps passed to --cap-drop from the list of requested caps + containerSpec.CapabilityAdd = make([]string, 0, len(capAdd)) + for _, cap := range capAdd { + if _, exists := addToRemove[cap]; !exists { + containerSpec.CapabilityAdd = append(containerSpec.CapabilityAdd, cap) + } + } + + // And remove the caps passed to --cap-add from the list of caps to drop + containerSpec.CapabilityDrop = make([]string, 0, len(capDrop)) + for _, cap := range capDrop { + if _, exists := dropToRemove[cap]; !exists { + containerSpec.CapabilityDrop = append(containerSpec.CapabilityDrop, cap) + } + } +} diff --git a/cli/command/service/update_test.go b/cli/command/service/update_test.go index 7a2511815b..b8e6dccbb4 100644 --- a/cli/command/service/update_test.go +++ b/cli/command/service/update_test.go @@ -1342,3 +1342,74 @@ func TestUpdateCredSpec(t *testing.T) { }) } } + +func TestUpdateCaps(t *testing.T) { + tests := []struct { + // name is the name of the testcase + name string + // flagAdd is the value passed to --cap-add + flagAdd []string + // flagDrop is the value passed to --cap-drop + flagDrop []string + // spec is the original ContainerSpec, before being updated + spec *swarm.ContainerSpec + // expectedAdd is the set of requested caps the ContainerSpec should have once updated + expectedAdd []string + // expectedDrop is the set of dropped caps the ContainerSpec should have once updated + expectedDrop []string + }{ + { + name: "Add new caps", + flagAdd: []string{"NET_ADMIN"}, + flagDrop: []string{}, + spec: &swarm.ContainerSpec{}, + expectedAdd: []string{"NET_ADMIN"}, + expectedDrop: []string{}, + }, + { + name: "Drop new caps", + flagAdd: []string{}, + flagDrop: []string{"CAP_MKNOD"}, + spec: &swarm.ContainerSpec{}, + expectedAdd: []string{}, + expectedDrop: []string{"CAP_MKNOD"}, + }, + { + name: "Add a previously dropped cap", + flagAdd: []string{"NET_ADMIN"}, + flagDrop: []string{}, + spec: &swarm.ContainerSpec{ + CapabilityDrop: []string{"NET_ADMIN"}, + }, + expectedAdd: []string{"NET_ADMIN"}, + expectedDrop: []string{}, + }, + { + name: "Drop a previously requested cap", + flagAdd: []string{}, + flagDrop: []string{"CAP_MKNOD"}, + spec: &swarm.ContainerSpec{ + CapabilityAdd: []string{"CAP_MKNOD"}, + }, + expectedAdd: []string{}, + expectedDrop: []string{"CAP_MKNOD"}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + flags := newUpdateCommand(nil).Flags() + for _, cap := range tc.flagAdd { + flags.Set(flagCapAdd, cap) + } + for _, cap := range tc.flagDrop { + flags.Set(flagCapDrop, cap) + } + + updateCapabilities(flags, tc.spec) + + assert.DeepEqual(t, tc.spec.CapabilityAdd, tc.expectedAdd) + assert.DeepEqual(t, tc.spec.CapabilityDrop, tc.expectedDrop) + }) + } +}