gitee.com/mysnapcore/mysnapd@v0.1.0/interfaces/builtin/mount_control_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2021 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 builtin_test
    21  
    22  import (
    23  	"fmt"
    24  
    25  	. "gopkg.in/check.v1"
    26  
    27  	"gitee.com/mysnapcore/mysnapd/interfaces"
    28  	"gitee.com/mysnapcore/mysnapd/interfaces/apparmor"
    29  	"gitee.com/mysnapcore/mysnapd/interfaces/builtin"
    30  	"gitee.com/mysnapcore/mysnapd/interfaces/seccomp"
    31  	"gitee.com/mysnapcore/mysnapd/snap"
    32  	"gitee.com/mysnapcore/mysnapd/systemd"
    33  	"gitee.com/mysnapcore/mysnapd/testutil"
    34  )
    35  
    36  type MountControlInterfaceSuite struct {
    37  	testutil.BaseTest
    38  
    39  	iface    interfaces.Interface
    40  	slotInfo *snap.SlotInfo
    41  	slot     *interfaces.ConnectedSlot
    42  	plugInfo *snap.PlugInfo
    43  	plug     *interfaces.ConnectedPlug
    44  }
    45  
    46  var _ = Suite(&MountControlInterfaceSuite{
    47  	iface: builtin.MustInterface("mount-control"),
    48  })
    49  
    50  const mountControlConsumerYaml = `name: consumer
    51  version: 0
    52  plugs:
    53   mntctl:
    54    interface: mount-control
    55    mount:
    56    - what: /dev/sd*
    57      where: /media/**
    58      type: [ext2, ext3, ext4]
    59      options: [rw, sync]
    60    - what: /usr/**
    61      where: $SNAP_COMMON/**
    62      options: [bind]
    63    - what: /dev/sda{0,1}
    64      where: $SNAP_COMMON/**
    65      options: [ro]
    66    - what: /dev/sda[0-1]
    67      where: $SNAP_COMMON/{foo,other,**}
    68      type: [mycustomfs]
    69      options: [sync]
    70  apps:
    71   app:
    72    plugs: [mntctl]
    73  `
    74  
    75  const mountControlCoreYaml = `name: core
    76  version: 0
    77  type: os
    78  slots:
    79    mount-control:
    80  `
    81  
    82  func (s *MountControlInterfaceSuite) SetUpTest(c *C) {
    83  	s.BaseTest.SetUpTest(c)
    84  
    85  	s.plug, s.plugInfo = MockConnectedPlug(c, mountControlConsumerYaml, nil, "mntctl")
    86  	s.slot, s.slotInfo = MockConnectedSlot(c, mountControlCoreYaml, nil, "mount-control")
    87  
    88  	s.AddCleanup(systemd.MockSystemdVersion(210, nil))
    89  }
    90  
    91  func (s *MountControlInterfaceSuite) TestName(c *C) {
    92  	c.Assert(s.iface.Name(), Equals, "mount-control")
    93  }
    94  
    95  func (s *MountControlInterfaceSuite) TestSanitizeSlot(c *C) {
    96  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil)
    97  }
    98  
    99  func (s *MountControlInterfaceSuite) TestSanitizePlug(c *C) {
   100  	c.Check(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil)
   101  	c.Check(interfaces.BeforeConnectPlug(s.iface, s.plug), IsNil)
   102  }
   103  
   104  func (s *MountControlInterfaceSuite) TestSanitizePlugOldSystemd(c *C) {
   105  	restore := systemd.MockSystemdVersion(208, nil)
   106  	defer restore()
   107  	err := interfaces.BeforeConnectPlug(s.iface, s.plug)
   108  	c.Assert(err, ErrorMatches, `systemd version 208 is too old \(expected at least 209\)`)
   109  }
   110  
   111  func (s *MountControlInterfaceSuite) TestSanitizePlugUnhappy(c *C) {
   112  	var mountControlYaml = `name: consumer
   113  version: 0
   114  plugs:
   115   mntctl:
   116    interface: mount-control
   117    %s
   118  apps:
   119   app:
   120    plugs: [mntctl]
   121  `
   122  	data := []struct {
   123  		plugYaml      string
   124  		expectedError string
   125  	}{
   126  		{
   127  			"", // missing "mount" attribute
   128  			`mount-control "mount" attribute must be a list of dictionaries`,
   129  		},
   130  		{
   131  			"mount: a string",
   132  			`mount-control "mount" attribute must be a list of dictionaries`,
   133  		},
   134  		{
   135  			"mount: [this, is, a, list]",
   136  			`mount-control "mount" attribute must be a list of dictionaries`,
   137  		},
   138  		{
   139  			"mount:\n  - what: [this, is, a, list]\n    where: /media/**",
   140  			`mount-control "what" must be a string`,
   141  		},
   142  		{
   143  			"mount:\n  - what: /path/\n    where: [this, is, a, list]",
   144  			`mount-control "where" must be a string`,
   145  		},
   146  		{
   147  			"mount:\n  - what: /\n    where: /\n    persistent: string",
   148  			`mount-control "persistent" must be a boolean`,
   149  		},
   150  		{
   151  			"mount:\n  - what: /\n    where: /\n    type: string",
   152  			`mount-control "type" must be an array of strings.*`,
   153  		},
   154  		{
   155  			"mount:\n  - what: /\n    where: /\n    type: [true, false]",
   156  			`mount-control "type" element 1 not a string.*`,
   157  		},
   158  		{
   159  			"mount:\n  - what: /\n    where: /media/*\n    type: [auto)]",
   160  			`mount-control filesystem type invalid.*`,
   161  		},
   162  		{
   163  			"mount:\n  - what: /\n    where: /media/*\n    type: [upperCase]",
   164  			`mount-control filesystem type invalid.*`,
   165  		},
   166  		{
   167  			"mount:\n  - what: /\n    where: /media/*\n    type: [two words]",
   168  			`mount-control filesystem type invalid.*`,
   169  		},
   170  		{
   171  			"mount:\n  - what: /\n    where: /media/*\n",
   172  			`mount-control "options" cannot be empty`,
   173  		},
   174  		{
   175  			"mount:\n  - what: /\n    where: /\n    options: string",
   176  			`mount-control "options" must be an array of strings.*`,
   177  		},
   178  		{
   179  			"mount:\n  - what: /\n    where: /media/*\n    options: []",
   180  			`mount-control "options" cannot be empty`,
   181  		},
   182  		{
   183  			"mount:\n  - what: here\n    where: /mnt",
   184  			`mount-control "what" attribute is invalid: must start with / and not contain special characters`,
   185  		},
   186  		{
   187  			"mount:\n  - what: /double\"quote\n    where: /mnt",
   188  			`mount-control "what" attribute is invalid: must start with / and not contain special characters`,
   189  		},
   190  		{
   191  			"mount:\n  - what: /variables/are/not/@{allowed}\n    where: /mnt",
   192  			`mount-control "what" attribute is invalid: must start with / and not contain special characters`,
   193  		},
   194  		{
   195  			"mount:\n  - what: /invalid}pattern\n    where: /mnt",
   196  			`mount-control "what" setting cannot be used: invalid closing brace, no matching open.*`,
   197  		},
   198  		{
   199  			"mount:\n  - what: /\n    where: /\n    options: [ro]",
   200  			`mount-control "where" attribute must start with \$SNAP_COMMON, \$SNAP_DATA or / and not contain special characters`,
   201  		},
   202  		{
   203  			"mount:\n  - what: /\n    where: /media/no\"quotes",
   204  			`mount-control "where" attribute must start with \$SNAP_COMMON, \$SNAP_DATA or / and not contain special characters`,
   205  		},
   206  		{
   207  			"mount:\n  - what: /\n    where: /media/no@{variables}",
   208  			`mount-control "where" attribute must start with \$SNAP_COMMON, \$SNAP_DATA or / and not contain special characters`,
   209  		},
   210  		{
   211  			"mount:\n  - what: /\n    where: $SNAP_DATA/$SNAP_DATA",
   212  			`mount-control "where" attribute must start with \$SNAP_COMMON, \$SNAP_DATA or / and not contain special characters`,
   213  		},
   214  		{
   215  			"mount:\n  - what: /\n    where: /$SNAP_DATA",
   216  			`mount-control "where" attribute must start with \$SNAP_COMMON, \$SNAP_DATA or / and not contain special characters`,
   217  		},
   218  		{
   219  			"mount:\n  - what: /\n    where: /media/invalid[path",
   220  			`mount-control "where" setting cannot be used: missing closing bracket ']'.*`,
   221  		},
   222  		{
   223  			"mount:\n  - what: /\n    where: /media/*\n    options: [sync,invalid]",
   224  			`mount-control option unrecognized or forbidden: "invalid"`,
   225  		},
   226  		{
   227  			"mount:\n  - what: /\n    where: /media/*\n    type: [ext4,debugfs]",
   228  			`mount-control forbidden filesystem type: "debugfs"`,
   229  		},
   230  		{
   231  			"mount:\n  - what: /\n    where: /media/*\n    type: [ext4]\n    options: [rw,bind]",
   232  			`mount-control option "bind" is incompatible with specifying filesystem type`,
   233  		},
   234  		{
   235  			"mount:\n  - what: /tmp/..\n    where: /media/*",
   236  			`mount-control "what" pattern is not clean:.*`,
   237  		},
   238  		{
   239  			"mount:\n  - what: /\n    where: /media/../etc",
   240  			`mount-control "where" pattern is not clean:.*`,
   241  		},
   242  		{
   243  			"mount:\n  - what: none\n    where: /media/*\n    options: [rw]",
   244  			`mount-control "what" attribute can be "none" only with "tmpfs"`,
   245  		},
   246  		{
   247  			"mount:\n  - what: none\n    where: /media/*\n    options: [rw]\n    type: [ext4,ntfs]",
   248  			`mount-control "what" attribute can be "none" only with "tmpfs"`,
   249  		},
   250  		{
   251  			"mount:\n  - what: none\n    where: /media/*\n    options: [rw]\n    type: [tmpfs,ext4]",
   252  			`mount-control filesystem type "tmpfs" cannot be listed with other types`,
   253  		},
   254  		{
   255  			"mount:\n  - what: /\n    where: /media/*\n    options: [rw]\n    type: [tmpfs]",
   256  			`mount-control "what" attribute must be "none" with "tmpfs"; found "/" instead`,
   257  		},
   258  		{
   259  			"mount:\n  - what: /\n    where: $SNAP_DATA/foo\n    options: [ro]\n    persistent: true",
   260  			`mount-control "persistent" attribute cannot be used to mount onto \$SNAP_DATA`,
   261  		},
   262  	}
   263  
   264  	for _, testData := range data {
   265  		snapYaml := fmt.Sprintf(mountControlYaml, testData.plugYaml)
   266  		plug, _ := MockConnectedPlug(c, snapYaml, nil, "mntctl")
   267  		err := interfaces.BeforeConnectPlug(s.iface, plug)
   268  		c.Check(err, ErrorMatches, testData.expectedError, Commentf("Yaml: %s", testData.plugYaml))
   269  	}
   270  }
   271  
   272  func (s *MountControlInterfaceSuite) TestSecCompSpec(c *C) {
   273  	spec := &seccomp.Specification{}
   274  	c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil)
   275  	c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"})
   276  	c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "mount\n")
   277  	c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "umount\n")
   278  	c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "umount2\n")
   279  }
   280  
   281  func (s *MountControlInterfaceSuite) TestAppArmorSpec(c *C) {
   282  	spec := &apparmor.Specification{}
   283  	c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil)
   284  	c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"})
   285  	c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `capability sys_admin,`)
   286  	c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `/{,usr/}bin/mount ixr,`)
   287  
   288  	expectedMountLine1 := `mount fstype=(ext2,ext3,ext4) options=(rw,sync) "/dev/sd*" -> "/media/**{,/}",`
   289  	c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, expectedMountLine1)
   290  
   291  	expectedMountLine2 := `mount  options=(bind) "/usr/**" -> "/var/snap/consumer/common/**{,/}",`
   292  	c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, expectedMountLine2)
   293  
   294  	expectedMountLine3 := `mount fstype=(` +
   295  		`aufs,autofs,btrfs,ext2,ext3,ext4,hfs,iso9660,jfs,msdos,ntfs,ramfs,` +
   296  		`reiserfs,squashfs,tmpfs,ubifs,udf,ufs,vfat,zfs,xfs` +
   297  		`) options=(ro) "/dev/sda{0,1}" -> "/var/snap/consumer/common/**{,/}",`
   298  	c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, expectedMountLine3)
   299  	expectedUmountLine3 := `umount "/var/snap/consumer/common/**{,/}",`
   300  	c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, expectedUmountLine3)
   301  
   302  	expectedMountLine4 := `mount fstype=(mycustomfs) options=(sync) ` +
   303  		`"/dev/sda[0-1]" -> "/var/snap/consumer/common/{foo,other,**}{,/}",`
   304  	c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, expectedMountLine4)
   305  	expectedUmountLine4 := `umount "/var/snap/consumer/common/{foo,other,**}{,/}",`
   306  	c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, expectedUmountLine4)
   307  }
   308  
   309  func (s *MountControlInterfaceSuite) TestStaticInfo(c *C) {
   310  	si := interfaces.StaticInfoOf(s.iface)
   311  	c.Assert(si.ImplicitOnCore, Equals, true)
   312  	c.Assert(si.ImplicitOnClassic, Equals, true)
   313  	c.Assert(si.Summary, Equals, `allows creating transient and persistent mounts`)
   314  	c.Assert(si.BaseDeclarationSlots, testutil.Contains, "mount-control")
   315  }
   316  
   317  func (s *MountControlInterfaceSuite) TestAutoConnect(c *C) {
   318  	c.Assert(s.iface.AutoConnect(s.plugInfo, s.slotInfo), Equals, true)
   319  }
   320  
   321  func (s *MountControlInterfaceSuite) TestInterfaces(c *C) {
   322  	c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface)
   323  }