diff options
-rw-r--r-- | cmd/kpod/create.go | 188 | ||||
-rw-r--r-- | cmd/kpod/run.go | 1 | ||||
-rw-r--r-- | cmd/kpod/spec.go | 23 | ||||
-rw-r--r-- | libpod/container.go | 4 | ||||
-rw-r--r-- | libpod/options.go | 12 | ||||
-rw-r--r-- | test/kpod_run.bats | 18 |
6 files changed, 174 insertions, 72 deletions
diff --git a/cmd/kpod/create.go b/cmd/kpod/create.go index ddfe9e5ed..9d4d08b08 100644 --- a/cmd/kpod/create.go +++ b/cmd/kpod/create.go @@ -2,9 +2,12 @@ package main import ( "fmt" + "os" "strconv" + "strings" "github.com/docker/go-units" + "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" "github.com/projectatomic/libpod/libpod" "github.com/urfave/cli" @@ -55,58 +58,62 @@ type createResourceConfig struct { } type createConfig struct { - args []string - capAdd []string // cap-add - capDrop []string // cap-drop - cidFile string - cgroupParent string // cgroup-parent - command []string - detach bool // detach - devices []*pb.Device // device - dnsOpt []string //dns-opt - dnsSearch []string //dns-search - dnsServers []string //dns - entrypoint string //entrypoint - env []string //env - expose []string //expose - groupAdd []uint32 // group-add - hostname string //hostname - image string - interactive bool //interactive - ip6Address string //ipv6 - ipAddress string //ip - labels map[string]string //label - linkLocalIP []string // link-local-ip - logDriver string // log-driver - logDriverOpt []string // log-opt - macAddress string //mac-address - name string //name - network string //network - networkAlias []string //network-alias - nsIPC string // ipc - nsNet string //net - nsPID string //pid - nsUser string - pod string //pod - privileged bool //privileged - publish []string //publish - publishAll bool //publish-all - readOnlyRootfs bool //read-only - resources createResourceConfig - rm bool //rm - securityOpts []string //security-opt - sigProxy bool //sig-proxy - stopSignal string // stop-signal - stopTimeout int64 // stop-timeout - storageOpts []string //storage-opt - sysctl map[string]string //sysctl - tmpfs []string // tmpfs - tty bool //tty - user uint32 //user - group uint32 // group - volumes []string //volume - volumesFrom []string //volumes-from - workDir string //workdir + args []string + capAdd []string // cap-add + capDrop []string // cap-drop + cidFile string + cgroupParent string // cgroup-parent + command []string + detach bool // detach + devices []*pb.Device // device + dnsOpt []string //dns-opt + dnsSearch []string //dns-search + dnsServers []string //dns + entrypoint string //entrypoint + env []string //env + expose []string //expose + groupAdd []uint32 // group-add + hostname string //hostname + image string + interactive bool //interactive + ip6Address string //ipv6 + ipAddress string //ip + labels map[string]string //label + linkLocalIP []string // link-local-ip + logDriver string // log-driver + logDriverOpt []string // log-opt + macAddress string //mac-address + name string //name + network string //network + networkAlias []string //network-alias + nsIPC string // ipc + nsNet string //net + nsPID string //pid + nsUser string + pod string //pod + privileged bool //privileged + publish []string //publish + publishAll bool //publish-all + readOnlyRootfs bool //read-only + resources createResourceConfig + rm bool //rm + sigProxy bool //sig-proxy + stopSignal string // stop-signal + stopTimeout int64 // stop-timeout + storageOpts []string //storage-opt + sysctl map[string]string //sysctl + tmpfs []string // tmpfs + tty bool //tty + user uint32 //user + group uint32 // group + volumes []string //volume + volumesFrom []string //volumes-from + workDir string //workdir + mountLabel string //SecurityOpts + processLabel string //SecurityOpts + noNewPrivileges bool //SecurityOpts + apparmorProfile string //SecurityOpts + seccompProfilePath string //SecurityOpts } var createDescription = "Creates a new container from the given image or" + @@ -169,6 +176,7 @@ func createCmd(c *cli.Context) error { } // Gather up the options for NewContainer which consist of With... funcs options = append(options, libpod.WithRootFSFromImage(imageID, imageName, false)) + options = append(options, libpod.WithSELinuxMountLabel(createConfig.mountLabel)) ctr, err := runtime.NewContainer(runtimeSpec, options...) if err != nil { return err @@ -183,6 +191,49 @@ func createCmd(c *cli.Context) error { return nil } +const seccompDefaultPath = "/etc/crio/seccomp.json" + +func parseSecurityOpt(config *createConfig, securityOpts []string) error { + var ( + labelOpts []string + err error + ) + + for _, opt := range securityOpts { + if opt == "no-new-privileges" { + config.noNewPrivileges = true + } else { + con := strings.SplitN(opt, "=", 2) + if len(con) != 2 { + return fmt.Errorf("Invalid --security-opt 1: %q", opt) + } + + switch con[0] { + case "label": + labelOpts = append(labelOpts, con[1]) + case "apparmor": + config.apparmorProfile = con[1] + case "seccomp": + config.seccompProfilePath = con[1] + default: + return fmt.Errorf("Invalid --security-opt 2: %q", opt) + } + } + } + + if config.seccompProfilePath == "" { + if _, err := os.Stat(seccompDefaultPath); err != nil { + if !os.IsNotExist(err) { + return errors.Wrapf(err, "can't check if %q exists", seccompDefaultPath) + } + } else { + config.seccompProfilePath = seccompDefaultPath + } + } + config.processLabel, config.mountLabel, err = label.InitLabels(labelOpts) + return err +} + // Parses CLI options related to container creation into a config which can be // parsed into an OCI runtime spec func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, error) { @@ -323,20 +374,25 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, er pidsLimit: c.Int64("pids-limit"), ulimit: c.StringSlice("ulimit"), }, - rm: c.Bool("rm"), - securityOpts: c.StringSlice("security-opt"), - sigProxy: c.Bool("sig-proxy"), - stopSignal: c.String("stop-signal"), - stopTimeout: c.Int64("stop-timeout"), - storageOpts: c.StringSlice("storage-opt"), - sysctl: sysctl, - tmpfs: c.StringSlice("tmpfs"), - tty: c.Bool("tty"), - user: uid, - group: gid, - volumes: c.StringSlice("volume"), - volumesFrom: c.StringSlice("volumes-from"), - workDir: c.String("workdir"), + rm: c.Bool("rm"), + sigProxy: c.Bool("sig-proxy"), + stopSignal: c.String("stop-signal"), + stopTimeout: c.Int64("stop-timeout"), + storageOpts: c.StringSlice("storage-opt"), + sysctl: sysctl, + tmpfs: c.StringSlice("tmpfs"), + tty: c.Bool("tty"), + user: uid, + group: gid, + volumes: c.StringSlice("volume"), + volumesFrom: c.StringSlice("volumes-from"), + workDir: c.String("workdir"), + } + + if !config.privileged { + if err := parseSecurityOpt(config, c.StringSlice("security-opt")); err != nil { + return nil, err + } } return config, nil diff --git a/cmd/kpod/run.go b/cmd/kpod/run.go index f1563edc3..cefa1d2d0 100644 --- a/cmd/kpod/run.go +++ b/cmd/kpod/run.go @@ -69,6 +69,7 @@ func runCmd(c *cli.Context) error { } // Gather up the options for NewContainer which consist of With... funcs options = append(options, libpod.WithRootFSFromImage(imageID, imageName, false)) + options = append(options, libpod.WithSELinuxMountLabel(createConfig.mountLabel)) ctr, err := runtime.NewContainer(runtimeSpec, options...) if err != nil { return err diff --git a/cmd/kpod/spec.go b/cmd/kpod/spec.go index b6fb8b128..d30c0d1a5 100644 --- a/cmd/kpod/spec.go +++ b/cmd/kpod/spec.go @@ -1,7 +1,9 @@ package main import ( + "encoding/json" "fmt" + "io/ioutil" "strings" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -91,16 +93,30 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) { configSpec.Linux.Resources.Pids.Limit = config.resources.pidsLimit } + // SECURITY OPTS + configSpec.Process.NoNewPrivileges = config.noNewPrivileges + configSpec.Process.ApparmorProfile = config.apparmorProfile + configSpec.Process.SelinuxLabel = config.processLabel + configSpec.Linux.MountLabel = config.mountLabel + if config.seccompProfilePath != "" && config.seccompProfilePath != "unconfined" { + seccompProfile, err := ioutil.ReadFile(config.seccompProfilePath) + if err != nil { + return nil, errors.Wrapf(err, "opening seccomp profile (%s) failed", config.seccompProfilePath) + } + var seccompConfig spec.LinuxSeccomp + if err := json.Unmarshal(seccompProfile, &seccompConfig); err != nil { + return nil, errors.Wrapf(err, "decoding seccomp profile (%s) failed", config.seccompProfilePath) + } + configSpec.Linux.Seccomp = &seccompConfig + } + /* Capabilities: &configSpec.LinuxCapabilities{ // Rlimits []PosixRlimit // Where does this come from // Type string // Hard uint64 // Limit uint64 - // NoNewPrivileges bool // No user input for this - // ApparmorProfile string // No user input for this OOMScoreAdj: &config.resources.oomScoreAdj, - // Selinuxlabel }, Hooks: &configSpec.Hooks{}, //Annotations @@ -116,7 +132,6 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) { //CgroupsPath: //Namespaces: []LinuxNamespace //Devices - Seccomp: &configSpec.LinuxSeccomp{ // DefaultAction: // Architectures // Syscalls: diff --git a/libpod/container.go b/libpod/container.go index 9f9d4ef3e..7c2f921f3 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -95,6 +95,7 @@ type containerConfig struct { // Information on the image used for the root filesystem RootfsImageID string `json:"rootfsImageID,omitempty"` RootfsImageName string `json:"rootfsImageName,omitempty"` + MountLabel string `json:"MountLabel,omitempty"` UseImageConfig bool `json:"useImageConfig"` // Whether to keep container STDIN open Stdin bool @@ -223,8 +224,7 @@ func (c *Container) setupImageRootfs() error { return errors.Wrapf(ErrInvalidArg, "must provide image ID and image name to use an image") } - // TODO SELinux mount label - containerInfo, err := c.runtime.storageService.CreateContainerStorage(c.runtime.imageContext, c.config.RootfsImageName, c.config.RootfsImageID, c.config.Name, c.config.ID, "") + containerInfo, err := c.runtime.storageService.CreateContainerStorage(c.runtime.imageContext, c.config.RootfsImageName, c.config.RootfsImageID, c.config.Name, c.config.ID, c.config.MountLabel) if err != nil { return errors.Wrapf(err, "error creating container storage") } diff --git a/libpod/options.go b/libpod/options.go index 982655fc0..10cb605c2 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -255,6 +255,18 @@ func WithRootFSFromPath(path string) CtrCreateOption { } } +// WithSELinuxMountLabel sets the mount label for SELinux +func WithSELinuxMountLabel(mountLabel string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + + ctr.config.MountLabel = mountLabel + return nil + } +} + // WithRootFSFromImage sets up a fresh root filesystem using the given image // If useImageConfig is specified, image volumes, environment variables, and // other configuration from the image will be added to the config diff --git a/test/kpod_run.bats b/test/kpod_run.bats index 4945691a7..d0cac96de 100644 --- a/test/kpod_run.bats +++ b/test/kpod_run.bats @@ -18,3 +18,21 @@ ALPINE="docker.io/library/alpine:latest" echo "$output" [ "$status" -eq 0 ] } + +@test "run selinux test" { + + if [ ! -e /usr/sbin/selinuxenabled ] || /usr/sbin/selinuxenabled; then + skip "SELinux not enabled" + fi + + firstLabel=$(${KPOD_BINARY} ${KPOD_OPTIONS} run ${ALPINE} cat /proc/self/attr/current) + run ${KPOD_BINARY} ${KPOD_OPTIONS} run ${ALPINE} cat /proc/self/attr/current + echo "$output" + [ "$status" -eq 0 ] + [ "$output" != "${firstLabel}" ] + + run bash -c "${KPOD_BINARY} ${KPOD_OPTIONS} run --security-opt label:level=s0:c1,c2 ${ALPINE} cat /proc/self/attr/current | grep s0:c1,c2" + echo "$output" + [ "$status" -eq 0 ] + +} |