Skip to content

Commit 091b5e6

Browse files
authored
Merge pull request moby#32339 from aluzzardi/selinux
services: Add support for Credential Spec and SELinux
2 parents 4a1a64c + 89a995a commit 091b5e6

4 files changed

Lines changed: 163 additions & 0 deletions

File tree

api/types/swarm/container.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,28 @@ type DNSConfig struct {
2121
Options []string `json:",omitempty"`
2222
}
2323

24+
// SELinuxContext contains the SELinux labels of the container.
25+
type SELinuxContext struct {
26+
Disable bool
27+
28+
User string
29+
Role string
30+
Type string
31+
Level string
32+
}
33+
34+
// CredentialSpec for managed service account (Windows only)
35+
type CredentialSpec struct {
36+
File string
37+
Registry string
38+
}
39+
40+
// Privileges defines the security options for the container.
41+
type Privileges struct {
42+
CredentialSpec *CredentialSpec
43+
SELinuxContext *SELinuxContext
44+
}
45+
2446
// ContainerSpec represents the spec of a container.
2547
type ContainerSpec struct {
2648
Image string `json:",omitempty"`
@@ -32,6 +54,7 @@ type ContainerSpec struct {
3254
Dir string `json:",omitempty"`
3355
User string `json:",omitempty"`
3456
Groups []string `json:",omitempty"`
57+
Privileges *Privileges `json:",omitempty"`
3558
StopSignal string `json:",omitempty"`
3659
TTY bool `json:",omitempty"`
3760
OpenStdin bool `json:",omitempty"`

cli/command/service/opts.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,38 @@ func (r *restartPolicyOptions) ToRestartPolicy() *swarm.RestartPolicy {
238238
}
239239
}
240240

241+
type credentialSpecOpt struct {
242+
value *swarm.CredentialSpec
243+
source string
244+
}
245+
246+
func (c *credentialSpecOpt) Set(value string) error {
247+
c.source = value
248+
c.value = &swarm.CredentialSpec{}
249+
switch {
250+
case strings.HasPrefix(value, "file://"):
251+
c.value.File = strings.TrimPrefix(value, "file://")
252+
case strings.HasPrefix(value, "registry://"):
253+
c.value.Registry = strings.TrimPrefix(value, "registry://")
254+
default:
255+
return errors.New("Invalid credential spec - value must be prefixed file:// or registry:// followed by a value")
256+
}
257+
258+
return nil
259+
}
260+
261+
func (c *credentialSpecOpt) Type() string {
262+
return "credential-spec"
263+
}
264+
265+
func (c *credentialSpecOpt) String() string {
266+
return c.source
267+
}
268+
269+
func (c *credentialSpecOpt) Value() *swarm.CredentialSpec {
270+
return c.value
271+
}
272+
241273
func convertNetworks(networks []string) []swarm.NetworkAttachmentConfig {
242274
nets := []swarm.NetworkAttachmentConfig{}
243275
for _, network := range networks {
@@ -355,6 +387,7 @@ type serviceOptions struct {
355387
workdir string
356388
user string
357389
groups opts.ListOpts
390+
credentialSpec credentialSpecOpt
358391
stopSignal string
359392
tty bool
360393
readOnly bool
@@ -500,6 +533,12 @@ func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
500533
EndpointSpec: opts.endpoint.ToEndpointSpec(),
501534
}
502535

536+
if opts.credentialSpec.Value() != nil {
537+
service.TaskTemplate.ContainerSpec.Privileges = &swarm.Privileges{
538+
CredentialSpec: opts.credentialSpec.Value(),
539+
}
540+
}
541+
503542
return service, nil
504543
}
505544

@@ -511,6 +550,8 @@ func addServiceFlags(flags *pflag.FlagSet, opts *serviceOptions) {
511550

512551
flags.StringVarP(&opts.workdir, flagWorkdir, "w", "", "Working directory inside the container")
513552
flags.StringVarP(&opts.user, flagUser, "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
553+
flags.Var(&opts.credentialSpec, flagCredentialSpec, "Credential spec for managed service account (Windows only)")
554+
flags.SetAnnotation(flagCredentialSpec, "version", []string{"1.29"})
514555
flags.StringVar(&opts.hostname, flagHostname, "", "Container hostname")
515556
flags.SetAnnotation(flagHostname, "version", []string{"1.25"})
516557
flags.Var(&opts.entrypoint, flagEntrypoint, "Overwrite the default ENTRYPOINT of the image")
@@ -582,6 +623,7 @@ func addServiceFlags(flags *pflag.FlagSet, opts *serviceOptions) {
582623
}
583624

584625
const (
626+
flagCredentialSpec = "credential-spec"
585627
flagPlacementPref = "placement-pref"
586628
flagPlacementPrefAdd = "placement-pref-add"
587629
flagPlacementPrefRemove = "placement-pref-rm"

daemon/cluster/convert/container.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package convert
22

33
import (
4+
"errors"
45
"fmt"
56
"strings"
67

@@ -39,6 +40,31 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) types.ContainerSpec {
3940
}
4041
}
4142

43+
// Privileges
44+
if c.Privileges != nil {
45+
containerSpec.Privileges = &types.Privileges{}
46+
47+
if c.Privileges.CredentialSpec != nil {
48+
containerSpec.Privileges.CredentialSpec = &types.CredentialSpec{}
49+
switch c.Privileges.CredentialSpec.Source.(type) {
50+
case *swarmapi.Privileges_CredentialSpec_File:
51+
containerSpec.Privileges.CredentialSpec.File = c.Privileges.CredentialSpec.GetFile()
52+
case *swarmapi.Privileges_CredentialSpec_Registry:
53+
containerSpec.Privileges.CredentialSpec.Registry = c.Privileges.CredentialSpec.GetRegistry()
54+
}
55+
}
56+
57+
if c.Privileges.SELinuxContext != nil {
58+
containerSpec.Privileges.SELinuxContext = &types.SELinuxContext{
59+
Disable: c.Privileges.SELinuxContext.Disable,
60+
User: c.Privileges.SELinuxContext.User,
61+
Type: c.Privileges.SELinuxContext.Type,
62+
Role: c.Privileges.SELinuxContext.Role,
63+
Level: c.Privileges.SELinuxContext.Level,
64+
}
65+
}
66+
}
67+
4268
// Mounts
4369
for _, m := range c.Mounts {
4470
mount := mounttypes.Mount{
@@ -166,6 +192,40 @@ func containerToGRPC(c types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
166192
containerSpec.StopGracePeriod = gogotypes.DurationProto(*c.StopGracePeriod)
167193
}
168194

195+
// Privileges
196+
if c.Privileges != nil {
197+
containerSpec.Privileges = &swarmapi.Privileges{}
198+
199+
if c.Privileges.CredentialSpec != nil {
200+
containerSpec.Privileges.CredentialSpec = &swarmapi.Privileges_CredentialSpec{}
201+
202+
if c.Privileges.CredentialSpec.File != "" && c.Privileges.CredentialSpec.Registry != "" {
203+
return nil, errors.New("cannot specify both \"file\" and \"registry\" credential specs")
204+
}
205+
if c.Privileges.CredentialSpec.File != "" {
206+
containerSpec.Privileges.CredentialSpec.Source = &swarmapi.Privileges_CredentialSpec_File{
207+
File: c.Privileges.CredentialSpec.File,
208+
}
209+
} else if c.Privileges.CredentialSpec.Registry != "" {
210+
containerSpec.Privileges.CredentialSpec.Source = &swarmapi.Privileges_CredentialSpec_Registry{
211+
Registry: c.Privileges.CredentialSpec.Registry,
212+
}
213+
} else {
214+
return nil, errors.New("must either provide \"file\" or \"registry\" for credential spec")
215+
}
216+
}
217+
218+
if c.Privileges.SELinuxContext != nil {
219+
containerSpec.Privileges.SELinuxContext = &swarmapi.Privileges_SELinuxContext{
220+
Disable: c.Privileges.SELinuxContext.Disable,
221+
User: c.Privileges.SELinuxContext.User,
222+
Type: c.Privileges.SELinuxContext.Type,
223+
Role: c.Privileges.SELinuxContext.Role,
224+
Level: c.Privileges.SELinuxContext.Level,
225+
}
226+
}
227+
}
228+
169229
// Mounts
170230
for _, m := range c.Mounts {
171231
mount := swarmapi.Mount{

daemon/cluster/executor/container/container.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,8 @@ func (c *containerConfig) hostConfig() *enginecontainer.HostConfig {
351351
hc.DNSOptions = c.spec().DNSConfig.Options
352352
}
353353

354+
c.applyPrivileges(hc)
355+
354356
// The format of extra hosts on swarmkit is specified in:
355357
// http://man7.org/linux/man-pages/man5/hosts.5.html
356358
// IP_address canonical_hostname [aliases...]
@@ -600,6 +602,42 @@ func (c *containerConfig) networkCreateRequest(name string) (clustertypes.Networ
600602
}, nil
601603
}
602604

605+
func (c *containerConfig) applyPrivileges(hc *enginecontainer.HostConfig) {
606+
privileges := c.spec().Privileges
607+
if privileges == nil {
608+
return
609+
}
610+
611+
credentials := privileges.CredentialSpec
612+
if credentials != nil {
613+
switch credentials.Source.(type) {
614+
case *api.Privileges_CredentialSpec_File:
615+
hc.SecurityOpt = append(hc.SecurityOpt, "credentialspec=file://"+credentials.GetFile())
616+
case *api.Privileges_CredentialSpec_Registry:
617+
hc.SecurityOpt = append(hc.SecurityOpt, "credentialspec=registry://"+credentials.GetRegistry())
618+
}
619+
}
620+
621+
selinux := privileges.SELinuxContext
622+
if selinux != nil {
623+
if selinux.Disable {
624+
hc.SecurityOpt = append(hc.SecurityOpt, "label=disable")
625+
}
626+
if selinux.User != "" {
627+
hc.SecurityOpt = append(hc.SecurityOpt, "label=user:"+selinux.User)
628+
}
629+
if selinux.Role != "" {
630+
hc.SecurityOpt = append(hc.SecurityOpt, "label=role:"+selinux.Role)
631+
}
632+
if selinux.Level != "" {
633+
hc.SecurityOpt = append(hc.SecurityOpt, "label=level:"+selinux.Level)
634+
}
635+
if selinux.Type != "" {
636+
hc.SecurityOpt = append(hc.SecurityOpt, "label=type:"+selinux.Type)
637+
}
638+
}
639+
}
640+
603641
func (c containerConfig) eventFilter() filters.Args {
604642
filter := filters.NewArgs()
605643
filter.Add("type", events.ContainerEventType)

0 commit comments

Comments
 (0)