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 }