1. 程式人生 > >Namespaces and Go Part 2

Namespaces and Go Part 2

In Part 1 of Namespace article series ,we were unable to set hostname from a shell even though the user was root
That program was missing UTS namespace which is responsible for isolating ‘hostname’

In this article we will use UTS namespace , but with a short hack

Go will not allow any changes after setting namespaces and before execution of a program
So here we have to spawn a new process in new namespace and that process can excute commands to set hostname

Picture explains far better than words , so below you can see the flow chart of the program

Now we will explain the the code (working code is at the end of this post)

‘main’ program will check if there is a “fork” in os.Args[0] (We discussed command line arguments in this article )
During first run of the program “fork” will not be set os.Args[0] always set to the executable’s name itself and program flow will change to function ‘setNameSpaces’

In ‘setNameSpaces’ function we will change os.Args[0] to “fork”.
Next we set needed namespace syscall.CLONE_NEWUTS using syscall.SysProcAttr with in the same function and call the process using exec.Run()

This time the flow will change to main again and condition “fork flag set ? ” will succeed ; this will invoke the function ‘startContainer’
‘startContainer’ will set new hostname using syscall.Sethostname and starts a shell using syscall.Exec.

Please note that syscall.Exec will never come back to main if it succeeds and returns -1 if there is any error (man 2 execvp)

Entire code is written below including comments and we will be using the same program to discuss remaining Namespaces .

package main

import (
        "fmt"
        "os"
        "os/exec"
        "syscall"
)

//Function to change Arg[0] to "fork" and set namespaces
//Finally call the binary itself  **
func setNameSpaces(shell string) {
        cmd := &exec.Cmd{
                Path: os.Args[0],
                Args: append([]string{"fork"}, shell),
        }
        cmd.Stdin = os.Stdin
        cmd.Stdout = os.Stdout
        cmd.Stderr = os.Stderr
        cmd.SysProcAttr = &syscall.SysProcAttr{
                Cloneflags: syscall.CLONE_NEWUSER |
                        syscall.CLONE_NEWUTS,
                UidMappings: []syscall.SysProcIDMap{
                        {
                                ContainerID: 0,
                                HostID:      os.Getuid(),
                                Size:        1,
                        },
                },
                GidMappings: []syscall.SysProcIDMap{
                        {
                                ContainerID: 0,
                                HostID:      os.Getgid(),
                                Size:        1,
                        },
                },
        }
        cmd.Run() // path=./executable , Args[0]=fork , Args[1]=bash
}

//Set new hostname in already initialized namespace and start a shell
func startContainer(shell string) {
        fmt.Println("Starting Container")
        //Set a new hostname for our container
        if err := syscall.Sethostname([]byte("container")); err != nil {
                fmt.Printf("Setting Hostname failed")
        }
        if err := syscall.Exec(shell, []string{""}, os.Environ()); err != nil {
                fmt.Println("Exec failed")
        }

}

func main() {
        // Get absolute path to bash
        shell, err := exec.LookPath("bash")
        if err != nil {
                fmt.Printf("Bash not found\n")
                os.Exit(1)
        }
        //This condition will fail first time as the Args[0] will be the name of program
        //But this condition will become true when ,
        //this program itslef calls with Arg[0] = "fork" from startProc() **
        if os.Args[0] == "fork" {
                startContainer(shell)
                os.Exit(0)
        }
        //Starting point
        setNameSpaces(shell)
}

Save the code as uts_demo.go and build it

[email protected]:~/.../uts_demo> go build uts_demo.go
[email protected]:~/.../uts_demo> ./uts_demo
Starting Container
container:~/.../uts_demo #
container:~/.../uts_demo # hostname
container
container:~/.../uts_demo # id
uid=0(root) gid=0(root) groups=0(root)
container:~/.../uts_demo # exit
exit
[email protected]:~/.../uts_demo>