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