gitee.com/mysnapcore/mysnapd@v0.1.0/interfaces/builtin/content_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 builtin_test 21 22 import ( 23 "path/filepath" 24 "strings" 25 26 . "gopkg.in/check.v1" 27 28 "gitee.com/mysnapcore/mysnapd/dirs" 29 "gitee.com/mysnapcore/mysnapd/interfaces" 30 "gitee.com/mysnapcore/mysnapd/interfaces/apparmor" 31 "gitee.com/mysnapcore/mysnapd/interfaces/builtin" 32 "gitee.com/mysnapcore/mysnapd/interfaces/mount" 33 "gitee.com/mysnapcore/mysnapd/osutil" 34 "gitee.com/mysnapcore/mysnapd/snap" 35 "gitee.com/mysnapcore/mysnapd/snap/snaptest" 36 "gitee.com/mysnapcore/mysnapd/testutil" 37 ) 38 39 type ContentSuite struct { 40 iface interfaces.Interface 41 } 42 43 var _ = Suite(&ContentSuite{ 44 iface: builtin.MustInterface("content"), 45 }) 46 47 func (s *ContentSuite) TestName(c *C) { 48 c.Assert(s.iface.Name(), Equals, "content") 49 } 50 51 func (s *ContentSuite) TestSanitizeSlotSimple(c *C) { 52 const mockSnapYaml = `name: content-slot-snap 53 version: 1.0 54 slots: 55 content-slot: 56 interface: content 57 content: mycont 58 read: 59 - shared/read 60 ` 61 info := snaptest.MockInfo(c, mockSnapYaml, nil) 62 slot := info.Slots["content-slot"] 63 c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), IsNil) 64 } 65 66 func (s *ContentSuite) TestSanitizeSlotContentLabelDefault(c *C) { 67 const mockSnapYaml = `name: content-slot-snap 68 version: 1.0 69 slots: 70 content-slot: 71 interface: content 72 read: 73 - shared/read 74 ` 75 info := snaptest.MockInfo(c, mockSnapYaml, nil) 76 slot := info.Slots["content-slot"] 77 c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), IsNil) 78 c.Assert(slot.Attrs["content"], Equals, slot.Name) 79 } 80 81 func (s *ContentSuite) TestSanitizeSlotNoPaths(c *C) { 82 const mockSnapYaml = `name: content-slot-snap 83 version: 1.0 84 slots: 85 content-slot: 86 interface: content 87 content: mycont 88 ` 89 info := snaptest.MockInfo(c, mockSnapYaml, nil) 90 slot := info.Slots["content-slot"] 91 c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "read or write path must be set") 92 } 93 94 func (s *ContentSuite) TestSanitizeSlotEmptyPaths(c *C) { 95 const mockSnapYaml = `name: content-slot-snap 96 version: 1.0 97 slots: 98 content-slot: 99 interface: content 100 content: mycont 101 read: [] 102 write: [] 103 ` 104 info := snaptest.MockInfo(c, mockSnapYaml, nil) 105 slot := info.Slots["content-slot"] 106 c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "read or write path must be set") 107 } 108 109 func (s *ContentSuite) TestSanitizeSlotHasRelativePath(c *C) { 110 const mockSnapYaml = `name: content-slot-snap 111 version: 1.0 112 slots: 113 content-slot: 114 interface: content 115 content: mycont 116 ` 117 for _, rw := range []string{"read: [../foo]", "write: [../bar]"} { 118 info := snaptest.MockInfo(c, mockSnapYaml+" "+rw, nil) 119 slot := info.Slots["content-slot"] 120 c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "content interface path is not clean:.*") 121 } 122 } 123 124 func (s *ContentSuite) TestSanitizeSlotSourceAndLegacy(c *C) { 125 slot := MockSlot(c, `name: snap 126 version: 0 127 slots: 128 content: 129 source: 130 write: [$SNAP_DATA/stuff] 131 read: [$SNAP/shared] 132 `, nil, "content") 133 c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, `move the "read" attribute into the "source" section`) 134 slot = MockSlot(c, `name: snap 135 version: 0 136 slots: 137 content: 138 source: 139 read: [$SNAP/shared] 140 write: [$SNAP_DATA/stuff] 141 `, nil, "content") 142 c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, `move the "write" attribute into the "source" section`) 143 } 144 145 func (s *ContentSuite) TestSanitizePlugSimple(c *C) { 146 const mockSnapYaml = `name: content-slot-snap 147 version: 1.0 148 plugs: 149 content-plug: 150 interface: content 151 content: mycont 152 target: import 153 ` 154 info := snaptest.MockInfo(c, mockSnapYaml, nil) 155 plug := info.Plugs["content-plug"] 156 c.Assert(interfaces.BeforePreparePlug(s.iface, plug), IsNil) 157 } 158 159 func (s *ContentSuite) TestSanitizePlugContentLabelDefault(c *C) { 160 const mockSnapYaml = `name: content-slot-snap 161 version: 1.0 162 plugs: 163 content-plug: 164 interface: content 165 target: import 166 ` 167 info := snaptest.MockInfo(c, mockSnapYaml, nil) 168 plug := info.Plugs["content-plug"] 169 c.Assert(interfaces.BeforePreparePlug(s.iface, plug), IsNil) 170 c.Assert(plug.Attrs["content"], Equals, plug.Name) 171 } 172 173 func (s *ContentSuite) TestSanitizePlugSimpleNoTarget(c *C) { 174 const mockSnapYaml = `name: content-slot-snap 175 version: 1.0 176 plugs: 177 content-plug: 178 interface: content 179 content: mycont 180 ` 181 info := snaptest.MockInfo(c, mockSnapYaml, nil) 182 plug := info.Plugs["content-plug"] 183 c.Assert(interfaces.BeforePreparePlug(s.iface, plug), ErrorMatches, "content plug must contain target path") 184 } 185 186 func (s *ContentSuite) TestSanitizePlugSimpleTargetRelative(c *C) { 187 const mockSnapYaml = `name: content-slot-snap 188 version: 1.0 189 plugs: 190 content-plug: 191 interface: content 192 content: mycont 193 target: ../foo 194 ` 195 info := snaptest.MockInfo(c, mockSnapYaml, nil) 196 plug := info.Plugs["content-plug"] 197 c.Assert(interfaces.BeforePreparePlug(s.iface, plug), ErrorMatches, "content interface path is not clean:.*") 198 } 199 200 func (s *ContentSuite) TestSanitizePlugApparmorInterpretedChar(c *C) { 201 const mockSnapYaml = `name: content-slot-snap 202 version: 1.0 203 plugs: 204 content-plug: 205 interface: content 206 content: mycont 207 target: foo"bar 208 ` 209 info := snaptest.MockInfo(c, mockSnapYaml, nil) 210 plug := info.Plugs["content-plug"] 211 c.Assert(interfaces.BeforePreparePlug(s.iface, plug), ErrorMatches, 212 `content interface path is invalid: "foo\\"bar" contains a reserved apparmor char.*`) 213 } 214 215 func (s *ContentSuite) TestSanitizePlugNilAttrMap(c *C) { 216 const mockSnapYaml = `name: content-slot-snap 217 version: 1.0 218 apps: 219 foo: 220 command: foo 221 plugs: [content] 222 ` 223 info := snaptest.MockInfo(c, mockSnapYaml, nil) 224 plug := info.Plugs["content"] 225 c.Assert(interfaces.BeforePreparePlug(s.iface, plug), ErrorMatches, "content plug must contain target path") 226 } 227 228 func (s *ContentSuite) TestSanitizeSlotNilAttrMap(c *C) { 229 const mockSnapYaml = `name: content-slot-snap 230 version: 1.0 231 apps: 232 foo: 233 command: foo 234 slots: [content] 235 ` 236 info := snaptest.MockInfo(c, mockSnapYaml, nil) 237 slot := info.Slots["content"] 238 c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "read or write path must be set") 239 } 240 241 func (s *ContentSuite) TestSanitizeSlotApparmorInterpretedChar(c *C) { 242 const mockSnapYaml = `name: content-slot-snap 243 version: 1.0 244 slots: 245 content-plug: 246 interface: content 247 source: 248 read: [$SNAP/shared] 249 write: ["$SNAP_DATA/foo}bar"] 250 ` 251 info := snaptest.MockInfo(c, mockSnapYaml, nil) 252 slot := info.Slots["content-plug"] 253 c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, 254 `content interface path is invalid: "\$SNAP_DATA/foo}bar" contains a reserved apparmor char.*`) 255 } 256 257 func (s *ContentSuite) TestResolveSpecialVariable(c *C) { 258 info := snaptest.MockInfo(c, "{name: name, version: 0}", &snap.SideInfo{Revision: snap.R(42)}) 259 c.Check(builtin.ResolveSpecialVariable("$SNAP/foo", info), Equals, filepath.Join(dirs.CoreSnapMountDir, "name/42/foo")) 260 c.Check(builtin.ResolveSpecialVariable("$SNAP_DATA/foo", info), Equals, "/var/snap/name/42/foo") 261 c.Check(builtin.ResolveSpecialVariable("$SNAP_COMMON/foo", info), Equals, "/var/snap/name/common/foo") 262 c.Check(builtin.ResolveSpecialVariable("$SNAP", info), Equals, filepath.Join(dirs.CoreSnapMountDir, "name/42")) 263 c.Check(builtin.ResolveSpecialVariable("$SNAP_DATA", info), Equals, "/var/snap/name/42") 264 c.Check(builtin.ResolveSpecialVariable("$SNAP_COMMON", info), Equals, "/var/snap/name/common") 265 c.Check(builtin.ResolveSpecialVariable("$SNAP_DATA/", info), Equals, "/var/snap/name/42/") 266 // automatically prefixed with $SNAP 267 c.Check(builtin.ResolveSpecialVariable("foo", info), Equals, filepath.Join(dirs.CoreSnapMountDir, "name/42/foo")) 268 c.Check(builtin.ResolveSpecialVariable("foo/snap/bar", info), Equals, "/snap/name/42/foo/snap/bar") 269 // contain invalid variables 270 c.Check(builtin.ResolveSpecialVariable("$PRUNE/bar", info), Equals, "/snap/name/42//bar") 271 c.Check(builtin.ResolveSpecialVariable("bar/$PRUNE/foo", info), Equals, "/snap/name/42/bar//foo") 272 } 273 274 // Check that legacy syntax works and allows sharing read-only snap content 275 func (s *ContentSuite) TestConnectedPlugSnippetSharingLegacy(c *C) { 276 const consumerYaml = `name: consumer 277 version: 0 278 plugs: 279 content: 280 target: import 281 ` 282 consumerInfo := snaptest.MockInfo(c, consumerYaml, &snap.SideInfo{Revision: snap.R(7)}) 283 plug := interfaces.NewConnectedPlug(consumerInfo.Plugs["content"], nil, nil) 284 const producerYaml = `name: producer 285 version: 0 286 slots: 287 content: 288 read: 289 - export 290 ` 291 producerInfo := snaptest.MockInfo(c, producerYaml, &snap.SideInfo{Revision: snap.R(5)}) 292 slot := interfaces.NewConnectedSlot(producerInfo.Slots["content"], nil, nil) 293 294 spec := &mount.Specification{} 295 c.Assert(spec.AddConnectedPlug(s.iface, plug, slot), IsNil) 296 expectedMnt := []osutil.MountEntry{{ 297 Name: filepath.Join(dirs.CoreSnapMountDir, "producer/5/export"), 298 Dir: filepath.Join(dirs.CoreSnapMountDir, "consumer/7/import"), 299 Options: []string{"bind", "ro"}, 300 }} 301 c.Assert(spec.MountEntries(), DeepEquals, expectedMnt) 302 } 303 304 // Check that sharing of read-only snap content is possible 305 func (s *ContentSuite) TestConnectedPlugSnippetSharingSnap(c *C) { 306 const consumerYaml = `name: consumer 307 version: 0 308 plugs: 309 content: 310 target: $SNAP/import 311 apps: 312 app: 313 command: foo 314 ` 315 consumerInfo := snaptest.MockInfo(c, consumerYaml, &snap.SideInfo{Revision: snap.R(7)}) 316 plug := interfaces.NewConnectedPlug(consumerInfo.Plugs["content"], nil, nil) 317 const producerYaml = `name: producer 318 version: 0 319 slots: 320 content: 321 read: 322 - $SNAP/export 323 ` 324 producerInfo := snaptest.MockInfo(c, producerYaml, &snap.SideInfo{Revision: snap.R(5)}) 325 slot := interfaces.NewConnectedSlot(producerInfo.Slots["content"], nil, nil) 326 327 spec := &mount.Specification{} 328 c.Assert(spec.AddConnectedPlug(s.iface, plug, slot), IsNil) 329 expectedMnt := []osutil.MountEntry{{ 330 Name: filepath.Join(dirs.CoreSnapMountDir, "producer/5/export"), 331 Dir: filepath.Join(dirs.CoreSnapMountDir, "consumer/7/import"), 332 Options: []string{"bind", "ro"}, 333 }} 334 c.Assert(spec.MountEntries(), DeepEquals, expectedMnt) 335 336 apparmorSpec := &apparmor.Specification{} 337 err := apparmorSpec.AddConnectedPlug(s.iface, plug, slot) 338 c.Assert(err, IsNil) 339 c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) 340 expected := ` 341 # In addition to the bind mount, add any AppArmor rules so that 342 # snaps may directly access the slot implementation's files 343 # read-only. 344 "/snap/producer/5/export/**" mrkix, 345 ` 346 c.Assert(apparmorSpec.SnippetForTag("snap.consumer.app"), Equals, expected) 347 348 updateNS := apparmorSpec.UpdateNS() 349 profile0 := ` # Read-only content sharing consumer:content -> producer:content (r#0) 350 mount options=(bind) "/snap/producer/5/export/" -> "/snap/consumer/7/import{,-[0-9]*}/", 351 remount options=(bind, ro) "/snap/consumer/7/import{,-[0-9]*}/", 352 mount options=(rprivate) -> "/snap/consumer/7/import{,-[0-9]*}/", 353 umount "/snap/consumer/7/import{,-[0-9]*}/", 354 # Writable mimic /snap/producer/5 355 # .. permissions for traversing the prefix that is assumed to exist 356 # .. variant with mimic at / 357 # Allow reading the mimic directory, it must exist in the first place. 358 "/" r, 359 # Allow setting the read-only directory aside via a bind mount. 360 "/tmp/.snap/" rw, 361 mount options=(rbind, rw) "/" -> "/tmp/.snap/", 362 # Allow mounting tmpfs over the read-only directory. 363 mount fstype=tmpfs options=(rw) tmpfs -> "/", 364 # Allow creating empty files and directories for bind mounting things 365 # to reconstruct the now-writable parent directory. 366 "/tmp/.snap/*/" rw, 367 "/*/" rw, 368 mount options=(rbind, rw) "/tmp/.snap/*/" -> "/*/", 369 "/tmp/.snap/*" rw, 370 "/*" rw, 371 mount options=(bind, rw) "/tmp/.snap/*" -> "/*", 372 # Allow unmounting the auxiliary directory. 373 # TODO: use fstype=tmpfs here for more strictness (LP: #1613403) 374 mount options=(rprivate) -> "/tmp/.snap/", 375 umount "/tmp/.snap/", 376 # Allow unmounting the destination directory as well as anything 377 # inside. This lets us perform the undo plan in case the writable 378 # mimic fails. 379 mount options=(rprivate) -> "/", 380 mount options=(rprivate) -> "/*", 381 mount options=(rprivate) -> "/*/", 382 umount "/", 383 umount "/*", 384 umount "/*/", 385 # .. variant with mimic at /snap/ 386 "/snap/" r, 387 "/tmp/.snap/snap/" rw, 388 mount options=(rbind, rw) "/snap/" -> "/tmp/.snap/snap/", 389 mount fstype=tmpfs options=(rw) tmpfs -> "/snap/", 390 "/tmp/.snap/snap/*/" rw, 391 "/snap/*/" rw, 392 mount options=(rbind, rw) "/tmp/.snap/snap/*/" -> "/snap/*/", 393 "/tmp/.snap/snap/*" rw, 394 "/snap/*" rw, 395 mount options=(bind, rw) "/tmp/.snap/snap/*" -> "/snap/*", 396 mount options=(rprivate) -> "/tmp/.snap/snap/", 397 umount "/tmp/.snap/snap/", 398 mount options=(rprivate) -> "/snap/", 399 mount options=(rprivate) -> "/snap/*", 400 mount options=(rprivate) -> "/snap/*/", 401 umount "/snap/", 402 umount "/snap/*", 403 umount "/snap/*/", 404 # .. variant with mimic at /snap/producer/ 405 "/snap/producer/" r, 406 "/tmp/.snap/snap/producer/" rw, 407 mount options=(rbind, rw) "/snap/producer/" -> "/tmp/.snap/snap/producer/", 408 mount fstype=tmpfs options=(rw) tmpfs -> "/snap/producer/", 409 "/tmp/.snap/snap/producer/*/" rw, 410 "/snap/producer/*/" rw, 411 mount options=(rbind, rw) "/tmp/.snap/snap/producer/*/" -> "/snap/producer/*/", 412 "/tmp/.snap/snap/producer/*" rw, 413 "/snap/producer/*" rw, 414 mount options=(bind, rw) "/tmp/.snap/snap/producer/*" -> "/snap/producer/*", 415 mount options=(rprivate) -> "/tmp/.snap/snap/producer/", 416 umount "/tmp/.snap/snap/producer/", 417 mount options=(rprivate) -> "/snap/producer/", 418 mount options=(rprivate) -> "/snap/producer/*", 419 mount options=(rprivate) -> "/snap/producer/*/", 420 umount "/snap/producer/", 421 umount "/snap/producer/*", 422 umount "/snap/producer/*/", 423 # .. variant with mimic at /snap/producer/5/ 424 "/snap/producer/5/" r, 425 "/tmp/.snap/snap/producer/5/" rw, 426 mount options=(rbind, rw) "/snap/producer/5/" -> "/tmp/.snap/snap/producer/5/", 427 mount fstype=tmpfs options=(rw) tmpfs -> "/snap/producer/5/", 428 "/tmp/.snap/snap/producer/5/*/" rw, 429 "/snap/producer/5/*/" rw, 430 mount options=(rbind, rw) "/tmp/.snap/snap/producer/5/*/" -> "/snap/producer/5/*/", 431 "/tmp/.snap/snap/producer/5/*" rw, 432 "/snap/producer/5/*" rw, 433 mount options=(bind, rw) "/tmp/.snap/snap/producer/5/*" -> "/snap/producer/5/*", 434 mount options=(rprivate) -> "/tmp/.snap/snap/producer/5/", 435 umount "/tmp/.snap/snap/producer/5/", 436 mount options=(rprivate) -> "/snap/producer/5/", 437 mount options=(rprivate) -> "/snap/producer/5/*", 438 mount options=(rprivate) -> "/snap/producer/5/*/", 439 umount "/snap/producer/5/", 440 umount "/snap/producer/5/*", 441 umount "/snap/producer/5/*/", 442 # Writable mimic /snap/consumer/7 443 # .. variant with mimic at /snap/consumer/ 444 "/snap/consumer/" r, 445 "/tmp/.snap/snap/consumer/" rw, 446 mount options=(rbind, rw) "/snap/consumer/" -> "/tmp/.snap/snap/consumer/", 447 mount fstype=tmpfs options=(rw) tmpfs -> "/snap/consumer/", 448 "/tmp/.snap/snap/consumer/*/" rw, 449 "/snap/consumer/*/" rw, 450 mount options=(rbind, rw) "/tmp/.snap/snap/consumer/*/" -> "/snap/consumer/*/", 451 "/tmp/.snap/snap/consumer/*" rw, 452 "/snap/consumer/*" rw, 453 mount options=(bind, rw) "/tmp/.snap/snap/consumer/*" -> "/snap/consumer/*", 454 mount options=(rprivate) -> "/tmp/.snap/snap/consumer/", 455 umount "/tmp/.snap/snap/consumer/", 456 mount options=(rprivate) -> "/snap/consumer/", 457 mount options=(rprivate) -> "/snap/consumer/*", 458 mount options=(rprivate) -> "/snap/consumer/*/", 459 umount "/snap/consumer/", 460 umount "/snap/consumer/*", 461 umount "/snap/consumer/*/", 462 # .. variant with mimic at /snap/consumer/7/ 463 "/snap/consumer/7/" r, 464 "/tmp/.snap/snap/consumer/7/" rw, 465 mount options=(rbind, rw) "/snap/consumer/7/" -> "/tmp/.snap/snap/consumer/7/", 466 mount fstype=tmpfs options=(rw) tmpfs -> "/snap/consumer/7/", 467 "/tmp/.snap/snap/consumer/7/*/" rw, 468 "/snap/consumer/7/*/" rw, 469 mount options=(rbind, rw) "/tmp/.snap/snap/consumer/7/*/" -> "/snap/consumer/7/*/", 470 "/tmp/.snap/snap/consumer/7/*" rw, 471 "/snap/consumer/7/*" rw, 472 mount options=(bind, rw) "/tmp/.snap/snap/consumer/7/*" -> "/snap/consumer/7/*", 473 mount options=(rprivate) -> "/tmp/.snap/snap/consumer/7/", 474 umount "/tmp/.snap/snap/consumer/7/", 475 mount options=(rprivate) -> "/snap/consumer/7/", 476 mount options=(rprivate) -> "/snap/consumer/7/*", 477 mount options=(rprivate) -> "/snap/consumer/7/*/", 478 umount "/snap/consumer/7/", 479 umount "/snap/consumer/7/*", 480 umount "/snap/consumer/7/*/", 481 ` 482 c.Assert(strings.Join(updateNS[:], ""), Equals, profile0) 483 } 484 485 // Check that sharing of writable data is possible 486 func (s *ContentSuite) TestConnectedPlugSnippetSharingSnapData(c *C) { 487 const consumerYaml = `name: consumer 488 version: 0 489 plugs: 490 content: 491 target: $SNAP_DATA/import 492 apps: 493 app: 494 command: foo 495 ` 496 consumerInfo := snaptest.MockInfo(c, consumerYaml, &snap.SideInfo{Revision: snap.R(7)}) 497 plug := interfaces.NewConnectedPlug(consumerInfo.Plugs["content"], nil, nil) 498 const producerYaml = `name: producer 499 version: 0 500 slots: 501 content: 502 write: 503 - $SNAP_DATA/export 504 ` 505 producerInfo := snaptest.MockInfo(c, producerYaml, &snap.SideInfo{Revision: snap.R(5)}) 506 slot := interfaces.NewConnectedSlot(producerInfo.Slots["content"], nil, nil) 507 508 spec := &mount.Specification{} 509 c.Assert(spec.AddConnectedPlug(s.iface, plug, slot), IsNil) 510 expectedMnt := []osutil.MountEntry{{ 511 Name: "/var/snap/producer/5/export", 512 Dir: "/var/snap/consumer/7/import", 513 Options: []string{"bind"}, 514 }} 515 c.Assert(spec.MountEntries(), DeepEquals, expectedMnt) 516 517 apparmorSpec := &apparmor.Specification{} 518 err := apparmorSpec.AddConnectedPlug(s.iface, plug, slot) 519 c.Assert(err, IsNil) 520 c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) 521 expected := ` 522 # In addition to the bind mount, add any AppArmor rules so that 523 # snaps may directly access the slot implementation's files. Due 524 # to a limitation in the kernel's LSM hooks for AF_UNIX, these 525 # are needed for using named sockets within the exported 526 # directory. 527 "/var/snap/producer/5/export/**" mrwklix, 528 ` 529 c.Assert(apparmorSpec.SnippetForTag("snap.consumer.app"), Equals, expected) 530 531 updateNS := apparmorSpec.UpdateNS() 532 profile0 := ` # Read-write content sharing consumer:content -> producer:content (w#0) 533 mount options=(bind, rw) "/var/snap/producer/5/export/" -> "/var/snap/consumer/7/import{,-[0-9]*}/", 534 mount options=(rprivate) -> "/var/snap/consumer/7/import{,-[0-9]*}/", 535 umount "/var/snap/consumer/7/import{,-[0-9]*}/", 536 # Writable directory /var/snap/producer/5/export 537 "/var/snap/producer/5/export/" rw, 538 "/var/snap/producer/5/" rw, 539 "/var/snap/producer/" rw, 540 # Writable directory /var/snap/consumer/7/import 541 "/var/snap/consumer/7/import/" rw, 542 "/var/snap/consumer/7/" rw, 543 "/var/snap/consumer/" rw, 544 # Writable directory /var/snap/consumer/7/import-[0-9]* 545 "/var/snap/consumer/7/import-[0-9]*/" rw, 546 ` 547 c.Assert(strings.Join(updateNS[:], ""), Equals, profile0) 548 } 549 550 // Check that sharing of writable common data is possible 551 func (s *ContentSuite) TestConnectedPlugSnippetSharingSnapCommon(c *C) { 552 const consumerYaml = `name: consumer 553 version: 0 554 plugs: 555 content: 556 target: $SNAP_COMMON/import 557 apps: 558 app: 559 command: foo 560 ` 561 consumerInfo := snaptest.MockInfo(c, consumerYaml, &snap.SideInfo{Revision: snap.R(7)}) 562 plug := interfaces.NewConnectedPlug(consumerInfo.Plugs["content"], nil, nil) 563 const producerYaml = `name: producer 564 version: 0 565 slots: 566 content: 567 write: 568 - $SNAP_COMMON/export 569 ` 570 producerInfo := snaptest.MockInfo(c, producerYaml, &snap.SideInfo{Revision: snap.R(5)}) 571 slot := interfaces.NewConnectedSlot(producerInfo.Slots["content"], nil, nil) 572 573 spec := &mount.Specification{} 574 c.Assert(spec.AddConnectedPlug(s.iface, plug, slot), IsNil) 575 expectedMnt := []osutil.MountEntry{{ 576 Name: "/var/snap/producer/common/export", 577 Dir: "/var/snap/consumer/common/import", 578 Options: []string{"bind"}, 579 }} 580 c.Assert(spec.MountEntries(), DeepEquals, expectedMnt) 581 582 apparmorSpec := &apparmor.Specification{} 583 err := apparmorSpec.AddConnectedPlug(s.iface, plug, slot) 584 c.Assert(err, IsNil) 585 c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) 586 expected := ` 587 # In addition to the bind mount, add any AppArmor rules so that 588 # snaps may directly access the slot implementation's files. Due 589 # to a limitation in the kernel's LSM hooks for AF_UNIX, these 590 # are needed for using named sockets within the exported 591 # directory. 592 "/var/snap/producer/common/export/**" mrwklix, 593 ` 594 c.Assert(apparmorSpec.SnippetForTag("snap.consumer.app"), Equals, expected) 595 596 updateNS := apparmorSpec.UpdateNS() 597 profile0 := ` # Read-write content sharing consumer:content -> producer:content (w#0) 598 mount options=(bind, rw) "/var/snap/producer/common/export/" -> "/var/snap/consumer/common/import{,-[0-9]*}/", 599 mount options=(rprivate) -> "/var/snap/consumer/common/import{,-[0-9]*}/", 600 umount "/var/snap/consumer/common/import{,-[0-9]*}/", 601 # Writable directory /var/snap/producer/common/export 602 "/var/snap/producer/common/export/" rw, 603 "/var/snap/producer/common/" rw, 604 "/var/snap/producer/" rw, 605 # Writable directory /var/snap/consumer/common/import 606 "/var/snap/consumer/common/import/" rw, 607 "/var/snap/consumer/common/" rw, 608 "/var/snap/consumer/" rw, 609 # Writable directory /var/snap/consumer/common/import-[0-9]* 610 "/var/snap/consumer/common/import-[0-9]*/" rw, 611 ` 612 c.Assert(strings.Join(updateNS[:], ""), Equals, profile0) 613 } 614 615 func (s *ContentSuite) TestInterfaces(c *C) { 616 c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) 617 } 618 619 func (s *ContentSuite) TestModernContentInterface(c *C) { 620 plug := MockPlug(c, `name: consumer 621 version: 0 622 plugs: 623 content: 624 target: $SNAP_COMMON/import 625 apps: 626 app: 627 command: foo 628 `, &snap.SideInfo{Revision: snap.R(1)}, "content") 629 connectedPlug := interfaces.NewConnectedPlug(plug, nil, nil) 630 631 slot := MockSlot(c, `name: producer 632 version: 0 633 slots: 634 content: 635 source: 636 read: 637 - $SNAP_COMMON/read-common 638 - $SNAP_DATA/read-data 639 - $SNAP/read-snap 640 write: 641 - $SNAP_COMMON/write-common 642 - $SNAP_DATA/write-data 643 `, &snap.SideInfo{Revision: snap.R(2)}, "content") 644 connectedSlot := interfaces.NewConnectedSlot(slot, nil, nil) 645 646 // Create the mount and apparmor specifications. 647 mountSpec := &mount.Specification{} 648 c.Assert(mountSpec.AddConnectedPlug(s.iface, connectedPlug, connectedSlot), IsNil) 649 apparmorSpec := &apparmor.Specification{} 650 c.Assert(apparmorSpec.AddConnectedPlug(s.iface, connectedPlug, connectedSlot), IsNil) 651 652 // Analyze the mount specification. 653 expectedMnt := []osutil.MountEntry{{ 654 Name: "/var/snap/producer/common/read-common", 655 Dir: "/var/snap/consumer/common/import/read-common", 656 Options: []string{"bind", "ro"}, 657 }, { 658 Name: "/var/snap/producer/2/read-data", 659 Dir: "/var/snap/consumer/common/import/read-data", 660 Options: []string{"bind", "ro"}, 661 }, { 662 Name: "/snap/producer/2/read-snap", 663 Dir: "/var/snap/consumer/common/import/read-snap", 664 Options: []string{"bind", "ro"}, 665 }, { 666 Name: "/var/snap/producer/common/write-common", 667 Dir: "/var/snap/consumer/common/import/write-common", 668 Options: []string{"bind"}, 669 }, { 670 Name: "/var/snap/producer/2/write-data", 671 Dir: "/var/snap/consumer/common/import/write-data", 672 Options: []string{"bind"}, 673 }} 674 c.Assert(mountSpec.MountEntries(), DeepEquals, expectedMnt) 675 676 // Analyze the apparmor specification. 677 c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) 678 expected := ` 679 # In addition to the bind mount, add any AppArmor rules so that 680 # snaps may directly access the slot implementation's files. Due 681 # to a limitation in the kernel's LSM hooks for AF_UNIX, these 682 # are needed for using named sockets within the exported 683 # directory. 684 "/var/snap/producer/common/write-common/**" mrwklix, 685 "/var/snap/producer/2/write-data/**" mrwklix, 686 687 # In addition to the bind mount, add any AppArmor rules so that 688 # snaps may directly access the slot implementation's files 689 # read-only. 690 "/var/snap/producer/common/read-common/**" mrkix, 691 "/var/snap/producer/2/read-data/**" mrkix, 692 "/snap/producer/2/read-snap/**" mrkix, 693 ` 694 c.Assert(apparmorSpec.SnippetForTag("snap.consumer.app"), Equals, expected) 695 696 updateNS := apparmorSpec.UpdateNS() 697 profile0 := ` # Read-write content sharing consumer:content -> producer:content (w#0) 698 mount options=(bind, rw) "/var/snap/producer/common/write-common/" -> "/var/snap/consumer/common/import/write-common{,-[0-9]*}/", 699 mount options=(rprivate) -> "/var/snap/consumer/common/import/write-common{,-[0-9]*}/", 700 umount "/var/snap/consumer/common/import/write-common{,-[0-9]*}/", 701 # Writable directory /var/snap/producer/common/write-common 702 "/var/snap/producer/common/write-common/" rw, 703 "/var/snap/producer/common/" rw, 704 "/var/snap/producer/" rw, 705 # Writable directory /var/snap/consumer/common/import/write-common 706 "/var/snap/consumer/common/import/write-common/" rw, 707 "/var/snap/consumer/common/import/" rw, 708 "/var/snap/consumer/common/" rw, 709 "/var/snap/consumer/" rw, 710 # Writable directory /var/snap/consumer/common/import/write-common-[0-9]* 711 "/var/snap/consumer/common/import/write-common-[0-9]*/" rw, 712 ` 713 // Find the slice that describes profile0 by looking for the first unique 714 // line of the next profile. 715 start := 0 716 end, _ := apparmorSpec.UpdateNSIndexOf(" # Read-write content sharing consumer:content -> producer:content (w#1)\n") 717 c.Assert(strings.Join(updateNS[start:end], ""), Equals, profile0) 718 719 profile1 := ` # Read-write content sharing consumer:content -> producer:content (w#1) 720 mount options=(bind, rw) "/var/snap/producer/2/write-data/" -> "/var/snap/consumer/common/import/write-data{,-[0-9]*}/", 721 mount options=(rprivate) -> "/var/snap/consumer/common/import/write-data{,-[0-9]*}/", 722 umount "/var/snap/consumer/common/import/write-data{,-[0-9]*}/", 723 # Writable directory /var/snap/producer/2/write-data 724 "/var/snap/producer/2/write-data/" rw, 725 "/var/snap/producer/2/" rw, 726 # Writable directory /var/snap/consumer/common/import/write-data 727 "/var/snap/consumer/common/import/write-data/" rw, 728 # Writable directory /var/snap/consumer/common/import/write-data-[0-9]* 729 "/var/snap/consumer/common/import/write-data-[0-9]*/" rw, 730 ` 731 // Find the slice that describes profile1 by looking for the first unique 732 // line of the next profile. 733 start = end 734 end, _ = apparmorSpec.UpdateNSIndexOf(" # Read-only content sharing consumer:content -> producer:content (r#0)\n") 735 c.Assert(strings.Join(updateNS[start:end], ""), Equals, profile1) 736 737 profile2 := ` # Read-only content sharing consumer:content -> producer:content (r#0) 738 mount options=(bind) "/var/snap/producer/common/read-common/" -> "/var/snap/consumer/common/import/read-common{,-[0-9]*}/", 739 remount options=(bind, ro) "/var/snap/consumer/common/import/read-common{,-[0-9]*}/", 740 mount options=(rprivate) -> "/var/snap/consumer/common/import/read-common{,-[0-9]*}/", 741 umount "/var/snap/consumer/common/import/read-common{,-[0-9]*}/", 742 # Writable directory /var/snap/producer/common/read-common 743 "/var/snap/producer/common/read-common/" rw, 744 # Writable directory /var/snap/consumer/common/import/read-common 745 "/var/snap/consumer/common/import/read-common/" rw, 746 # Writable directory /var/snap/consumer/common/import/read-common-[0-9]* 747 "/var/snap/consumer/common/import/read-common-[0-9]*/" rw, 748 ` 749 // Find the slice that describes profile2 by looking for the first unique 750 // line of the next profile. 751 start = end 752 end, _ = apparmorSpec.UpdateNSIndexOf(" # Read-only content sharing consumer:content -> producer:content (r#1)\n") 753 c.Assert(strings.Join(updateNS[start:end], ""), Equals, profile2) 754 755 profile3 := ` # Read-only content sharing consumer:content -> producer:content (r#1) 756 mount options=(bind) "/var/snap/producer/2/read-data/" -> "/var/snap/consumer/common/import/read-data{,-[0-9]*}/", 757 remount options=(bind, ro) "/var/snap/consumer/common/import/read-data{,-[0-9]*}/", 758 mount options=(rprivate) -> "/var/snap/consumer/common/import/read-data{,-[0-9]*}/", 759 umount "/var/snap/consumer/common/import/read-data{,-[0-9]*}/", 760 # Writable directory /var/snap/producer/2/read-data 761 "/var/snap/producer/2/read-data/" rw, 762 # Writable directory /var/snap/consumer/common/import/read-data 763 "/var/snap/consumer/common/import/read-data/" rw, 764 # Writable directory /var/snap/consumer/common/import/read-data-[0-9]* 765 "/var/snap/consumer/common/import/read-data-[0-9]*/" rw, 766 ` 767 // Find the slice that describes profile3 by looking for the first unique 768 // line of the next profile. 769 start = end 770 end, _ = apparmorSpec.UpdateNSIndexOf(" # Read-only content sharing consumer:content -> producer:content (r#2)\n") 771 c.Assert(strings.Join(updateNS[start:end], ""), Equals, profile3) 772 773 profile4 := ` # Read-only content sharing consumer:content -> producer:content (r#2) 774 mount options=(bind) "/snap/producer/2/read-snap/" -> "/var/snap/consumer/common/import/read-snap{,-[0-9]*}/", 775 remount options=(bind, ro) "/var/snap/consumer/common/import/read-snap{,-[0-9]*}/", 776 mount options=(rprivate) -> "/var/snap/consumer/common/import/read-snap{,-[0-9]*}/", 777 umount "/var/snap/consumer/common/import/read-snap{,-[0-9]*}/", 778 # Writable mimic /snap/producer/2 779 # .. permissions for traversing the prefix that is assumed to exist 780 # .. variant with mimic at / 781 # Allow reading the mimic directory, it must exist in the first place. 782 "/" r, 783 # Allow setting the read-only directory aside via a bind mount. 784 "/tmp/.snap/" rw, 785 mount options=(rbind, rw) "/" -> "/tmp/.snap/", 786 # Allow mounting tmpfs over the read-only directory. 787 mount fstype=tmpfs options=(rw) tmpfs -> "/", 788 # Allow creating empty files and directories for bind mounting things 789 # to reconstruct the now-writable parent directory. 790 "/tmp/.snap/*/" rw, 791 "/*/" rw, 792 mount options=(rbind, rw) "/tmp/.snap/*/" -> "/*/", 793 "/tmp/.snap/*" rw, 794 "/*" rw, 795 mount options=(bind, rw) "/tmp/.snap/*" -> "/*", 796 # Allow unmounting the auxiliary directory. 797 # TODO: use fstype=tmpfs here for more strictness (LP: #1613403) 798 mount options=(rprivate) -> "/tmp/.snap/", 799 umount "/tmp/.snap/", 800 # Allow unmounting the destination directory as well as anything 801 # inside. This lets us perform the undo plan in case the writable 802 # mimic fails. 803 mount options=(rprivate) -> "/", 804 mount options=(rprivate) -> "/*", 805 mount options=(rprivate) -> "/*/", 806 umount "/", 807 umount "/*", 808 umount "/*/", 809 # .. variant with mimic at /snap/ 810 "/snap/" r, 811 "/tmp/.snap/snap/" rw, 812 mount options=(rbind, rw) "/snap/" -> "/tmp/.snap/snap/", 813 mount fstype=tmpfs options=(rw) tmpfs -> "/snap/", 814 "/tmp/.snap/snap/*/" rw, 815 "/snap/*/" rw, 816 mount options=(rbind, rw) "/tmp/.snap/snap/*/" -> "/snap/*/", 817 "/tmp/.snap/snap/*" rw, 818 "/snap/*" rw, 819 mount options=(bind, rw) "/tmp/.snap/snap/*" -> "/snap/*", 820 mount options=(rprivate) -> "/tmp/.snap/snap/", 821 umount "/tmp/.snap/snap/", 822 mount options=(rprivate) -> "/snap/", 823 mount options=(rprivate) -> "/snap/*", 824 mount options=(rprivate) -> "/snap/*/", 825 umount "/snap/", 826 umount "/snap/*", 827 umount "/snap/*/", 828 # .. variant with mimic at /snap/producer/ 829 "/snap/producer/" r, 830 "/tmp/.snap/snap/producer/" rw, 831 mount options=(rbind, rw) "/snap/producer/" -> "/tmp/.snap/snap/producer/", 832 mount fstype=tmpfs options=(rw) tmpfs -> "/snap/producer/", 833 "/tmp/.snap/snap/producer/*/" rw, 834 "/snap/producer/*/" rw, 835 mount options=(rbind, rw) "/tmp/.snap/snap/producer/*/" -> "/snap/producer/*/", 836 "/tmp/.snap/snap/producer/*" rw, 837 "/snap/producer/*" rw, 838 mount options=(bind, rw) "/tmp/.snap/snap/producer/*" -> "/snap/producer/*", 839 mount options=(rprivate) -> "/tmp/.snap/snap/producer/", 840 umount "/tmp/.snap/snap/producer/", 841 mount options=(rprivate) -> "/snap/producer/", 842 mount options=(rprivate) -> "/snap/producer/*", 843 mount options=(rprivate) -> "/snap/producer/*/", 844 umount "/snap/producer/", 845 umount "/snap/producer/*", 846 umount "/snap/producer/*/", 847 # .. variant with mimic at /snap/producer/2/ 848 "/snap/producer/2/" r, 849 "/tmp/.snap/snap/producer/2/" rw, 850 mount options=(rbind, rw) "/snap/producer/2/" -> "/tmp/.snap/snap/producer/2/", 851 mount fstype=tmpfs options=(rw) tmpfs -> "/snap/producer/2/", 852 "/tmp/.snap/snap/producer/2/*/" rw, 853 "/snap/producer/2/*/" rw, 854 mount options=(rbind, rw) "/tmp/.snap/snap/producer/2/*/" -> "/snap/producer/2/*/", 855 "/tmp/.snap/snap/producer/2/*" rw, 856 "/snap/producer/2/*" rw, 857 mount options=(bind, rw) "/tmp/.snap/snap/producer/2/*" -> "/snap/producer/2/*", 858 mount options=(rprivate) -> "/tmp/.snap/snap/producer/2/", 859 umount "/tmp/.snap/snap/producer/2/", 860 mount options=(rprivate) -> "/snap/producer/2/", 861 mount options=(rprivate) -> "/snap/producer/2/*", 862 mount options=(rprivate) -> "/snap/producer/2/*/", 863 umount "/snap/producer/2/", 864 umount "/snap/producer/2/*", 865 umount "/snap/producer/2/*/", 866 # Writable directory /var/snap/consumer/common/import/read-snap 867 "/var/snap/consumer/common/import/read-snap/" rw, 868 # Writable directory /var/snap/consumer/common/import/read-snap-[0-9]* 869 "/var/snap/consumer/common/import/read-snap-[0-9]*/" rw, 870 ` 871 // Find the slice that describes profile4 by looking till the end of the list. 872 start = end 873 c.Assert(strings.Join(updateNS[start:], ""), Equals, profile4) 874 c.Assert(strings.Join(updateNS, ""), DeepEquals, strings.Join([]string{profile0, profile1, profile2, profile3, profile4}, "")) 875 } 876 877 func (s *ContentSuite) TestModernContentInterfacePlugins(c *C) { 878 // Define one app snap and two snaps plugin snaps. 879 plug := MockPlug(c, `name: app 880 version: 0 881 plugs: 882 plugins: 883 interface: content 884 content: plugin-for-app 885 target: $SNAP/plugins 886 apps: 887 app: 888 command: foo 889 890 `, &snap.SideInfo{Revision: snap.R(1)}, "plugins") 891 connectedPlug := interfaces.NewConnectedPlug(plug, nil, nil) 892 893 // XXX: realistically the plugin may be a single file and we don't support 894 // those very well. 895 slotOne := MockSlot(c, `name: plugin-one 896 version: 0 897 slots: 898 plugin-for-app: 899 interface: content 900 source: 901 read: [$SNAP/plugin] 902 `, &snap.SideInfo{Revision: snap.R(1)}, "plugin-for-app") 903 connectedSlotOne := interfaces.NewConnectedSlot(slotOne, nil, nil) 904 905 slotTwo := MockSlot(c, `name: plugin-two 906 version: 0 907 slots: 908 plugin-for-app: 909 interface: content 910 source: 911 read: [$SNAP/plugin] 912 `, &snap.SideInfo{Revision: snap.R(1)}, "plugin-for-app") 913 connectedSlotTwo := interfaces.NewConnectedSlot(slotTwo, nil, nil) 914 915 // Create the mount and apparmor specifications. 916 mountSpec := &mount.Specification{} 917 apparmorSpec := &apparmor.Specification{} 918 for _, connectedSlot := range []*interfaces.ConnectedSlot{connectedSlotOne, connectedSlotTwo} { 919 c.Assert(mountSpec.AddConnectedPlug(s.iface, connectedPlug, connectedSlot), IsNil) 920 c.Assert(apparmorSpec.AddConnectedPlug(s.iface, connectedPlug, connectedSlot), IsNil) 921 } 922 923 // Analyze the mount specification. 924 expectedMnt := []osutil.MountEntry{{ 925 Name: "/snap/plugin-one/1/plugin", 926 Dir: "/snap/app/1/plugins/plugin", 927 Options: []string{"bind", "ro"}, 928 }, { 929 Name: "/snap/plugin-two/1/plugin", 930 Dir: "/snap/app/1/plugins/plugin-2", 931 Options: []string{"bind", "ro"}, 932 }} 933 c.Assert(mountSpec.MountEntries(), DeepEquals, expectedMnt) 934 935 // Analyze the apparmor specification. 936 // 937 // NOTE: the paths below refer to the original locations and are *NOT* 938 // altered like the mount entries above. This is intended. See the comment 939 // below for explanation as to why those are necessary. 940 c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.app.app"}) 941 expected := ` 942 # In addition to the bind mount, add any AppArmor rules so that 943 # snaps may directly access the slot implementation's files 944 # read-only. 945 "/snap/plugin-one/1/plugin/**" mrkix, 946 947 948 # In addition to the bind mount, add any AppArmor rules so that 949 # snaps may directly access the slot implementation's files 950 # read-only. 951 "/snap/plugin-two/1/plugin/**" mrkix, 952 ` 953 c.Assert(apparmorSpec.SnippetForTag("snap.app.app"), Equals, expected) 954 } 955 956 func (s *ContentSuite) TestModernContentSameReadAndWriteClash(c *C) { 957 plug := MockPlug(c, `name: consumer 958 version: 0 959 plugs: 960 content: 961 target: $SNAP_COMMON/import 962 apps: 963 app: 964 command: foo 965 `, &snap.SideInfo{Revision: snap.R(1)}, "content") 966 connectedPlug := interfaces.NewConnectedPlug(plug, nil, nil) 967 968 slot := MockSlot(c, `name: producer 969 version: 0 970 slots: 971 content: 972 source: 973 read: 974 - $SNAP_DATA/directory 975 write: 976 - $SNAP_DATA/directory 977 `, &snap.SideInfo{Revision: snap.R(2)}, "content") 978 connectedSlot := interfaces.NewConnectedSlot(slot, nil, nil) 979 980 // Create the mount and apparmor specifications. 981 mountSpec := &mount.Specification{} 982 c.Assert(mountSpec.AddConnectedPlug(s.iface, connectedPlug, connectedSlot), IsNil) 983 apparmorSpec := &apparmor.Specification{} 984 c.Assert(apparmorSpec.AddConnectedPlug(s.iface, connectedPlug, connectedSlot), IsNil) 985 986 // Analyze the mount specification 987 expectedMnt := []osutil.MountEntry{{ 988 Name: "/var/snap/producer/2/directory", 989 Dir: "/var/snap/consumer/common/import/directory", 990 Options: []string{"bind"}, 991 }} 992 c.Assert(mountSpec.MountEntries(), DeepEquals, expectedMnt) 993 994 // Analyze the apparmor specification. 995 // 996 // NOTE: Although there are duplicate entries with different permissions 997 // one is a superset of the other so they do not conflict. 998 c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) 999 expected := ` 1000 # In addition to the bind mount, add any AppArmor rules so that 1001 # snaps may directly access the slot implementation's files. Due 1002 # to a limitation in the kernel's LSM hooks for AF_UNIX, these 1003 # are needed for using named sockets within the exported 1004 # directory. 1005 "/var/snap/producer/2/directory/**" mrwklix, 1006 1007 # In addition to the bind mount, add any AppArmor rules so that 1008 # snaps may directly access the slot implementation's files 1009 # read-only. 1010 "/var/snap/producer/2/directory/**" mrkix, 1011 ` 1012 c.Assert(apparmorSpec.SnippetForTag("snap.consumer.app"), Equals, expected) 1013 } 1014 1015 // Check that slot can access shared directory in plug's namespace 1016 func (s *ContentSuite) TestSlotCanAccessConnectedPlugSharedDirectory(c *C) { 1017 const consumerYaml = `name: consumer 1018 version: 0 1019 plugs: 1020 content: 1021 target: $SNAP_COMMON/import 1022 ` 1023 consumerInfo := snaptest.MockInfo(c, consumerYaml, &snap.SideInfo{Revision: snap.R(7)}) 1024 plug := interfaces.NewConnectedPlug(consumerInfo.Plugs["content"], nil, nil) 1025 const producerYaml = `name: producer 1026 version: 0 1027 slots: 1028 content: 1029 write: 1030 - $SNAP_COMMON/export 1031 apps: 1032 app: 1033 command: bar 1034 ` 1035 producerInfo := snaptest.MockInfo(c, producerYaml, &snap.SideInfo{Revision: snap.R(5)}) 1036 slot := interfaces.NewConnectedSlot(producerInfo.Slots["content"], nil, nil) 1037 1038 apparmorSpec := &apparmor.Specification{} 1039 err := apparmorSpec.AddConnectedSlot(s.iface, plug, slot) 1040 c.Assert(err, IsNil) 1041 c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) 1042 expected := ` 1043 # When the content interface is writable, allow this slot 1044 # implementation to access the slot's exported files at the plugging 1045 # snap's mountpoint to accommodate software where the plugging app 1046 # tells the slotting app about files to share. 1047 "/var/snap/consumer/common/import/**" mrwklix, 1048 ` 1049 c.Assert(apparmorSpec.SnippetForTag("snap.producer.app"), Equals, expected) 1050 } 1051 1052 func (s *ContentSuite) TestStaticInfo(c *C) { 1053 si := interfaces.StaticInfoOf(s.iface) 1054 c.Assert(si.ImplicitOnCore, Equals, false) 1055 c.Assert(si.ImplicitOnClassic, Equals, false) 1056 c.Assert(si.Summary, Equals, `allows sharing code and data with other snaps`) 1057 c.Assert(si.BaseDeclarationSlots, testutil.Contains, "content: $SLOT(content)") 1058 c.Assert(si.AffectsPlugOnRefresh, Equals, true) 1059 }