mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
services: Add support for Credential Spec and SELinux
- Defined "normalized" type for Credential Spec and SELinux - Added --credential-spec to docker service create & update - SELinux is API only at the time Signed-off-by: Andrea Luzzardi <aluzzardi@gmail.com>
This commit is contained in:
parent
c4010e257f
commit
89a995a9d7
4 changed files with 163 additions and 0 deletions
|
@ -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"`
|
||||
|
|
|
@ -236,6 +236,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 {
|
||||
|
@ -353,6 +385,7 @@ type serviceOptions struct {
|
|||
workdir string
|
||||
user string
|
||||
groups opts.ListOpts
|
||||
credentialSpec credentialSpecOpt
|
||||
stopSignal string
|
||||
tty bool
|
||||
readOnly bool
|
||||
|
@ -498,6 +531,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
|
||||
}
|
||||
|
||||
|
@ -509,6 +548,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")
|
||||
|
@ -576,6 +617,7 @@ func addServiceFlags(flags *pflag.FlagSet, opts *serviceOptions) {
|
|||
}
|
||||
|
||||
const (
|
||||
flagCredentialSpec = "credential-spec"
|
||||
flagPlacementPref = "placement-pref"
|
||||
flagPlacementPrefAdd = "placement-pref-add"
|
||||
flagPlacementPrefRemove = "placement-pref-rm"
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue