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>