Change namespaces config to include path for setns
This changes the namespace configuration on the config to include the name of the namespace along with an optional path. This path is used to point to a file of another namespace for the namespace so that it can be joined in place of the empty, initialized namespace. Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
parent
51aa43f44c
commit
549f508d5b
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,23 @@ 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 != "" {
|
||||
nsf := GetNamespace(ns.Name)
|
||||
f, err := os.OpenFile(ns.Path, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = system.Setns(f.Fd(), uintptr(nsf.Value))
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ package namespaces
|
|||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/libcontainer"
|
||||
)
|
||||
|
||||
type initError struct {
|
||||
|
@ -26,12 +28,10 @@ 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 {
|
||||
if ns := GetNamespace(v.Name); ns != nil {
|
||||
flag |= ns.Value
|
||||
}
|
||||
}
|
||||
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