service: Add --cap-add & --cap-drop to service cmds

Signed-off-by: Albin Kerouanton <albin@akerouanton.name>
This commit is contained in:
Albin Kerouanton 2020-07-29 14:35:51 +02:00 committed by Sebastiaan van Stijn
parent 0db61ff6da
commit c6ec4e081e
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
4 changed files with 151 additions and 0 deletions

View File

@ -97,6 +97,15 @@ ContainerSpec:
{{- if .ContainerUser }} {{- if .ContainerUser }}
User: {{ .ContainerUser }} User: {{ .ContainerUser }}
{{- end }} {{- end }}
{{- if .HasCapabilities }}
Capabilities:
{{- if .HasCapabilityAdd }}
Add: {{ .CapabilityAdd }}
{{- end }}
{{- if .HasCapabilityDrop }}
Drop: {{ .CapabilityDrop }}
{{- end }}
{{- end }}
{{- if .ContainerSysCtls }} {{- if .ContainerSysCtls }}
SysCtls: SysCtls:
{{- range $k, $v := .ContainerSysCtls }} {{- range $k, $v := .ContainerSysCtls }}
@ -532,6 +541,26 @@ func (ctx *serviceInspectContext) Ports() []swarm.PortConfig {
return ctx.Service.Endpoint.Ports 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 ( const (
defaultServiceTableFormat = "table {{.ID}}\t{{.Name}}\t{{.Mode}}\t{{.Replicas}}\t{{.Image}}\t{{.Ports}}" defaultServiceTableFormat = "table {{.ID}}\t{{.Name}}\t{{.Mode}}\t{{.Replicas}}\t{{.Image}}\t{{.Ports}}"

View File

@ -506,6 +506,8 @@ type serviceOptions struct {
dnsOption opts.ListOpts dnsOption opts.ListOpts
hosts opts.ListOpts hosts opts.ListOpts
sysctls opts.ListOpts sysctls opts.ListOpts
capAdd opts.ListOpts
capDrop opts.ListOpts
resources resourceOptions resources resourceOptions
stopGrace opts.DurationOpt stopGrace opts.DurationOpt
@ -549,6 +551,8 @@ func newServiceOptions() *serviceOptions {
dnsSearch: opts.NewListOpts(opts.ValidateDNSSearch), dnsSearch: opts.NewListOpts(opts.ValidateDNSSearch),
hosts: opts.NewListOpts(opts.ValidateExtraHost), hosts: opts.NewListOpts(opts.ValidateExtraHost),
sysctls: opts.NewListOpts(nil), 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, Healthcheck: healthConfig,
Isolation: container.Isolation(options.isolation), Isolation: container.Isolation(options.isolation),
Sysctls: opts.ConvertKVStringsToMap(options.sysctls.GetAll()), Sysctls: opts.ConvertKVStringsToMap(options.sysctls.GetAll()),
CapabilityAdd: options.capAdd.GetAll(),
CapabilityDrop: options.capDrop.GetAll(),
}, },
Networks: networks, Networks: networks,
Resources: resources, Resources: resources,
@ -818,6 +824,10 @@ func addServiceFlags(flags *pflag.FlagSet, opts *serviceOptions, defaultFlagValu
flags.StringVar(&opts.hostname, flagHostname, "", "Container hostname") flags.StringVar(&opts.hostname, flagHostname, "", "Container hostname")
flags.SetAnnotation(flagHostname, "version", []string{"1.25"}) flags.SetAnnotation(flagHostname, "version", []string{"1.25"})
flags.Var(&opts.entrypoint, flagEntrypoint, "Overwrite the default ENTRYPOINT of the image") 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.limitCPU, flagLimitCPU, "Limit CPUs")
flags.Var(&opts.resources.limitMemBytes, flagLimitMemory, "Limit Memory") flags.Var(&opts.resources.limitMemBytes, flagLimitMemory, "Limit Memory")
@ -1001,6 +1011,8 @@ const (
flagConfigAdd = "config-add" flagConfigAdd = "config-add"
flagConfigRemove = "config-rm" flagConfigRemove = "config-rm"
flagIsolation = "isolation" flagIsolation = "isolation"
flagCapAdd = "cap-add"
flagCapDrop = "cap-drop"
) )
func validateAPIVersion(c swarm.ServiceSpec, serverAPIVersion string) error { func validateAPIVersion(c swarm.ServiceSpec, serverAPIVersion string) error {

View File

@ -505,6 +505,7 @@ func updateService(ctx context.Context, apiClient client.NetworkAPIClient, flags
} }
updateString(flagStopSignal, &cspec.StopSignal) updateString(flagStopSignal, &cspec.StopSignal)
updateCapabilities(flags, cspec)
return nil return nil
} }
@ -1349,3 +1350,41 @@ func updateCredSpecConfig(flags *pflag.FlagSet, containerSpec *swarm.ContainerSp
containerSpec.Privileges.CredentialSpec = credSpec 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)
}
}
}

View File

@ -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)
})
}
}