Merge pull request #32339 from aluzzardi/selinux

services: Add support for Credential Spec and SELinux
This commit is contained in:
Sebastiaan van Stijn 2017-04-08 01:37:17 +02:00 committed by GitHub
commit 091b5e68ea
4 changed files with 163 additions and 0 deletions

View File

@ -21,6 +21,28 @@ type DNSConfig struct {
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.
type ContainerSpec struct {
Image string `json:",omitempty"`
@ -32,6 +54,7 @@ type ContainerSpec struct {
Dir string `json:",omitempty"`
User string `json:",omitempty"`
Groups []string `json:",omitempty"`
Privileges *Privileges `json:",omitempty"`
StopSignal string `json:",omitempty"`
TTY bool `json:",omitempty"`
OpenStdin bool `json:",omitempty"`

View File

@ -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 {
nets := []swarm.NetworkAttachmentConfig{}
for _, network := range networks {
@ -355,6 +387,7 @@ type serviceOptions struct {
workdir string
user string
groups opts.ListOpts
credentialSpec credentialSpecOpt
stopSignal string
tty bool
readOnly bool
@ -500,6 +533,12 @@ func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
EndpointSpec: opts.endpoint.ToEndpointSpec(),
}
if opts.credentialSpec.Value() != nil {
service.TaskTemplate.ContainerSpec.Privileges = &swarm.Privileges{
CredentialSpec: opts.credentialSpec.Value(),
}
}
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.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.SetAnnotation(flagHostname, "version", []string{"1.25"})
flags.Var(&opts.entrypoint, flagEntrypoint, "Overwrite the default ENTRYPOINT of the image")
@ -582,6 +623,7 @@ func addServiceFlags(flags *pflag.FlagSet, opts *serviceOptions) {
}
const (
flagCredentialSpec = "credential-spec"
flagPlacementPref = "placement-pref"
flagPlacementPrefAdd = "placement-pref-add"
flagPlacementPrefRemove = "placement-pref-rm"

View File

@ -1,6 +1,7 @@
package convert
import (
"errors"
"fmt"
"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
for _, m := range c.Mounts {
mount := mounttypes.Mount{
@ -166,6 +192,40 @@ func containerToGRPC(c types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
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
for _, m := range c.Mounts {
mount := swarmapi.Mount{

View File

@ -351,6 +351,8 @@ func (c *containerConfig) hostConfig() *enginecontainer.HostConfig {
hc.DNSOptions = c.spec().DNSConfig.Options
}
c.applyPrivileges(hc)
// The format of extra hosts on swarmkit is specified in:
// http://man7.org/linux/man-pages/man5/hosts.5.html
// IP_address canonical_hostname [aliases...]
@ -600,6 +602,42 @@ func (c *containerConfig) networkCreateRequest(name string) (clustertypes.Networ
}, 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 {
filter := filters.NewArgs()
filter.Add("type", events.ContainerEventType)