github.com/rigado/snapd@v2.42.5-go-mod+incompatible/interfaces/mount/backend_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package mount_test
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  	"sort"
    28  	"strings"
    29  	"testing"
    30  
    31  	. "gopkg.in/check.v1"
    32  
    33  	"github.com/snapcore/snapd/dirs"
    34  	"github.com/snapcore/snapd/interfaces"
    35  	"github.com/snapcore/snapd/interfaces/ifacetest"
    36  	"github.com/snapcore/snapd/interfaces/mount"
    37  	"github.com/snapcore/snapd/osutil"
    38  	"github.com/snapcore/snapd/snap"
    39  	"github.com/snapcore/snapd/testutil"
    40  )
    41  
    42  func Test(t *testing.T) {
    43  	TestingT(t)
    44  }
    45  
    46  type backendSuite struct {
    47  	ifacetest.BackendSuite
    48  
    49  	iface2 *ifacetest.TestInterface
    50  }
    51  
    52  var _ = Suite(&backendSuite{})
    53  
    54  func (s *backendSuite) SetUpTest(c *C) {
    55  	s.Backend = &mount.Backend{}
    56  	s.BackendSuite.SetUpTest(c)
    57  
    58  	c.Assert(s.Repo.AddBackend(s.Backend), IsNil)
    59  
    60  	c.Assert(os.MkdirAll(dirs.SnapMountPolicyDir, 0700), IsNil)
    61  	c.Assert(os.MkdirAll(dirs.SnapRunNsDir, 0700), IsNil)
    62  
    63  	// add second iface so that we actually test combining snippets
    64  	s.iface2 = &ifacetest.TestInterface{InterfaceName: "iface2"}
    65  	c.Assert(s.Repo.AddInterface(s.iface2), IsNil)
    66  }
    67  
    68  func (s *backendSuite) TearDownTest(c *C) {
    69  	s.BackendSuite.TearDownTest(c)
    70  }
    71  
    72  func (s *backendSuite) TestName(c *C) {
    73  	c.Check(s.Backend.Name(), Equals, interfaces.SecurityMount)
    74  }
    75  
    76  func (s *backendSuite) TestRemove(c *C) {
    77  	appCanaryToGo := filepath.Join(dirs.SnapMountPolicyDir, "snap.hello-world.hello-world.fstab")
    78  	err := ioutil.WriteFile(appCanaryToGo, []byte("ni! ni! ni!"), 0644)
    79  	c.Assert(err, IsNil)
    80  
    81  	hookCanaryToGo := filepath.Join(dirs.SnapMountPolicyDir, "snap.hello-world.hook.configure.fstab")
    82  	err = ioutil.WriteFile(hookCanaryToGo, []byte("ni! ni! ni!"), 0644)
    83  	c.Assert(err, IsNil)
    84  
    85  	snapCanaryToGo := filepath.Join(dirs.SnapMountPolicyDir, "snap.hello-world.fstab")
    86  	err = ioutil.WriteFile(snapCanaryToGo, []byte("ni! ni! ni!"), 0644)
    87  	c.Assert(err, IsNil)
    88  
    89  	appCanaryToStay := filepath.Join(dirs.SnapMountPolicyDir, "snap.i-stay.really.fstab")
    90  	err = ioutil.WriteFile(appCanaryToStay, []byte("stay!"), 0644)
    91  	c.Assert(err, IsNil)
    92  
    93  	snapCanaryToStay := filepath.Join(dirs.SnapMountPolicyDir, "snap.i-stay.fstab")
    94  	err = ioutil.WriteFile(snapCanaryToStay, []byte("stay!"), 0644)
    95  	c.Assert(err, IsNil)
    96  
    97  	// Write the .mnt file, the logic for discarding mount namespaces uses it
    98  	// as a canary file to look for to even attempt to run the mount discard
    99  	// tool.
   100  	mntFile := filepath.Join(dirs.SnapRunNsDir, "hello-world.mnt")
   101  	err = ioutil.WriteFile(mntFile, []byte(""), 0644)
   102  	c.Assert(err, IsNil)
   103  
   104  	// Mock snap-discard-ns and allow tweak distro libexec dir so that it is used.
   105  	cmd := testutil.MockCommand(c, "snap-discard-ns", "")
   106  	defer cmd.Restore()
   107  	dirs.DistroLibExecDir = cmd.BinDir()
   108  
   109  	err = s.Backend.Remove("hello-world")
   110  	c.Assert(err, IsNil)
   111  
   112  	c.Assert(osutil.FileExists(snapCanaryToGo), Equals, false)
   113  	c.Assert(osutil.FileExists(appCanaryToGo), Equals, false)
   114  	c.Assert(osutil.FileExists(hookCanaryToGo), Equals, false)
   115  	c.Assert(appCanaryToStay, testutil.FileEquals, "stay!")
   116  	c.Assert(snapCanaryToStay, testutil.FileEquals, "stay!")
   117  	c.Assert(cmd.Calls(), DeepEquals, [][]string{{"snap-discard-ns", "hello-world"}})
   118  }
   119  
   120  var mockSnapYaml = `name: snap-name
   121  version: 1
   122  apps:
   123      app1:
   124      app2:
   125  hooks:
   126      configure:
   127          plugs: [iface-plug]
   128  plugs:
   129      iface-plug:
   130          interface: iface
   131  slots:
   132      iface-slot:
   133          interface: iface2
   134  `
   135  
   136  func (s *backendSuite) TestSetupSetsupSimple(c *C) {
   137  	fsEntry1 := osutil.MountEntry{Name: "/src-1", Dir: "/dst-1", Type: "none", Options: []string{"bind", "ro"}, DumpFrequency: 0, CheckPassNumber: 0}
   138  	fsEntry2 := osutil.MountEntry{Name: "/src-2", Dir: "/dst-2", Type: "none", Options: []string{"bind", "ro"}, DumpFrequency: 0, CheckPassNumber: 0}
   139  	fsEntry3 := osutil.MountEntry{Name: "/src-3", Dir: "/dst-3", Type: "none", Options: []string{"bind", "ro"}, DumpFrequency: 0, CheckPassNumber: 0}
   140  
   141  	// Give the plug a permanent effect
   142  	s.Iface.MountPermanentPlugCallback = func(spec *mount.Specification, plug *snap.PlugInfo) error {
   143  		if err := spec.AddMountEntry(fsEntry1); err != nil {
   144  			return err
   145  		}
   146  		return spec.AddUserMountEntry(fsEntry3)
   147  	}
   148  	// Give the slot a permanent effect
   149  	s.iface2.MountPermanentSlotCallback = func(spec *mount.Specification, slot *snap.SlotInfo) error {
   150  		return spec.AddMountEntry(fsEntry2)
   151  	}
   152  
   153  	// confinement options are irrelevant to this security backend
   154  	s.InstallSnap(c, interfaces.ConfinementOptions{}, "", mockSnapYaml, 0)
   155  
   156  	// ensure both security effects from iface/iface2 are combined
   157  	// (because mount profiles are global in the whole snap)
   158  	expected := strings.Split(fmt.Sprintf("%s\n%s\n", fsEntry1, fsEntry2), "\n")
   159  	sort.Strings(expected)
   160  	// and that we have the modern fstab file (global for snap)
   161  	fn := filepath.Join(dirs.SnapMountPolicyDir, "snap.snap-name.fstab")
   162  	content, err := ioutil.ReadFile(fn)
   163  	c.Assert(err, IsNil, Commentf("Expected mount profile for the whole snap"))
   164  	got := strings.Split(string(content), "\n")
   165  	sort.Strings(got)
   166  	c.Check(got, DeepEquals, expected)
   167  
   168  	// Check that the user-fstab file was written with the user mount
   169  	fn = filepath.Join(dirs.SnapMountPolicyDir, "snap.snap-name.user-fstab")
   170  	content, err = ioutil.ReadFile(fn)
   171  	c.Assert(err, IsNil, Commentf("Expected user mount profile for the whole snap"))
   172  	c.Check(string(content), Equals, fsEntry3.String()+"\n")
   173  }
   174  
   175  func (s *backendSuite) TestSetupSetsupWithoutDir(c *C) {
   176  	s.Iface.MountPermanentPlugCallback = func(spec *mount.Specification, plug *snap.PlugInfo) error {
   177  		return spec.AddMountEntry(osutil.MountEntry{})
   178  	}
   179  
   180  	// Ensure that backend.Setup() creates the required dir on demand
   181  	os.Remove(dirs.SnapMountPolicyDir)
   182  	s.InstallSnap(c, interfaces.ConfinementOptions{}, "", mockSnapYaml, 0)
   183  }
   184  
   185  func (s *backendSuite) TestParallelInstanceSetup(c *C) {
   186  	old := dirs.SnapDataDir
   187  	defer func() {
   188  		dirs.SnapDataDir = old
   189  	}()
   190  	dirs.SnapDataDir = "/var/snap"
   191  	snapEntry := osutil.MountEntry{Name: "/snap/snap-name_instance", Dir: "/snap/snap-name", Type: "none", Options: []string{"rbind", osutil.XSnapdOriginOvername()}}
   192  	dataEntry := osutil.MountEntry{Name: "/var/snap/snap-name_instance", Dir: "/var/snap/snap-name", Type: "none", Options: []string{"rbind", osutil.XSnapdOriginOvername()}}
   193  	fsEntry1 := osutil.MountEntry{Name: "/src-1", Dir: "/dst-1", Type: "none", Options: []string{"bind", "ro"}}
   194  	fsEntry2 := osutil.MountEntry{Name: "/src-2", Dir: "/dst-2", Type: "none", Options: []string{"bind", "ro"}}
   195  	userFsEntry := osutil.MountEntry{Name: "/src-3", Dir: "/dst-3", Type: "none", Options: []string{"bind", "ro"}}
   196  
   197  	// Give the plug a permanent effect
   198  	s.Iface.MountPermanentPlugCallback = func(spec *mount.Specification, plug *snap.PlugInfo) error {
   199  		if err := spec.AddMountEntry(fsEntry1); err != nil {
   200  			return err
   201  		}
   202  		return spec.AddUserMountEntry(userFsEntry)
   203  	}
   204  	// Give the slot a permanent effect
   205  	s.iface2.MountPermanentSlotCallback = func(spec *mount.Specification, slot *snap.SlotInfo) error {
   206  		return spec.AddMountEntry(fsEntry2)
   207  	}
   208  
   209  	// confinement options are irrelevant to this security backend
   210  	s.InstallSnap(c, interfaces.ConfinementOptions{}, "snap-name_instance", mockSnapYaml, 0)
   211  
   212  	// Check that snap fstab file contains parallel instance setup and data from interfaces
   213  	expected := strings.Join([]string{snapEntry.String(), dataEntry.String(), fsEntry2.String(), fsEntry1.String()}, "\n") + "\n"
   214  	fn := filepath.Join(dirs.SnapMountPolicyDir, "snap.snap-name_instance.fstab")
   215  	c.Check(fn, testutil.FileEquals, expected)
   216  
   217  	// Check that the user-fstab file was written with user mount only
   218  	fn = filepath.Join(dirs.SnapMountPolicyDir, "snap.snap-name_instance.user-fstab")
   219  	c.Check(fn, testutil.FileEquals, userFsEntry.String()+"\n")
   220  }
   221  
   222  func (s *backendSuite) TestSandboxFeatures(c *C) {
   223  	c.Assert(s.Backend.SandboxFeatures(), DeepEquals, []string{
   224  		"freezer-cgroup-v1",
   225  		"layouts",
   226  		"mount-namespace",
   227  		"per-snap-persistency",
   228  		"per-snap-profiles",
   229  		"per-snap-updates",
   230  		"per-snap-user-profiles",
   231  		"stale-base-invalidation",
   232  	})
   233  }