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

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2021 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  	"io/ioutil"
    24  	"os"
    25  	"path/filepath"
    26  	"strings"
    27  
    28  	. "gopkg.in/check.v1"
    29  
    30  	"gitee.com/mysnapcore/mysnapd/dirs"
    31  	"gitee.com/mysnapcore/mysnapd/interfaces"
    32  	"gitee.com/mysnapcore/mysnapd/interfaces/apparmor"
    33  	"gitee.com/mysnapcore/mysnapd/interfaces/builtin"
    34  	"gitee.com/mysnapcore/mysnapd/interfaces/polkit"
    35  	"gitee.com/mysnapcore/mysnapd/osutil"
    36  	"gitee.com/mysnapcore/mysnapd/snap"
    37  	"gitee.com/mysnapcore/mysnapd/snap/snaptest"
    38  	"gitee.com/mysnapcore/mysnapd/testutil"
    39  )
    40  
    41  type polkitInterfaceSuite struct {
    42  	testutil.BaseTest
    43  
    44  	iface    interfaces.Interface
    45  	slot     *interfaces.ConnectedSlot
    46  	slotInfo *snap.SlotInfo
    47  	plug     *interfaces.ConnectedPlug
    48  	plugInfo *snap.PlugInfo
    49  }
    50  
    51  var _ = Suite(&polkitInterfaceSuite{
    52  	iface: builtin.MustInterface("polkit"),
    53  })
    54  
    55  func (s *polkitInterfaceSuite) SetUpTest(c *C) {
    56  	s.BaseTest.SetUpTest(c)
    57  	dirs.SetRootDir(c.MkDir())
    58  	s.AddCleanup(func() {
    59  		dirs.SetRootDir("/")
    60  	})
    61  
    62  	const mockPlugSnapInfo = `name: other
    63  version: 1.0
    64  plugs:
    65   polkit:
    66    action-prefix: org.example.foo
    67  apps:
    68   app:
    69    command: foo
    70    plugs: [polkit]
    71  `
    72  	s.slotInfo = &snap.SlotInfo{
    73  		Snap:      &snap.Info{SuggestedName: "core", SnapType: snap.TypeOS},
    74  		Name:      "polkit",
    75  		Interface: "polkit",
    76  	}
    77  	s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil, nil)
    78  	plugSnap := snaptest.MockSnap(c, mockPlugSnapInfo, &snap.SideInfo{
    79  		RealName: "other",
    80  		Revision: snap.R(1),
    81  	})
    82  	s.plugInfo = plugSnap.Plugs["polkit"]
    83  	s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil, nil)
    84  }
    85  
    86  func (s *polkitInterfaceSuite) TestName(c *C) {
    87  	c.Assert(s.iface.Name(), Equals, "polkit")
    88  }
    89  
    90  func (s *polkitInterfaceSuite) TestConnectedPlugAppArmor(c *C) {
    91  	apparmorSpec := &apparmor.Specification{}
    92  	err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot)
    93  	c.Assert(err, IsNil)
    94  	c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"})
    95  	c.Check(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, "# Description: Can talk to polkitd's CheckAuthorization API")
    96  	c.Check(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `member="{,Cancel}CheckAuthorization"`)
    97  }
    98  
    99  func (s *polkitInterfaceSuite) TestConnectedPlugPolkit(c *C) {
   100  	const samplePolicy1 = `<policyconfig>
   101    <action id="org.example.foo.some-action">
   102      <description>Some action</description>
   103      <message>Authentication is required to do some action</message>
   104      <defaults>
   105        <allow_any>no</allow_any>
   106        <allow_inactive>no</allow_inactive>
   107        <allow_active>auth_admin</allow_active>
   108      </defaults>
   109    </action>
   110  </policyconfig>`
   111  	const samplePolicy2 = `<policyconfig/>`
   112  
   113  	c.Assert(os.MkdirAll(filepath.Join(s.plugInfo.Snap.MountDir(), "meta/polkit"), 0755), IsNil)
   114  	policyPath := filepath.Join(s.plugInfo.Snap.MountDir(), "meta/polkit/polkit.foo.policy")
   115  	c.Assert(ioutil.WriteFile(policyPath, []byte(samplePolicy1), 0644), IsNil)
   116  	policyPath = filepath.Join(s.plugInfo.Snap.MountDir(), "meta/polkit/polkit.bar.policy")
   117  	c.Assert(ioutil.WriteFile(policyPath, []byte(samplePolicy2), 0644), IsNil)
   118  
   119  	polkitSpec := &polkit.Specification{}
   120  	err := polkitSpec.AddConnectedPlug(s.iface, s.plug, s.slot)
   121  	c.Assert(err, IsNil)
   122  
   123  	c.Check(polkitSpec.Policies(), DeepEquals, map[string]polkit.Policy{
   124  		"polkit.foo": polkit.Policy(samplePolicy1),
   125  		"polkit.bar": polkit.Policy(samplePolicy2),
   126  	})
   127  }
   128  
   129  func (s *polkitInterfaceSuite) TestConnectedPlugPolkitMissing(c *C) {
   130  	polkitSpec := &polkit.Specification{}
   131  	err := polkitSpec.AddConnectedPlug(s.iface, s.plug, s.slot)
   132  	c.Check(err, ErrorMatches, `cannot find any policy files for plug "polkit"`)
   133  }
   134  
   135  func (s *polkitInterfaceSuite) TestConnectedPlugPolkitNotFile(c *C) {
   136  	c.Assert(os.MkdirAll(filepath.Join(s.plugInfo.Snap.MountDir(), "meta/polkit"), 0755), IsNil)
   137  	policyPath := filepath.Join(s.plugInfo.Snap.MountDir(), "meta/polkit/polkit.foo.policy")
   138  	c.Assert(os.Mkdir(policyPath, 0755), IsNil)
   139  
   140  	polkitSpec := &polkit.Specification{}
   141  	err := polkitSpec.AddConnectedPlug(s.iface, s.plug, s.slot)
   142  	c.Check(err, ErrorMatches, `cannot read file ".*/meta/polkit/polkit.foo.policy": read .*: is a directory`)
   143  }
   144  
   145  func (s *polkitInterfaceSuite) TestConnectedPlugPolkitBadXML(c *C) {
   146  	const samplePolicy = `<malformed`
   147  	c.Assert(os.MkdirAll(filepath.Join(s.plugInfo.Snap.MountDir(), "meta/polkit"), 0755), IsNil)
   148  	policyPath := filepath.Join(s.plugInfo.Snap.MountDir(), "meta/polkit/polkit.foo.policy")
   149  	c.Assert(ioutil.WriteFile(policyPath, []byte(samplePolicy), 0644), IsNil)
   150  
   151  	polkitSpec := &polkit.Specification{}
   152  	err := polkitSpec.AddConnectedPlug(s.iface, s.plug, s.slot)
   153  	c.Check(err, ErrorMatches, `cannot validate policy file ".*/meta/polkit/polkit.foo.policy": XML syntax error on line 1: unexpected EOF`)
   154  }
   155  
   156  func (s *polkitInterfaceSuite) TestConnectedPlugPolkitBadAction(c *C) {
   157  	const samplePolicy = `<policyconfig>
   158    <action id="org.freedesktop.systemd1.manage-units">
   159      <description>A conflict with systemd's polkit actions</description>
   160      <message>Manage system services</message>
   161      <defaults>
   162        <allow_any>yes</allow_any>
   163      </defaults>
   164    </action>
   165  </policyconfig>`
   166  	c.Assert(os.MkdirAll(filepath.Join(s.plugInfo.Snap.MountDir(), "meta/polkit"), 0755), IsNil)
   167  	policyPath := filepath.Join(s.plugInfo.Snap.MountDir(), "meta/polkit/polkit.foo.policy")
   168  	c.Assert(ioutil.WriteFile(policyPath, []byte(samplePolicy), 0644), IsNil)
   169  
   170  	polkitSpec := &polkit.Specification{}
   171  	err := polkitSpec.AddConnectedPlug(s.iface, s.plug, s.slot)
   172  	c.Check(err, ErrorMatches, `policy file ".*/meta/polkit/polkit.foo.policy" contains unexpected action ID "org.freedesktop.systemd1.manage-units"`)
   173  }
   174  
   175  func (s *polkitInterfaceSuite) TestConnectedPlugPolkitBadImplies(c *C) {
   176  	const samplePolicy = `<policyconfig>
   177    <action id="org.example.foo.some-action">
   178      <description>Some action</description>
   179      <message>Allow "some action" (and also managing system services for some reason)</message>
   180      <defaults>
   181        <allow_any>yes</allow_any>
   182      </defaults>
   183      <annotate key="org.freedesktop.policykit.imply">org.freedesktop.systemd1.manage-units</annotate>
   184    </action>
   185  </policyconfig>`
   186  	c.Assert(os.MkdirAll(filepath.Join(s.plugInfo.Snap.MountDir(), "meta/polkit"), 0755), IsNil)
   187  	policyPath := filepath.Join(s.plugInfo.Snap.MountDir(), "meta/polkit/polkit.foo.policy")
   188  	c.Assert(ioutil.WriteFile(policyPath, []byte(samplePolicy), 0644), IsNil)
   189  
   190  	polkitSpec := &polkit.Specification{}
   191  	err := polkitSpec.AddConnectedPlug(s.iface, s.plug, s.slot)
   192  	c.Check(err, ErrorMatches, `policy file ".*/meta/polkit/polkit.foo.policy" contains unexpected action ID "org.freedesktop.systemd1.manage-units"`)
   193  }
   194  
   195  func (s *polkitInterfaceSuite) TestSanitizeSlot(c *C) {
   196  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil)
   197  }
   198  
   199  func (s *polkitInterfaceSuite) TestSanitizePlug(c *C) {
   200  	c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil)
   201  }
   202  
   203  func (s *polkitInterfaceSuite) TestSanitizePlugHappy(c *C) {
   204  	const mockSnapYaml = `name: polkit-plug-snap
   205  version: 1.0
   206  plugs:
   207   polkit:
   208    action-prefix: org.example.bar
   209  `
   210  	info := snaptest.MockInfo(c, mockSnapYaml, nil)
   211  	plug := info.Plugs["polkit"]
   212  	c.Assert(interfaces.BeforePreparePlug(s.iface, plug), IsNil)
   213  }
   214  
   215  func (s *polkitInterfaceSuite) TestSanitizePlugUnhappy(c *C) {
   216  	const mockSnapYaml = `name: polkit-plug-snap
   217  version: 1.0
   218  plugs:
   219   polkit:
   220    $t
   221  `
   222  	var testCases = []struct {
   223  		inp    string
   224  		errStr string
   225  	}{
   226  		{"", `snap "polkit-plug-snap" does not have attribute "action-prefix" for interface "polkit"`},
   227  		{`action-prefix: true`, `snap "polkit-plug-snap" has interface "polkit" with invalid value type bool for "action-prefix" attribute: \*string`},
   228  		{`action-prefix: 42`, `snap "polkit-plug-snap" has interface "polkit" with invalid value type int64 for "action-prefix" attribute: \*string`},
   229  		{`action-prefix: []`, `snap "polkit-plug-snap" has interface "polkit" with invalid value type \[\]interface {} for "action-prefix" attribute: \*string`},
   230  		{`action-prefix: {}`, `snap "polkit-plug-snap" has interface "polkit" with invalid value type map\[string\]interface {} for "action-prefix" attribute: \*string`},
   231  
   232  		{`action-prefix: ""`, `plug has invalid action-prefix: ""`},
   233  		{`action-prefix: "org"`, `plug has invalid action-prefix: "org"`},
   234  		{`action-prefix: "a+b"`, `plug has invalid action-prefix: "a\+b"`},
   235  		{`action-prefix: "org.example\n"`, `plug has invalid action-prefix: "org.example\\n"`},
   236  		{`action-prefix: "com.example "`, `plug has invalid action-prefix: "com.example "`},
   237  		{`action-prefix: "123.foo.bar"`, `plug has invalid action-prefix: "123.foo.bar"`},
   238  	}
   239  
   240  	for _, t := range testCases {
   241  		yml := strings.Replace(mockSnapYaml, "$t", t.inp, -1)
   242  		info := snaptest.MockInfo(c, yml, nil)
   243  		plug := info.Plugs["polkit"]
   244  
   245  		c.Check(interfaces.BeforePreparePlug(s.iface, plug), ErrorMatches, t.errStr, Commentf("unexpected error for %q", t.inp))
   246  	}
   247  }
   248  
   249  func (s *polkitInterfaceSuite) TestConnectedPlugPolkitInternalError(c *C) {
   250  	const mockPlugSnapInfo = `name: other
   251  version: 1.0
   252  plugs:
   253   polkit:
   254    action-prefix: false
   255  apps:
   256   app:
   257    command: foo
   258    plugs: [polkit]
   259  `
   260  	s.slotInfo = &snap.SlotInfo{
   261  		Snap:      &snap.Info{SuggestedName: "core", SnapType: snap.TypeOS},
   262  		Name:      "polkit",
   263  		Interface: "polkit",
   264  	}
   265  	s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil, nil)
   266  	plugSnap := snaptest.MockInfo(c, mockPlugSnapInfo, nil)
   267  	s.plugInfo = plugSnap.Plugs["polkit"]
   268  	s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil, nil)
   269  
   270  	polkitSpec := &polkit.Specification{}
   271  	err := polkitSpec.AddConnectedPlug(s.iface, s.plug, s.slot)
   272  	c.Assert(err, ErrorMatches, `snap "other" has interface "polkit" with invalid value type bool for "action-prefix" attribute: \*string`)
   273  }
   274  
   275  func (s *polkitInterfaceSuite) TestStaticInfo(c *C) {
   276  	si := interfaces.StaticInfoOf(s.iface)
   277  	c.Check(si.ImplicitOnCore, Equals, osutil.IsExecutable("/usr/libexec/polkitd"))
   278  	c.Check(si.ImplicitOnClassic, Equals, true)
   279  	c.Check(si.Summary, Equals, "allows access to polkitd to check authorisation")
   280  	c.Check(si.BaseDeclarationPlugs, testutil.Contains, "polkit")
   281  	c.Check(si.BaseDeclarationSlots, testutil.Contains, "polkit")
   282  }
   283  
   284  func (s *polkitInterfaceSuite) TestInterfaces(c *C) {
   285  	c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface)
   286  }