Browse Source

Merge pull request #32339 from aluzzardi/selinux

services: Add support for Credential Spec and SELinux
Sebastiaan van Stijn 8 years ago
parent
commit
091b5e68ea

+ 23 - 0
api/types/swarm/container.go

@@ -21,6 +21,28 @@ type DNSConfig struct {
 	Options []string `json:",omitempty"`
 	Options []string `json:",omitempty"`
 }
 }
 
 
+// SELinuxContext contains the SELinux labels of the container.
+type SELinuxContext struct {
+	Disable bool
+
+	User  string
+	Role  string
+	Type  string
+	Level string
+}
+
+// CredentialSpec for managed service account (Windows only)
+type CredentialSpec struct {
+	File     string
+	Registry string
+}
+
+// Privileges defines the security options for the container.
+type Privileges struct {
+	CredentialSpec *CredentialSpec
+	SELinuxContext *SELinuxContext
+}
+
 // ContainerSpec represents the spec of a container.
 // ContainerSpec represents the spec of a container.
 type ContainerSpec struct {
 type ContainerSpec struct {
 	Image           string                  `json:",omitempty"`
 	Image           string                  `json:",omitempty"`
@@ -32,6 +54,7 @@ type ContainerSpec struct {
 	Dir             string                  `json:",omitempty"`
 	Dir             string                  `json:",omitempty"`
 	User            string                  `json:",omitempty"`
 	User            string                  `json:",omitempty"`
 	Groups          []string                `json:",omitempty"`
 	Groups          []string                `json:",omitempty"`
+	Privileges      *Privileges             `json:",omitempty"`
 	StopSignal      string                  `json:",omitempty"`
 	StopSignal      string                  `json:",omitempty"`
 	TTY             bool                    `json:",omitempty"`
 	TTY             bool                    `json:",omitempty"`
 	OpenStdin       bool                    `json:",omitempty"`
 	OpenStdin       bool                    `json:",omitempty"`

+ 42 - 0
cli/command/service/opts.go

@@ -238,6 +238,38 @@ func (r *restartPolicyOptions) ToRestartPolicy() *swarm.RestartPolicy {
 	}
 	}
 }
 }
 
 
