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  }