github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/interfaces/dbus/backend_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 dbus_test
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  
    28  	. "gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/dirs"
    31  	"github.com/snapcore/snapd/interfaces"
    32  	"github.com/snapcore/snapd/interfaces/dbus"
    33  	"github.com/snapcore/snapd/interfaces/ifacetest"
    34  	"github.com/snapcore/snapd/snap"
    35  	"github.com/snapcore/snapd/snap/snaptest"
    36  	"github.com/snapcore/snapd/snapdtool"
    37  	"github.com/snapcore/snapd/testutil"
    38  )
    39  
    40  type backendSuite struct {
    41  	ifacetest.BackendSuite
    42  }
    43  
    44  var _ = Suite(&backendSuite{})
    45  
    46  var testedConfinementOpts = []interfaces.ConfinementOptions{
    47  	{},
    48  	{DevMode: true},
    49  	{JailMode: true},
    50  	{Classic: true},
    51  }
    52  
    53  func (s *backendSuite) SetUpTest(c *C) {
    54  	s.Backend = &dbus.Backend{}
    55  	s.BackendSuite.SetUpTest(c)
    56  	c.Assert(s.Repo.AddBackend(s.Backend), IsNil)
    57  
    58  	// Prepare a directory for DBus configuration files.
    59  	// NOTE: Normally this is a part of the OS snap.
    60  	err := os.MkdirAll(dirs.SnapDBusSystemPolicyDir, 0700)
    61  	c.Assert(err, IsNil)
    62  }
    63  
    64  func (s *backendSuite) TearDownTest(c *C) {
    65  	s.BackendSuite.TearDownTest(c)
    66  }
    67  
    68  // Tests for Setup() and Remove()
    69  func (s *backendSuite) TestName(c *C) {
    70  	c.Check(s.Backend.Name(), Equals, interfaces.SecurityDBus)
    71  }
    72  
    73  func (s *backendSuite) TestInstallingSnapWritesConfigFiles(c *C) {
    74  	// NOTE: Hand out a permanent snippet so that .conf file is generated.
    75  	s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *snap.SlotInfo) error {
    76  		spec.AddSnippet("<policy/>")
    77  		return nil
    78  	}
    79  	for _, opts := range testedConfinementOpts {
    80  		snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 0)
    81  		profile := filepath.Join(dirs.SnapDBusSystemPolicyDir, "snap.samba.smbd.conf")
    82  		// file called "snap.sambda.smbd.conf" was created
    83  		_, err := os.Stat(profile)
    84  		c.Check(err, IsNil)
    85  		s.RemoveSnap(c, snapInfo)
    86  	}
    87  }
    88  
    89  func (s *backendSuite) TestInstallingSnapWithHookWritesConfigFiles(c *C) {
    90  	// NOTE: Hand out a permanent snippet so that .conf file is generated.
    91  	s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *snap.SlotInfo) error {
    92  		spec.AddSnippet("<policy/>")
    93  		return nil
    94  	}
    95  	s.Iface.DBusPermanentPlugCallback = func(spec *dbus.Specification, plug *snap.PlugInfo) error {
    96  		spec.AddSnippet("<policy/>")
    97  		return nil
    98  	}
    99  	for _, opts := range testedConfinementOpts {
   100  		snapInfo := s.InstallSnap(c, opts, "", ifacetest.HookYaml, 0)
   101  		profile := filepath.Join(dirs.SnapDBusSystemPolicyDir, "snap.foo.hook.configure.conf")
   102  
   103  		// Verify that "snap.foo.hook.configure.conf" was created
   104  		_, err := os.Stat(profile)
   105  		c.Check(err, IsNil)
   106  		s.RemoveSnap(c, snapInfo)
   107  	}
   108  }
   109  
   110  func (s *backendSuite) TestRemovingSnapRemovesConfigFiles(c *C) {
   111  	// NOTE: Hand out a permanent snippet so that .conf file is generated.
   112  	s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *snap.SlotInfo) error {
   113  		spec.AddSnippet("<policy/>")
   114  		return nil
   115  	}
   116  	for _, opts := range testedConfinementOpts {
   117  		snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 0)
   118  		s.RemoveSnap(c, snapInfo)
   119  		profile := filepath.Join(dirs.SnapDBusSystemPolicyDir, "snap.samba.smbd.conf")
   120  		// file called "snap.sambda.smbd.conf" was removed
   121  		_, err := os.Stat(profile)
   122  		c.Check(os.IsNotExist(err), Equals, true)
   123  	}
   124  }
   125  
   126  func (s *backendSuite) TestRemovingSnapWithHookRemovesConfigFiles(c *C) {
   127  	// NOTE: Hand out a permanent snippet so that .conf file is generated.
   128  	s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *snap.SlotInfo) error {
   129  		spec.AddSnippet("<policy/>")
   130  		return nil
   131  	}
   132  	s.Iface.DBusPermanentPlugCallback = func(spec *dbus.Specification, plug *snap.PlugInfo) error {
   133  		spec.AddSnippet("<policy/>")
   134  		return nil
   135  	}
   136  	for _, opts := range testedConfinementOpts {
   137  		snapInfo := s.InstallSnap(c, opts, "", ifacetest.HookYaml, 0)
   138  		s.RemoveSnap(c, snapInfo)
   139  		profile := filepath.Join(dirs.SnapDBusSystemPolicyDir, "snap.foo.hook.configure.conf")
   140  
   141  		// Verify that "snap.foo.hook.configure.conf" was removed
   142  		_, err := os.Stat(profile)
   143  		c.Check(os.IsNotExist(err), Equals, true)
   144  	}
   145  }
   146  
   147  func (s *backendSuite) TestUpdatingSnapToOneWithMoreApps(c *C) {
   148  	// NOTE: Hand out a permanent snippet so that .conf file is generated.
   149  	s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *snap.SlotInfo) error {
   150  		spec.AddSnippet("<policy/>")
   151  		return nil
   152  	}
   153  	for _, opts := range testedConfinementOpts {
   154  		snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 0)
   155  		snapInfo = s.UpdateSnap(c, snapInfo, opts, ifacetest.SambaYamlV1WithNmbd, 0)
   156  		profile := filepath.Join(dirs.SnapDBusSystemPolicyDir, "snap.samba.nmbd.conf")
   157  		// file called "snap.sambda.nmbd.conf" was created
   158  		_, err := os.Stat(profile)
   159  		c.Check(err, IsNil)
   160  		s.RemoveSnap(c, snapInfo)
   161  	}
   162  }
   163  
   164  func (s *backendSuite) TestUpdatingSnapToOneWithMoreHooks(c *C) {
   165  	// NOTE: Hand out a permanent snippet so that .conf file is generated.
   166  	s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *snap.SlotInfo) error {
   167  		spec.AddSnippet("<policy/>")
   168  		return nil
   169  	}
   170  	s.Iface.DBusPermanentPlugCallback = func(spec *dbus.Specification, plug *snap.PlugInfo) error {
   171  		spec.AddSnippet("<policy/>")
   172  		return nil
   173  	}
   174  	for _, opts := range testedConfinementOpts {
   175  		snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 0)
   176  		snapInfo = s.UpdateSnap(c, snapInfo, opts, ifacetest.SambaYamlWithHook, 0)
   177  		profile := filepath.Join(dirs.SnapDBusSystemPolicyDir, "snap.samba.hook.configure.conf")
   178  
   179  		// Verify that "snap.samba.hook.configure.conf" was created
   180  		_, err := os.Stat(profile)
   181  		c.Check(err, IsNil)
   182  		s.RemoveSnap(c, snapInfo)
   183  	}
   184  }
   185  
   186  func (s *backendSuite) TestUpdatingSnapToOneWithFewerApps(c *C) {
   187  	// NOTE: Hand out a permanent snippet so that .conf file is generated.
   188  	s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *snap.SlotInfo) error {
   189  		spec.AddSnippet("<policy/>")
   190  		return nil
   191  	}
   192  	for _, opts := range testedConfinementOpts {
   193  		snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1WithNmbd, 0)
   194  		snapInfo = s.UpdateSnap(c, snapInfo, opts, ifacetest.SambaYamlV1, 0)
   195  		profile := filepath.Join(dirs.SnapDBusSystemPolicyDir, "snap.samba.nmbd.conf")
   196  		// file called "snap.sambda.nmbd.conf" was removed
   197  		_, err := os.Stat(profile)
   198  		c.Check(os.IsNotExist(err), Equals, true)
   199  		s.RemoveSnap(c, snapInfo)
   200  	}
   201  }
   202  
   203  func (s *backendSuite) TestUpdatingSnapToOneWithFewerHooks(c *C) {
   204  	// NOTE: Hand out a permanent snippet so that .conf file is generated.
   205  	s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *snap.SlotInfo) error {
   206  		spec.AddSnippet("<policy/>")
   207  		return nil
   208  	}
   209  	s.Iface.DBusPermanentPlugCallback = func(spec *dbus.Specification, plug *snap.PlugInfo) error {
   210  		spec.AddSnippet("<policy/>")
   211  		return nil
   212  	}
   213  	for _, opts := range testedConfinementOpts {
   214  		snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlWithHook, 0)
   215  		snapInfo = s.UpdateSnap(c, snapInfo, opts, ifacetest.SambaYamlV1, 0)
   216  		profile := filepath.Join(dirs.SnapDBusSystemPolicyDir, "snap.samba.hook.configure.conf")
   217  
   218  		// Verify that "snap.samba.hook.configure.conf" was removed
   219  		_, err := os.Stat(profile)
   220  		c.Check(os.IsNotExist(err), Equals, true)
   221  		s.RemoveSnap(c, snapInfo)
   222  	}
   223  }
   224  
   225  func (s *backendSuite) TestCombineSnippetsWithActualSnippets(c *C) {
   226  	// NOTE: replace the real template with a shorter variant
   227  	restore := dbus.MockXMLEnvelope([]byte("<?xml>\n"), []byte("</xml>"))
   228  	defer restore()
   229  	s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *snap.SlotInfo) error {
   230  		spec.AddSnippet("<policy>...</policy>")
   231  		return nil
   232  	}
   233  	for _, opts := range testedConfinementOpts {
   234  		snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 0)
   235  		profile := filepath.Join(dirs.SnapDBusSystemPolicyDir, "snap.samba.smbd.conf")
   236  		c.Check(profile, testutil.FileEquals, "<?xml>\n<policy>...</policy>\n</xml>")
   237  		stat, err := os.Stat(profile)
   238  		c.Assert(err, IsNil)
   239  		c.Check(stat.Mode(), Equals, os.FileMode(0644))
   240  		s.RemoveSnap(c, snapInfo)
   241  	}
   242  }
   243  
   244  func (s *backendSuite) TestCombineSnippetsWithoutAnySnippets(c *C) {
   245  	for _, opts := range testedConfinementOpts {
   246  		snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 0)
   247  		profile := filepath.Join(dirs.SnapDBusSystemPolicyDir, "snap.samba.smbd.conf")
   248  		_, err := os.Stat(profile)
   249  		// Without any snippets, there the .conf file is not created.
   250  		c.Check(os.IsNotExist(err), Equals, true)
   251  		s.RemoveSnap(c, snapInfo)
   252  	}
   253  }
   254  
   255  const sambaYamlWithIfaceBoundToNmbd = `
   256  name: samba
   257  version: 1
   258  developer: acme
   259  apps:
   260      smbd:
   261      nmbd:
   262          slots: [iface]
   263  `
   264  
   265  func (s *backendSuite) TestAppBoundIfaces(c *C) {
   266  	// NOTE: Hand out a permanent snippet so that .conf file is generated.
   267  	s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *snap.SlotInfo) error {
   268  		spec.AddSnippet("<policy/>")
   269  		return nil
   270  	}
   271  	// Install a snap with two apps, only one of which needs a .conf file
   272  	// because the interface is app-bound.
   273  	snapInfo := s.InstallSnap(c, interfaces.ConfinementOptions{}, "", sambaYamlWithIfaceBoundToNmbd, 0)
   274  	defer s.RemoveSnap(c, snapInfo)
   275  	// Check that only one of the .conf files is actually created
   276  	_, err := os.Stat(filepath.Join(dirs.SnapDBusSystemPolicyDir, "snap.samba.smbd.conf"))
   277  	c.Check(os.IsNotExist(err), Equals, true)
   278  	_, err = os.Stat(filepath.Join(dirs.SnapDBusSystemPolicyDir, "snap.samba.nmbd.conf"))
   279  	c.Check(err, IsNil)
   280  }
   281  
   282  func (s *backendSuite) TestSandboxFeatures(c *C) {
   283  	c.Assert(s.Backend.SandboxFeatures(), DeepEquals, []string{"mediated-bus-access"})
   284  }
   285  
   286  func makeFakeDbusConfigAndUserdServiceFiles(c *C, coreOrSnapdSnap *snap.Info) {
   287  	err := os.MkdirAll(filepath.Join(coreOrSnapdSnap.MountDir(), "/usr/share/dbus-1/session.d"), 0755)
   288  	c.Assert(err, IsNil)
   289  	content := fmt.Sprintf("content of snapd.session-services.conf for snap %s", coreOrSnapdSnap.InstanceName())
   290  	err = ioutil.WriteFile(filepath.Join(coreOrSnapdSnap.MountDir(), "/usr/share/dbus-1/session.d/snapd.session-services.conf"), []byte(content), 0644)
   291  	c.Assert(err, IsNil)
   292  
   293  	err = os.MkdirAll(filepath.Join(coreOrSnapdSnap.MountDir(), "/usr/share/dbus-1/system.d"), 0755)
   294  	c.Assert(err, IsNil)
   295  	content = fmt.Sprintf("content of snapd.system-services.conf for snap %s", coreOrSnapdSnap.InstanceName())
   296  	err = ioutil.WriteFile(filepath.Join(coreOrSnapdSnap.MountDir(), "/usr/share/dbus-1/system.d/snapd.system-services.conf"), []byte(content), 0644)
   297  	c.Assert(err, IsNil)
   298  
   299  	err = os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "/usr/share/dbus-1/services"), 0755)
   300  	c.Assert(err, IsNil)
   301  
   302  	servicesPath := filepath.Join(coreOrSnapdSnap.MountDir(), "/usr/share/dbus-1/services")
   303  	err = os.MkdirAll(servicesPath, 0755)
   304  	c.Assert(err, IsNil)
   305  
   306  	for _, fn := range []string{
   307  		"io.snapcraft.Launcher.service",
   308  		"io.snapcraft.Settings.service",
   309  	} {
   310  		content := fmt.Sprintf("content of %s for snap %s", fn, coreOrSnapdSnap.InstanceName())
   311  		err = ioutil.WriteFile(filepath.Join(servicesPath, fn), []byte(content), 0644)
   312  		c.Assert(err, IsNil)
   313  	}
   314  }
   315  
   316  var expectedDBusConfigFiles = []string{
   317  	"/usr/share/dbus-1/services/io.snapcraft.Launcher.service",
   318  	"/usr/share/dbus-1/services/io.snapcraft.Settings.service",
   319  	"/usr/share/dbus-1/session.d/snapd.session-services.conf",
   320  	"/usr/share/dbus-1/system.d/snapd.system-services.conf",
   321  }
   322  
   323  func (s *backendSuite) testSetupWritesDbusFilesForCoreOrSnapd(c *C, coreOrSnapdYaml string) {
   324  	coreOrSnapdInfo := snaptest.MockInfo(c, coreOrSnapdYaml, &snap.SideInfo{Revision: snap.R(2)})
   325  	makeFakeDbusConfigAndUserdServiceFiles(c, coreOrSnapdInfo)
   326  
   327  	// Config files are not copied if we haven't reexecuted
   328  	err := s.Backend.Setup(coreOrSnapdInfo, interfaces.ConfinementOptions{}, s.Repo, nil)
   329  	c.Assert(err, IsNil)
   330  
   331  	for _, fn := range expectedDBusConfigFiles {
   332  		c.Check(filepath.Join(dirs.GlobalRootDir, fn), testutil.FileAbsent)
   333  	}
   334  
   335  	// Now make it look like snapd was reexecuted
   336  	restore := snapdtool.MockOsReadlink(func(string) (string, error) {
   337  		return filepath.Join(coreOrSnapdInfo.MountDir(), "/usr/lib/snapd/snapd"), nil
   338  	})
   339  	defer restore()
   340  
   341  	err = s.Backend.Setup(coreOrSnapdInfo, interfaces.ConfinementOptions{}, s.Repo, nil)
   342  	c.Assert(err, IsNil)
   343  
   344  	for _, fn := range expectedDBusConfigFiles {
   345  		c.Check(filepath.Join(dirs.GlobalRootDir, fn), testutil.FilePresent)
   346  	}
   347  }
   348  
   349  var (
   350  	coreYaml  string = "name: core\nversion: 1\ntype: os"
   351  	snapdYaml string = "name: snapd\nversion: 1\ntype: snapd"
   352  )
   353  
   354  func (s *backendSuite) TestSetupWritesDbusFilesForCore(c *C) {
   355  	s.testSetupWritesDbusFilesForCoreOrSnapd(c, coreYaml)
   356  }
   357  
   358  func (s *backendSuite) TestSetupWritesDbusFilesForSnapd(c *C) {
   359  	s.testSetupWritesDbusFilesForCoreOrSnapd(c, snapdYaml)
   360  }
   361  
   362  func (s *backendSuite) TestSetupWritesDbusFilesBothSnapdAndCoreInstalled(c *C) {
   363  	err := os.MkdirAll(filepath.Join(dirs.SnapMountDir, "snapd/current"), 0755)
   364  	c.Assert(err, IsNil)
   365  
   366  	coreInfo := snaptest.MockInfo(c, coreYaml, &snap.SideInfo{Revision: snap.R(2)})
   367  	makeFakeDbusConfigAndUserdServiceFiles(c, coreInfo)
   368  	snapdInfo := snaptest.MockInfo(c, snapdYaml, &snap.SideInfo{Revision: snap.R(3)})
   369  	makeFakeDbusConfigAndUserdServiceFiles(c, snapdInfo)
   370  
   371  	restore := snapdtool.MockOsReadlink(func(string) (string, error) {
   372  		return filepath.Join(snapdInfo.MountDir(), "/usr/lib/snapd/snapd"), nil
   373  	})
   374  	defer restore()
   375  
   376  	// first setup snapd which writes the files
   377  	err = s.Backend.Setup(snapdInfo, interfaces.ConfinementOptions{}, s.Repo, nil)
   378  	c.Assert(err, IsNil)
   379  
   380  	// then setup core - if both are installed snapd should win
   381  	err = s.Backend.Setup(coreInfo, interfaces.ConfinementOptions{}, s.Repo, nil)
   382  	c.Assert(err, IsNil)
   383  
   384  	for _, fn := range expectedDBusConfigFiles {
   385  		c.Check(filepath.Join(dirs.GlobalRootDir, fn), testutil.FileEquals, fmt.Sprintf("content of %s for snap snapd", filepath.Base(fn)))
   386  	}
   387  }