This patch adds SELinux labeling support.

docker will run the process(es) within the container with an SELinux label and will label
all of  the content within the container with mount label.  Any temporary file systems
created within the container need to be mounted with the same mount label.

The user can override the process label by specifying

-Z With a string of space separated options.

-Z "user=unconfined_u role=unconfined_r type=unconfined_t level=s0"

Would cause the process label to run with unconfined_u:unconfined_r:unconfined_t:s0"

By default the processes will run execute within the container as svirt_lxc_net_t.
All of the content in the container as svirt_sandbox_file_t.

The process mcs level is based of the PID of the docker process that is creating the container.

If you run the container in --priv mode, the labeling will be disabled.

Docker-DCO-1.1-Signed-off-by: Dan Walsh <dwalsh@redhat.com> (github: rhatdan)
This commit is contained in:
Dan Walsh 2014-03-18 16:49:16 -04:00
parent 5506e4b62d
commit 4c43566925
26 changed files with 700 additions and 72 deletions

View file

@ -87,7 +87,7 @@ RUN git config --global user.email 'docker-dummy@example.com'
VOLUME /var/lib/docker
WORKDIR /go/src/github.com/dotcloud/docker
ENV DOCKER_BUILDTAGS apparmor
ENV DOCKER_BUILDTAGS apparmor selinux
# Wrap all commands in the "docker-in-docker" script to allow nested containers
ENTRYPOINT ["hack/dind"]

View file

