gitee.com/mysnapcore/mysnapd@v0.1.0/interfaces/builtin/cups_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2020 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  	"fmt"
    24  	"strings"
    25  
    26  	. "gopkg.in/check.v1"
    27  
    28  	"gitee.com/mysnapcore/mysnapd/interfaces"
    29  	"gitee.com/mysnapcore/mysnapd/interfaces/apparmor"
    30  	"gitee.com/mysnapcore/mysnapd/interfaces/builtin"
    31  	"gitee.com/mysnapcore/mysnapd/interfaces/mount"
    32  	"gitee.com/mysnapcore/mysnapd/osutil"
    33  	"gitee.com/mysnapcore/mysnapd/snap"
    34  	"gitee.com/mysnapcore/mysnapd/testutil"
    35  )
    36  
    37  type cupsSuite struct {
    38  	iface interfaces.Interface
    39  
    40  	plugInfo *snap.PlugInfo
    41  	plug     *interfaces.ConnectedPlug
    42  
    43  	providerSlotInfo *snap.SlotInfo
    44  	providerSlot     *interfaces.ConnectedSlot
    45  
    46  	providerLegacySlotInfo *snap.SlotInfo
    47  	providerLegacySlot     *interfaces.ConnectedSlot
    48  }
    49  
    50  var _ = Suite(&cupsSuite{iface: builtin.MustInterface("cups")})
    51  
    52  const cupsConsumerYaml = `name: consumer
    53  version: 0
    54  apps:
    55   app:
    56    plugs: [cups]
    57  `
    58  
    59  const cupsProviderYaml = `name: provider
    60  version: 0
    61  slots:
    62    cups-socket:
    63      interface: cups
    64      cups-socket-directory: $SNAP_COMMON/foo-subdir
    65  apps:
    66   app:
    67    slots: [cups-socket]
    68  `
    69  
    70  const cupsProviderLegacyYaml = `name: provider
    71  version: 0
    72  slots:
    73    # no attribute
    74    cups: {}
    75  `
    76  
    77  func (s *cupsSuite) SetUpTest(c *C) {
    78  	s.plug, s.plugInfo = MockConnectedPlug(c, cupsConsumerYaml, nil, "cups")
    79  	s.providerSlot, s.providerSlotInfo = MockConnectedSlot(c, cupsProviderYaml, nil, "cups-socket")
    80  	s.providerLegacySlot, s.providerLegacySlotInfo = MockConnectedSlot(c, cupsProviderLegacyYaml, nil, "cups")
    81  }
    82  
    83  func (s *cupsSuite) TestName(c *C) {
    84  	c.Assert(s.iface.Name(), Equals, "cups")
    85  }
    86  
    87  func (s *cupsSuite) TestSanitizeSlot(c *C) {
    88  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.providerSlotInfo), IsNil)
    89  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.providerLegacySlotInfo), IsNil)
    90  }
    91  
    92  const invalidCupsProviderYamlFmt = `name: provider
    93  version: 0
    94  slots:
    95    cups-socket:
    96      interface: cups
    97      # regex not allowed
    98      cups-socket-directory: %s
    99  apps:
   100   app:
   101    slots: [cups-socket]
   102  `
   103  
   104  func (s *cupsSuite) TestSanitizeInvalidSlot(c *C) {
   105  	tt := []struct {
   106  		snippet string
   107  		err     string
   108  	}{
   109  		{
   110  			"$SNAP_COMMON/foo-subdir/*",
   111  			"cups-socket-directory is not usable: .* contains a reserved apparmor char .*",
   112  		},
   113  		{
   114  			"$SNAP_COMMON/foo-subdir/../../../",
   115  			`cups-socket-directory is not clean: \"\$SNAP_COMMON/foo-subdir/../../../\"`,
   116  		},
   117  		{
   118  			"$SNAP/foo",
   119  			`cups-socket-directory must be a directory of \$SNAP_COMMON or \$SNAP_DATA`,
   120  		},
   121  	}
   122  
   123  	for _, t := range tt {
   124  		yaml := fmt.Sprintf(invalidCupsProviderYamlFmt, t.snippet)
   125  
   126  		_, invalidSlotInfo := MockConnectedSlot(c, yaml, nil, "cups-socket")
   127  
   128  		err := interfaces.BeforePrepareSlot(s.iface, invalidSlotInfo)
   129  		c.Assert(err, ErrorMatches, t.err)
   130  	}
   131  }
   132  
   133  func (s *cupsSuite) TestSanitizePlug(c *C) {
   134  	c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil)
   135  }
   136  
   137  const expSnapUpdateNsSnippet = `  # Mount cupsd socket from cups snap to client snap
   138    mount options=(rw bind) "/var/snap/provider/common/foo-subdir/" -> /var/cups/,
   139    umount /var/cups/,
   140    # Writable directory /var/snap/provider/common/foo-subdir
   141    "/var/snap/provider/common/foo-subdir/" rw,
   142    "/var/snap/provider/common/" rw,
   143    "/var/snap/provider/" rw,
   144    # Writable mimic /var
   145    # .. permissions for traversing the prefix that is assumed to exist
   146    # .. variant with mimic at /
   147    # Allow reading the mimic directory, it must exist in the first place.
   148    "/" r,
   149    # Allow setting the read-only directory aside via a bind mount.
   150    "/tmp/.snap/" rw,
   151    mount options=(rbind, rw) "/" -> "/tmp/.snap/",
   152    # Allow mounting tmpfs over the read-only directory.
   153    mount fstype=tmpfs options=(rw) tmpfs -> "/",
   154    # Allow creating empty files and directories for bind mounting things
   155    # to reconstruct the now-writable parent directory.
   156    "/tmp/.snap/*/" rw,
   157    "/*/" rw,
   158    mount options=(rbind, rw) "/tmp/.snap/*/" -> "/*/",
   159    "/tmp/.snap/*" rw,
   160    "/*" rw,
   161    mount options=(bind, rw) "/tmp/.snap/*" -> "/*",
   162    # Allow unmounting the auxiliary directory.
   163    # TODO: use fstype=tmpfs here for more strictness (LP: #1613403)
   164    mount options=(rprivate) -> "/tmp/.snap/",
   165    umount "/tmp/.snap/",
   166    # Allow unmounting the destination directory as well as anything
   167    # inside.  This lets us perform the undo plan in case the writable
   168    # mimic fails.
   169    mount options=(rprivate) -> "/",
   170    mount options=(rprivate) -> "/*",
   171    mount options=(rprivate) -> "/*/",
   172    umount "/",
   173    umount "/*",
   174    umount "/*/",
   175    # .. variant with mimic at /var/
   176    "/var/" r,
   177    "/tmp/.snap/var/" rw,
   178    mount options=(rbind, rw) "/var/" -> "/tmp/.snap/var/",
   179    mount fstype=tmpfs options=(rw) tmpfs -> "/var/",
   180    "/tmp/.snap/var/*/" rw,
   181    "/var/*/" rw,
   182    mount options=(rbind, rw) "/tmp/.snap/var/*/" -> "/var/*/",
   183    "/tmp/.snap/var/*" rw,
   184    "/var/*" rw,
   185    mount options=(bind, rw) "/tmp/.snap/var/*" -> "/var/*",
   186    mount options=(rprivate) -> "/tmp/.snap/var/",
   187    umount "/tmp/.snap/var/",
   188    mount options=(rprivate) -> "/var/",
   189    mount options=(rprivate) -> "/var/*",
   190    mount options=(rprivate) -> "/var/*/",
   191    umount "/var/",
   192    umount "/var/*",
   193    umount "/var/*/",
   194  `
   195  
   196  func (s *cupsSuite) TestAppArmorSpec(c *C) {
   197  	// consumer to provider on core for ConnectedPlug
   198  	spec := &apparmor.Specification{}
   199  	c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.providerSlot), IsNil)
   200  	c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"})
   201  	c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "# Allow communicating with the cups server")
   202  	// no cups abstractions
   203  	c.Assert(spec.SnippetForTag("snap.consumer.app"), Not(testutil.Contains), "#include <abstractions/cups-client>")
   204  	// but has the lpoptions config file though
   205  	c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `owner @{HOME}/.cups/lpoptions r,`)
   206  	// the special mount rules are present
   207  	c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `"/var/snap/provider/common/foo-subdir/**" mrwklix,`)
   208  	// the writable mimic profile for snap-update-ns is generated as well
   209  	c.Assert(strings.Join(spec.UpdateNS(), ""), Equals, expSnapUpdateNsSnippet)
   210  
   211  	// consumer to legacy provider
   212  	specLegacy := &apparmor.Specification{}
   213  	c.Assert(specLegacy.AddConnectedPlug(s.iface, s.plug, s.providerLegacySlot), IsNil)
   214  	c.Assert(specLegacy.SecurityTags(), DeepEquals, []string{"snap.consumer.app"})
   215  	c.Assert(specLegacy.SnippetForTag("snap.consumer.app"), testutil.Contains, "# Allow communicating with the cups server")
   216  	// no cups abstractions
   217  	c.Assert(specLegacy.SnippetForTag("snap.consumer.app"), Not(testutil.Contains), "#include <abstractions/cups-client>")
   218  	// but has the lpoptions config file though
   219  	c.Assert(specLegacy.SnippetForTag("snap.consumer.app"), testutil.Contains, `owner @{HOME}/.cups/lpoptions r,`)
   220  	// no special mounting rules
   221  	c.Assert(specLegacy.SnippetForTag("snap.consumer.app"), Not(testutil.Contains), "/var/snap/provider/common/foo-subdir/** mrwklix,")
   222  	// no writable mimic profile for snap-update-ns
   223  	c.Assert(specLegacy.UpdateNS(), HasLen, 0)
   224  }
   225  
   226  func (s *cupsSuite) TestMountSpec(c *C) {
   227  	// consumer to provider on core for ConnectedPlug
   228  	spec := &mount.Specification{}
   229  	c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.providerSlot), IsNil)
   230  	// mount entry for /var/cups/ for all namespaces
   231  	c.Assert(spec.MountEntries(), DeepEquals, []osutil.MountEntry{
   232  		{
   233  			Name:    "/var/snap/provider/common/foo-subdir",
   234  			Dir:     "/var/cups/",
   235  			Options: []string{"bind", "rw"},
   236  		},
   237  	})
   238  	// no user specific mounts
   239  	c.Assert(spec.UserMountEntries(), HasLen, 0)
   240  
   241  	// consumer to legacy provider has no mounts at all
   242  	specLegacy := &mount.Specification{}
   243  	c.Assert(specLegacy.AddConnectedPlug(s.iface, s.plug, s.providerLegacySlot), IsNil)
   244  	c.Assert(specLegacy.MountEntries(), HasLen, 0)
   245  	c.Assert(specLegacy.UserMountEntries(), HasLen, 0)
   246  }
   247  
   248  func (s *cupsSuite) TestStaticInfo(c *C) {
   249  	si := interfaces.StaticInfoOf(s.iface)
   250  	c.Assert(si.ImplicitOnCore, Equals, false)
   251  	c.Assert(si.ImplicitOnClassic, Equals, false)
   252  	c.Assert(si.Summary, Equals, `allows access to the CUPS socket for printing`)
   253  	c.Assert(si.BaseDeclarationSlots, testutil.Contains, "cups")
   254  	c.Assert(si.BaseDeclarationSlots, testutil.Contains, "deny-connection: true")
   255  	c.Assert(si.BaseDeclarationSlots, testutil.Contains, "deny-auto-connection: true")
   256  }
   257  
   258  func (s *cupsSuite) TestInterfaces(c *C) {
   259  	c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface)
   260  }