Merge pull request #385 from crosbymichael/userns-updates
Make usernamespaces work without sidecar process
This commit is contained in:
commit
d06a2dab9f
8
error.go
8
error.go
|
@ -57,11 +57,3 @@ type Error interface {
|
||||||
// Returns the error code for this error.
|
// Returns the error code for this error.
|
||||||
Code() ErrorCode
|
Code() ErrorCode
|
||||||
}
|
}
|
||||||
|
|
||||||
type initError struct {
|
|
||||||
Message string `json:"message,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i initError) Error() string {
|
|
||||||
return i.Message
|
|
||||||
}
|
|
||||||
|
|
|
@ -11,8 +11,8 @@ import (
|
||||||
|
|
||||||
var errorTemplate = template.Must(template.New("error").Parse(`Timestamp: {{.Timestamp}}
|
var errorTemplate = template.Must(template.New("error").Parse(`Timestamp: {{.Timestamp}}
|
||||||
Code: {{.ECode}}
|
Code: {{.ECode}}
|
||||||
{{if .Err }}
|
{{if .Message }}
|
||||||
Message: {{.Err.Error}}
|
Message: {{.Message}}
|
||||||
{{end}}
|
{{end}}
|
||||||
Frames:{{range $i, $frame := .Stack.Frames}}
|
Frames:{{range $i, $frame := .Stack.Frames}}
|
||||||
---
|
---
|
||||||
|
@ -28,6 +28,7 @@ func newGenericError(err error, c ErrorCode) Error {
|
||||||
return &genericError{
|
return &genericError{
|
||||||
Timestamp: time.Now(),
|
Timestamp: time.Now(),
|
||||||
Err: err,
|
Err: err,
|
||||||
|
Message: err.Error(),
|
||||||
ECode: c,
|
ECode: c,
|
||||||
Stack: stacktrace.Capture(1),
|
Stack: stacktrace.Capture(1),
|
||||||
}
|
}
|
||||||
|
@ -41,6 +42,7 @@ func newSystemError(err error) Error {
|
||||||
Timestamp: time.Now(),
|
Timestamp: time.Now(),
|
||||||
Err: err,
|
Err: err,
|
||||||
ECode: SystemError,
|
ECode: SystemError,
|
||||||
|
Message: err.Error(),
|
||||||
Stack: stacktrace.Capture(1),
|
Stack: stacktrace.Capture(1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,12 +50,13 @@ func newSystemError(err error) Error {
|
||||||
type genericError struct {
|
type genericError struct {
|
||||||
Timestamp time.Time
|
Timestamp time.Time
|
||||||
ECode ErrorCode
|
ECode ErrorCode
|
||||||
Err error
|
Err error `json:"-"`
|
||||||
|
Message string
|
||||||
Stack stacktrace.Stacktrace
|
Stack stacktrace.Stacktrace
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *genericError) Error() string {
|
func (e *genericError) Error() string {
|
||||||
return fmt.Sprintf("[%d] %s: %s", e.ECode, e.ECode, e.Err)
|
return fmt.Sprintf("[%d] %s: %s", e.ECode, e.ECode, e.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *genericError) Code() ErrorCode {
|
func (e *genericError) Code() ErrorCode {
|
||||||
|
|
|
@ -12,13 +12,9 @@ import (
|
||||||
"github.com/docker/libcontainer/label"
|
"github.com/docker/libcontainer/label"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
containerConsolePath string = "/dev/console"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewConsole returns an initalized console that can be used within a container by copying bytes
|
// NewConsole returns an initalized console that can be used within a container by copying bytes
|
||||||
// from the master side to the slave that is attached as the tty for the container's init process.
|
// from the master side to the slave that is attached as the tty for the container's init process.
|
||||||
func NewConsole() (Console, error) {
|
func NewConsole(uid, gid int) (Console, error) {
|
||||||
master, err := os.OpenFile("/dev/ptmx", syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0)
|
master, err := os.OpenFile("/dev/ptmx", syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -30,6 +26,12 @@ func NewConsole() (Console, error) {
|
||||||
if err := unlockpt(master); err != nil {
|
if err := unlockpt(master); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err := os.Chmod(console, 0600); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := os.Chown(console, uid, gid); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return &linuxConsole{
|
return &linuxConsole{
|
||||||
slavePath: console,
|
slavePath: console,
|
||||||
master: master,
|
master: master,
|
||||||
|
@ -78,16 +80,10 @@ func (c *linuxConsole) Close() error {
|
||||||
func (c *linuxConsole) mount(rootfs, mountLabel string, uid, gid int) error {
|
func (c *linuxConsole) mount(rootfs, mountLabel string, uid, gid int) error {
|
||||||
oldMask := syscall.Umask(0000)
|
oldMask := syscall.Umask(0000)
|
||||||
defer syscall.Umask(oldMask)
|
defer syscall.Umask(oldMask)
|
||||||
if err := os.Chmod(c.slavePath, 0600); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := os.Chown(c.slavePath, uid, gid); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := label.SetFileLabel(c.slavePath, mountLabel); err != nil {
|
if err := label.SetFileLabel(c.slavePath, mountLabel); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dest := filepath.Join(rootfs, containerConsolePath)
|
dest := filepath.Join(rootfs, "/dev/console")
|
||||||
f, err := os.Create(dest)
|
f, err := os.Create(dest)
|
||||||
if err != nil && !os.IsExist(err) {
|
if err != nil && !os.IsExist(err) {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -147,7 +147,6 @@ func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, c
|
||||||
if cmd.SysProcAttr.Credential == nil {
|
if cmd.SysProcAttr.Credential == nil {
|
||||||
cmd.SysProcAttr.Credential = &syscall.Credential{}
|
cmd.SysProcAttr.Credential = &syscall.Credential{}
|
||||||
}
|
}
|
||||||
t = "_LIBCONTAINER_INITTYPE=userns"
|
|
||||||
}
|
}
|
||||||
cmd.Env = append(cmd.Env, t)
|
cmd.Env = append(cmd.Env, t)
|
||||||
cmd.SysProcAttr.Cloneflags = cloneFlags
|
cmd.SysProcAttr.Cloneflags = cloneFlags
|
||||||
|
|
|
@ -166,9 +166,7 @@ func (l *LinuxFactory) StartInitialization(pipefd uintptr) (err error) {
|
||||||
// ensure that any data sent from the parent is consumed so it doesn't
|
// ensure that any data sent from the parent is consumed so it doesn't
|
||||||
// receive ECONNRESET when the child writes to the pipe.
|
// receive ECONNRESET when the child writes to the pipe.
|
||||||
ioutil.ReadAll(pipe)
|
ioutil.ReadAll(pipe)
|
||||||
if err := json.NewEncoder(pipe).Encode(initError{
|
if err := json.NewEncoder(pipe).Encode(newSystemError(err)); err != nil {
|
||||||
Message: err.Error(),
|
|
||||||
}); err != nil {
|
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,8 @@ import (
|
||||||
type initType string
|
type initType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
initSetns initType = "setns"
|
initSetns initType = "setns"
|
||||||
initStandard initType = "standard"
|
initStandard initType = "standard"
|
||||||
initUserns initType = "userns"
|
|
||||||
initUsernsSetup initType = "userns_setup"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type pid struct {
|
type pid struct {
|
||||||
|
@ -67,14 +65,6 @@ func newContainerInit(t initType, pipe *os.File) (initer, error) {
|
||||||
return &linuxSetnsInit{
|
return &linuxSetnsInit{
|
||||||
config: config,
|
config: config,
|
||||||
}, nil
|
}, nil
|
||||||
case initUserns:
|
|
||||||
return &linuxUsernsInit{
|
|
||||||
config: config,
|
|
||||||
}, nil
|
|
||||||
case initUsernsSetup:
|
|
||||||
return &linuxUsernsSideCar{
|
|
||||||
config: config,
|
|
||||||
}, nil
|
|
||||||
case initStandard:
|
case initStandard:
|
||||||
return &linuxStandardInit{
|
return &linuxStandardInit{
|
||||||
config: config,
|
config: config,
|
||||||
|
|
|
@ -4,13 +4,11 @@ package libcontainer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/libcontainer/cgroups"
|
"github.com/docker/libcontainer/cgroups"
|
||||||
"github.com/docker/libcontainer/system"
|
"github.com/docker/libcontainer/system"
|
||||||
)
|
)
|
||||||
|
@ -145,28 +143,12 @@ func (p *initProcess) start() error {
|
||||||
if err := p.createNetworkInterfaces(); err != nil {
|
if err := p.createNetworkInterfaces(); err != nil {
|
||||||
return newSystemError(err)
|
return newSystemError(err)
|
||||||
}
|
}
|
||||||
// Start the setup process to setup the init process
|
|
||||||
if p.cmd.SysProcAttr.Cloneflags&syscall.CLONE_NEWUSER != 0 {
|
|
||||||
parent, err := p.newUsernsSetupProcess()
|
|
||||||
if err != nil {
|
|
||||||
return newSystemError(err)
|
|
||||||
}
|
|
||||||
if err := parent.start(); err != nil {
|
|
||||||
if err := parent.terminate(); err != nil {
|
|
||||||
log.Warn(err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := parent.wait(); err != nil {
|
|
||||||
return newSystemError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := p.sendConfig(); err != nil {
|
if err := p.sendConfig(); err != nil {
|
||||||
return newSystemError(err)
|
return newSystemError(err)
|
||||||
}
|
}
|
||||||
// wait for the child process to fully complete and receive an error message
|
// wait for the child process to fully complete and receive an error message
|
||||||
// if one was encoutered
|
// if one was encoutered
|
||||||
var ierr *initError
|
var ierr *genericError
|
||||||
if err := json.NewDecoder(p.parentPipe).Decode(&ierr); err != nil && err != io.EOF {
|
if err := json.NewDecoder(p.parentPipe).Decode(&ierr); err != nil && err != io.EOF {
|
||||||
return newSystemError(err)
|
return newSystemError(err)
|
||||||
}
|
}
|
||||||
|
@ -229,26 +211,6 @@ func (p *initProcess) createNetworkInterfaces() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *initProcess) newUsernsSetupProcess() (parentProcess, error) {
|
|
||||||
parentPipe, childPipe, err := newPipe()
|
|
||||||
if err != nil {
|
|
||||||
return nil, newSystemError(err)
|
|
||||||
}
|
|
||||||
cmd := exec.Command(p.cmd.Args[0], p.cmd.Args[1:]...)
|
|
||||||
cmd.ExtraFiles = []*os.File{childPipe}
|
|
||||||
cmd.Dir = p.cmd.Dir
|
|
||||||
cmd.Env = append(cmd.Env,
|
|
||||||
fmt.Sprintf("_LIBCONTAINER_INITPID=%d", p.pid()),
|
|
||||||
fmt.Sprintf("_LIBCONTAINER_INITTYPE=userns_setup"),
|
|
||||||
)
|
|
||||||
return &setnsProcess{
|
|
||||||
cmd: cmd,
|
|
||||||
childPipe: childPipe,
|
|
||||||
parentPipe: parentPipe,
|
|
||||||
config: p.config,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *initProcess) signal(s os.Signal) error {
|
func (p *initProcess) signal(s os.Signal) error {
|
||||||
return p.cmd.Process.Signal(s)
|
return p.cmd.Process.Signal(s)
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ var baseMounts = []*configs.Mount{
|
||||||
Destination: "/dev/pts",
|
Destination: "/dev/pts",
|
||||||
Device: "devpts",
|
Device: "devpts",
|
||||||
Flags: syscall.MS_NOSUID | syscall.MS_NOEXEC,
|
Flags: syscall.MS_NOSUID | syscall.MS_NOEXEC,
|
||||||
Data: "newinstance,ptmxmode=0666,mode=620,gid=5",
|
Data: "newinstance,ptmxmode=0666,mode=0620,gid=5",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,32 +43,29 @@ var baseMounts = []*configs.Mount{
|
||||||
// new mount namespace.
|
// new mount namespace.
|
||||||
func setupRootfs(config *configs.Config) (err error) {
|
func setupRootfs(config *configs.Config) (err error) {
|
||||||
if err := prepareRoot(config); err != nil {
|
if err := prepareRoot(config); err != nil {
|
||||||
return err
|
return newSystemError(err)
|
||||||
}
|
}
|
||||||
for _, m := range append(baseMounts, config.Mounts...) {
|
for _, m := range append(baseMounts, config.Mounts...) {
|
||||||
if err := mount(m, config.Rootfs, config.MountLabel); err != nil {
|
if err := mount(m, config.Rootfs, config.MountLabel); err != nil {
|
||||||
return err
|
return newSystemError(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := createDevices(config); err != nil {
|
if err := createDevices(config); err != nil {
|
||||||
return err
|
return newSystemError(err)
|
||||||
}
|
}
|
||||||
if err := setupPtmx(config); err != nil {
|
if err := setupPtmx(config); err != nil {
|
||||||
return err
|
return newSystemError(err)
|
||||||
}
|
}
|
||||||
// stdin, stdout and stderr could be pointing to /dev/null from parent namespace.
|
// stdin, stdout and stderr could be pointing to /dev/null from parent namespace.
|
||||||
// Re-open them inside this namespace.
|
// re-open them inside this namespace.
|
||||||
// FIXME: Need to fix this for user namespaces.
|
if err := reOpenDevNull(config.Rootfs); err != nil {
|
||||||
if !config.Namespaces.Contains(configs.NEWUSER) {
|
return newSystemError(err)
|
||||||
if err := reOpenDevNull(config.Rootfs); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err := setupDevSymlinks(config.Rootfs); err != nil {
|
if err := setupDevSymlinks(config.Rootfs); err != nil {
|
||||||
return err
|
return newSystemError(err)
|
||||||
}
|
}
|
||||||
if err := syscall.Chdir(config.Rootfs); err != nil {
|
if err := syscall.Chdir(config.Rootfs); err != nil {
|
||||||
return err
|
return newSystemError(err)
|
||||||
}
|
}
|
||||||
if config.NoPivotRoot {
|
if config.NoPivotRoot {
|
||||||
err = msMoveRoot(config.Rootfs)
|
err = msMoveRoot(config.Rootfs)
|
||||||
|
@ -76,11 +73,11 @@ func setupRootfs(config *configs.Config) (err error) {
|
||||||
err = pivotRoot(config.Rootfs, config.PivotDir)
|
err = pivotRoot(config.Rootfs, config.PivotDir)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return newSystemError(err)
|
||||||
}
|
}
|
||||||
if config.Readonlyfs {
|
if config.Readonlyfs {
|
||||||
if err := setReadonly(); err != nil {
|
if err := setReadonly(); err != nil {
|
||||||
return err
|
return newSystemError(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
syscall.Umask(0022)
|
syscall.Umask(0022)
|
||||||
|
@ -202,13 +199,32 @@ func createDevices(config *configs.Config) error {
|
||||||
|
|
||||||
// Creates the device node in the rootfs of the container.
|
// Creates the device node in the rootfs of the container.
|
||||||
func createDeviceNode(rootfs string, node *configs.Device) error {
|
func createDeviceNode(rootfs string, node *configs.Device) error {
|
||||||
var (
|
dest := filepath.Join(rootfs, node.Path)
|
||||||
dest = filepath.Join(rootfs, node.Path)
|
if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil {
|
||||||
parent = filepath.Dir(dest)
|
|
||||||
)
|
|
||||||
if err := os.MkdirAll(parent, 0755); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := mknodDevice(dest, node); err != nil {
|
||||||
|
if os.IsExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != syscall.EPERM {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// containers running in a user namespace are not allowed to mknod
|
||||||
|
// devices so we can just bind mount it from the host.
|
||||||
|
f, err := os.Create(dest)
|
||||||
|
if err != nil && !os.IsExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if f != nil {
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
return syscall.Mount(node.Path, dest, "bind", syscall.MS_BIND, "")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mknodDevice(dest string, node *configs.Device) error {
|
||||||
fileMode := node.FileMode
|
fileMode := node.FileMode
|
||||||
switch node.Type {
|
switch node.Type {
|
||||||
case 'c':
|
case 'c':
|
||||||
|
@ -218,13 +234,10 @@ func createDeviceNode(rootfs string, node *configs.Device) error {
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("%c is not a valid device type for device %s", node.Type, node.Path)
|
return fmt.Errorf("%c is not a valid device type for device %s", node.Type, node.Path)
|
||||||
}
|
}
|
||||||
if err := syscall.Mknod(dest, uint32(fileMode), node.Mkdev()); err != nil && !os.IsExist(err) {
|
if err := syscall.Mknod(dest, uint32(fileMode), node.Mkdev()); err != nil {
|
||||||
return fmt.Errorf("mknod %s %s", node.Path, err)
|
return err
|
||||||
}
|
}
|
||||||
if err := syscall.Chown(dest, int(node.Uid), int(node.Gid)); err != nil {
|
return syscall.Chown(dest, int(node.Uid), int(node.Gid))
|
||||||
return fmt.Errorf("chown %s to %d:%d", node.Path, node.Uid, node.Gid)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareRoot(config *configs.Config) error {
|
func prepareRoot(config *configs.Config) error {
|
||||||
|
@ -251,16 +264,8 @@ func setupPtmx(config *configs.Config) error {
|
||||||
return fmt.Errorf("symlink dev ptmx %s", err)
|
return fmt.Errorf("symlink dev ptmx %s", err)
|
||||||
}
|
}
|
||||||
if config.Console != "" {
|
if config.Console != "" {
|
||||||
uid, err := config.HostUID()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
gid, err := config.HostGID()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
console := newConsoleFromPath(config.Console)
|
console := newConsoleFromPath(config.Console)
|
||||||
return console.mount(config.Rootfs, config.MountLabel, uid, gid)
|
return console.mount(config.Rootfs, config.MountLabel, 0, 0)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,84 +0,0 @@
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package libcontainer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/docker/libcontainer/apparmor"
|
|
||||||
"github.com/docker/libcontainer/label"
|
|
||||||
"github.com/docker/libcontainer/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
type linuxUsernsInit struct {
|
|
||||||
config *initConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *linuxUsernsInit) Init() error {
|
|
||||||
// join any namespaces via a path to the namespace fd if provided
|
|
||||||
if err := joinExistingNamespaces(l.config.Config.Namespaces); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
consolePath := l.config.Config.Console
|
|
||||||
if consolePath != "" {
|
|
||||||
// We use the containerConsolePath here, because the console has already been
|
|
||||||
// setup by the side car process for the user namespace scenario.
|
|
||||||
console := newConsoleFromPath(containerConsolePath)
|
|
||||||
if err := console.dupStdio(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err := syscall.Setsid(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if consolePath != "" {
|
|
||||||
if err := system.Setctty(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if l.config.Cwd == "" {
|
|
||||||
l.config.Cwd = "/"
|
|
||||||
}
|
|
||||||
if err := setupRlimits(l.config.Config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if hostname := l.config.Config.Hostname; hostname != "" {
|
|
||||||
if err := syscall.Sethostname([]byte(hostname)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := apparmor.ApplyProfile(l.config.Config.AppArmorProfile); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := label.SetProcessLabel(l.config.Config.ProcessLabel); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, path := range l.config.Config.ReadonlyPaths {
|
|
||||||
if err := remountReadonly(path); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, path := range l.config.Config.MaskPaths {
|
|
||||||
if err := maskFile(path); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pdeath, err := system.GetParentDeathSignal()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := finalizeNamespace(l.config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// finalizeNamespace can change user/group which clears the parent death
|
|
||||||
// signal, so we restore it here.
|
|
||||||
if err := pdeath.Restore(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Signal self if parent is already dead. Does nothing if running in a new
|
|
||||||
// PID namespace, as Getppid will always return 0.
|
|
||||||
if syscall.Getppid() == 1 {
|
|
||||||
return syscall.Kill(syscall.Getpid(), syscall.SIGKILL)
|
|
||||||
}
|
|
||||||
return system.Execv(l.config.Args[0], l.config.Args[0:], l.config.Env)
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package libcontainer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/docker/libcontainer/configs"
|
|
||||||
"github.com/docker/libcontainer/label"
|
|
||||||
)
|
|
||||||
|
|
||||||
// linuxUsernsSideCar is run to setup mounts and networking related operations
|
|
||||||
// for a user namespace enabled process as a user namespace root doesn't
|
|
||||||
// have permissions to perform these operations.
|
|
||||||
// The setup process joins all the namespaces of user namespace enabled init
|
|
||||||
// except the user namespace, so it run as root in the root user namespace
|
|
||||||
// to perform these operations.
|
|
||||||
type linuxUsernsSideCar struct {
|
|
||||||
config *initConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *linuxUsernsSideCar) Init() error {
|
|
||||||
if err := setupNetwork(l.config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := setupRoute(l.config.Config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
label.Init()
|
|
||||||
// InitializeMountNamespace() can be executed only for a new mount namespace
|
|
||||||
if l.config.Config.Namespaces.Contains(configs.NEWNS) {
|
|
||||||
if err := setupRootfs(l.config.Config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
108
nsinit/config.go
108
nsinit/config.go
|
@ -7,10 +7,13 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/docker/libcontainer/configs"
|
"github.com/docker/libcontainer/configs"
|
||||||
|
"github.com/docker/libcontainer/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
|
const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
|
||||||
|
@ -28,7 +31,18 @@ var createFlags = []cli.Flag{
|
||||||
cli.StringFlag{Name: "apparmor-profile", Usage: "set the apparmor profile"},
|
cli.StringFlag{Name: "apparmor-profile", Usage: "set the apparmor profile"},
|
||||||
cli.StringFlag{Name: "process-label", Usage: "set the process label"},
|
cli.StringFlag{Name: "process-label", Usage: "set the process label"},
|
||||||
cli.StringFlag{Name: "mount-label", Usage: "set the mount label"},
|
cli.StringFlag{Name: "mount-label", Usage: "set the mount label"},
|
||||||
|
cli.StringFlag{Name: "rootfs", Usage: "set the rootfs"},
|
||||||
cli.IntFlag{Name: "userns-root-uid", Usage: "set the user namespace root uid"},
|
cli.IntFlag{Name: "userns-root-uid", Usage: "set the user namespace root uid"},
|
||||||
|
cli.StringFlag{Name: "hostname", Value: "nsinit", Usage: "hostname value for the container"},
|
||||||
|
cli.StringFlag{Name: "net", Value: "", Usage: "network namespace"},
|
||||||
|
cli.StringFlag{Name: "ipc", Value: "", Usage: "ipc namespace"},
|
||||||
|
cli.StringFlag{Name: "pid", Value: "", Usage: "pid namespace"},
|
||||||
|
cli.StringFlag{Name: "uts", Value: "", Usage: "uts namespace"},
|
||||||
|
cli.StringFlag{Name: "mnt", Value: "", Usage: "mount namespace"},
|
||||||
|
cli.StringFlag{Name: "veth-bridge", Usage: "veth bridge"},
|
||||||
|
cli.StringFlag{Name: "veth-address", Usage: "veth ip address"},
|
||||||
|
cli.StringFlag{Name: "veth-gateway", Usage: "veth gateway address"},
|
||||||
|
cli.IntFlag{Name: "veth-mtu", Usage: "veth mtu"},
|
||||||
}
|
}
|
||||||
|
|
||||||
var configCommand = cli.Command{
|
var configCommand = cli.Command{
|
||||||
|
@ -72,10 +86,11 @@ func modify(config *configs.Config, context *cli.Context) {
|
||||||
config.AppArmorProfile = context.String("apparmor-profile")
|
config.AppArmorProfile = context.String("apparmor-profile")
|
||||||
config.ProcessLabel = context.String("process-label")
|
config.ProcessLabel = context.String("process-label")
|
||||||
config.MountLabel = context.String("mount-label")
|
config.MountLabel = context.String("mount-label")
|
||||||
|
config.Rootfs = context.String("rootfs")
|
||||||
|
|
||||||
userns_uid := context.Int("userns-root-uid")
|
userns_uid := context.Int("userns-root-uid")
|
||||||
if userns_uid != 0 {
|
if userns_uid != 0 {
|
||||||
config.Namespaces = append(config.Namespaces, configs.Namespace{Type: configs.NEWUSER})
|
config.Namespaces.Add(configs.NEWUSER, "")
|
||||||
config.UidMappings = []configs.IDMap{
|
config.UidMappings = []configs.IDMap{
|
||||||
{ContainerID: 0, HostID: userns_uid, Size: 1},
|
{ContainerID: 0, HostID: userns_uid, Size: 1},
|
||||||
{ContainerID: 1, HostID: 1, Size: userns_uid - 1},
|
{ContainerID: 1, HostID: 1, Size: userns_uid - 1},
|
||||||
|
@ -86,6 +101,87 @@ func modify(config *configs.Config, context *cli.Context) {
|
||||||
{ContainerID: 1, HostID: 1, Size: userns_uid - 1},
|
{ContainerID: 1, HostID: 1, Size: userns_uid - 1},
|
||||||
{ContainerID: userns_uid + 1, HostID: userns_uid + 1, Size: math.MaxInt32 - userns_uid},
|
{ContainerID: userns_uid + 1, HostID: userns_uid + 1, Size: math.MaxInt32 - userns_uid},
|
||||||
}
|
}
|
||||||
|
for _, node := range config.Devices {
|
||||||
|
node.Uid = uint32(userns_uid)
|
||||||
|
node.Gid = uint32(userns_uid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, rawBind := range context.StringSlice("bind") {
|
||||||
|
mount := &configs.Mount{
|
||||||
|
Device: "bind",
|
||||||
|
Flags: syscall.MS_BIND | syscall.MS_REC,
|
||||||
|
}
|
||||||
|
parts := strings.SplitN(rawBind, ":", 3)
|
||||||
|
switch len(parts) {
|
||||||
|
default:
|
||||||
|
logrus.Fatalf("invalid bind mount %s", rawBind)
|
||||||
|
case 2:
|
||||||
|
mount.Source, mount.Destination = parts[0], parts[1]
|
||||||
|
case 3:
|
||||||
|
mount.Source, mount.Destination = parts[0], parts[1]
|
||||||
|
switch parts[2] {
|
||||||
|
case "ro":
|
||||||
|
mount.Flags |= syscall.MS_RDONLY
|
||||||
|
case "rw":
|
||||||
|
default:
|
||||||
|
logrus.Fatalf("invalid bind mount mode %s", parts[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.Mounts = append(config.Mounts, mount)
|
||||||
|
}
|
||||||
|
for _, tmpfs := range context.StringSlice("tmpfs") {
|
||||||
|
config.Mounts = append(config.Mounts, &configs.Mount{
|
||||||
|
Device: "tmpfs",
|
||||||
|
Destination: tmpfs,
|
||||||
|
Flags: syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for flag, value := range map[string]configs.NamespaceType{
|
||||||
|
"net": configs.NEWNET,
|
||||||
|
"mnt": configs.NEWNS,
|
||||||
|
"pid": configs.NEWPID,
|
||||||
|
"ipc": configs.NEWIPC,
|
||||||
|
"uts": configs.NEWUTS,
|
||||||
|
} {
|
||||||
|
switch v := context.String(flag); v {
|
||||||
|
case "host":
|
||||||
|
config.Namespaces.Remove(value)
|
||||||
|
case "", "private":
|
||||||
|
if !config.Namespaces.Contains(value) {
|
||||||
|
config.Namespaces.Add(value, "")
|
||||||
|
}
|
||||||
|
if flag == "net" {
|
||||||
|
config.Networks = []*configs.Network{
|
||||||
|
{
|
||||||
|
Type: "loopback",
|
||||||
|
Address: "127.0.0.1/0",
|
||||||
|
Gateway: "localhost",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if flag == "uts" {
|
||||||
|
config.Hostname = context.String("hostname")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
config.Namespaces.Remove(value)
|
||||||
|
config.Namespaces.Add(value, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if bridge := context.String("veth-bridge"); bridge != "" {
|
||||||
|
hostName, err := utils.GenerateRandomName("veth", 7)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
network := &configs.Network{
|
||||||
|
Type: "veth",
|
||||||
|
Name: "eth0",
|
||||||
|
Bridge: bridge,
|
||||||
|
Address: context.String("veth-address"),
|
||||||
|
Gateway: context.String("veth-gateway"),
|
||||||
|
Mtu: context.Int("veth-mtu"),
|
||||||
|
HostInterfaceName: hostName,
|
||||||
|
}
|
||||||
|
config.Networks = append(config.Networks, network)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,8 +222,7 @@ func getTemplate() *configs.Config {
|
||||||
AllowAllDevices: false,
|
AllowAllDevices: false,
|
||||||
AllowedDevices: configs.DefaultAllowedDevices,
|
AllowedDevices: configs.DefaultAllowedDevices,
|
||||||
},
|
},
|
||||||
Devices: configs.DefaultAutoCreatedDevices,
|
Devices: configs.DefaultAutoCreatedDevices,
|
||||||
Hostname: "nsinit",
|
|
||||||
MaskPaths: []string{
|
MaskPaths: []string{
|
||||||
"/proc/kcore",
|
"/proc/kcore",
|
||||||
},
|
},
|
||||||
|
@ -155,13 +250,6 @@ func getTemplate() *configs.Config {
|
||||||
Flags: defaultMountFlags | syscall.MS_RDONLY,
|
Flags: defaultMountFlags | syscall.MS_RDONLY,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Networks: []*configs.Network{
|
|
||||||
{
|
|
||||||
Type: "loopback",
|
|
||||||
Address: "127.0.0.1/0",
|
|
||||||
Gateway: "localhost",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Rlimits: []configs.Rlimit{
|
Rlimits: []configs.Rlimit{
|
||||||
{
|
{
|
||||||
Type: syscall.RLIMIT_NOFILE,
|
Type: syscall.RLIMIT_NOFILE,
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/docker/libcontainer"
|
"github.com/docker/libcontainer"
|
||||||
"github.com/docker/libcontainer/utils"
|
"github.com/docker/libcontainer/utils"
|
||||||
|
@ -27,33 +26,34 @@ var execCommand = cli.Command{
|
||||||
cli.StringFlag{Name: "config", Value: "container.json", Usage: "path to the configuration file"},
|
cli.StringFlag{Name: "config", Value: "container.json", Usage: "path to the configuration file"},
|
||||||
cli.BoolFlag{Name: "create", Usage: "create the container's configuration on the fly with arguments"},
|
cli.BoolFlag{Name: "create", Usage: "create the container's configuration on the fly with arguments"},
|
||||||
cli.StringFlag{Name: "user,u", Value: "root", Usage: "set the user, uid, and/or gid for the process"},
|
cli.StringFlag{Name: "user,u", Value: "root", Usage: "set the user, uid, and/or gid for the process"},
|
||||||
|
cli.StringFlag{Name: "cwd", Value: "", Usage: "set the current working dir"},
|
||||||
cli.StringSliceFlag{Name: "env", Value: standardEnvironment, Usage: "set environment variables for the process"},
|
cli.StringSliceFlag{Name: "env", Value: standardEnvironment, Usage: "set environment variables for the process"},
|
||||||
}, createFlags...),
|
}, createFlags...),
|
||||||
}
|
}
|
||||||
|
|
||||||
func execAction(context *cli.Context) {
|
func execAction(context *cli.Context) {
|
||||||
entry := log.WithField("parent", "nsinit")
|
|
||||||
factory, err := loadFactory(context)
|
factory, err := loadFactory(context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatal(err)
|
fatal(err)
|
||||||
}
|
}
|
||||||
tty, err := newTty(context)
|
config, err := loadConfig(context)
|
||||||
|
if err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
rootuid, err := config.HostUID()
|
||||||
|
if err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
tty, err := newTty(context, rootuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatal(err)
|
fatal(err)
|
||||||
}
|
}
|
||||||
created := false
|
created := false
|
||||||
container, err := factory.Load(context.String("id"))
|
container, err := factory.Load(context.String("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
entry.Debug("creating container")
|
|
||||||
config, err := loadConfig(context)
|
|
||||||
if err != nil {
|
|
||||||
tty.Close()
|
|
||||||
fatal(err)
|
|
||||||
}
|
|
||||||
if tty.console != nil {
|
if tty.console != nil {
|
||||||
config.Console = tty.console.Path()
|
config.Console = tty.console.Path()
|
||||||
}
|
}
|
||||||
|
|
||||||
created = true
|
created = true
|
||||||
if container, err = factory.Create(context.String("id"), config); err != nil {
|
if container, err = factory.Create(context.String("id"), config); err != nil {
|
||||||
tty.Close()
|
tty.Close()
|
||||||
|
@ -65,11 +65,14 @@ func execAction(context *cli.Context) {
|
||||||
Args: context.Args(),
|
Args: context.Args(),
|
||||||
Env: context.StringSlice("env"),
|
Env: context.StringSlice("env"),
|
||||||
User: context.String("user"),
|
User: context.String("user"),
|
||||||
|
Cwd: context.String("cwd"),
|
||||||
Stdin: os.Stdin,
|
Stdin: os.Stdin,
|
||||||
Stdout: os.Stdout,
|
Stdout: os.Stdout,
|
||||||
Stderr: os.Stderr,
|
Stderr: os.Stderr,
|
||||||
}
|
}
|
||||||
tty.attach(process)
|
if err := tty.attach(process); err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
pid, err := container.Start(process)
|
pid, err := container.Start(process)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tty.Close()
|
tty.Close()
|
||||||
|
|
|
@ -18,10 +18,10 @@ var initCommand = cli.Command{
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
factory, err := libcontainer.New("")
|
factory, err := libcontainer.New("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
fatal(err)
|
||||||
}
|
}
|
||||||
if err := factory.StartInitialization(3); err != nil {
|
if err := factory.StartInitialization(3); err != nil {
|
||||||
log.Fatal(err)
|
fatal(err)
|
||||||
}
|
}
|
||||||
panic("This line should never been executed")
|
panic("This line should never been executed")
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,21 +9,14 @@ import (
|
||||||
"github.com/docker/libcontainer"
|
"github.com/docker/libcontainer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newTty(context *cli.Context) (*tty, error) {
|
func newTty(context *cli.Context, rootuid int) (*tty, error) {
|
||||||
if context.Bool("tty") {
|
if context.Bool("tty") {
|
||||||
console, err := libcontainer.NewConsole()
|
console, err := libcontainer.NewConsole(rootuid, rootuid)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
go io.Copy(console, os.Stdin)
|
|
||||||
go io.Copy(os.Stdout, console)
|
|
||||||
state, err := term.SetRawTerminal(os.Stdin.Fd())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &tty{
|
return &tty{
|
||||||
console: console,
|
console: console,
|
||||||
state: state,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return &tty{}, nil
|
return &tty{}, nil
|
||||||
|
@ -44,12 +37,20 @@ func (t *tty) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tty) attach(process *libcontainer.Process) {
|
func (t *tty) attach(process *libcontainer.Process) error {
|
||||||
if t.console != nil {
|
if t.console != nil {
|
||||||
|
go io.Copy(t.console, os.Stdin)
|
||||||
|
go io.Copy(os.Stdout, t.console)
|
||||||
|
state, err := term.SetRawTerminal(os.Stdin.Fd())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.state = state
|
||||||
process.Stderr = nil
|
process.Stderr = nil
|
||||||
process.Stdout = nil
|
process.Stdout = nil
|
||||||
process.Stdin = nil
|
process.Stdin = nil
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tty) resize() error {
|
func (t *tty) resize() error {
|
||||||
|
|
Loading…
Reference in New Issue