github.com/schwarzm/garden-linux@v0.0.0-20150507151835-33bca2147c47/integration/bind_mount/bind_mount_test.go (about)

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