+type credentialSpecOpt struct {
+	value  *swarm.CredentialSpec
+	source string
+}
+
+func (c *credentialSpecOpt) Set(value string) error {
+	c.source = value
+	c.value = &swarm.CredentialSpec{}
+	switch {
+	case strings.HasPrefix(value, "file://"):
+		c.value.File = strings.TrimPrefix(value, "file://")
+	case strings.HasPrefix(value, "registry://"):
+		c.value.Registry = strings.TrimPrefix(value, "registry://")
+	default:
+		return errors.New("Invalid credential spec - value must be prefixed file:// or registry:// followed by a value")
+	}
+
+	return nil
+}
+
+func (c *credentialSpecOpt) Type() string {
+	return "credential-spec"
+}
+
+func (c *credentialSpecOpt) String() string {
+	return c.source
+}
+
+func (c *credentialSpecOpt) Value() *swarm.CredentialSpec {
+	return c.value
+}
+
 func convertNetworks(networks []string) []swarm.NetworkAttachmentConfig {
 func convertNetworks(networks []string) []swarm.NetworkAttachmentConfig {
 	nets := []swarm.NetworkAttachmentConfig{}
 	nets := []swarm.NetworkAttachmentConfig{}
 	for _, network := range networks {
 	for _, network := range networks {
@@ -355,6 +387,7 @@ type serviceOptions struct {
 	workdir         string
 	workdir         string
 	user            string
 	user            string
 	groups          opts.ListOpts
 	groups          opts.ListOpts
+	credentialSpec  credentialSpecOpt
 	stopSignal      string
 	stopSignal      string
 	tty             bool
 	tty             bool
 	readOnly        bool
 	readOnly        bool
@@ -500,6 +533,12 @@ func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
 		EndpointSpec:   opts.endpoint.ToEndpointSpec(),
 		EndpointSpec:   opts.endpoint.ToEndpointSpec(),
 	}
 	}
 
 
+	if opts.credentialSpec.Value() != nil {
+		service.TaskTemplate.ContainerSpec.Privileges = &swarm.Privileges{
+			CredentialSpec: opts.credentialSpec.Value(),
+		}
+	}
+
 	return service, nil
 	return service, nil
 }
 }
 
 
@@ -511,6 +550,8 @@ func addServiceFlags(flags *pflag.FlagSet, opts *serviceOptions) {
 
 
 	flags.StringVarP(&opts.workdir, flagWorkdir, "w", "", "Working directory inside the container")
 	flags.StringVarP(&opts.workdir, flagWorkdir, "w", "", "Working directory inside the container")
 	flags.StringVarP(&opts.user, flagUser, "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
 	flags.StringVarP(&opts.user, flagUser, "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
+	flags.Var(&opts.credentialSpec, flagCredentialSpec, "Credential spec for managed service account (Windows only)")
+	flags.SetAnnotation(flagCredentialSpec, "version", []string{"1.29"})
 	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")
@@ -582,6 +623,7 @@ func addServiceFlags(flags *pflag.FlagSet, opts *serviceOptions) {
 }
 }
 
 
 const (
 const (
+	flagCredentialSpec          = "credential-spec"
 	flagPlacementPref           = "placement-pref"
 	flagPlacementPref           = "placement-pref"
 	flagPlacementPrefAdd        = "placement-pref-add"
 	flagPlacementPrefAdd        = "placement-pref-add"
 	flagPlacementPrefRemove     = "placement-pref-rm"
 	flagPlacementPrefRemove     = "placement-pref-rm"

+ 60 - 0
daemon/cluster/convert/container.go

@@ -1,6 +1,7 @@
 package convert
 package convert
 
 
 import (
 import (
+	"errors"
 	"fmt"
 	"fmt"
 	"strings"
 	"strings"
 
 
@@ -39,6 +40,31 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) types.ContainerSpec {
 		}
 		}
 	}
 	}
 
 
+	// Privileges
+	if c.Privileges != nil {
+		containerSpec.Privileges = &types.Privileges{}
+
+		if c.Privileges.CredentialSpec != nil {
+			containerSpec.Privileges.CredentialSpec = &types.CredentialSpec{}
+			switch c.Privileges.CredentialSpec.Source.(type) {
+			case *swarmapi.Privileges_CredentialSpec_File:
+				containerSpec.Privileges.CredentialSpec.File = c.Privileges.CredentialSpec.GetFile()
+			case *swarmapi.Privileges_CredentialSpec_Registry:
+				containerSpec.Privileges.CredentialSpec.Registry = c.Privileges.CredentialSpec.GetRegistry()
+			}
+		}
+
+		if c.Privileges.SELinuxContext != nil {
+			containerSpec.Privileges.SELinuxContext = &types.SELinuxContext{
+				Disable: c.Privileges.SELinuxContext.Disable,
+				User:    c.Privileges.SELinuxContext.User,
+				Type:    c.Privileges.SELinuxContext.Type,
+				Role:    c.Privileges.SELinuxContext.Role,
+				Level:   c.Privileges.SELinuxContext.Level,
+			}
+		}
+	}
+
 	// Mounts
 	// Mounts
 	for _, m := range c.Mounts {
 	for _, m := range c.Mounts {
 		mount := mounttypes.Mount{
 		mount := mounttypes.Mount{
@@ -166,6 +192,40 @@ func containerToGRPC(c types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
 		containerSpec.StopGracePeriod = gogotypes.DurationProto(*c.StopGracePeriod)
 		containerSpec.StopGracePeriod = gogotypes.DurationProto(*c.StopGracePeriod)
 	}
 	}
 
 
+	// Privileges
+	if c.Privileges != nil {
+		containerSpec.Privileges = &swarmapi.Privileges{}
+
+		if c.Privileges.CredentialSpec != nil {
+			containerSpec.Privileges.CredentialSpec = &swarmapi.Privileges_CredentialSpec{}
+
+			if c.Privileges.CredentialSpec.File != "" && c.Privileges.CredentialSpec.Registry != "" {
+				return nil, errors.New("cannot specify both \"file\" and \"registry\" credential specs")
+			}
+			if c.Privileges.CredentialSpec.File != "" {
+				containerSpec.Privileges.CredentialSpec.Source = &swarmapi.Privileges_CredentialSpec_File{
+					File: c.Privileges.CredentialSpec.File,
+				}
+			} else if c.Privileges.CredentialSpec.Registry != "" {
+				containerSpec.Privileges.CredentialSpec.Source = &swarmapi.Privileges_CredentialSpec_Registry{
+					Registry: c.Privileges.CredentialSpec.Registry,
+				}
+			} else {
+				return nil, errors.New("must either provide \"file\" or \"registry\" for credential spec")
+			}
+		}
+
+		if c.Privileges.SELinuxContext != nil {
+			containerSpec.Privileges.SELinuxContext = &swarmapi.Privileges_SELinuxContext{
+				Disable: c.Privileges.SELinuxContext.Disable,
+				User:    c.Privileges.SELinuxContext.User,
+				Type:    c.Privileges.SELinuxContext.Type,
+				Role:    c.Privileges.SELinuxContext.Role,
+				Level:   c.Privileges.SELinuxContext.Level,
+			}
+		}
+	}
+
 	// Mounts
 	// Mounts
 	for _, m := range c.Mounts {
 	for _, m := range c.Mounts {
 		mount := swarmapi.Mount{
 		mount := swarmapi.Mount{

+ 38 - 0
daemon/cluster/executor/container/container.go

@@ -351,6 +351,8 @@ func (c *containerConfig) hostConfig() *enginecontainer.HostConfig {
 		hc.DNSOptions = c.spec().DNSConfig.Options
 		hc.DNSOptions = c.spec().DNSConfig.Options
 	}
 	}
 
 
+	c.applyPrivileges(hc)
+
 	// The format of extra hosts on swarmkit is specified in:
 	// The format of extra hosts on swarmkit is specified in:
 	// http://man7.org/linux/man-pages/man5/hosts.5.html
 	// http://man7.org/linux/man-pages/man5/hosts.5.html
 	//    IP_address canonical_hostname [aliases...]
 	//    IP_address canonical_hostname [aliases...]
@@ -600,6 +602,42 @@ func (c *containerConfig) networkCreateRequest(name string) (clustertypes.Networ
 	}, nil
 	}, nil
 }
 }
 
 
+func (c *containerConfig) applyPrivileges(hc *enginecontainer.HostConfig) {
+	privileges := c.spec().Privileges
+	if privileges == nil {
+		return
+	}
+
+	credentials := privileges.CredentialSpec
+	if credentials != nil {
+		switch credentials.Source.(type) {
+		case *api.Privileges_CredentialSpec_File:
+			hc.SecurityOpt = append(hc.SecurityOpt, "credentialspec=file://"+credentials.GetFile())
+		case *api.Privileges_CredentialSpec_Registry:
+			hc.SecurityOpt = append(hc.SecurityOpt, "credentialspec=registry://"+credentials.GetRegistry())
+		}
+	}
+
+	selinux := privileges.SELinuxContext
+	if selinux != nil {
+		if selinux.Disable {
+			hc.SecurityOpt = append(hc.SecurityOpt, "label=disable")
+		}
+		if selinux.User != "" {
+			hc.SecurityOpt = append(hc.SecurityOpt, "label=user:"+selinux.User)
+		}
+		if selinux.Role != "" {
+			hc.SecurityOpt = append(hc.SecurityOpt, "label=role:"+selinux.Role)
+		}
+		if selinux.Level != "" {
+			hc.SecurityOpt = append(hc.SecurityOpt, "label=level:"+selinux.Level)
+		}
+		if selinux.Type != "" {
+			hc.SecurityOpt = append(hc.SecurityOpt, "label=type:"+selinux.Type)
+		}
+	}
+}
+
 func (c containerConfig) eventFilter() filters.Args {
 func (c containerConfig) eventFilter() filters.Args {
 	filter := filters.NewArgs()
 	filter := filters.NewArgs()
 	filter.Add("type", events.ContainerEventType)
 	filter.Add("type", events.ContainerEventType)