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 }