github.com/cloudfoundry-attic/garden-linux@v0.333.2-candidate/integration/bind_mount/bind_mount_test.go (about)

     1  package bind_mount_test
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  	"os"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"time"
    11  
    12  	"github.com/cloudfoundry-incubator/garden"
    13  	. "github.com/onsi/ginkgo"
    14  	. "github.com/onsi/gomega"
    15  	"github.com/onsi/gomega/gbytes"
    16  	"github.com/onsi/gomega/gexec"
    17  )
    18  
    19  var _ = Describe("A container", func() {
    20  	var (
    21  		container garden.Container
    22  
    23  		// container create parms
    24  		privilegedContainer bool
    25  		srcPath             string                 // bm: source
    26  		dstPath             string                 // bm: destination
    27  		bindMountMode       garden.BindMountMode   // bm: RO or RW
    28  		bindMountOrigin     garden.BindMountOrigin // bm: Container or Host
    29  
    30  		// pre-existing file for permissions testing
    31  		testFileName string
    32  	)
    33  
    34  	allBridges := func() []byte {
    35  		stdout := gbytes.NewBuffer()
    36  		cmd, err := gexec.Start(exec.Command("ip", "a"), stdout, GinkgoWriter)
    37  		Expect(err).ToNot(HaveOccurred())
    38  		cmd.Wait(time.Second * 5)
    39  
    40  		return stdout.Contents()
    41  	}
    42  
    43  	BeforeEach(func() {
    44  		privilegedContainer = false
    45  		container = nil
    46  		srcPath = ""
    47  		dstPath = ""
    48  		bindMountMode = garden.BindMountModeRO
    49  		bindMountOrigin = garden.BindMountOriginHost
    50  		testFileName = ""
    51  	})
    52  
    53  	JustBeforeEach(func() {
    54  		client = startGarden()
    55  
    56  		var err error
    57  		container, err = client.Create(
    58  			garden.ContainerSpec{
    59  				Privileged: privilegedContainer,
    60  				BindMounts: []garden.BindMount{garden.BindMount{
    61  					SrcPath: srcPath,
    62  					DstPath: dstPath,
    63  					Mode:    bindMountMode,
    64  					Origin:  bindMountOrigin,
    65  				}},
    66  				Network: fmt.Sprintf("10.0.%d.0/24", GinkgoParallelNode()),
    67  			})
    68  		Expect(err).NotTo(HaveOccurred())
    69  	})
    70  
    71  	AfterEach(func() {
    72  		if container != nil {
    73  			err := client.Destroy(container.Handle())
    74  			Expect(err).ToNot(HaveOccurred())
    75  		}
    76  
    77  		// sanity check that bridges were cleaned up
    78  		bridgePrefix := fmt.Sprintf("w%db-", GinkgoParallelNode())
    79  		Expect(allBridges()).ToNot(ContainSubstring(bridgePrefix))
    80  	})
    81  
    82  	Context("with a host origin bind-mount", func() {
    83  		BeforeEach(func() {
    84  			srcPath, testFileName = createTestHostDirAndTestFile()
    85  			bindMountOrigin = garden.BindMountOriginHost
    86  		})
    87  
    88  		AfterEach(func() {
    89  			err := os.RemoveAll(srcPath)
    90  			Expect(err).ToNot(HaveOccurred())
    91  		})
    92  
    93  		Context("which is read-only", func() {
    94  			BeforeEach(func() {
    95  				bindMountMode = garden.BindMountModeRO
    96  				dstPath = "/home/alice/readonly"
    97  			})
    98  
    99  			Context("and with privileged=true", func() {
   100  				BeforeEach(func() {
   101  					privilegedContainer = true
   102  				})
   103  
   104  				It("is successfully created with correct privileges for non-root in container", func() {
   105  					checkFileAccess(container, bindMountMode, bindMountOrigin, dstPath, testFileName, privilegedContainer, false)
   106  				})
   107  
   108  				It("is successfully created with correct privileges for root in container", func() {
   109  					checkFileAccess(container, bindMountMode, bindMountOrigin, dstPath, testFileName, privilegedContainer, true)
   110  				})
   111  			})
   112  
   113  			Context("and with privileged=false", func() {
   114  				BeforeEach(func() {
   115  					privilegedContainer = false
   116  				})
   117  
   118  				It("is successfully created with correct privileges for non-root in container", func() {
   119  					checkFileAccess(container, bindMountMode, bindMountOrigin, dstPath, testFileName, privilegedContainer, false)
   120  				})
   121  
   122  				It("is successfully created with correct privileges for root in container", func() {
   123  					checkFileAccess(container, bindMountMode, bindMountOrigin, dstPath, testFileName, privilegedContainer, true)
   124  				})
   125  
   126  				Context("and the parents of the dstPath don't yet exist", func() {
   127  					BeforeEach(func() {
   128  						dstPath = "/home/alice/has/a/restaurant/readonly"
   129  					})
   130  
   131  					It("successfully creates the parents of the dstPath with correct ownership for root in the container", func() {
   132  						out := gbytes.NewBuffer()
   133  						proc, err := container.Run(garden.ProcessSpec{
   134  							User: "root",
   135  							Path: "ls",
   136  							Args: []string{"-l", "/home/alice/has"},
   137  						}, garden.ProcessIO{
   138  							Stdout: io.MultiWriter(out, GinkgoWriter),
   139  							Stderr: GinkgoWriter,
   140  						})
   141  						Expect(err).NotTo(HaveOccurred())
   142  						Expect(proc.Wait()).To(Equal(0))
   143  						Expect(out).To(gbytes.Say(`root`))
   144  					})
   145  				})
   146  			})
   147  		})
   148  
   149  		Context("which is read-write", func() {
   150  			BeforeEach(func() {
   151  				bindMountMode = garden.BindMountModeRW
   152  				dstPath = "/home/alice/readwrite"
   153  			})
   154  
   155  			Context("and with privileged=true", func() {
   156  				BeforeEach(func() {
   157  					privilegedContainer = true
   158  				})
   159  
   160  				It("is successfully created with correct privileges for non-root in container", func() {
   161  					checkFileAccess(container, bindMountMode, bindMountOrigin, dstPath, testFileName, privilegedContainer, false)
   162  				})
   163  
   164  				It("is successfully created with correct privileges for root in container", func() {
   165  					checkFileAccess(container, bindMountMode, bindMountOrigin, dstPath, testFileName, privilegedContainer, true)
   166  				})
   167  			})
   168  
   169  			Context("and with privileged=false", func() {
   170  				BeforeEach(func() {
   171  					privilegedContainer = false
   172  				})
   173  
   174  				It("is successfully created with correct privileges for non-root in container", func() {
   175  					checkFileAccess(container, bindMountMode, bindMountOrigin, dstPath, testFileName, privilegedContainer, false)
   176  				})
   177  
   178  				It("is successfully created with correct privileges for root in container", func() {
   179  					checkFileAccess(container, bindMountMode, bindMountOrigin, dstPath, testFileName, privilegedContainer, true)
   180  				})
   181  			})
   182  		})
   183  	})
   184  
   185  	Context("with a container origin bind-mount", func() {
   186  		BeforeEach(func() {
   187  			srcPath = "/home/alice"
   188  			bindMountOrigin = garden.BindMountOriginContainer
   189  		})
   190  
   191  		JustBeforeEach(func() {
   192  			testFileName = createContainerTestFileIn(container, srcPath)
   193  		})
   194  
   195  		Context("which is read-only", func() {
   196  			BeforeEach(func() {
   197  				bindMountMode = garden.BindMountModeRO
   198  				dstPath = "/home/alice/readonly"
   199  			})
   200  
   201  			Context("and with privileged=true", func() {
   202  				BeforeEach(func() {
   203  					privilegedContainer = true
   204  				})
   205  
   206  				It("is successfully created with correct privileges for non-root in container", func() {
   207  					checkFileAccess(container, bindMountMode, bindMountOrigin, dstPath, testFileName, privilegedContainer, false)
   208  				})
   209  
   210  				It("is successfully created with correct privileges for root in container", func() {
   211  					checkFileAccess(container, bindMountMode, bindMountOrigin, dstPath, testFileName, privilegedContainer, true)
   212  				})
   213  			})
   214  
   215  			Context("and with privileged=false", func() {
   216  				BeforeEach(func() {
   217  					privilegedContainer = false
   218  				})
   219  
   220  				It("is successfully created with correct privileges for non-root in container", func() {
   221  					checkFileAccess(container, bindMountMode, bindMountOrigin, dstPath, testFileName, privilegedContainer, false)
   222  				})
   223  
   224  				It("is successfully created with correct privileges for root in container", func() {
   225  					checkFileAccess(container, bindMountMode, bindMountOrigin, dstPath, testFileName, privilegedContainer, true)
   226  				})
   227  			})
   228  
   229  		})
   230  
   231  		Context("which is read-write", func() {
   232  			BeforeEach(func() {
   233  				bindMountMode = garden.BindMountModeRW
   234  				dstPath = "/home/alice/readwrite"
   235  			})
   236  
   237  			Context("and with privileged=true", func() {
   238  				BeforeEach(func() {
   239  					privilegedContainer = true
   240  				})
   241  
   242  				It("is successfully created with correct privileges for non-root in container", func() {
   243  					checkFileAccess(container, bindMountMode, bindMountOrigin, dstPath, testFileName, privilegedContainer, false)
   244  				})
   245  
   246  				It("is successfully created with correct privileges for root in container", func() {
   247  					checkFileAccess(container, bindMountMode, bindMountOrigin, dstPath, testFileName, privilegedContainer, true)
   248  				})
   249  			})
   250  
   251  			Context("and with privileged=false", func() {
   252  				BeforeEach(func() {
   253  					privilegedContainer = false
   254  				})
   255  
   256  				It("is successfully created with correct privileges for non-root in container", func() {
   257  					checkFileAccess(container, bindMountMode, bindMountOrigin, dstPath, testFileName, privilegedContainer, false)
   258  				})
   259  
   260  				It("is successfully created with correct privileges for root in container", func() {
   261  					checkFileAccess(container, bindMountMode, bindMountOrigin, dstPath, testFileName, privilegedContainer, true)
   262  				})
   263  			})
   264  		})
   265  	})
   266  })
   267  
   268  func createTestHostDirAndTestFile() (string, string) {
   269  	tstHostDir, err := ioutil.TempDir("", "bind-mount-test-dir")
   270  	Expect(err).ToNot(HaveOccurred())
   271  	err = os.Chown(tstHostDir, 0, 0)
   272  	Expect(err).ToNot(HaveOccurred())
   273  	err = os.Chmod(tstHostDir, 0755)
   274  	Expect(err).ToNot(HaveOccurred())
   275  
   276  	fileName := fmt.Sprintf("bind-mount-%d-test-file", GinkgoParallelNode())
   277  	file, err := os.OpenFile(filepath.Join(tstHostDir, fileName), os.O_CREATE|os.O_RDWR, 0777)
   278  	Expect(err).ToNot(HaveOccurred())
   279  	Expect(file.Close()).ToNot(HaveOccurred())
   280  
   281  	return tstHostDir, fileName
   282  }
   283  
   284  func createContainerTestFileIn(container garden.Container, dir string) string {
   285  	fileName := "bind-mount-test-file"
   286  	filePath := filepath.Join(dir, fileName)
   287  
   288  	process, err := container.Run(garden.ProcessSpec{
   289  		Path: "touch",
   290  		Args: []string{filePath},
   291  		User: "root",
   292  	}, garden.ProcessIO{nil, os.Stdout, os.Stderr})
   293  	Expect(err).ToNot(HaveOccurred())
   294  	Expect(process.Wait()).To(Equal(0))
   295  
   296  	process, err = container.Run(garden.ProcessSpec{
   297  		Path: "chmod",
   298  		Args: []string{"0777", filePath},
   299  		User: "root",
   300  	}, garden.ProcessIO{nil, os.Stdout, os.Stderr})
   301  	Expect(err).ToNot(HaveOccurred())
   302  	Expect(process.Wait()).To(Equal(0))
   303  
   304  	return fileName
   305  }
   306  
   307  func checkFileAccess(container garden.Container, bindMountMode garden.BindMountMode, bindMountOrigin garden.BindMountOrigin, dstPath string, fileName string, privCtr, privReq bool) {
   308  	readOnly := (garden.BindMountModeRO == bindMountMode)
   309  	ctrOrigin := (garden.BindMountOriginContainer == bindMountOrigin)
   310  	realRoot := (privReq && privCtr)
   311  
   312  	// can we read a file?
   313  	filePath := filepath.Join(dstPath, fileName)
   314  
   315  	var user string
   316  	if privReq {
   317  		user = "root"
   318  	} else {
   319  		user = "alice"
   320  	}
   321  
   322  	process, err := container.Run(garden.ProcessSpec{
   323  		Path: "cat",
   324  		Args: []string{filePath},
   325  		User: user,
   326  	}, garden.ProcessIO{})
   327  	Expect(err).ToNot(HaveOccurred())
   328  
   329  	Expect(process.Wait()).To(Equal(0))
   330  
   331  	// try to write a new file
   332  	filePath = filepath.Join(dstPath, "checkFileAccess-file")
   333  
   334  	process, err = container.Run(garden.ProcessSpec{
   335  		Path: "touch",
   336  		Args: []string{filePath},
   337  		User: user,
   338  	}, garden.ProcessIO{
   339  		Stderr: GinkgoWriter,
   340  		Stdout: GinkgoWriter,
   341  	})
   342  	Expect(err).ToNot(HaveOccurred())
   343  
   344  	if readOnly || (!realRoot && !ctrOrigin) {
   345  		Expect(process.Wait()).ToNot(Equal(0))
   346  	} else {
   347  		Expect(process.Wait()).To(Equal(0))
   348  	}
   349  
   350  	// try to delete an existing file
   351  	filePath = filepath.Join(dstPath, fileName)
   352  
   353  	process, err = container.Run(garden.ProcessSpec{
   354  		Path: "rm",
   355  		Args: []string{filePath},
   356  		User: user,
   357  	}, garden.ProcessIO{})
   358  	Expect(err).ToNot(HaveOccurred())
   359  	if readOnly || (!realRoot && !ctrOrigin) {
   360  		Expect(process.Wait()).ToNot(Equal(0))
   361  	} else {
   362  		Expect(process.Wait()).To(Equal(0))
   363  	}
   364  }