github.com/rigado/snapd@v2.42.5-go-mod+incompatible/interfaces/apparmor/spec_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-2018 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 apparmor_test
    21  
    22  import (
    23  	"strings"
    24  
    25  	. "gopkg.in/check.v1"
    26  
    27  	"github.com/snapcore/snapd/interfaces"
    28  	"github.com/snapcore/snapd/interfaces/apparmor"
    29  	"github.com/snapcore/snapd/interfaces/ifacetest"
    30  	"github.com/snapcore/snapd/snap"
    31  	"github.com/snapcore/snapd/snap/snaptest"
    32  
    33  	"github.com/snapcore/snapd/testutil"
    34  )
    35  
    36  type specSuite struct {
    37  	testutil.BaseTest
    38  	iface    *ifacetest.TestInterface
    39  	spec     *apparmor.Specification
    40  	plugInfo *snap.PlugInfo
    41  	plug     *interfaces.ConnectedPlug
    42  	slotInfo *snap.SlotInfo
    43  	slot     *interfaces.ConnectedSlot
    44  }
    45  
    46  var _ = Suite(&specSuite{
    47  	iface: &ifacetest.TestInterface{
    48  		InterfaceName: "test",
    49  		AppArmorConnectedPlugCallback: func(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
    50  			spec.AddSnippet("connected-plug")
    51  			return nil
    52  		},
    53  		AppArmorConnectedSlotCallback: func(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
    54  			spec.AddSnippet("connected-slot")
    55  			return nil
    56  		},
    57  		AppArmorPermanentPlugCallback: func(spec *apparmor.Specification, plug *snap.PlugInfo) error {
    58  			spec.AddSnippet("permanent-plug")
    59  			return nil
    60  		},
    61  		AppArmorPermanentSlotCallback: func(spec *apparmor.Specification, slot *snap.SlotInfo) error {
    62  			spec.AddSnippet("permanent-slot")
    63  			return nil
    64  		},
    65  	},
    66  	plugInfo: &snap.PlugInfo{
    67  		Snap:      &snap.Info{SuggestedName: "snap1"},
    68  		Name:      "name",
    69  		Interface: "test",
    70  		Apps: map[string]*snap.AppInfo{
    71  			"app1": {
    72  				Snap: &snap.Info{
    73  					SuggestedName: "snap1",
    74  				},
    75  				Name: "app1"}},
    76  	},
    77  	slotInfo: &snap.SlotInfo{
    78  		Snap:      &snap.Info{SuggestedName: "snap2"},
    79  		Name:      "name",
    80  		Interface: "test",
    81  		Apps: map[string]*snap.AppInfo{
    82  			"app2": {
    83  				Snap: &snap.Info{
    84  					SuggestedName: "snap2",
    85  				},
    86  				Name: "app2"}},
    87  	},
    88  })
    89  
    90  func (s *specSuite) SetUpTest(c *C) {
    91  	s.BaseTest.SetUpTest(c)
    92  	s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
    93  
    94  	s.spec = &apparmor.Specification{}
    95  	s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil, nil)
    96  	s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil, nil)
    97  }
    98  
    99  func (s *specSuite) TearDownTest(c *C) {
   100  	s.BaseTest.TearDownTest(c)
   101  }
   102  
   103  // The spec.Specification can be used through the interfaces.Specification interface
   104  func (s *specSuite) TestSpecificationIface(c *C) {
   105  	var r interfaces.Specification = s.spec
   106  	c.Assert(r.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil)
   107  	c.Assert(r.AddConnectedSlot(s.iface, s.plug, s.slot), IsNil)
   108  	c.Assert(r.AddPermanentPlug(s.iface, s.plugInfo), IsNil)
   109  	c.Assert(r.AddPermanentSlot(s.iface, s.slotInfo), IsNil)
   110  	c.Assert(s.spec.Snippets(), DeepEquals, map[string][]string{
   111  		"snap.snap1.app1": {"connected-plug", "permanent-plug"},
   112  		"snap.snap2.app2": {"connected-slot", "permanent-slot"},
   113  	})
   114  }
   115  
   116  // AddSnippet adds a snippet for the given security tag.
   117  func (s *specSuite) TestAddSnippet(c *C) {
   118  	restore := apparmor.SetSpecScope(s.spec, []string{"snap.demo.command", "snap.demo.service"})
   119  	defer restore()
   120  
   121  	// Add two snippets in the context we are in.
   122  	s.spec.AddSnippet("snippet 1")
   123  	s.spec.AddSnippet("snippet 2")
   124  
   125  	// The snippets were recorded correctly.
   126  	c.Assert(s.spec.UpdateNS(), HasLen, 0)
   127  	c.Assert(s.spec.Snippets(), DeepEquals, map[string][]string{
   128  		"snap.demo.command": {"snippet 1", "snippet 2"},
   129  		"snap.demo.service": {"snippet 1", "snippet 2"},
   130  	})
   131  	c.Assert(s.spec.SnippetForTag("snap.demo.command"), Equals, "snippet 1\nsnippet 2")
   132  	c.Assert(s.spec.SecurityTags(), DeepEquals, []string{"snap.demo.command", "snap.demo.service"})
   133  }
   134  
   135  // AddUpdateNS adds a snippet for the snap-update-ns profile for a given snap.
   136  func (s *specSuite) TestAddUpdateNS(c *C) {
   137  	restore := apparmor.SetSpecScope(s.spec, []string{"snap.demo.command", "snap.demo.service"})
   138  	defer restore()
   139  
   140  	// Add a two snap-update-ns snippets in the context we are in.
   141  	s.spec.AddUpdateNS("s-u-n snippet 1")
   142  	s.spec.AddUpdateNS("s-u-n snippet 2")
   143  
   144  	// Check the order of the snippets can be retrieved.
   145  	idx, ok := s.spec.UpdateNSIndexOf("s-u-n snippet 2")
   146  	c.Assert(ok, Equals, true)
   147  	c.Check(idx, Equals, 1)
   148  
   149  	// The snippets were recorded correctly and in the right place.
   150  	c.Assert(s.spec.UpdateNS(), DeepEquals, []string{
   151  		"s-u-n snippet 1", "s-u-n snippet 2",
   152  	})
   153  	c.Assert(s.spec.SnippetForTag("snap.demo.command"), Equals, "")
   154  	c.Assert(s.spec.SecurityTags(), HasLen, 0)
   155  }
   156  
   157  const snapWithLayout = `
   158  name: vanguard
   159  version: 0
   160  apps:
   161    vanguard:
   162      command: vanguard
   163  layout:
   164    /usr/foo:
   165      bind: $SNAP/usr/foo
   166    /var/tmp:
   167      type: tmpfs
   168      mode: 1777
   169    /var/cache/mylink:
   170      symlink: $SNAP_DATA/link/target
   171    /etc/foo.conf:
   172      bind-file: $SNAP/foo.conf
   173  `
   174  
   175  func (s *specSuite) TestApparmorSnippetsFromLayout(c *C) {
   176  	snapInfo := snaptest.MockInfo(c, snapWithLayout, &snap.SideInfo{Revision: snap.R(42)})
   177  	restore := apparmor.SetSpecScope(s.spec, []string{"snap.vanguard.vanguard"})
   178  	defer restore()
   179  
   180  	s.spec.AddLayout(snapInfo)
   181  	c.Assert(s.spec.Snippets(), DeepEquals, map[string][]string{
   182  		"snap.vanguard.vanguard": {
   183  			"# Layout path: /etc/foo.conf\n/etc/foo.conf mrwklix,",
   184  			"# Layout path: /usr/foo\n/usr/foo{,/**} mrwklix,",
   185  			"# Layout path: /var/cache/mylink\n# (no extra permissions required for symlink)",
   186  			"# Layout path: /var/tmp\n/var/tmp{,/**} mrwklix,",
   187  		},
   188  	})
   189  	updateNS := s.spec.UpdateNS()
   190  
   191  	profile0 := `  # Layout /etc/foo.conf: bind-file $SNAP/foo.conf
   192    mount options=(bind, rw) /snap/vanguard/42/foo.conf -> /etc/foo.conf,
   193    mount options=(rprivate) -> /etc/foo.conf,
   194    umount /etc/foo.conf,
   195    # Writable mimic /etc
   196    # .. permissions for traversing the prefix that is assumed to exist
   197    / r,
   198    # .. variant with mimic at /etc/
   199    # Allow reading the mimic directory, it must exist in the first place.
   200    /etc/ r,
   201    # Allow setting the read-only directory aside via a bind mount.
   202    /tmp/.snap/etc/ rw,
   203    mount options=(rbind, rw) /etc/ -> /tmp/.snap/etc/,
   204    # Allow mounting tmpfs over the read-only directory.
   205    mount fstype=tmpfs options=(rw) tmpfs -> /etc/,
   206    # Allow creating empty files and directories for bind mounting things
   207    # to reconstruct the now-writable parent directory.
   208    /tmp/.snap/etc/*/ rw,
   209    /etc/*/ rw,
   210    mount options=(rbind, rw) /tmp/.snap/etc/*/ -> /etc/*/,
   211    /tmp/.snap/etc/* rw,
   212    /etc/* rw,
   213    mount options=(bind, rw) /tmp/.snap/etc/* -> /etc/*,
   214    # Allow unmounting the auxiliary directory.
   215    # TODO: use fstype=tmpfs here for more strictness (LP: #1613403)
   216    mount options=(rprivate) -> /tmp/.snap/etc/,
   217    umount /tmp/.snap/etc/,
   218    # Allow unmounting the destination directory as well as anything
   219    # inside.  This lets us perform the undo plan in case the writable
   220    # mimic fails.
   221    mount options=(rprivate) -> /etc/,
   222    mount options=(rprivate) -> /etc/*,
   223    mount options=(rprivate) -> /etc/*/,
   224    umount /etc/,
   225    umount /etc/*,
   226    umount /etc/*/,
   227    # Writable mimic /snap/vanguard/42
   228    /snap/ r,
   229    /snap/vanguard/ r,
   230    # .. variant with mimic at /snap/vanguard/42/
   231    /snap/vanguard/42/ r,
   232    /tmp/.snap/snap/vanguard/42/ rw,
   233    mount options=(rbind, rw) /snap/vanguard/42/ -> /tmp/.snap/snap/vanguard/42/,
   234    mount fstype=tmpfs options=(rw) tmpfs -> /snap/vanguard/42/,
   235    /tmp/.snap/snap/vanguard/42/*/ rw,
   236    /snap/vanguard/42/*/ rw,
   237    mount options=(rbind, rw) /tmp/.snap/snap/vanguard/42/*/ -> /snap/vanguard/42/*/,
   238    /tmp/.snap/snap/vanguard/42/* rw,
   239    /snap/vanguard/42/* rw,
   240    mount options=(bind, rw) /tmp/.snap/snap/vanguard/42/* -> /snap/vanguard/42/*,
   241    mount options=(rprivate) -> /tmp/.snap/snap/vanguard/42/,
   242    umount /tmp/.snap/snap/vanguard/42/,
   243    mount options=(rprivate) -> /snap/vanguard/42/,
   244    mount options=(rprivate) -> /snap/vanguard/42/*,
   245    mount options=(rprivate) -> /snap/vanguard/42/*/,
   246    umount /snap/vanguard/42/,
   247    umount /snap/vanguard/42/*,
   248    umount /snap/vanguard/42/*/,
   249  `
   250  	// Find the slice that describes profile0 by looking for the first unique
   251  	// line of the next profile.
   252  	start := 0
   253  	end, _ := s.spec.UpdateNSIndexOf("  # Layout /usr/foo: bind $SNAP/usr/foo\n")
   254  	c.Assert(strings.Join(updateNS[start:end], ""), Equals, profile0)
   255  
   256  	profile1 := `  # Layout /usr/foo: bind $SNAP/usr/foo
   257    mount options=(rbind, rw) /snap/vanguard/42/usr/foo/ -> /usr/foo/,
   258    mount options=(rprivate) -> /usr/foo/,
   259    umount /usr/foo/,
   260    # Writable mimic /usr
   261    # .. variant with mimic at /usr/
   262    /usr/ r,
   263    /tmp/.snap/usr/ rw,
   264    mount options=(rbind, rw) /usr/ -> /tmp/.snap/usr/,
   265    mount fstype=tmpfs options=(rw) tmpfs -> /usr/,
   266    /tmp/.snap/usr/*/ rw,
   267    /usr/*/ rw,
   268    mount options=(rbind, rw) /tmp/.snap/usr/*/ -> /usr/*/,
   269    /tmp/.snap/usr/* rw,
   270    /usr/* rw,
   271    mount options=(bind, rw) /tmp/.snap/usr/* -> /usr/*,
   272    mount options=(rprivate) -> /tmp/.snap/usr/,
   273    umount /tmp/.snap/usr/,
   274    mount options=(rprivate) -> /usr/,
   275    mount options=(rprivate) -> /usr/*,
   276    mount options=(rprivate) -> /usr/*/,
   277    umount /usr/,
   278    umount /usr/*,
   279    umount /usr/*/,
   280    # Writable mimic /snap/vanguard/42/usr
   281    # .. variant with mimic at /snap/vanguard/42/usr/
   282    /snap/vanguard/42/usr/ r,
   283    /tmp/.snap/snap/vanguard/42/usr/ rw,
   284    mount options=(rbind, rw) /snap/vanguard/42/usr/ -> /tmp/.snap/snap/vanguard/42/usr/,
   285    mount fstype=tmpfs options=(rw) tmpfs -> /snap/vanguard/42/usr/,
   286    /tmp/.snap/snap/vanguard/42/usr/*/ rw,
   287    /snap/vanguard/42/usr/*/ rw,
   288    mount options=(rbind, rw) /tmp/.snap/snap/vanguard/42/usr/*/ -> /snap/vanguard/42/usr/*/,
   289    /tmp/.snap/snap/vanguard/42/usr/* rw,
   290    /snap/vanguard/42/usr/* rw,
   291    mount options=(bind, rw) /tmp/.snap/snap/vanguard/42/usr/* -> /snap/vanguard/42/usr/*,
   292    mount options=(rprivate) -> /tmp/.snap/snap/vanguard/42/usr/,
   293    umount /tmp/.snap/snap/vanguard/42/usr/,
   294    mount options=(rprivate) -> /snap/vanguard/42/usr/,
   295    mount options=(rprivate) -> /snap/vanguard/42/usr/*,
   296    mount options=(rprivate) -> /snap/vanguard/42/usr/*/,
   297    umount /snap/vanguard/42/usr/,
   298    umount /snap/vanguard/42/usr/*,
   299    umount /snap/vanguard/42/usr/*/,
   300  `
   301  	// Find the slice that describes profile1 by looking for the first unique
   302  	// line of the next profile.
   303  	start = end
   304  	end, _ = s.spec.UpdateNSIndexOf("  # Layout /var/cache/mylink: symlink $SNAP_DATA/link/target\n")
   305  	c.Assert(strings.Join(updateNS[start:end], ""), Equals, profile1)
   306  
   307  	profile2 := `  # Layout /var/cache/mylink: symlink $SNAP_DATA/link/target
   308    /var/cache/mylink rw,
   309    # Writable mimic /var/cache
   310    # .. variant with mimic at /var/
   311    /var/ r,
   312    /tmp/.snap/var/ rw,
   313    mount options=(rbind, rw) /var/ -> /tmp/.snap/var/,
   314    mount fstype=tmpfs options=(rw) tmpfs -> /var/,
   315    /tmp/.snap/var/*/ rw,
   316    /var/*/ rw,
   317    mount options=(rbind, rw) /tmp/.snap/var/*/ -> /var/*/,
   318    /tmp/.snap/var/* rw,
   319    /var/* rw,
   320    mount options=(bind, rw) /tmp/.snap/var/* -> /var/*,
   321    mount options=(rprivate) -> /tmp/.snap/var/,
   322    umount /tmp/.snap/var/,
   323    mount options=(rprivate) -> /var/,
   324    mount options=(rprivate) -> /var/*,
   325    mount options=(rprivate) -> /var/*/,
   326    umount /var/,
   327    umount /var/*,
   328    umount /var/*/,
   329    # .. variant with mimic at /var/cache/
   330    /var/cache/ r,
   331    /tmp/.snap/var/cache/ rw,
   332    mount options=(rbind, rw) /var/cache/ -> /tmp/.snap/var/cache/,
   333    mount fstype=tmpfs options=(rw) tmpfs -> /var/cache/,
   334    /tmp/.snap/var/cache/*/ rw,
   335    /var/cache/*/ rw,
   336    mount options=(rbind, rw) /tmp/.snap/var/cache/*/ -> /var/cache/*/,
   337    /tmp/.snap/var/cache/* rw,
   338    /var/cache/* rw,
   339    mount options=(bind, rw) /tmp/.snap/var/cache/* -> /var/cache/*,
   340    mount options=(rprivate) -> /tmp/.snap/var/cache/,
   341    umount /tmp/.snap/var/cache/,
   342    mount options=(rprivate) -> /var/cache/,
   343    mount options=(rprivate) -> /var/cache/*,
   344    mount options=(rprivate) -> /var/cache/*/,
   345    umount /var/cache/,
   346    umount /var/cache/*,
   347    umount /var/cache/*/,
   348  `
   349  	// Find the slice that describes profile2 by looking for the first unique
   350  	// line of the next profile.
   351  	start = end
   352  	end, _ = s.spec.UpdateNSIndexOf("  # Layout /var/tmp: type tmpfs, mode: 01777\n")
   353  	c.Assert(strings.Join(updateNS[start:end], ""), Equals, profile2)
   354  
   355  	profile3 := `  # Layout /var/tmp: type tmpfs, mode: 01777
   356    mount fstype=tmpfs tmpfs -> /var/tmp/,
   357    mount options=(rprivate) -> /var/tmp/,
   358    umount /var/tmp/,
   359    # Writable mimic /var
   360  `
   361  	// Find the slice that describes profile2 by looking till the end of the list.
   362  	start = end
   363  	c.Assert(strings.Join(updateNS[start:], ""), Equals, profile3)
   364  	c.Assert(strings.Join(updateNS, ""), DeepEquals, strings.Join([]string{profile0, profile1, profile2, profile3}, ""))
   365  }
   366  
   367  const snapTrivial = `
   368  name: some-snap
   369  version: 0
   370  apps:
   371    app:
   372      command: app-command
   373  `
   374  
   375  func (s *specSuite) TestApparmorOvernameSnippetsNotInstanceKeyed(c *C) {
   376  	snapInfo := snaptest.MockInfo(c, snapTrivial, &snap.SideInfo{Revision: snap.R(42)})
   377  	restore := apparmor.SetSpecScope(s.spec, []string{"snap.some-snap.app"})
   378  	defer restore()
   379  
   380  	s.spec.AddOvername(snapInfo)
   381  	c.Assert(s.spec.Snippets(), HasLen, 0)
   382  	// non instance-keyed snaps require no extra snippets
   383  	c.Assert(s.spec.UpdateNS(), HasLen, 0)
   384  }
   385  
   386  func (s *specSuite) TestApparmorOvernameSnippets(c *C) {
   387  	snapInfo := snaptest.MockInfo(c, snapTrivial, &snap.SideInfo{Revision: snap.R(42)})
   388  	snapInfo.InstanceKey = "instance"
   389  
   390  	restore := apparmor.SetSpecScope(s.spec, []string{"snap.some-snap_instace.app"})
   391  	defer restore()
   392  
   393  	s.spec.AddOvername(snapInfo)
   394  	c.Assert(s.spec.Snippets(), HasLen, 0)
   395  
   396  	updateNS := s.spec.UpdateNS()
   397  	c.Assert(updateNS, HasLen, 1)
   398  
   399  	profile := `  # Allow parallel instance snap mount namespace adjustments
   400    mount options=(rw rbind) /snap/some-snap_instance/ -> /snap/some-snap/,
   401    mount options=(rw rbind) /var/snap/some-snap_instance/ -> /var/snap/some-snap/,
   402  `
   403  	c.Assert(updateNS[0], Equals, profile)
   404  }
   405  
   406  func (s *specSuite) TestUsesPtraceTrace(c *C) {
   407  	c.Assert(s.spec.UsesPtraceTrace(), Equals, false)
   408  	s.spec.SetUsesPtraceTrace()
   409  	c.Assert(s.spec.UsesPtraceTrace(), Equals, true)
   410  }
   411  
   412  func (s *specSuite) TestSuppressPtraceTrace(c *C) {
   413  	c.Assert(s.spec.SuppressPtraceTrace(), Equals, false)
   414  	s.spec.SetSuppressPtraceTrace()
   415  	c.Assert(s.spec.SuppressPtraceTrace(), Equals, true)
   416  }
   417  
   418  func (s *specSuite) TestSetSuppressHomeIx(c *C) {
   419  	c.Assert(s.spec.SuppressHomeIx(), Equals, false)
   420  	s.spec.SetSuppressHomeIx()
   421  	c.Assert(s.spec.SuppressHomeIx(), Equals, true)
   422  }