github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/interfaces/policy/basedeclaration_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 policy_test 21 22 import ( 23 "fmt" 24 "strings" 25 26 . "gopkg.in/check.v1" 27 28 "github.com/snapcore/snapd/asserts" 29 "github.com/snapcore/snapd/interfaces" 30 "github.com/snapcore/snapd/interfaces/builtin" 31 "github.com/snapcore/snapd/interfaces/policy" 32 "github.com/snapcore/snapd/release" 33 "github.com/snapcore/snapd/snap" 34 "github.com/snapcore/snapd/snap/snaptest" 35 "github.com/snapcore/snapd/strutil" 36 "github.com/snapcore/snapd/testutil" 37 ) 38 39 type baseDeclSuite struct { 40 baseDecl *asserts.BaseDeclaration 41 restoreSanitize func() 42 } 43 44 var _ = Suite(&baseDeclSuite{}) 45 46 func (s *baseDeclSuite) SetUpSuite(c *C) { 47 s.restoreSanitize = snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}) 48 s.baseDecl = asserts.BuiltinBaseDeclaration() 49 } 50 51 func (s *baseDeclSuite) TearDownSuite(c *C) { 52 s.restoreSanitize() 53 } 54 55 func (s *baseDeclSuite) connectCand(c *C, iface, slotYaml, plugYaml string) *policy.ConnectCandidate { 56 if slotYaml == "" { 57 slotYaml = fmt.Sprintf(`name: slot-snap 58 version: 0 59 slots: 60 %s: 61 `, iface) 62 } 63 if plugYaml == "" { 64 plugYaml = fmt.Sprintf(`name: plug-snap 65 version: 0 66 plugs: 67 %s: 68 `, iface) 69 } 70 slotSnap := snaptest.MockInfo(c, slotYaml, nil) 71 plugSnap := snaptest.MockInfo(c, plugYaml, nil) 72 return &policy.ConnectCandidate{ 73 Plug: interfaces.NewConnectedPlug(plugSnap.Plugs[iface], nil, nil), 74 Slot: interfaces.NewConnectedSlot(slotSnap.Slots[iface], nil, nil), 75 BaseDeclaration: s.baseDecl, 76 } 77 } 78 79 func (s *baseDeclSuite) installSlotCand(c *C, iface string, snapType snap.Type, yaml string) *policy.InstallCandidate { 80 if yaml == "" { 81 yaml = fmt.Sprintf(`name: install-slot-snap 82 version: 0 83 type: %s 84 slots: 85 %s: 86 `, snapType, iface) 87 } 88 snap := snaptest.MockInfo(c, yaml, nil) 89 return &policy.InstallCandidate{ 90 Snap: snap, 91 BaseDeclaration: s.baseDecl, 92 } 93 } 94 95 func (s *baseDeclSuite) installPlugCand(c *C, iface string, snapType snap.Type, yaml string) *policy.InstallCandidate { 96 if yaml == "" { 97 yaml = fmt.Sprintf(`name: install-plug-snap 98 version: 0 99 type: %s 100 plugs: 101 %s: 102 `, snapType, iface) 103 } 104 snap := snaptest.MockInfo(c, yaml, nil) 105 return &policy.InstallCandidate{ 106 Snap: snap, 107 BaseDeclaration: s.baseDecl, 108 } 109 } 110 111 const declTempl = `type: snap-declaration 112 authority-id: canonical 113 series: 16 114 snap-name: @name@ 115 snap-id: @snapid@ 116 publisher-id: @publisher@ 117 @plugsSlots@ 118 timestamp: 2016-09-30T12:00:00Z 119 sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij 120 121 AXNpZw==` 122 123 func (s *baseDeclSuite) mockSnapDecl(c *C, name, snapID, publisher string, plugsSlots string) *asserts.SnapDeclaration { 124 encoded := strings.Replace(declTempl, "@name@", name, 1) 125 encoded = strings.Replace(encoded, "@snapid@", snapID, 1) 126 encoded = strings.Replace(encoded, "@publisher@", publisher, 1) 127 if plugsSlots != "" { 128 encoded = strings.Replace(encoded, "@plugsSlots@", strings.TrimSpace(plugsSlots), 1) 129 } else { 130 encoded = strings.Replace(encoded, "@plugsSlots@\n", "", 1) 131 } 132 a, err := asserts.Decode([]byte(encoded)) 133 c.Assert(err, IsNil) 134 return a.(*asserts.SnapDeclaration) 135 } 136 137 func (s *baseDeclSuite) TestAutoConnection(c *C) { 138 all := builtin.Interfaces() 139 140 // these have more complex or in flux policies and have their 141 // own separate tests 142 snowflakes := map[string]bool{ 143 "content": true, 144 "core-support": true, 145 "home": true, 146 "lxd-support": true, 147 "multipass-support": true, 148 "packagekit-control": true, 149 "snapd-control": true, 150 "dummy": true, 151 } 152 153 // these simply auto-connect, anything else doesn't 154 autoconnect := map[string]bool{ 155 "audio-playback": true, 156 "browser-support": true, 157 "desktop": true, 158 "desktop-legacy": true, 159 "gsettings": true, 160 "media-hub": true, 161 "mir": true, 162 "network": true, 163 "network-bind": true, 164 "network-status": true, 165 "online-accounts-service": true, 166 "opengl": true, 167 "optical-drive": true, 168 "screen-inhibit-control": true, 169 "ubuntu-download-manager": true, 170 "unity7": true, 171 "unity8": true, 172 "upower-observe": true, 173 "wayland": true, 174 "x11": true, 175 } 176 177 for _, iface := range all { 178 if snowflakes[iface.Name()] { 179 continue 180 } 181 expected := autoconnect[iface.Name()] 182 comm := Commentf(iface.Name()) 183 184 // check base declaration 185 cand := s.connectCand(c, iface.Name(), "", "") 186 arity, err := cand.CheckAutoConnect() 187 if expected { 188 c.Check(err, IsNil, comm) 189 c.Check(arity.SlotsPerPlugAny(), Equals, false) 190 } else { 191 c.Check(err, NotNil, comm) 192 } 193 } 194 } 195 196 func (s *baseDeclSuite) TestAutoConnectPlugSlot(c *C) { 197 all := builtin.Interfaces() 198 199 // these have more complex or in flux policies and have their 200 // own separate tests 201 snowflakes := map[string]bool{ 202 "classic-support": true, 203 "content": true, 204 "home": true, 205 "lxd-support": true, 206 } 207 208 for _, iface := range all { 209 if snowflakes[iface.Name()] { 210 continue 211 } 212 c.Check(iface.AutoConnect(nil, nil), Equals, true) 213 } 214 } 215 216 func (s *baseDeclSuite) TestInterimAutoConnectionHome(c *C) { 217 restore := release.MockOnClassic(true) 218 defer restore() 219 cand := s.connectCand(c, "home", "", "") 220 arity, err := cand.CheckAutoConnect() 221 c.Check(err, IsNil) 222 c.Check(arity.SlotsPerPlugAny(), Equals, false) 223 224 release.OnClassic = false 225 _, err = cand.CheckAutoConnect() 226 c.Check(err, ErrorMatches, `auto-connection denied by slot rule of interface \"home\"`) 227 } 228 229 func (s *baseDeclSuite) TestHomeReadAll(c *C) { 230 const plugYaml = `name: plug-snap 231 version: 0 232 plugs: 233 home: 234 read: all 235 ` 236 restore := release.MockOnClassic(true) 237 defer restore() 238 cand := s.connectCand(c, "home", "", plugYaml) 239 err := cand.Check() 240 c.Check(err, NotNil) 241 242 _, err = cand.CheckAutoConnect() 243 c.Check(err, NotNil) 244 245 release.OnClassic = false 246 err = cand.Check() 247 c.Check(err, NotNil) 248 249 _, err = cand.CheckAutoConnect() 250 c.Check(err, NotNil) 251 } 252 253 func (s *baseDeclSuite) TestHomeReadDefault(c *C) { 254 const plugYaml = `name: plug-snap 255 version: 0 256 plugs: 257 home: null 258 ` 259 restore := release.MockOnClassic(true) 260 defer restore() 261 cand := s.connectCand(c, "home", "", plugYaml) 262 err := cand.Check() 263 c.Check(err, IsNil) 264 265 // Same as TestInterimAutoConnectionHome() 266 arity, err := cand.CheckAutoConnect() 267 c.Check(err, IsNil) 268 c.Check(arity.SlotsPerPlugAny(), Equals, false) 269 270 release.OnClassic = false 271 err = cand.Check() 272 c.Check(err, IsNil) 273 274 // Same as TestInterimAutoConnectionHome() 275 _, err = cand.CheckAutoConnect() 276 c.Check(err, NotNil) 277 } 278 279 func (s *baseDeclSuite) TestAutoConnectionSnapdControl(c *C) { 280 cand := s.connectCand(c, "snapd-control", "", "") 281 _, err := cand.CheckAutoConnect() 282 c.Check(err, NotNil) 283 c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"snapd-control\"") 284 285 plugsSlots := ` 286 plugs: 287 snapd-control: 288 allow-auto-connection: true 289 ` 290 291 lxdDecl := s.mockSnapDecl(c, "some-snap", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots) 292 cand.PlugSnapDeclaration = lxdDecl 293 arity, err := cand.CheckAutoConnect() 294 c.Check(err, IsNil) 295 c.Check(arity.SlotsPerPlugAny(), Equals, false) 296 } 297 298 func (s *baseDeclSuite) TestAutoConnectionContent(c *C) { 299 // random snaps cannot connect with content 300 // (Sanitize* will now also block this) 301 cand := s.connectCand(c, "content", "", "") 302 _, err := cand.CheckAutoConnect() 303 c.Check(err, NotNil) 304 305 slotDecl1 := s.mockSnapDecl(c, "slot-snap", "slot-snap-id", "pub1", "") 306 plugDecl1 := s.mockSnapDecl(c, "plug-snap", "plug-snap-id", "pub1", "") 307 plugDecl2 := s.mockSnapDecl(c, "plug-snap", "plug-snap-id", "pub2", "") 308 309 // same publisher, same content 310 cand = s.connectCand(c, "stuff", ` 311 name: slot-snap 312 version: 0 313 slots: 314 stuff: 315 interface: content 316 content: mk1 317 `, ` 318 name: plug-snap 319 version: 0 320 plugs: 321 stuff: 322 interface: content 323 content: mk1 324 `) 325 cand.SlotSnapDeclaration = slotDecl1 326 cand.PlugSnapDeclaration = plugDecl1 327 arity, err := cand.CheckAutoConnect() 328 c.Check(err, IsNil) 329 c.Check(arity.SlotsPerPlugAny(), Equals, false) 330 331 // different publisher, same content 332 cand.SlotSnapDeclaration = slotDecl1 333 cand.PlugSnapDeclaration = plugDecl2 334 _, err = cand.CheckAutoConnect() 335 c.Check(err, NotNil) 336 337 // same publisher, different content 338 cand = s.connectCand(c, "stuff", `name: slot-snap 339 version: 0 340 slots: 341 stuff: 342 interface: content 343 content: mk1 344 `, ` 345 name: plug-snap 346 version: 0 347 plugs: 348 stuff: 349 interface: content 350 content: mk2 351 `) 352 cand.SlotSnapDeclaration = slotDecl1 353 cand.PlugSnapDeclaration = plugDecl1 354 _, err = cand.CheckAutoConnect() 355 c.Check(err, NotNil) 356 } 357 358 func (s *baseDeclSuite) TestAutoConnectionLxdSupportOverride(c *C) { 359 // by default, don't auto-connect 360 cand := s.connectCand(c, "lxd-support", "", "") 361 _, err := cand.CheckAutoConnect() 362 c.Check(err, NotNil) 363 364 plugsSlots := ` 365 plugs: 366 lxd-support: 367 allow-auto-connection: true 368 ` 369 370 lxdDecl := s.mockSnapDecl(c, "lxd", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots) 371 cand.PlugSnapDeclaration = lxdDecl 372 _, err = cand.CheckAutoConnect() 373 c.Check(err, IsNil) 374 } 375 376 func (s *baseDeclSuite) TestAutoConnectionLxdSupportOverrideRevoke(c *C) { 377 cand := s.connectCand(c, "lxd-support", "", "") 378 plugsSlots := ` 379 plugs: 380 lxd-support: 381 allow-auto-connection: false 382 ` 383 384 lxdDecl := s.mockSnapDecl(c, "notlxd", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots) 385 cand.PlugSnapDeclaration = lxdDecl 386 _, err := cand.CheckAutoConnect() 387 c.Check(err, NotNil) 388 c.Assert(err, ErrorMatches, "auto-connection not allowed by plug rule of interface \"lxd-support\" for \"notlxd\" snap") 389 } 390 391 func (s *baseDeclSuite) TestAutoConnectionKernelModuleControlOverride(c *C) { 392 cand := s.connectCand(c, "kernel-module-control", "", "") 393 _, err := cand.CheckAutoConnect() 394 c.Check(err, NotNil) 395 c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"kernel-module-control\"") 396 397 plugsSlots := ` 398 plugs: 399 kernel-module-control: 400 allow-auto-connection: true 401 ` 402 403 snapDecl := s.mockSnapDecl(c, "some-snap", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots) 404 cand.PlugSnapDeclaration = snapDecl 405 _, err = cand.CheckAutoConnect() 406 c.Check(err, IsNil) 407 } 408 409 func (s *baseDeclSuite) TestAutoConnectionDockerSupportOverride(c *C) { 410 cand := s.connectCand(c, "docker-support", "", "") 411 _, err := cand.CheckAutoConnect() 412 c.Check(err, NotNil) 413 c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"docker-support\"") 414 415 plugsSlots := ` 416 plugs: 417 docker-support: 418 allow-auto-connection: true 419 ` 420 421 snapDecl := s.mockSnapDecl(c, "some-snap", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots) 422 cand.PlugSnapDeclaration = snapDecl 423 _, err = cand.CheckAutoConnect() 424 c.Check(err, IsNil) 425 } 426 427 func (s *baseDeclSuite) TestAutoConnectionClassicSupportOverride(c *C) { 428 cand := s.connectCand(c, "classic-support", "", "") 429 _, err := cand.CheckAutoConnect() 430 c.Check(err, NotNil) 431 c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"classic-support\"") 432 433 plugsSlots := ` 434 plugs: 435 classic-support: 436 allow-auto-connection: true 437 ` 438 439 snapDecl := s.mockSnapDecl(c, "classic", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots) 440 cand.PlugSnapDeclaration = snapDecl 441 _, err = cand.CheckAutoConnect() 442 c.Check(err, IsNil) 443 } 444 445 func (s *baseDeclSuite) TestAutoConnectionKubernetesSupportOverride(c *C) { 446 cand := s.connectCand(c, "kubernetes-support", "", "") 447 _, err := cand.CheckAutoConnect() 448 c.Check(err, NotNil) 449 c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"kubernetes-support\"") 450 451 plugsSlots := ` 452 plugs: 453 kubernetes-support: 454 allow-auto-connection: true 455 ` 456 457 snapDecl := s.mockSnapDecl(c, "some-snap", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots) 458 cand.PlugSnapDeclaration = snapDecl 459 _, err = cand.CheckAutoConnect() 460 c.Check(err, IsNil) 461 } 462 463 func (s *baseDeclSuite) TestAutoConnectionGreengrassSupportOverride(c *C) { 464 cand := s.connectCand(c, "greengrass-support", "", "") 465 _, err := cand.CheckAutoConnect() 466 c.Check(err, NotNil) 467 c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"greengrass-support\"") 468 469 plugsSlots := ` 470 plugs: 471 greengrass-support: 472 allow-auto-connection: true 473 ` 474 475 snapDecl := s.mockSnapDecl(c, "some-snap", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots) 476 cand.PlugSnapDeclaration = snapDecl 477 _, err = cand.CheckAutoConnect() 478 c.Check(err, IsNil) 479 } 480 481 func (s *baseDeclSuite) TestAutoConnectionMultipassSupportOverride(c *C) { 482 cand := s.connectCand(c, "multipass-support", "", "") 483 _, err := cand.CheckAutoConnect() 484 c.Check(err, NotNil) 485 c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"multipass-support\"") 486 487 plugsSlots := ` 488 plugs: 489 multipass-support: 490 allow-auto-connection: true 491 ` 492 493 snapDecl := s.mockSnapDecl(c, "multipass-snap", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots) 494 cand.PlugSnapDeclaration = snapDecl 495 _, err = cand.CheckAutoConnect() 496 c.Check(err, IsNil) 497 } 498 499 func (s *baseDeclSuite) TestAutoConnectionBlockDevicesOverride(c *C) { 500 cand := s.connectCand(c, "block-devices", "", "") 501 _, err := cand.CheckAutoConnect() 502 c.Check(err, NotNil) 503 c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"block-devices\"") 504 505 plugsSlots := ` 506 plugs: 507 block-devices: 508 allow-auto-connection: true 509 ` 510 511 snapDecl := s.mockSnapDecl(c, "some-snap", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots) 512 cand.PlugSnapDeclaration = snapDecl 513 _, err = cand.CheckAutoConnect() 514 c.Check(err, IsNil) 515 } 516 517 func (s *baseDeclSuite) TestAutoConnectionPackagekitControlOverride(c *C) { 518 cand := s.connectCand(c, "packagekit-control", "", "") 519 _, err := cand.CheckAutoConnect() 520 c.Check(err, NotNil) 521 c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"packagekit-control\"") 522 523 plugsSlots := ` 524 plugs: 525 packagekit-control: 526 allow-auto-connection: true 527 ` 528 529 snapDecl := s.mockSnapDecl(c, "some-snap", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots) 530 cand.PlugSnapDeclaration = snapDecl 531 _, err = cand.CheckAutoConnect() 532 c.Check(err, IsNil) 533 } 534 535 func (s *baseDeclSuite) TestAutoConnectionOverrideMultiple(c *C) { 536 plugsSlots := ` 537 plugs: 538 network-bind: 539 allow-auto-connection: true 540 network-control: 541 allow-auto-connection: true 542 kernel-module-control: 543 allow-auto-connection: true 544 system-observe: 545 allow-auto-connection: true 546 hardware-observe: 547 allow-auto-connection: true 548 ` 549 550 snapDecl := s.mockSnapDecl(c, "some-snap", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots) 551 552 all := builtin.Interfaces() 553 // these are a mixture interfaces that the snap plugs 554 plugged := map[string]bool{ 555 "network-bind": true, 556 "network-control": true, 557 "kernel-module-control": true, 558 "system-observe": true, 559 "hardware-observe": true, 560 } 561 for _, iface := range all { 562 if !plugged[iface.Name()] { 563 continue 564 } 565 566 cand := s.connectCand(c, iface.Name(), "", "") 567 cand.PlugSnapDeclaration = snapDecl 568 arity, err := cand.CheckAutoConnect() 569 c.Check(err, IsNil) 570 c.Check(arity.SlotsPerPlugAny(), Equals, false) 571 } 572 } 573 574 // describe installation rules for slots succinctly for cross-checking, 575 // if an interface is not mentioned here a slot of its type can only 576 // be installed by a core snap (and this was taken care by 577 // BeforePrepareSlot), 578 // otherwise the entry for the interface is the list of snap types it 579 // can be installed by (using the declaration naming); 580 // ATM a nil entry means even stricter rules that would need be tested 581 // separately and whose implementation is in flux for now 582 var ( 583 slotInstallation = map[string][]string{ 584 // other 585 "adb-support": {"core"}, 586 "audio-playback": {"app", "core"}, 587 "audio-record": {"app", "core"}, 588 "autopilot-introspection": {"core"}, 589 "avahi-control": {"app", "core"}, 590 "avahi-observe": {"app", "core"}, 591 "bluez": {"app", "core"}, 592 "bool-file": {"core", "gadget"}, 593 "browser-support": {"core"}, 594 "content": {"app", "gadget"}, 595 "core-support": {"core"}, 596 "cups": {"app"}, 597 "cups-control": {"app", "core"}, 598 "dbus": {"app"}, 599 "docker-support": {"core"}, 600 "desktop-launch": {"core"}, 601 "dsp": {"core", "gadget"}, 602 "dummy": {"app"}, 603 "fwupd": {"app", "core"}, 604 "gpio": {"core", "gadget"}, 605 "gpio-control": {"core"}, 606 "greengrass-support": {"core"}, 607 "hidraw": {"core", "gadget"}, 608 "i2c": {"core", "gadget"}, 609 "iio": {"core", "gadget"}, 610 "kubernetes-support": {"core"}, 611 "location-control": {"app"}, 612 "location-observe": {"app"}, 613 "lxd-support": {"core"}, 614 "maliit": {"app"}, 615 "media-hub": {"app", "core"}, 616 "mir": {"app"}, 617 "modem-manager": {"app", "core"}, 618 "mpris": {"app"}, 619 "network-manager": {"app", "core"}, 620 "network-manager-observe": {"app", "core"}, 621 "network-status": {"core"}, 622 "ofono": {"app", "core"}, 623 "online-accounts-service": {"app"}, 624 "power-control": {"core"}, 625 "ppp": {"core"}, 626 "pulseaudio": {"app", "core"}, 627 "pwm": {"core", "gadget"}, 628 "raw-volume": {"core", "gadget"}, 629 "serial-port": {"core", "gadget"}, 630 "spi": {"core", "gadget"}, 631 "storage-framework-service": {"app"}, 632 "thumbnailer-service": {"app"}, 633 "ubuntu-download-manager": {"app"}, 634 "udisks2": {"app", "core"}, 635 "uhid": {"core"}, 636 "uio": {"core", "gadget"}, 637 "unity8": {"app"}, 638 "unity8-calendar": {"app"}, 639 "unity8-contacts": {"app"}, 640 "upower-observe": {"app", "core"}, 641 "wayland": {"app", "core"}, 642 "x11": {"app", "core"}, 643 // snowflakes 644 "classic-support": nil, 645 "docker": nil, 646 "lxd": nil, 647 } 648 649 restrictedPlugInstallation = map[string][]string{ 650 "core-support": {"core"}, 651 } 652 653 snapTypeMap = map[string]snap.Type{ 654 "core": snap.TypeOS, 655 "app": snap.TypeApp, 656 "kernel": snap.TypeKernel, 657 "gadget": snap.TypeGadget, 658 } 659 ) 660 661 func (s *baseDeclSuite) TestSlotInstallation(c *C) { 662 all := builtin.Interfaces() 663 664 for _, iface := range all { 665 types, ok := slotInstallation[iface.Name()] 666 if !ok { // common ones, only core can install them, 667 types = []string{"core"} 668 } 669 670 if types == nil { 671 // snowflake needs to be tested specially 672 continue 673 } 674 for name, snapType := range snapTypeMap { 675 ok := strutil.ListContains(types, name) 676 ic := s.installSlotCand(c, iface.Name(), snapType, ``) 677 err := ic.Check() 678 comm := Commentf("%s by %s snap", iface.Name(), name) 679 if ok { 680 c.Check(err, IsNil, comm) 681 } else { 682 c.Check(err, NotNil, comm) 683 } 684 } 685 } 686 687 // test docker specially 688 ic := s.installSlotCand(c, "docker", snap.TypeApp, ``) 689 err := ic.Check() 690 c.Assert(err, Not(IsNil)) 691 c.Assert(err, ErrorMatches, "installation not allowed by \"docker\" slot rule of interface \"docker\"") 692 693 // test lxd specially 694 ic = s.installSlotCand(c, "lxd", snap.TypeApp, ``) 695 err = ic.Check() 696 c.Assert(err, Not(IsNil)) 697 c.Assert(err, ErrorMatches, "installation not allowed by \"lxd\" slot rule of interface \"lxd\"") 698 } 699 700 func (s *baseDeclSuite) TestPlugInstallation(c *C) { 701 all := builtin.Interfaces() 702 703 restricted := map[string]bool{ 704 "block-devices": true, 705 "classic-support": true, 706 "desktop-launch": true, 707 "dm-crypt": true, 708 "docker-support": true, 709 "greengrass-support": true, 710 "gpio-control": true, 711 "kernel-module-control": true, 712 "kubernetes-support": true, 713 "lxd-support": true, 714 "multipass-support": true, 715 "packagekit-control": true, 716 "personal-files": true, 717 "snapd-control": true, 718 "system-files": true, 719 "tee": true, 720 "uinput": true, 721 "unity8": true, 722 } 723 724 for _, iface := range all { 725 types, ok := restrictedPlugInstallation[iface.Name()] 726 // If plug installation is restricted to specific snap types we 727 // need to make sure this is really the case here. If that is not 728 // the case we continue as normal. 729 if ok { 730 for name, snapType := range snapTypeMap { 731 ok := strutil.ListContains(types, name) 732 ic := s.installPlugCand(c, iface.Name(), snapType, ``) 733 err := ic.Check() 734 comm := Commentf("%s by %s snap", iface.Name(), name) 735 if ok { 736 c.Check(err, IsNil, comm) 737 } else { 738 c.Check(err, NotNil, comm) 739 } 740 } 741 } else { 742 ic := s.installPlugCand(c, iface.Name(), snap.TypeApp, ``) 743 err := ic.Check() 744 comm := Commentf("%s", iface.Name()) 745 if restricted[iface.Name()] { 746 c.Check(err, NotNil, comm) 747 } else { 748 c.Check(err, IsNil, comm) 749 } 750 } 751 } 752 } 753 754 func (s *baseDeclSuite) TestConnection(c *C) { 755 all := builtin.Interfaces() 756 757 // connecting with these interfaces needs to be allowed on 758 // case-by-case basis 759 noconnect := map[string]bool{ 760 "content": true, 761 "cups": true, 762 "docker": true, 763 "fwupd": true, 764 "location-control": true, 765 "location-observe": true, 766 "lxd": true, 767 "maliit": true, 768 "mir": true, 769 "online-accounts-service": true, 770 "raw-volume": true, 771 "storage-framework-service": true, 772 "thumbnailer-service": true, 773 "ubuntu-download-manager": true, 774 "unity8-calendar": true, 775 "unity8-contacts": true, 776 } 777 778 for _, iface := range all { 779 expected := !noconnect[iface.Name()] 780 comm := Commentf(iface.Name()) 781 782 // check base declaration 783 cand := s.connectCand(c, iface.Name(), "", "") 784 err := cand.Check() 785 786 if expected { 787 c.Check(err, IsNil, comm) 788 } else { 789 c.Check(err, NotNil, comm) 790 } 791 } 792 } 793 794 func (s *baseDeclSuite) TestConnectionOnClassic(c *C) { 795 restore := release.MockOnClassic(false) 796 defer restore() 797 798 all := builtin.Interfaces() 799 800 // connecting with these interfaces needs to be allowed on 801 // case-by-case basis when not on classic 802 noconnect := map[string]bool{ 803 "audio-record": true, 804 "modem-manager": true, 805 "network-manager": true, 806 "ofono": true, 807 "pulseaudio": true, 808 "upower-observe": true, 809 } 810 811 for _, onClassic := range []bool{true, false} { 812 release.OnClassic = onClassic 813 for _, iface := range all { 814 if !noconnect[iface.Name()] { 815 continue 816 } 817 expected := onClassic 818 comm := Commentf(iface.Name()) 819 820 // check base declaration 821 cand := s.connectCand(c, iface.Name(), "", "") 822 err := cand.Check() 823 824 if expected { 825 c.Check(err, IsNil, comm) 826 } else { 827 c.Check(err, NotNil, comm) 828 } 829 } 830 } 831 } 832 833 func (s *baseDeclSuite) TestConnectionImplicitOnClassicOrAppSnap(c *C) { 834 restore := release.MockOnClassic(false) 835 defer restore() 836 837 all := builtin.Interfaces() 838 839 // These interfaces represent when the interface might be implicit on 840 // classic or when the interface is provided via an app snap. As such, 841 // they all share the following: 842 // 843 // - implicitOnCore: false 844 // - implicitOnClassis: true 845 // - base declaration uses: 846 // allow-installation: 847 // slot-snap-type: 848 // - app 849 // - core 850 // deny-connection: 851 // on-classic: false 852 // deny-auto-connection: true|false|unspecified 853 // 854 // connecting with these interfaces needs to be allowed on 855 // case-by-case basis when not on classic 856 ifaces := map[string]bool{ 857 "audio-playback": true, 858 "audio-record": true, 859 "cups-control": true, 860 "modem-manager": true, 861 "network-manager": true, 862 "ofono": true, 863 "pulseaudio": true, 864 "upower-observe": true, 865 } 866 867 for _, iface := range all { 868 if !ifaces[iface.Name()] { 869 continue 870 } 871 comm := Commentf(iface.Name()) 872 873 // verify the interface is setup as expected wrt 874 // implicitOnCore, implicitOnClassic, no plugs and has 875 // expected slots (ignoring AutoConnection) 876 si := interfaces.StaticInfoOf(iface) 877 c.Assert(si.ImplicitOnCore, Equals, false) 878 c.Assert(si.ImplicitOnClassic, Equals, true) 879 880 c.Assert(s.baseDecl.PlugRule(iface.Name()), IsNil) 881 882 sr := s.baseDecl.SlotRule(iface.Name()) 883 c.Assert(sr, Not(IsNil)) 884 c.Assert(sr.AllowInstallation, HasLen, 1) 885 c.Check(sr.AllowInstallation[0].SlotSnapTypes, DeepEquals, []string{"app", "core"}, comm) 886 c.Assert(sr.DenyConnection, HasLen, 1) 887 c.Check(sr.DenyConnection[0].OnClassic, DeepEquals, &asserts.OnClassicConstraint{Classic: false}, comm) 888 889 for _, onClassic := range []bool{true, false} { 890 release.OnClassic = onClassic 891 892 for _, implicit := range []bool{true, false} { 893 // When implicitOnCore is false, there is 894 // nothing to test on Core 895 if implicit && !onClassic { 896 continue 897 } 898 899 snapType := "app" 900 if implicit { 901 snapType = "os" 902 } 903 slotYaml := fmt.Sprintf(`name: slot-snap 904 version: 0 905 type: %s 906 slots: 907 %s: 908 `, snapType, iface.Name()) 909 910 // XXX: eventually 'onClassic && !implicit' but 911 // the current declaration allows connection on 912 // Core when 'type: app'. See: 913 // https://github.com/snapcore/snapd/pull/8920/files#r471678529 914 expected := onClassic 915 916 // check base declaration 917 cand := s.connectCand(c, iface.Name(), slotYaml, "") 918 err := cand.Check() 919 920 if expected { 921 c.Check(err, IsNil, comm) 922 } else { 923 c.Check(err, NotNil, comm) 924 } 925 } 926 } 927 } 928 } 929 930 func (s *baseDeclSuite) TestSanity(c *C) { 931 all := builtin.Interfaces() 932 933 // these interfaces have rules both for the slots and plugs side 934 // given how the rules work this can be delicate, 935 // listed here to make sure that was a conscious decision 936 bothSides := map[string]bool{ 937 "block-devices": true, 938 "audio-playback": true, 939 "classic-support": true, 940 "core-support": true, 941 "desktop-launch": true, 942 "dm-crypt": true, 943 "docker-support": true, 944 "greengrass-support": true, 945 "gpio-control": true, 946 "kernel-module-control": true, 947 "kubernetes-support": true, 948 "lxd-support": true, 949 "multipass-support": true, 950 "packagekit-control": true, 951 "personal-files": true, 952 "snapd-control": true, 953 "system-files": true, 954 "tee": true, 955 "udisks2": true, 956 "uinput": true, 957 "unity8": true, 958 "wayland": true, 959 } 960 961 for _, iface := range all { 962 plugRule := s.baseDecl.PlugRule(iface.Name()) 963 slotRule := s.baseDecl.SlotRule(iface.Name()) 964 if plugRule == nil && slotRule == nil { 965 c.Logf("%s is not considered in the base declaration", iface.Name()) 966 c.Fail() 967 } 968 if plugRule != nil && slotRule != nil { 969 if !bothSides[iface.Name()] { 970 c.Logf("%s have both a base declaration slot rule and plug rule, make sure that's intended and correct", iface.Name()) 971 c.Fail() 972 } 973 } 974 } 975 } 976 977 func (s *baseDeclSuite) TestConnectionContent(c *C) { 978 // we let connect explicitly as long as content matches (or is absent on both sides) 979 980 // random (Sanitize* will now also block this) 981 cand := s.connectCand(c, "content", "", "") 982 err := cand.Check() 983 c.Check(err, NotNil) 984 985 slotDecl1 := s.mockSnapDecl(c, "slot-snap", "slot-snap-id", "pub1", "") 986 plugDecl1 := s.mockSnapDecl(c, "plug-snap", "plug-snap-id", "pub1", "") 987 plugDecl2 := s.mockSnapDecl(c, "plug-snap", "plug-snap-id", "pub2", "") 988 989 // same publisher, same content 990 cand = s.connectCand(c, "stuff", `name: slot-snap 991 version: 0 992 slots: 993 stuff: 994 interface: content 995 content: mk1 996 `, ` 997 name: plug-snap 998 version: 0 999 plugs: 1000 stuff: 1001 interface: content 1002 content: mk1 1003 `) 1004 cand.SlotSnapDeclaration = slotDecl1 1005 cand.PlugSnapDeclaration = plugDecl1 1006 err = cand.Check() 1007 c.Check(err, IsNil) 1008 1009 // different publisher, same content 1010 cand.SlotSnapDeclaration = slotDecl1 1011 cand.PlugSnapDeclaration = plugDecl2 1012 err = cand.Check() 1013 c.Check(err, IsNil) 1014 1015 // same publisher, different content 1016 cand = s.connectCand(c, "stuff", ` 1017 name: slot-snap 1018 version: 0 1019 slots: 1020 stuff: 1021 interface: content 1022 content: mk1 1023 `, ` 1024 name: plug-snap 1025 version: 0 1026 plugs: 1027 stuff: 1028 interface: content 1029 content: mk2 1030 `) 1031 cand.SlotSnapDeclaration = slotDecl1 1032 cand.PlugSnapDeclaration = plugDecl1 1033 err = cand.Check() 1034 c.Check(err, NotNil) 1035 } 1036 1037 func (s *baseDeclSuite) TestComposeBaseDeclaration(c *C) { 1038 decl, err := policy.ComposeBaseDeclaration(nil) 1039 c.Assert(err, IsNil) 1040 c.Assert(string(decl), testutil.Contains, ` 1041 type: base-declaration 1042 authority-id: canonical 1043 series: 16 1044 revision: 0 1045 `) 1046 } 1047 1048 func (s *baseDeclSuite) TestDoesNotPanic(c *C) { 1049 // In case there are any issues in the actual interfaces we'd get a panic 1050 // on snapd startup. This test prevents this from happing unnoticed. 1051 _, err := policy.ComposeBaseDeclaration(builtin.Interfaces()) 1052 c.Assert(err, IsNil) 1053 } 1054 1055 func (s *baseDeclSuite) TestBrowserSupportAllowSandbox(c *C) { 1056 const plugYaml = `name: plug-snap 1057 version: 0 1058 plugs: 1059 browser-support: 1060 allow-sandbox: true 1061 ` 1062 cand := s.connectCand(c, "browser-support", "", plugYaml) 1063 err := cand.Check() 1064 c.Check(err, NotNil) 1065 1066 _, err = cand.CheckAutoConnect() 1067 c.Check(err, NotNil) 1068 } 1069 1070 func (s *baseDeclSuite) TestOpticalDriveWrite(c *C) { 1071 type options struct { 1072 readonlyYamls []string 1073 writableYamls []string 1074 } 1075 1076 opts := &options{ 1077 readonlyYamls: []string{ 1078 // Non-specified "write" attribute 1079 `name: plug-snap 1080 version: 0 1081 plugs: 1082 optical-drive: null 1083 `, 1084 // Undefined "write" attribute 1085 `name: plug-snap 1086 version: 0 1087 plugs: 1088 optical-drive: {} 1089 `, 1090 // False "write" attribute 1091 `name: plug-snap 1092 version: 0 1093 plugs: 1094 optical-drive: 1095 write: false 1096 `, 1097 }, 1098 writableYamls: []string{ 1099 // True "write" attribute 1100 `name: plug-snap 1101 version: 0 1102 plugs: 1103 optical-drive: 1104 write: true 1105 `, 1106 }, 1107 } 1108 1109 checkOpticalDriveAutoConnect := func(plugYaml string, checker Checker) { 1110 cand := s.connectCand(c, "optical-drive", "", plugYaml) 1111 err := cand.Check() 1112 c.Check(err, checker) 1113 _, err = cand.CheckAutoConnect() 1114 c.Check(err, checker) 1115 } 1116 1117 for _, plugYaml := range opts.readonlyYamls { 1118 checkOpticalDriveAutoConnect(plugYaml, IsNil) 1119 } 1120 for _, plugYaml := range opts.writableYamls { 1121 checkOpticalDriveAutoConnect(plugYaml, NotNil) 1122 } 1123 } 1124 1125 func (s *baseDeclSuite) TestRawVolumeOverride(c *C) { 1126 slotYaml := `name: slot-snap 1127 type: gadget 1128 version: 0 1129 slots: 1130 raw-volume: 1131 path: /dev/mmcblk0p1 1132 ` 1133 slotSnap := snaptest.MockInfo(c, slotYaml, nil) 1134 // mock a well-formed slot snap decl with SnapID 1135 slotSnapDecl := s.mockSnapDecl(c, "slot-snap", "slotsnapidididididididididididid", "canonical", "") 1136 1137 plugYaml := `name: plug-snap 1138 version: 0 1139 plugs: 1140 raw-volume: 1141 ` 1142 plugSnap := snaptest.MockInfo(c, plugYaml, nil) 1143 1144 // no plug-side declaration 1145 cand := &policy.ConnectCandidate{ 1146 Plug: interfaces.NewConnectedPlug(plugSnap.Plugs["raw-volume"], nil, nil), 1147 Slot: interfaces.NewConnectedSlot(slotSnap.Slots["raw-volume"], nil, nil), 1148 SlotSnapDeclaration: slotSnapDecl, 1149 BaseDeclaration: s.baseDecl, 1150 } 1151 1152 err := cand.Check() 1153 c.Check(err, NotNil) 1154 c.Assert(err, ErrorMatches, "connection denied by slot rule of interface \"raw-volume\"") 1155 _, err = cand.CheckAutoConnect() 1156 c.Check(err, NotNil) 1157 c.Assert(err, ErrorMatches, "auto-connection denied by slot rule of interface \"raw-volume\"") 1158 1159 // specific plug-side declaration for connection only 1160 plugsOverride := ` 1161 plugs: 1162 raw-volume: 1163 allow-connection: 1164 slot-snap-id: 1165 - slotsnapidididididididididididid 1166 allow-auto-connection: false 1167 ` 1168 plugSnapDecl := s.mockSnapDecl(c, "plug-snap", "plugsnapidididididididididididid", "canonical", plugsOverride) 1169 cand.PlugSnapDeclaration = plugSnapDecl 1170 err = cand.Check() 1171 c.Check(err, IsNil) 1172 _, err = cand.CheckAutoConnect() 1173 c.Check(err, NotNil) 1174 c.Assert(err, ErrorMatches, "auto-connection not allowed by plug rule of interface \"raw-volume\" for \"plug-snap\" snap") 1175 1176 // specific plug-side declaration for connection and auto-connection 1177 plugsOverride = ` 1178 plugs: 1179 raw-volume: 1180 allow-connection: 1181 slot-snap-id: 1182 - slotsnapidididididididididididid 1183 allow-auto-connection: 1184 slot-snap-id: 1185 - slotsnapidididididididididididid 1186 ` 1187 plugSnapDecl = s.mockSnapDecl(c, "plug-snap", "plugsnapidididididididididididid", "canonical", plugsOverride) 1188 cand.PlugSnapDeclaration = plugSnapDecl 1189 err = cand.Check() 1190 c.Check(err, IsNil) 1191 arity, err := cand.CheckAutoConnect() 1192 c.Check(err, IsNil) 1193 c.Check(arity.SlotsPerPlugAny(), Equals, false) 1194 1195 // blanket allow for connection and auto-connection to any slotting snap 1196 plugsOverride = ` 1197 plugs: 1198 raw-volume: 1199 allow-connection: true 1200 allow-auto-connection: true 1201 ` 1202 plugSnapDecl = s.mockSnapDecl(c, "some-snap", "plugsnapidididididididididididid", "canonical", plugsOverride) 1203 cand.PlugSnapDeclaration = plugSnapDecl 1204 err = cand.Check() 1205 c.Check(err, IsNil) 1206 arity, err = cand.CheckAutoConnect() 1207 c.Check(err, IsNil) 1208 c.Check(arity.SlotsPerPlugAny(), Equals, false) 1209 } 1210 1211 func (s *baseDeclSuite) TestAutoConnectionDesktopLaunchOverride(c *C) { 1212 cand := s.connectCand(c, "desktop-launch", "", "") 1213 _, err := cand.CheckAutoConnect() 1214 c.Check(err, NotNil) 1215 c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"desktop-launch\"") 1216 1217 plugsSlots := ` 1218 plugs: 1219 desktop-launch: 1220 allow-auto-connection: true 1221 ` 1222 1223 snapDecl := s.mockSnapDecl(c, "some-snap", "some-snap-with-desktop-launch-id", "canonical", plugsSlots) 1224 cand.PlugSnapDeclaration = snapDecl 1225 _, err = cand.CheckAutoConnect() 1226 c.Check(err, IsNil) 1227 }