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 }