Merge pull request #279 from crosbymichael/namespaces-join
Change namespaces config to include path for setns
This commit is contained in:
commit
6ffd59a784
12
config.go
12
config.go
|
@ -10,6 +10,13 @@ type MountConfig mount.MountConfig
|
|||
|
||||
type Network network.Network
|
||||
|
||||
// Namespace defines configuration for each namespace. It specifies an
|
||||
// alternate path that is able to be joined via setns.
|
||||
type Namespace struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path,omitempty"`
|
||||
}
|
||||
|
||||
// Config defines configuration options for executing a process inside a contained environment.
|
||||
type Config struct {
|
||||
// Mount specific options.
|
||||
|
@ -38,7 +45,7 @@ type Config struct {
|
|||
|
||||
// Namespaces specifies the container's namespaces that it should setup when cloning the init process
|
||||
// If a namespace is not provided that namespace is shared from the container's parent process
|
||||
Namespaces map[string]bool `json:"namespaces,omitempty"`
|
||||
Namespaces []Namespace `json:"namespaces,omitempty"`
|
||||
|
||||
// Capabilities specify the capabilities to keep when executing the process inside the container
|
||||
// All capbilities not specified will be dropped from the processes capability mask
|
||||
|
@ -47,9 +54,6 @@ type Config struct {
|
|||
// Networks specifies the container's network setup to be created
|
||||
Networks []*Network `json:"networks,omitempty"`
|
||||
|
||||
// Ipc specifies the container's ipc setup to be created
|
||||
IpcNsPath string `json:"ipc,omitempty"`
|
||||
|
||||
// Routes can be specified to create entries in the route table as the container is started
|
||||
Routes []*Route `json:"routes,omitempty"`
|
||||
|
||||
|
|
|
@ -64,12 +64,12 @@ func TestConfigJsonFormat(t *testing.T) {
|
|||
t.Fail()
|
||||
}
|
||||
|
||||
if !container.Namespaces["NEWNET"] {
|
||||
if getNamespaceIndex(container, "NEWNET") == -1 {
|
||||
t.Log("namespaces should contain NEWNET")
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if container.Namespaces["NEWUSER"] {
|
||||
if getNamespaceIndex(container, "NEWUSER") != -1 {
|
||||
t.Log("namespaces should not contain NEWUSER")
|
||||
t.Fail()
|
||||
}
|
||||
|
@ -158,3 +158,12 @@ func TestSelinuxLabels(t *testing.T) {
|
|||
t.Fatalf("expected mount label %q but received %q", label, container.MountConfig.MountLabel)
|
||||
}
|
||||
}
|
||||
|
||||
func getNamespaceIndex(config *Config, name string) int {
|
||||
for i, v := range config.Namespaces {
|
||||
if v.Name == name {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/libcontainer"
|
||||
)
|
||||
|
||||
func TestExecPS(t *testing.T) {
|
||||
|
@ -55,7 +57,6 @@ func TestIPCPrivate(t *testing.T) {
|
|||
}
|
||||
|
||||
config := newTemplateConfig(rootfs)
|
||||
config.Namespaces["NEWIPC"] = true
|
||||
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -87,7 +88,8 @@ func TestIPCHost(t *testing.T) {
|
|||
}
|
||||
|
||||
config := newTemplateConfig(rootfs)
|
||||
config.Namespaces["NEWIPC"] = false
|
||||
i := getNamespaceIndex(config, "NEWIPC")
|
||||
config.Namespaces = append(config.Namespaces[:i], config.Namespaces[i+1:]...)
|
||||
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -119,8 +121,8 @@ func TestIPCJoinPath(t *testing.T) {
|
|||
}
|
||||
|
||||
config := newTemplateConfig(rootfs)
|
||||
config.Namespaces["NEWIPC"] = false
|
||||
config.IpcNsPath = "/proc/1/ns/ipc"
|
||||
i := getNamespaceIndex(config, "NEWIPC")
|
||||
config.Namespaces[i].Path = "/proc/1/ns/ipc"
|
||||
|
||||
buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc")
|
||||
if err != nil {
|
||||
|
@ -148,8 +150,8 @@ func TestIPCBadPath(t *testing.T) {
|
|||
defer remove(rootfs)
|
||||
|
||||
config := newTemplateConfig(rootfs)
|
||||
config.Namespaces["NEWIPC"] = false
|
||||
config.IpcNsPath = "/proc/1/ns/ipcc"
|
||||
i := getNamespaceIndex(config, "NEWIPC")
|
||||
config.Namespaces[i].Path = "/proc/1/ns/ipcc"
|
||||
|
||||
_, _, err = runContainer(config, "", "true")
|
||||
if err == nil {
|
||||
|
@ -177,3 +179,12 @@ func TestRlimit(t *testing.T) {
|
|||
t.Fatalf("expected rlimit to be 1024, got %s", limit)
|
||||
}
|
||||
}
|
||||
|
||||
func getNamespaceIndex(config *libcontainer.Config, name string) int {
|
||||
for i, v := range config.Namespaces {
|
||||
if v.Name == name {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
|
|
@ -32,12 +32,12 @@ func newTemplateConfig(rootfs string) *libcontainer.Config {
|
|||
"KILL",
|
||||
"AUDIT_WRITE",
|
||||
},
|
||||
Namespaces: map[string]bool{
|
||||
"NEWNS": true,
|
||||
"NEWUTS": true,
|
||||
"NEWIPC": true,
|
||||
"NEWPID": true,
|
||||
"NEWNET": true,
|
||||
Namespaces: []libcontainer.Namespace{
|
||||
{Name: "NEWNS"},
|
||||
{Name: "NEWUTS"},
|
||||
{Name: "NEWIPC"},
|
||||
{Name: "NEWPID"},
|
||||
{Name: "NEWNET"},
|
||||
},
|
||||
Cgroups: &cgroups.Cgroup{
|
||||
Parent: "integration",
|
||||
|
|
29
ipc/ipc.go
29
ipc/ipc.go
|
@ -1,29 +0,0 @@
|
|||
package ipc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/libcontainer/system"
|
||||
)
|
||||
|
||||
// Join the IPC Namespace of specified ipc path if it exists.
|
||||
// If the path does not exist then you are not joining a container.
|
||||
func Initialize(nsPath string) error {
|
||||
if nsPath == "" {
|
||||
return nil
|
||||
}
|
||||
f, err := os.OpenFile(nsPath, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed get IPC namespace fd: %v", err)
|
||||
}
|
||||
|
||||
err = system.Setns(f.Fd(), syscall.CLONE_NEWIPC)
|
||||
f.Close()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to setns current IPC namespace: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -13,7 +13,6 @@ import (
|
|||
"github.com/docker/libcontainer"
|
||||
"github.com/docker/libcontainer/apparmor"
|
||||
"github.com/docker/libcontainer/console"
|
||||
"github.com/docker/libcontainer/ipc"
|
||||
"github.com/docker/libcontainer/label"
|
||||
"github.com/docker/libcontainer/mount"
|
||||
"github.com/docker/libcontainer/netlink"
|
||||
|
@ -65,7 +64,10 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, pip
|
|||
if err := json.NewDecoder(pipe).Decode(&networkState); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// join any namespaces via a path to the namespace fd if provided
|
||||
if err := joinExistingNamespaces(container.Namespaces); err != nil {
|
||||
return err
|
||||
}
|
||||
if consolePath != "" {
|
||||
if err := console.OpenAndDup(consolePath); err != nil {
|
||||
return err
|
||||
|
@ -79,9 +81,7 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, pip
|
|||
return fmt.Errorf("setctty %s", err)
|
||||
}
|
||||
}
|
||||
if err := ipc.Initialize(container.IpcNsPath); err != nil {
|
||||
return fmt.Errorf("setup IPC %s", err)
|
||||
}
|
||||
|
||||
if err := setupNetwork(container, networkState); err != nil {
|
||||
return fmt.Errorf("setup networking %s", err)
|
||||
}
|
||||
|
@ -308,3 +308,22 @@ func LoadContainerEnvironment(container *libcontainer.Config) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// joinExistingNamespaces gets all the namespace paths specified for the container and
|
||||
// does a setns on the namespace fd so that the current process joins the namespace.
|
||||
func joinExistingNamespaces(namespaces []libcontainer.Namespace) error {
|
||||
for _, ns := range namespaces {
|
||||
if ns.Path != "" {
|
||||
f, err := os.OpenFile(ns.Path, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = system.Setns(f.Fd(), uintptr(namespaceInfo[ns.Name]))
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
package namespaces
|
||||
|
||||
import "errors"
|
||||
|
||||
type (
|
||||
Namespace struct {
|
||||
Key string `json:"key,omitempty"`
|
||||
Value int `json:"value,omitempty"`
|
||||
File string `json:"file,omitempty"`
|
||||
}
|
||||
Namespaces []*Namespace
|
||||
)
|
||||
|
||||
// namespaceList is used to convert the libcontainer types
|
||||
// into the names of the files located in /proc/<pid>/ns/* for
|
||||
// each namespace
|
||||
var (
|
||||
namespaceList = Namespaces{}
|
||||
ErrUnkownNamespace = errors.New("Unknown namespace")
|
||||
ErrUnsupported = errors.New("Unsupported method")
|
||||
)
|
||||
|
||||
func (ns *Namespace) String() string {
|
||||
return ns.Key
|
||||
}
|
||||
|
||||
func GetNamespace(key string) *Namespace {
|
||||
for _, ns := range namespaceList {
|
||||
if ns.Key == key {
|
||||
cpy := *ns
|
||||
return &cpy
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Contains returns true if the specified Namespace is
|
||||
// in the slice
|
||||
func (n Namespaces) Contains(ns string) bool {
|
||||
return n.Get(ns) != nil
|
||||
}
|
||||
|
||||
func (n Namespaces) Get(ns string) *Namespace {
|
||||
for _, nsp := range n {
|
||||
if nsp != nil && nsp.Key == ns {
|
||||
return nsp
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
package namespaces
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func init() {
|
||||
namespaceList = Namespaces{
|
||||
{Key: "NEWNS", Value: syscall.CLONE_NEWNS, File: "mnt"},
|
||||
{Key: "NEWUTS", Value: syscall.CLONE_NEWUTS, File: "uts"},
|
||||
{Key: "NEWIPC", Value: syscall.CLONE_NEWIPC, File: "ipc"},
|
||||
{Key: "NEWUSER", Value: syscall.CLONE_NEWUSER, File: "user"},
|
||||
{Key: "NEWPID", Value: syscall.CLONE_NEWPID, File: "pid"},
|
||||
{Key: "NEWNET", Value: syscall.CLONE_NEWNET, File: "net"},
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
package namespaces
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNamespacesContains(t *testing.T) {
|
||||
ns := Namespaces{
|
||||
GetNamespace("NEWPID"),
|
||||
GetNamespace("NEWNS"),
|
||||
GetNamespace("NEWUTS"),
|
||||
}
|
||||
|
||||
if ns.Contains("NEWNET") {
|
||||
t.Fatal("namespaces should not contain NEWNET")
|
||||
}
|
||||
|
||||
if !ns.Contains("NEWPID") {
|
||||
t.Fatal("namespaces should contain NEWPID but does not")
|
||||
}
|
||||
|
||||
withNil := Namespaces{
|
||||
GetNamespace("UNDEFINED"), // this element will be nil
|
||||
GetNamespace("NEWPID"),
|
||||
}
|
||||
|
||||
if !withNil.Contains("NEWPID") {
|
||||
t.Fatal("namespaces should contain NEWPID but does not")
|
||||
}
|
||||
}
|
|
@ -5,6 +5,8 @@ package namespaces
|
|||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/libcontainer"
|
||||
)
|
||||
|
||||
type initError struct {
|
||||
|
@ -15,6 +17,15 @@ func (i initError) Error() string {
|
|||
return i.Message
|
||||
}
|
||||
|
||||
var namespaceInfo = map[string]int{
|
||||
"NEWNET": syscall.CLONE_NEWNET,
|
||||
"NEWNS": syscall.CLONE_NEWNS,
|
||||
"NEWUSER": syscall.CLONE_NEWUSER,
|
||||
"NEWIPC": syscall.CLONE_NEWIPC,
|
||||
"NEWUTS": syscall.CLONE_NEWUTS,
|
||||
"NEWPID": syscall.CLONE_NEWPID,
|
||||
}
|
||||
|
||||
// New returns a newly initialized Pipe for communication between processes
|
||||
func newInitPipe() (parent *os.File, child *os.File, err error) {
|
||||
fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
|
||||
|
@ -26,13 +37,9 @@ func newInitPipe() (parent *os.File, child *os.File, err error) {
|
|||
|
||||
// GetNamespaceFlags parses the container's Namespaces options to set the correct
|
||||
// flags on clone, unshare, and setns
|
||||
func GetNamespaceFlags(namespaces map[string]bool) (flag int) {
|
||||
for key, enabled := range namespaces {
|
||||
if enabled {
|
||||
if ns := GetNamespace(key); ns != nil {
|
||||
flag |= ns.Value
|
||||
}
|
||||
}
|
||||
func GetNamespaceFlags(namespaces []libcontainer.Namespace) (flag int) {
|
||||
for _, v := range namespaces {
|
||||
flag |= namespaceInfo[v.Name]
|
||||
}
|
||||
return flag
|
||||
}
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
// +build linux
|
||||
|
||||
package network
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/libcontainer/system"
|
||||
)
|
||||
|
||||
// crosbymichael: could make a network strategy that instead of returning veth pair names it returns a pid to an existing network namespace
|
||||
type NetNS struct {
|
||||
}
|
||||
|
||||
func (v *NetNS) Create(n *Network, nspid int, networkState *NetworkState) error {
|
||||
networkState.NsPath = n.NsPath
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *NetNS) Initialize(config *Network, networkState *NetworkState) error {
|
||||
if networkState.NsPath == "" {
|
||||
return fmt.Errorf("nspath does is not specified in NetworkState")
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(networkState.NsPath, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed get network namespace fd: %v", err)
|
||||
}
|
||||
|
||||
if err := system.Setns(f.Fd(), syscall.CLONE_NEWNET); err != nil {
|
||||
f.Close()
|
||||
return fmt.Errorf("failed to setns current network namespace: %v", err)
|
||||
}
|
||||
|
||||
f.Close()
|
||||
return nil
|
||||
}
|
|
@ -13,7 +13,6 @@ var (
|
|||
var strategies = map[string]NetworkStrategy{
|
||||
"veth": &Veth{},
|
||||
"loopback": &Loopback{},
|
||||
"netns": &NetNS{},
|
||||
}
|
||||
|
||||
// NetworkStrategy represents a specific network configuration for
|
||||
|
|
|
@ -8,9 +8,6 @@ type Network struct {
|
|||
// Type sets the networks type, commonly veth and loopback
|
||||
Type string `json:"type,omitempty"`
|
||||
|
||||
// Path to network namespace
|
||||
NsPath string `json:"ns_path,omitempty"`
|
||||
|
||||
// The bridge to use.
|
||||
Bridge string `json:"bridge,omitempty"`
|
||||
|
||||
|
@ -50,6 +47,4 @@ type NetworkState struct {
|
|||
VethHost string `json:"veth_host,omitempty"`
|
||||
// The name of the veth interface created inside the container for the child.
|
||||
VethChild string `json:"veth_child,omitempty"`
|
||||
// Net namespace path.
|
||||
NsPath string `json:"ns_path,omitempty"`
|
||||
}
|
||||
|
|
|
@ -176,13 +176,13 @@
|
|||
"TERM=xterm"
|
||||
],
|
||||
"hostname": "koye",
|
||||
"namespaces": {
|
||||
"NEWIPC": true,
|
||||
"NEWNET": true,
|
||||
"NEWNS": true,
|
||||
"NEWPID": true,
|
||||
"NEWUTS": true
|
||||
},
|
||||
"namespaces": [
|
||||
{"name":"NEWIPC"},
|
||||
{"name": "NEWNET"},
|
||||
{"name": "NEWNS"},
|
||||
{"name": "NEWPID"},
|
||||
{"name": "NEWUTS"}
|
||||
],
|
||||
"networks": [
|
||||
{
|
||||
"address": "127.0.0.1/0",
|
||||
|
|
|
@ -175,13 +175,13 @@
|
|||
"TERM=xterm"
|
||||
],
|
||||
"hostname": "koye",
|
||||
"namespaces": {
|
||||
"NEWIPC": true,
|
||||
"NEWNET": true,
|
||||
"NEWNS": true,
|
||||
"NEWPID": true,
|
||||
"NEWUTS": true
|
||||
},
|
||||
"namespaces": [
|
||||
{"name": "NEWIPC"},
|
||||
{"name": "NEWNET"},
|
||||
{"name": "NEWNS"},
|
||||
{"name": "NEWPID"},
|
||||
{"name": "NEWUTS"}
|
||||
],
|
||||
"networks": [
|
||||
{
|
||||
"address": "127.0.0.1/0",
|
||||
|
|
|
@ -181,13 +181,13 @@
|
|||
"TERM=xterm"
|
||||
],
|
||||
"hostname": "koye",
|
||||
"namespaces": {
|
||||
"NEWIPC": true,
|
||||
"NEWNET": true,
|
||||
"NEWNS": true,
|
||||
"NEWPID": true,
|
||||
"NEWUTS": true
|
||||
},
|
||||
"namespaces": [
|
||||
{"name": "NEWIPC"},
|
||||
{"name": "NEWNET"},
|
||||
{"name": "NEWNS"},
|
||||
{"name": "NEWPID"},
|
||||
{"name": "NEWUTS"}
|
||||
],
|
||||
"networks": [
|
||||
{
|
||||
"address": "127.0.0.1/0",
|
||||
|
|
|
@ -175,13 +175,13 @@
|
|||
"TERM=xterm"
|
||||
],
|
||||
"hostname": "koye",
|
||||
"namespaces": {
|
||||
"NEWIPC": true,
|
||||
"NEWNET": true,
|
||||
"NEWNS": true,
|
||||
"NEWPID": true,
|
||||
"NEWUTS": true
|
||||
},
|
||||
"namespaces": [
|
||||
{"name": "NEWIPC"},
|
||||
{"name": "NEWNET"},
|
||||
{"name": "NEWNS"},
|
||||
{"name": "NEWPID"},
|
||||
{"name": "NEWUTS"}
|
||||
],
|
||||
"networks": [
|
||||
{
|
||||
"address": "127.0.0.1/0",
|
||||
|
|
|
@ -177,13 +177,13 @@
|
|||
"TERM=xterm"
|
||||
],
|
||||
"hostname": "koye",
|
||||
"namespaces": {
|
||||
"NEWIPC": true,
|
||||
"NEWNET": true,
|
||||
"NEWNS": true,
|
||||
"NEWPID": true,
|
||||
"NEWUTS": true
|
||||
},
|
||||
"namespaces": [
|
||||
{"name": "NEWIPC"},
|
||||
{"name": "NEWNET"},
|
||||
{"name": "NEWNS"},
|
||||
{"name": "NEWPID"},
|
||||
{"name": "NEWUTS"}
|
||||
],
|
||||
"networks": [
|
||||
{
|
||||
"address": "127.0.0.1/0",
|
||||
|
|
Loading…
Reference in New Issue