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