@ -189,7 +189,7 @@ func (graph *Graph) Register(jsonData []byte, layerData archive.ArchiveReader, i
}
// Create root filesystem in the driver
if err := graph.driver.Create(img.ID, img.Parent); err != nil {
if err := graph.driver.Create(img.ID, img.Parent, ""); err != nil {
return fmt.Errorf("Driver %s failed to create image rootfs %s: %s", graph.driver, img.ID, err)
}
// Mount the root filesystem so we can apply the diff/layer

View file

@ -177,6 +177,13 @@ export DOCKER_BUILDTAGS='exclude_graphdriver_aufs'
NOTE: if you need to set more than one build tag, space separate them.
If you're building a binary that may need to be used on platforms that include
SELinux, you will need to set `DOCKER_BUILDTAGS` as follows:
```bash
export DOCKER_BUILDTAGS='selinux'
```
### Static Daemon
If it is feasible within the constraints of your distribution, you should

23
pkg/label/label.go Normal file
View file

@ -0,0 +1,23 @@
// +build !selinux !linux
package label
func GenLabels(options string) (string, string, error) {
return "", "", nil
}
func FormatMountLabel(src string, MountLabel string) string {
return src
}
func SetProcessLabel(processLabel string) error {
return nil
}
func SetFileLabel(path string, fileLabel string) error {
return nil
}
func GetPidCon(pid int) (string, error) {
return "", nil
}

View file

@ -0,0 +1,69 @@
// +build selinux,linux
package label
import (
"fmt"
"github.com/dotcloud/docker/pkg/selinux"
"strings"
)
func GenLabels(options string) (string, string, error) {
processLabel, mountLabel := selinux.GetLxcContexts()
var err error
if processLabel == "" { // SELinux is disabled
return "", "", err
}
s := strings.Fields(options)
l := len(s)
if l > 0 {
pcon := selinux.NewContext(processLabel)
for i := 0; i < l; i++ {
o := strings.Split(s[i], "=")
pcon[o[0]] = o[1]
}
processLabel = pcon.Get()
mountLabel, err = selinux.CopyLevel(processLabel, mountLabel)
}
return processLabel, mountLabel, err
}
func FormatMountLabel(src string, MountLabel string) string {
var mountLabel string
if src != "" {
mountLabel = src
if MountLabel != "" {
mountLabel = fmt.Sprintf("%s,context=\"%s\"", mountLabel, MountLabel)
}
} else {
if MountLabel != "" {
mountLabel = fmt.Sprintf("context=\"%s\"", MountLabel)
}
}
return mountLabel
}
func SetProcessLabel(processLabel string) error {
if selinux.SelinuxEnabled() {
return selinux.Setexeccon(processLabel)
}
return nil
}
func GetProcessLabel() (string, error) {
if selinux.SelinuxEnabled() {
return selinux.Getexeccon()
}
return "", nil
}
func SetFileLabel(path string, fileLabel string) error {
if selinux.SelinuxEnabled() && fileLabel != "" {
return selinux.Setfilecon(path, fileLabel)
}
return nil
}
func GetPidCon(pid int) (string, error) {
return selinux.Getpidcon(pid)
}

View file

@ -4,6 +4,7 @@ package nsinit
import (
"fmt"
"github.com/dotcloud/docker/pkg/label"
"github.com/dotcloud/docker/pkg/libcontainer"
"github.com/dotcloud/docker/pkg/system"
"os"
@ -32,7 +33,11 @@ func (ns *linuxNs) ExecIn(container *libcontainer.Container, nspid int, args []s
closeFds()
return -1, err
}
processLabel, err := label.GetPidCon(nspid)
if err != nil {
closeFds()
return -1, err
}
// foreach namespace fd, use setns to join an existing container's namespaces
for _, fd := range fds {
if fd > 0 {
@ -80,6 +85,10 @@ dropAndExec:
if err := finalizeNamespace(container); err != nil {
return -1, err
}
err = label.SetProcessLabel(processLabel)
if err != nil {
return -1, err
}
if err := system.Execv(args[0], args[0:], container.Env); err != nil {
return -1, err
}

View file

@ -4,6 +4,7 @@ package nsinit
import (
"fmt"
"github.com/dotcloud/docker/pkg/label"
"github.com/dotcloud/docker/pkg/libcontainer"
"github.com/dotcloud/docker/pkg/libcontainer/apparmor"
"github.com/dotcloud/docker/pkg/libcontainer/capabilities"
@ -12,6 +13,7 @@ import (
"github.com/dotcloud/docker/pkg/system"
"github.com/dotcloud/docker/pkg/user"
"os"
"runtime"
"syscall"
)
@ -57,7 +59,7 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol
return fmt.Errorf("parent death signal %s", err)
}
ns.logger.Println("setup mount namespace")
if err := setupNewMountNamespace(rootfs, container.Mounts, console, container.ReadonlyFs, container.NoPivotRoot); err != nil {
if err := setupNewMountNamespace(rootfs, container.Mounts, console, container.ReadonlyFs, container.NoPivotRoot, container.Context["mount_label"]); err != nil {
return fmt.Errorf("setup mount namespace %s", err)
}
if err := setupNetwork(container, context); err != nil {
@ -76,6 +78,10 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol
return err
}
}
runtime.LockOSThread()
if err := label.SetProcessLabel(container.Context["process_label"]); err != nil {
return fmt.Errorf("SetProcessLabel label %s", err)
}
ns.logger.Printf("execing %s\n", args[0])
return system.Execv(args[0], args[0:], container.Env)
}

View file

@ -4,6 +4,7 @@ package nsinit
import (
"fmt"
"github.com/dotcloud/docker/pkg/label"
"github.com/dotcloud/docker/pkg/libcontainer"
"github.com/dotcloud/docker/pkg/system"
"io/ioutil"
@ -20,7 +21,7 @@ const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NOD
//
// There is no need to unmount the new mounts because as soon as the mount namespace
// is no longer in use, the mounts will be removed automatically
func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, console string, readonly, noPivotRoot bool) error {
func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, console string, readonly, noPivotRoot bool, mountLabel string) error {
flag := syscall.MS_PRIVATE
if noPivotRoot {
flag = syscall.MS_SLAVE
@ -36,7 +37,7 @@ func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, cons
return fmt.Errorf("mounting %s as readonly %s", rootfs, err)
}
}
if err := mountSystem(rootfs); err != nil {
if err := mountSystem(rootfs, mountLabel); err != nil {
return fmt.Errorf("mount system %s", err)
}
@ -64,7 +65,7 @@ func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, cons
if err := setupDev(rootfs); err != nil {
return err
}
if err := setupPtmx(rootfs, console); err != nil {
if err := setupPtmx(rootfs, console, mountLabel); err != nil {
return err
}
if err := system.Chdir(rootfs); err != nil {
@ -196,7 +197,7 @@ func setupDev(rootfs string) error {
}
// setupConsole ensures that the container has a proper /dev/console setup
func setupConsole(rootfs, console string) error {
func setupConsole(rootfs, console string, mountLabel string) error {
oldMask := system.Umask(0000)
defer system.Umask(oldMask)
@ -220,6 +221,9 @@ func setupConsole(rootfs, console string) error {
if err := system.Mknod(dest, (st.Mode&^07777)|0600, int(st.Rdev)); err != nil {
return fmt.Errorf("mknod %s %s", dest, err)
}
if err := label.SetFileLabel(console, mountLabel); err != nil {
return fmt.Errorf("SetFileLabel Failed %s %s", dest, err)
}
if err := system.Mount(console, dest, "bind", syscall.MS_BIND, ""); err != nil {
return fmt.Errorf("bind %s to %s %s", console, dest, err)
}
@ -228,7 +232,7 @@ func setupConsole(rootfs, console string) error {
// mountSystem sets up linux specific system mounts like sys, proc, shm, and devpts
// inside the mount namespace
func mountSystem(rootfs string) error {
func mountSystem(rootfs string, mountLabel string) error {
for _, m := range []struct {
source string
path string
@ -238,8 +242,8 @@ func mountSystem(rootfs string) error {
}{
{source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags},
{source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags},
{source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: "mode=1777,size=65536k"},
{source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: "newinstance,ptmxmode=0666,mode=620,gid=5"},
{source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1755,size=65536k", mountLabel)},
{source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)},
} {
if err := os.MkdirAll(m.path, 0755); err != nil && !os.IsExist(err) {
return fmt.Errorf("mkdirall %s %s", m.path, err)
@ -253,7 +257,7 @@ func mountSystem(rootfs string) error {
// setupPtmx adds a symlink to pts/ptmx for /dev/ptmx and
// finishes setting up /dev/console
func setupPtmx(rootfs, console string) error {
func setupPtmx(rootfs, console string, mountLabel string) error {
ptmx := filepath.Join(rootfs, "dev/ptmx")
if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) {
return err
@ -262,7 +266,7 @@ func setupPtmx(rootfs, console string) error {
return fmt.Errorf("symlink dev ptmx %s", err)
}
if console != "" {
if err := setupConsole(rootfs, console); err != nil {
if err := setupConsole(rootfs, console, mountLabel); err != nil {
return err
}
}

387
pkg/selinux/selinux.go Normal file
View file

@ -0,0 +1,387 @@
package selinux
import (
"bufio"
"crypto/rand"
"encoding/binary"
"fmt"
"github.com/dotcloud/docker/pkg/mount"
"github.com/dotcloud/docker/pkg/system"
"io"
"os"
"regexp"
"strconv"
"strings"
"syscall"
)
const (
Enforcing = 1
Permissive = 0
Disabled = -1
selinuxDir = "/etc/selinux/"
selinuxConfig = selinuxDir + "config"
selinuxTypeTag = "SELINUXTYPE"
selinuxTag = "SELINUX"
selinuxPath = "/sys/fs/selinux"
xattrNameSelinux = "security.selinux"
stRdOnly = 0x01
)
var (
assignRegex = regexp.MustCompile(`^([^=]+)=(.*)$`)
spaceRegex = regexp.MustCompile(`^([^=]+) (.*)$`)
mcsList = make(map[string]bool)
selinuxfs = "unknown"
selinuxEnabled = false
selinuxEnabledChecked = false
)
type SELinuxContext map[string]string
func GetSelinuxMountPoint() string {
if selinuxfs != "unknown" {
return selinuxfs
}
selinuxfs = ""
mounts, err := mount.GetMounts()
if err != nil {
return selinuxfs
}
for _, mount := range mounts {
if mount.Fstype == "selinuxfs" {
selinuxfs = mount.Mountpoint
break
}
}
if selinuxfs != "" {
var buf syscall.Statfs_t
syscall.Statfs(selinuxfs, &buf)
if (buf.Flags & stRdOnly) == 1 {
selinuxfs = ""
}
}
return selinuxfs
}
func SelinuxEnabled() bool {
if selinuxEnabledChecked {
return selinuxEnabled
}
selinuxEnabledChecked = true
if fs := GetSelinuxMountPoint(); fs != "" {
if con, _ := Getcon(); con != "kernel" {
selinuxEnabled = true
}
}
return selinuxEnabled
}
func ReadConfig(target string) (value string) {
var (
val, key string
bufin *bufio.Reader
)
in, err := os.Open(selinuxConfig)
if err != nil {
return ""
}
defer in.Close()
bufin = bufio.NewReader(in)
for done := false; !done; {
var line string
if line, err = bufin.ReadString('\n'); err != nil {
if err != io.EOF {
return ""
}
done = true
}
line = strings.TrimSpace(line)
if len(line) == 0 {
// Skip blank lines
continue
}
if line[0] == ';' || line[0] == '#' {
// Skip comments
continue
}
if groups := assignRegex.FindStringSubmatch(line); groups != nil {
key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2])
if key == target {
return strings.Trim(val, "\"")
}
}
}
return ""
}
func GetSELinuxPolicyRoot() string {
return selinuxDir + ReadConfig(selinuxTypeTag)
}
func readCon(name string) (string, error) {
var val string
in, err := os.Open(name)
if err != nil {
return "", err
}
defer in.Close()
_, err = fmt.Fscanf(in, "%s", &val)
return val, err
}
func Setfilecon(path string, scon string) error {
return system.Lsetxattr(path, xattrNameSelinux, []byte(scon), 0)
}
func Getfilecon(path string) (string, error) {
var scon []byte
cnt, err := syscall.Getxattr(path, xattrNameSelinux, scon)
scon = make([]byte, cnt)
cnt, err = syscall.Getxattr(path, xattrNameSelinux, scon)
return string(scon), err
}
func Setfscreatecon(scon string) error {
return writeCon("/proc/self/attr/fscreate", scon)
}
func Getfscreatecon() (string, error) {
return readCon("/proc/self/attr/fscreate")
}
func Getcon() (string, error) {
return readCon("/proc/self/attr/current")
}
func Getpidcon(pid int) (string, error) {
return readCon(fmt.Sprintf("/proc/%d/attr/current", pid))
}
func Getexeccon() (string, error) {
return readCon("/proc/self/attr/exec")
}
func writeCon(name string, val string) error {
if !SelinuxEnabled() {
return nil
}
out, err := os.OpenFile(name, os.O_WRONLY, 0)
if err != nil {
return err
}
defer out.Close()
if val != "" {
_, err = out.Write([]byte(val))
} else {
_, err = out.Write(nil)
}
return err
}
func Setexeccon(scon string) error {
return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid()), scon)
}
func (c SELinuxContext) Get() string {
return fmt.Sprintf("%s:%s:%s:%s", c["user"], c["role"], c["type"], c["level"])
}
func NewContext(scon string) SELinuxContext {
c := make(SELinuxContext)
if len(scon) != 0 {
con := strings.SplitN(scon, ":", 4)
c["user"] = con[0]
c["role"] = con[1]
c["type"] = con[2]
c["level"] = con[3]
}
return c
}
func SelinuxGetEnforce() int {
var enforce int
enforceS, err := readCon(fmt.Sprintf("%s/enforce", selinuxPath))
if err != nil {
return -1
}
enforce, err = strconv.Atoi(string(enforceS))
if err != nil {
return -1
}
return enforce
}
func SelinuxGetEnforceMode() int {
switch ReadConfig(selinuxTag) {
case "enforcing":
return Enforcing
case "permissive":
return Permissive
}
return Disabled
}
func mcsAdd(mcs string) {
mcsList[mcs] = true
}
func mcsDelete(mcs string) {
mcsList[mcs] = false
}
func mcsExists(mcs string) bool {
return mcsList[mcs]
}
func IntToMcs(id int, catRange uint32) string {
var (
SETSIZE = int(catRange)
TIER = SETSIZE
ORD = id
)
if id < 1 || id > 523776 {
return ""
}
for ORD > TIER {
ORD = ORD - TIER
TIER -= 1
}
TIER = SETSIZE - TIER
ORD = ORD + TIER
return fmt.Sprintf("s0:c%d,c%d", TIER, ORD)
}
func uniqMcs(catRange uint32) string {
var (
n uint32
c1, c2 uint32
mcs string
)
for {
binary.Read(rand.Reader, binary.LittleEndian, &n)
c1 = n % catRange
binary.Read(rand.Reader, binary.LittleEndian, &n)
c2 = n % catRange
if c1 == c2 {
continue
} else {
if c1 > c2 {
t := c1
c1 = c2
c2 = t
}
}
mcs = fmt.Sprintf("s0:c%d,c%d", c1, c2)
if mcsExists(mcs) {
continue
}
mcsAdd(mcs)
break
}
return mcs
}
func FreeContext(con string) {
if con != "" {
scon := NewContext(con)
mcsDelete(scon["level"])
}
}
func GetLxcContexts() (processLabel string, fileLabel string) {
var (
val, key string
bufin *bufio.Reader
)
if !SelinuxEnabled() {
return "", ""
}
lxcPath := fmt.Sprintf("%s/content/lxc_contexts", GetSELinuxPolicyRoot())
fileLabel = "system_u:object_r:svirt_sandbox_file_t:s0"
processLabel = "system_u:system_r:svirt_lxc_net_t:s0"
in, err := os.Open(lxcPath)
if err != nil {
goto exit
}
defer in.Close()
bufin = bufio.NewReader(in)
for done := false; !done; {
var line string
if line, err = bufin.ReadString('\n'); err != nil {
if err == io.EOF {
done = true
} else {
goto exit
}
}
line = strings.TrimSpace(line)
if len(line) == 0 {
// Skip blank lines
continue
}
if line[0] == ';' || line[0] == '#' {
// Skip comments
continue
}
if groups := assignRegex.FindStringSubmatch(line); groups != nil {
key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2])
if key == "process" {
processLabel = strings.Trim(val, "\"")
}
if key == "file" {
fileLabel = strings.Trim(val, "\"")
}
}
}
exit:
mcs := IntToMcs(os.Getpid(), 1024)
scon := NewContext(processLabel)
scon["level"] = mcs
processLabel = scon.Get()
scon = NewContext(fileLabel)
scon["level"] = mcs
fileLabel = scon.Get()
return processLabel, fileLabel
}
func SecurityCheckContext(val string) error {
return writeCon(fmt.Sprintf("%s.context", selinuxPath), val)
}
func CopyLevel(src, dest string) (string, error) {
if !SelinuxEnabled() {
return "", nil
}
if src == "" {
return "", nil
}
if err := SecurityCheckContext(src); err != nil {
return "", err
}
if err := SecurityCheckContext(dest); err != nil {
return "", err
}
scon := NewContext(src)
tcon := NewContext(dest)
tcon["level"] = scon["level"]
return tcon.Get(), nil
}

View file

@ -0,0 +1,64 @@
package selinux_test
import (
"github.com/dotcloud/docker/pkg/selinux"
"os"
"testing"
)
func testSetfilecon(t *testing.T) {
if selinux.SelinuxEnabled() {
tmp := "selinux_test"
out, _ := os.OpenFile(tmp, os.O_WRONLY, 0)
out.Close()
err := selinux.Setfilecon(tmp, "system_u:object_r:bin_t:s0")
if err == nil {
t.Log(selinux.Getfilecon(tmp))
} else {
t.Log("Setfilecon failed")
t.Fatal(err)
}
os.Remove(tmp)
}
}
func TestSELinux(t *testing.T) {
var (
err error
plabel, flabel string
)
if selinux.SelinuxEnabled() {
t.Log("Enabled")
plabel, flabel = selinux.GetLxcContexts()
t.Log(plabel)
t.Log(flabel)
plabel, flabel = selinux.GetLxcContexts()
t.Log(plabel)
t.Log(flabel)
t.Log("getenforce ", selinux.SelinuxGetEnforce())
t.Log("getenforcemode ", selinux.SelinuxGetEnforceMode())
pid := os.Getpid()
t.Log("PID:%d MCS:%s\n", pid, selinux.IntToMcs(pid, 1023))
t.Log(selinux.Getcon())
t.Log(selinux.Getfilecon("/etc/passwd"))
err = selinux.Setfscreatecon("unconfined_u:unconfined_r:unconfined_t:s0")
if err == nil {
t.Log(selinux.Getfscreatecon())
} else {
t.Log("setfscreatecon failed", err)
t.Fatal(err)
}
err = selinux.Setfscreatecon("")
if err == nil {
t.Log(selinux.Getfscreatecon())
} else {
t.Log("setfscreatecon failed", err)
t.Fatal(err)
}
t.Log(selinux.Getpidcon(1))
t.Log(selinux.GetSelinuxMountPoint())
} else {
t.Log("Disabled")
}
}

View file

@ -1,8 +1,10 @@
package runconfig
import (
"encoding/json"
"github.com/dotcloud/docker/engine"
"github.com/dotcloud/docker/nat"
"github.com/dotcloud/docker/runtime/execdriver"
)
// Note: the Config structure should hold only portable information about the container.
@ -34,9 +36,17 @@ type Config struct {
Entrypoint []string
NetworkDisabled bool
OnBuild []string
Context execdriver.Context
}
func ContainerConfigFromJob(job *engine.Job) *Config {
var context execdriver.Context
val := job.Getenv("Context")
if val != "" {
if err := json.Unmarshal([]byte(val), &context); err != nil {
panic(err)
}
}
config := &Config{
Hostname: job.Getenv("Hostname"),
Domainname: job.Getenv("Domainname"),
@ -54,6 +64,7 @@ func ContainerConfigFromJob(job *engine.Job) *Config {
VolumesFrom: job.Getenv("VolumesFrom"),
WorkingDir: job.Getenv("WorkingDir"),
NetworkDisabled: job.GetenvBool("NetworkDisabled"),
Context: context,
}
job.GetenvJson("ExposedPorts", &config.ExposedPorts)
job.GetenvJson("Volumes", &config.Volumes)

View file

@ -4,8 +4,10 @@ import (
"fmt"
"github.com/dotcloud/docker/nat"
"github.com/dotcloud/docker/opts"
"github.com/dotcloud/docker/pkg/label"
flag "github.com/dotcloud/docker/pkg/mflag"
"github.com/dotcloud/docker/pkg/sysinfo"
"github.com/dotcloud/docker/runtime/execdriver"
"github.com/dotcloud/docker/utils"
"io/ioutil"
"path"
@ -32,6 +34,10 @@ func ParseSubcommand(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo)
}
func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Config, *HostConfig, *flag.FlagSet, error) {
var (
processLabel string
mountLabel string
)
var (
// FIXME: use utils.ListOpts for attach and volumes?
flAttach = opts.NewListOpts(opts.ValidateAttach)
@ -60,6 +66,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID")
flWorkingDir = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container")
flCpuShares = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
flLabelOptions = cmd.String([]string{"Z", "-label"}, "", "Options to pass to underlying labeling system")
// For documentation purpose
_ = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxify all received signal to the process (even in non-tty mode)")
@ -150,6 +157,15 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
entrypoint = []string{*flEntrypoint}
}
if !*flPrivileged {
pLabel, mLabel, e := label.GenLabels(*flLabelOptions)
if e != nil {
return nil, nil, cmd, fmt.Errorf("Invalid security labels : %s", e)
}
processLabel = pLabel
mountLabel = mLabel
}
lxcConf, err := parseLxcConfOpts(flLxcOpts)
if err != nil {
return nil, nil, cmd, err
@ -204,6 +220,10 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
VolumesFrom: strings.Join(flVolumesFrom.GetAll(), ","),
Entrypoint: entrypoint,
WorkingDir: *flWorkingDir,
Context: execdriver.Context{
"mount_label": mountLabel,
"process_label": processLabel,
},
}
hostConfig := &HostConfig{

View file

@ -402,6 +402,7 @@ func populateCommand(c *Container) {
User: c.Config.User,
Config: driverConfig,
Resources: resources,
Context: c.Config.Context,
}
c.command.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
}

View file

@ -7,6 +7,10 @@ import (
"os/exec"
)
// Context is a generic key value pair that allows
// arbatrary data to be sent
type Context map[string]string
var (
ErrNotRunning = errors.New("Process could not be started")
ErrWaitTimeoutReached = errors.New("Wait timeout reached")
@ -121,6 +125,7 @@ type Command struct {
Arguments []string `json:"arguments"`
WorkingDir string `json:"working_dir"`
ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver
Context Context `json:"context"` // generic context for specific options (apparmor, selinux)
Tty bool `json:"tty"`
Network *Network `json:"network"`
Config []string `json:"config"` // generic values that specific drivers can consume

View file

@ -1,6 +1,7 @@
package lxc
import (
"github.com/dotcloud/docker/pkg/label"
"github.com/dotcloud/docker/runtime/execdriver"
"strings"
"text/template"
@ -29,6 +30,10 @@ lxc.pts = 1024
# disable the main console
lxc.console = none
{{if getProcessLabel .Context}}
lxc.se_context = {{ getProcessLabel .Context}}
{{$MOUNTLABEL := getMountLabel .Context}}
{{end}}
# no controlling tty at all
lxc.tty = 1
@ -85,8 +90,8 @@ lxc.mount.entry = sysfs {{escapeFstabSpaces $ROOTFS}}/sys sysfs nosuid,nodev,noe
lxc.mount.entry = {{.Console}} {{escapeFstabSpaces $ROOTFS}}/dev/console none bind,rw 0 0
{{end}}
lxc.mount.entry = devpts {{escapeFstabSpaces $ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,nosuid,noexec 0 0
lxc.mount.entry = shm {{escapeFstabSpaces $ROOTFS}}/dev/shm tmpfs size=65536k,nosuid,nodev,noexec 0 0
lxc.mount.entry = devpts {{escapeFstabSpaces $ROOTFS}}/dev/pts devpts {{formatMountLabel "newinstance,ptmxmode=0666,nosuid,noexec" "$MOUNTLABEL"}} 0 0
lxc.mount.entry = shm {{escapeFstabSpaces $ROOTFS}}/dev/shm tmpfs {{formatMountLabel "size=65536k,nosuid,nodev,noexec" "$MOUNTLABEL"}} 0 0
{{range $value := .Mounts}}
{{if $value.Writable}}
@ -142,11 +147,22 @@ func getMemorySwap(v *execdriver.Resources) int64 {
return v.Memory * 2
}
func getProcessLabel(c execdriver.Context) string {
return c["process_label"]
}
func getMountLabel(c execdriver.Context) string {
return c["mount_label"]
}
func init() {
var err error
funcMap := template.FuncMap{
"getMemorySwap": getMemorySwap,
"getProcessLabel": getProcessLabel,
"getMountLabel": getMountLabel,
"escapeFstabSpaces": escapeFstabSpaces,
"formatMountLabel": label.FormatMountLabel,
}
LxcTemplateCompiled, err = template.New("lxc").Funcs(funcMap).Parse(LxcTemplate)
if err != nil {

View file

@ -18,6 +18,8 @@ func createContainer(c *execdriver.Command) *libcontainer.Container {
container.User = c.User
container.WorkingDir = c.WorkingDir
container.Env = c.Env
container.Context["mount_label"] = c.Context["mount_label"]
container.Context["process_label"] = c.Context["process_label"]
loopbackNetwork := libcontainer.Network{
Mtu: c.Network.Mtu,

View file

@ -134,7 +134,7 @@ func (a Driver) Exists(id string) bool {
// Three folders are created for each id
// mnt, layers, and diff
func (a *Driver) Create(id, parent string) error {
func (a *Driver) Create(id, parent string, mountLabel string) error {
if err := a.createDirsFor(id); err != nil {
return err
}

View file

@ -90,7 +90,7 @@ func TestCreateNewDir(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}
}
@ -99,7 +99,7 @@ func TestCreateNewDirStructure(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}
@ -120,7 +120,7 @@ func TestRemoveImage(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}
@ -145,7 +145,7 @@ func TestGetWithoutParent(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}
@ -172,7 +172,7 @@ func TestCleanupWithDir(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}
@ -185,7 +185,7 @@ func TestMountedFalseResponse(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}
@ -204,10 +204,10 @@ func TestMountedTrueReponse(t *testing.T) {
defer os.RemoveAll(tmp)
defer d.Cleanup()
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}
if err := d.Create("2", "1"); err != nil {
if err := d.Create("2", "1", ""); err != nil {
t.Fatal(err)
}
@ -230,10 +230,10 @@ func TestMountWithParent(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}
if err := d.Create("2", "1"); err != nil {
if err := d.Create("2", "1", ""); err != nil {
t.Fatal(err)
}
@ -261,10 +261,10 @@ func TestRemoveMountedDir(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}
if err := d.Create("2", "1"); err != nil {
if err := d.Create("2", "1", ""); err != nil {
t.Fatal(err)
}
@ -300,7 +300,7 @@ func TestCreateWithInvalidParent(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Create("1", "docker"); err == nil {
if err := d.Create("1", "docker", ""); err == nil {
t.Fatalf("Error should not be nil with parent does not exist")
}
}
@ -309,7 +309,7 @@ func TestGetDiff(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}
@ -343,10 +343,10 @@ func TestChanges(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}
if err := d.Create("2", "1"); err != nil {
if err := d.Create("2", "1", ""); err != nil {
t.Fatal(err)
}
@ -392,7 +392,7 @@ func TestChanges(t *testing.T) {
t.Fatalf("Change kind should be ChangeAdd got %s", change.Kind)
}
if err := d.Create("3", "2"); err != nil {
if err := d.Create("3", "2", ""); err != nil {
t.Fatal(err)
}
mntPoint, err = d.Get("3")
@ -437,7 +437,7 @@ func TestDiffSize(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}
@ -479,7 +479,7 @@ func TestChildDiffSize(t *testing.T) {
defer os.RemoveAll(tmp)
defer d.Cleanup()
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}
@ -515,7 +515,7 @@ func TestChildDiffSize(t *testing.T) {
t.Fatalf("Expected size to be %d got %d", size, diffSize)
}
if err := d.Create("2", "1"); err != nil {
if err := d.Create("2", "1", ""); err != nil {
t.Fatal(err)
}
@ -534,7 +534,7 @@ func TestExists(t *testing.T) {
defer os.RemoveAll(tmp)
defer d.Cleanup()
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}
@ -552,7 +552,7 @@ func TestStatus(t *testing.T) {
defer os.RemoveAll(tmp)
defer d.Cleanup()
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}
@ -581,7 +581,7 @@ func TestApplyDiff(t *testing.T) {
defer os.RemoveAll(tmp)
defer d.Cleanup()
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}
@ -607,10 +607,10 @@ func TestApplyDiff(t *testing.T) {
t.Fatal(err)
}
if err := d.Create("2", ""); err != nil {
if err := d.Create("2", "", ""); err != nil {
t.Fatal(err)
}
if err := d.Create("3", "2"); err != nil {
if err := d.Create("3", "2", ""); err != nil {
t.Fatal(err)
}
@ -656,7 +656,7 @@ func TestMountMoreThan42Layers(t *testing.T) {
}
current = hash(current)
if err := d.Create(current, parent); err != nil {
if err := d.Create(current, parent, ""); err != nil {
t.Logf("Current layer %d", i)
t.Fatal(err)
}

View file

@ -77,7 +77,7 @@ func (a *Driver) migrateContainers(pth string, setupInit func(p string) error) e
}
initID := fmt.Sprintf("%s-init", id)
if err := a.Create(initID, metadata.Image); err != nil {
if err := a.Create(initID, metadata.Image, ""); err != nil {
return err
}
@ -90,7 +90,7 @@ func (a *Driver) migrateContainers(pth string, setupInit func(p string) error) e
return err
}
if err := a.Create(id, initID); err != nil {
if err := a.Create(id, initID, ""); err != nil {
return err
}
}
@ -144,7 +144,7 @@ func (a *Driver) migrateImage(m *metadata, pth string, migrated map[string]bool)
return err
}
if !a.Exists(m.ID) {
if err := a.Create(m.ID, m.ParentID); err != nil {
if err := a.Create(m.ID, m.ParentID, ""); err != nil {
return err
}
}

View file

@ -80,7 +80,7 @@ func getDirFd(dir *C.DIR) uintptr {
return uintptr(C.dirfd(dir))
}
func subvolCreate(path, name string) error {
func subvolCreate(path, name string, mountLabel string) error {
dir, err := openDir(path)
if err != nil {
return err
@ -155,13 +155,13 @@ func (d *Driver) subvolumesDirId(id string) string {
return path.Join(d.subvolumesDir(), id)
}
func (d *Driver) Create(id string, parent string) error {
func (d *Driver) Create(id string, parent string, mountLabel string) error {
subvolumes := path.Join(d.home, "subvolumes")
if err := os.MkdirAll(subvolumes, 0700); err != nil {
return err
}
if parent == "" {
if err := subvolCreate(subvolumes, id); err != nil {
if err := subvolCreate(subvolumes, id, mountLabel); err != nil {
return err
}
} else {

View file

@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/dotcloud/docker/pkg/label"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
@ -827,7 +828,7 @@ func (devices *DeviceSet) Shutdown() error {
return nil
}
func (devices *DeviceSet) MountDevice(hash, path string) error {
func (devices *DeviceSet) MountDevice(hash, path string, mountLabel string) error {
devices.Lock()
defer devices.Unlock()
@ -859,9 +860,11 @@ func (devices *DeviceSet) MountDevice(hash, path string) error {
var flags uintptr = sysMsMgcVal
err := sysMount(info.DevName(), path, "ext4", flags, "discard")
mountOptions := label.FormatMountLabel("discard", mountLabel)
err := sysMount(info.DevName(), path, "ext4", flags, mountOptions)
if err != nil && err == sysEInval {
err = sysMount(info.DevName(), path, "ext4", flags, "")
mountOptions = label.FormatMountLabel(mountLabel, "")
err = sysMount(info.DevName(), path, "ext4", flags, mountOptions)
}
if err != nil {
return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err)

View file

@ -22,7 +22,8 @@ func init() {
type Driver struct {
*DeviceSet
home string
home string
MountLabel string
}
var Init = func(home string) (graphdriver.Driver, error) {
@ -60,13 +61,13 @@ func (d *Driver) Cleanup() error {
return d.DeviceSet.Shutdown()
}
func (d *Driver) Create(id, parent string) error {
func (d *Driver) Create(id, parent string, mountLabel string) error {
d.MountLabel = mountLabel
if err := d.DeviceSet.AddDevice(id, parent); err != nil {
return err
}
mp := path.Join(d.home, "mnt", id)
if err := d.mount(id, mp); err != nil {
if err := d.mount(id, mp, d.MountLabel); err != nil {
return err
}
@ -116,7 +117,7 @@ func (d *Driver) Remove(id string) error {
func (d *Driver) Get(id string) (string, error) {
mp := path.Join(d.home, "mnt", id)
if err := d.mount(id, mp); err != nil {
if err := d.mount(id, mp, d.MountLabel); err != nil {
return "", err
}
@ -129,13 +130,13 @@ func (d *Driver) Put(id string) {
}
}
func (d *Driver) mount(id, mountPoint string) error {
func (d *Driver) mount(id, mountPoint string, mountLabel string) error {
// Create the target directories if they don't exist
if err := osMkdirAll(mountPoint, 0755); err != nil && !osIsExist(err) {
return err
}
// Mount the device
return d.DeviceSet.MountDevice(id, mountPoint)
return d.DeviceSet.MountDevice(id, mountPoint, mountLabel)
}
func (d *Driver) Exists(id string) bool {

View file

@ -494,7 +494,7 @@ func TestDriverCreate(t *testing.T) {
"?ioctl.loopctlgetfree",
)
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}
calls.Assert(t,
@ -612,7 +612,7 @@ func TestDriverRemove(t *testing.T) {
"?ioctl.loopctlgetfree",
)
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}
@ -668,7 +668,7 @@ func TestCleanup(t *testing.T) {
mountPoints := make([]string, 2)
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}
// Mount the id
@ -678,7 +678,7 @@ func TestCleanup(t *testing.T) {
}
mountPoints[0] = p
if err := d.Create("2", "1"); err != nil {
if err := d.Create("2", "1", ""); err != nil {
t.Fatal(err)
}
@ -731,7 +731,7 @@ func TestNotMounted(t *testing.T) {
d := newDriver(t)
defer cleanup(d)
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}
@ -749,7 +749,7 @@ func TestMounted(t *testing.T) {
d := newDriver(t)
defer cleanup(d)
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}
if _, err := d.Get("1"); err != nil {
@ -769,7 +769,7 @@ func TestInitCleanedDriver(t *testing.T) {
t.Skip("FIXME: not a unit test")
d := newDriver(t)
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}
if _, err := d.Get("1"); err != nil {
@ -797,7 +797,7 @@ func TestMountMountedDriver(t *testing.T) {
d := newDriver(t)
defer cleanup(d)
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}
@ -816,7 +816,7 @@ func TestGetReturnsValidDevice(t *testing.T) {
d := newDriver(t)
defer cleanup(d)
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}
@ -844,7 +844,7 @@ func TestDriverGetSize(t *testing.T) {
d := newDriver(t)
defer cleanup(d)
if err := d.Create("1", ""); err != nil {
if err := d.Create("1", "", ""); err != nil {
t.Fatal(err)
}

View file

@ -13,7 +13,7 @@ type InitFunc func(root string) (Driver, error)
type Driver interface {
String() string
Create(id, parent string) error
Create(id, parent string, mountLabel string) error
Remove(id string) error
Get(id string) (dir string, err error)

View file

@ -42,7 +42,7 @@ func copyDir(src, dst string) error {
return nil
}
func (d *Driver) Create(id string, parent string) error {
func (d *Driver) Create(id string, parent string, mountLabel string) error {
dir := d.dir(id)
if err := os.MkdirAll(path.Dir(dir), 0700); err != nil {
return err

View file

@ -467,7 +467,7 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe
}
initID := fmt.Sprintf("%s-init", container.ID)
if err := runtime.driver.Create(initID, img.ID); err != nil {
if err := runtime.driver.Create(initID, img.ID, config.Context["mount_label"]); err != nil {
return nil, nil, err
}
initPath, err := runtime.driver.Get(initID)
@ -480,7 +480,7 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe
return nil, nil, err
}
if err := runtime.driver.Create(container.ID, initID); err != nil {
if err := runtime.driver.Create(container.ID, initID, config.Context["mount_label"]); err != nil {
return nil, nil, err
}
resolvConf, err := utils.GetResolvConf()