1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592
|
# Interface policy
## Plug and slot rules
Declarative rules are used to control what plugs or slots a snap is
allowed to use, and if a snap is allowed to use a plug/slot, what other
slots/plugs can connect to that plug/slot on this snap.
The overall structure has two top-level keys: plugs and slots. These affect the
plugs and slots of the snap respectively. Beneath these are the names of
interfaces. Each interface key introduces a map with 6 possible keys:
- `allow-installation`
- `deny-installation`
- `allow-connection`
- `deny-connection`
- `allow-auto-connection`
- `deny-auto-connection`
Each of these keys can either have a static value of true/false or can be a
more complex object/list which is “evaluated” by snapd on a device to
determine the actual value, be it true or false.
### Base declaration and snap declarations
The rules defined in the snapd interfaces source code (via setting
`commonInterface.baseDeclarationSlots/Plugs`) form the
“base declaration” and can be overridden by per-snap rules in the
snap store published via the per-snap `snap-declaration` assertions,
which make it possible for a store to alter the policy hardcoded in
snapd. For example, a store assertion is typically used to grant
auto-connection to some plugs of a specific application where this is
deemed reasonable or safe (such as auto-connecting the `camera`
interface in a web-streaming application).
### Basic evaluation and precedence
When not otherwise specified, the default values for `allow-*` keys are `true`,
while the default values for `deny-*` keys are `false`. A matched `deny-*`
constraint overrides and takes precedence over a matching `allow-*`
constraint from within the same stanza. The order of evaluation of rules
is the following (execution stops as soon as a matching rule is found, meaning
that the topmost elements in this list have higher priority):
- `deny-*` keys in plug snap-declaration rule
- `allow-*` keys in plug snap-declaration rule
- `deny-*` keys in slot snap-declaration rule
- `allow-*` keys in slot snap-declaration rule
- `deny-*` keys in plug base-declaration rule
- `allow-*` keys in plug base-declaration rule
- `deny-*` keys in slot base-declaration rule
- `allow-*` keys in slot base-declaration rule
In other words, snap-declaration (store) rules have priority over
base-declaration rules; then, plug rules have priority over slot rules, and
finally, deny rules have priority over allow rules within the same stanza.
### allow-installation
The `allow-installation` key is evaluated when the snap is being installed. If
this evaluates to false, the snap cannot be installed if an interface plug or
slot for which `allow-installation` evaluated to `false` exists in the snap. An
example would be the `snapd-control` interface, which has in the
base-declaration the static `allow-installation: false` rule for plugs:
snapd-control:
allow-installation: false
deny-auto-connection: true
If a snap does not plug `snapd-control` then this rule does not apply, but if
the snap does declare a `snapd-control` plug and there are no assertions in the
store for this snap about allowing `snapd-control`, then snap installation will
fail.
Snap interfaces that have `allow-installation` set to `false` for their plugs
in the base-declaration are said to be **super-privileged**, meaning they
cannot be used at all without a snap-declaration assertion.
A snap's interface slot provided by a non-system snap is considered
**super-privileged** if it has `allow-installation` that evaluates to `false`
in the base-declaration. An application snap or gadget defining such slots
cannot be used without an accompanying snap-declaration assertion.
### allow-connection
The `allow-connection` key controls whether an API/manual connection
is permitted at all and usually is used to ensure that only
“compatible” plugs and slots are connected to each other. A great
example is the content interface, where the following (abbreviated)
rule from the base-declaration is used to ensure that a candidate plug
and slot content interface have matching `content` attribute values:
allow-connection:
plug-attributes:
content: $SLOT(content)
This can be read as `allow-connection` evaluating to `true` only when the plug
has an attribute `content` with the same value as the attribute `content` in
the slot. That is to say, these plug and slots are compatible because `content`
does match for the plug and slot:
# in the snap providing the content:
slots:
foo-content:
interface: content
content: specific-files
# in the snap consuming the content:
plugs:
foo-content:
interface: content
content: specific-files
While the following plug and slots are not compatible:
slots:
foo-content:
interface: content
content: other-files
plugs:
foo-content:
interface: content
content: specific-files
### allow-auto-connection
The allow-auto-connection key is the final key considered when snapd is
evaluating the automatic connection of interface plugs and slots. If this key
evaluates to `true`, then this plug/slot combination is considered a valid
candidate for automatic connection. In this context allow-connection is ignored.
An automatic connection will happen normally only if there is one single
candidate combination with a slot for a given plug.
### Supported rule constraints
Each of the keys seen before (`allow/deny-installation`,
`allow/deny-connection`, and `allow/deny-auto-connection`) has a set of
sub-keys that can be used as rules with each constraint. The authoritative
place where this information comes from is inside snapd in the `asserts`
package, specifically the file `ifacedecls.go` is the main place where these
are defined.
In `allow-connection` or `allow-auto-connection` constraints about snap type,
snap ID and publisher can only be specified for the other side snap (e.g. a
slot-side `allow-connection` constraint can only specify `plug-snap-type`,
`plug-snap-id`, `plug-snap-publisher`).
As an exception, constraints on snap type for the slot providing snap
(`slot-snap-type`) can be specified on the slot side as well. This is only
meaningful/useful in the base declaration as it allows for a constraint on
whether the slot side is provided by the system snap or not.
For the `plug-snap-type` and `slot-snap-type` rules there are 4
possible values: `core`, `gadget`, `kernel`, and `app`. The `core` snap
type refers to whichever snap is providing snapd on the system and
therefore the system interface slots, either the `core` snap or `snapd`
snap (typically `core` snap on UC16 devices, `snapd` snap on UC18+
systems, and either on classic systems depending on re-exec logic).
The `on-store`, `on-brand`, and `on-model` rules are generally not hard-coded
within snapd interfaces. They are instead specified in store assertions where
they are known as "device context constraints". These device context
constraints are primarily used to ensure a given rule only applies to a device
with a serial assertion (and thus model assertion) from a given brand or using
a given store (as specified by the model). This is because if the assertion and
snap from a brand store were copied to a non-branded device, the assertion
could still be acknowledged by the device and the snap installed, but the
assertion would not operate, and snap connections would not take place as they
do on the branded device.
The `plug-names` and `slot-names` rules are also only used in store assertions.
They refer to the naming of a plug or slot when that slot is scoped globally
with a name other than the generic interface name.
For example this assertion:
plugs:
gpio:
allow-auto-connection:
slot-names: [ gpio1 ]
plug-names: [ gpio-red-led ]
only allows the plugging snap to have its plug named `gpio-red-led`
auto-connected to a gpio slot named `gpio1`.
### Rule evaluation
#### Greedy connection / single slot rule
The first rule about whether an automatic connection happens between a plug and
a slot has to do with “arity” or how many slots a given plug is being
considered to connect to and vice versa. This is expressed with the
`slots-per-plug` and `plugs-per-slot` rules, with the default value of
`plugs-per-slot` being “`*`” meaning any number of plugs can be connected to a
specific slot. The default value of `slots-per-plug` is "`1`", however, meaning
that a plug can in general without a special snap-declaration only
automatically connect to one slot. All that is to say, if there are multiple
candidate slots, in the default case a plug will auto-connect to neither of
them and snapd will issue a warning.
See also [this forum
post](https://forum.snapcraft.io/t/plug-slot-declaration-rules-greedy-plugs/12438)
which was written when this logic was first implemented.
#### Maps and Lists
The next rule about evaluating snap-declaration assertions is that maps are
treated as logical ANDs where each key in the map must individually evaluate to
`true` and lists are treated as logical ORs where only one of the elements of
the list must evaluate to `true`. With the following example assertion,
order
for the `serial-port` plug in this snap to auto-connect, either the first
element of the list must evaluate to `true` or the second element of the list
must evaluate to `true` (or they could both theoretically evaluate to `true`,
but this is impossible in practice since the slots can only come from gadgets
and so to match both the system would have to have two gadget snaps installed
simultaneously which is impossible).
plugs:
serial-port:
allow-auto-connection:
-
on-store:
- my-app-store
plug-names:
- serial-rf-nic
slot-attributes:
path: /dev/serial-port-rfnic
slot-names:
- serial-rf-nic
slot-snap-id:
- Cx4J8ADDq8xULNaAjO7mQid75ru4rObB
-
on-store:
- my-app-store
plug-names:
- serial-rf-nic
slot-attributes:
path: /dev/serial-port-rfnic
slot-names:
- serial-rf-nic
slot-snap-id:
- WabnwLoV48BCMj8NoOetmdxFFMxDsPGb
For the first element of the allow-auto-connection list to evaluate to
`true`, the following things must be true:
- The device must be on a brand device in `my-app-store` AND
- The plug name must be `serial-rf-nic` AND
- The slot name must be `serial-rf-nic` AND
- The slot must declare an attribute, `path`, with the value
`/dev/serial-port-rfnic` AND
- The slot must come from a snap with a snap ID of
`Cx4J8ADDq8xULNaAjO7mQid75ru4rObB`
The above is also true for the second element of the allow-auto-connection list.
An equivalent way to write an assertion that works in exactly the same way would be:
plugs:
serial-port:
allow-auto-connection:
on-store:
- my-app-store
plug-names:
- serial-rf-nic
slot-attributes:
path: /dev/serial-port-rfnic
slot-names:
- serial-rf-nic
slot-snap-id:
- Cx4J8ADDq8xULNaAjO7mQid75ru4rObB
- WabnwLoV48BCMj8NoOetmdxFFMxDsPGb
The chief difference here is that instead of having a list of two maps, we
instead have a single map, and the key which changes for the two gadgets is the
`slot-snap-id` which now has two values in a list. In this case, the slot snap
ID must be one of the snap IDs in the list in order for the `slot-snap-id` rule
to evaluate to true. So the following things must be true:
- The device must be on a brand device in `my-app-store` AND
- The plug name must be `serial-rf-nic` AND
- The slot-name must be `serial-rf-nic` AND
- The slot must declare an attribute, `path`, with the value
`/dev/serial-port-rfnic` AND
- The slot must come from a snap with a snap ID of any of the following elements:
- `Cx4J8ADDq8xULNaAjO7mQid75ru4rObB`, OR
- `WabnwLoV48BCMj8NoOetmdxFFMxDsPGb`
Lists and maps can also be used as values for attributes under
plug/slot-attributes constraints. A map will match only if the attribute value
contains all the entries in the constraints map with the same values (extra
attribute elements are ignored).
A list will match against a non-list attribute value if the value matches any
of the list elements. A list will match against a list attribute value if all
the elements in the attribute list value in turn match something in the list.
This means, for example, a constraint list of value constraints will match a list
of attribute scalars if the two groups of values match as a set (order doesn't
matter).
#### Attribute constraints and special matches and variables
Plug/slot-attributes string value constraints are interpreted as regexps
(wrapped implicitly in `^$` anchors), unless they are one of the special forms
starting with `$`.
The special forms starting with `$` currently consist of:
- `$SLOT_PUBLISHER_ID`, `$PLUG_PUBLISHER_ID`: these can be specified in the
`plug-publisher-id` and `slot-publisher-id` constraints respectively and are
used from the plug side and slot side of a declaration to refer to the
publisher-id of the other side of the connection.
- `$PLUG()`, `$SLOT()`: similar to the above, but used in the `plug-attributes`
and `slot-attributes` constraints for specifying attributes instead of
publisher-ids.
- `$MISSING`: used in the `plug-attributes` and `slot-attributes` constraints
to match when the attribute set to `$MISSING` is not specified in the snap.
For example, these features are used in the base-declaration for the `content`
interface to express that a connection is only allowed when the attribute value
of the verbatim “content” attribute on the slot side is the same as the plug
side and that auto-connection should only take place by default when the plug
publisher ID is the same as the slot publisher ID (unless this is overridden by
a store assertion):
content:
allow-installation:
slot-snap-type:
- app
- gadget
allow-connection:
plug-attributes:
content: $SLOT(content)
allow-auto-connection:
plug-publisher-id:
- $SLOT_PUBLISHER_ID
plug-attributes:
content: $SLOT(content)
## Unasserted local installation
When a snap is installed without matching assertions using `--dangerous`, many
checks are not performed. This helps with local snap development.
For installation only, any slot-snap-type constraint in a base-declaration
`allow-installation` rule for slots is checked. Any `deny-installation`
rule here is ignored. Nothing is checked for the snap plugs.
For connections involving the snap, no rules are checked and it is always
allowed.
For auto-connection, the base-declaration and any rule for the other side (if
available) is considered. This usually means no auto-connection will happen
unless allowed by the base-declaration or by a slot-side snap-declaration.
## Base declaration policy patterns
This section discusses the patterns to follow when writing base declaration
rules for an interface (via setting
`commonInterface.baseDeclarationSlots/Plugs`), depending on the security
characteristics of the interface.
Slots for a specific interface can be provided by the system snap (so called
implicit slots), or by an application snap, or sometimes by both.
For almost all interfaces, and whenever possible, the base declaration rules are
written as slot-side-only rules.
Importantly, items are not merged between the slots and plugs, or between the base declaration and snap declaration for a particular type of rule.
This means that if a connection rule for both the slot-side and plug-side is specified in the base declaration, only the plug side is used (plug-side overrides the slot side).
Installation rules target their side only; a slot-side installation rule is for
allowing snaps with a slot of the given interface. A plug-side installation
rule is for snaps with a plug of the given interface.
Interfaces can be broadly categorized as:
- auto-connected implicit slots (eg, network)
- manually connected implicit slots (eg, bluetooth-control)
- auto-connected app-provided slots (eg, mir)
- manually connected app-provided slots (eg, bluez)
As such, that they will follow this pattern:
slots:
auto-connected-implicit-slot:
allow-installation:
slot-snap-type:
- core # implicit slot
#allow-auto-connection: true # allow auto-connect, default
manual-connected-implicit-slot:
allow-installation:
slot-snap-type:
- core # implicit slot
deny-auto-connection: true # force manual connect
auto-connected-provided-slot:
allow-installation:
slot-snap-type:
- app # app provided slot
deny-connection: true # require allow-connection in snap decl
manual-connected-provided-slot:
allow-installation:
slot-snap-type:
- app # app provided slot
deny-connection: true # require allow-connection in snap decl
deny-auto-connection: true # force manual connect
As some slots can be provided by both the system or an application snap, some interfaces will be in more than one category.
Auto-connection should be allowed in the base-declaration if the access to the
resources provided by the interface does not have security implications.
App-provided slots use 'deny-connection: true' since slot implementations
require privileged access to the system and the snap must be trusted. In this
manner, a snap declaration for the slot-providing snap is required to override
the base declaration to allow connections with the app-provided slot.
Slots dealing with hardware will typically specify 'gadget' and 'core' as
the slot-snap-type (eg, serial-port). Eg:
slots:
manual-connected-hw-slot:
allow-installation:
slot-snap-type:
- core
- gadget
deny-auto-connection: true
Denying auto-connection not only stops access but also covers the fact that
there might be multiple slots for an hardware interface for which there is no
way to choose one.
So called super-privileged plugs should also disallow installation on a
system. A snap declaration is required to override the base declaration to
allow installation (eg, kernel-module-control). Eg:
plugs:
manual-connected-super-privileged-plug:
allow-installation: false
deny-auto-connection: true
(remember this overrides slot side rules)
This pattern makes sense for interfaces that carry great security risks and
allow the snap to take control outside of the sandbox, e.g. installing a kernel
module.
So called super-privileged slot implementations should also disallow
installation on a system and a snap declaration is required to override the
base declaration to allow installation (eg, docker). Eg:
slots:
manual-connected-super-privileged-slot:
allow-installation: false
deny-connection: true
deny-auto-connection: true
This pattern makes sense for interfaces where implementing them requires
extensive system access, or where there is the need for a review to check for
policy or to avoid resource use/naming clashes.
Some interfaces have policy that is meant to cover application snap slot
implementations but also classic systems. Since the slot implementation is
privileged, we require a snap declaration to be used for app-provided slot
implementations on non-classic systems (eg, `network-manager`). Eg:
slots:
classic-or-not-slot:
allow-installation:
slot-snap-type:
- app
- core
deny-auto-connection: true
deny-connection:
on-classic: false
The idea of this pattern is that on classic we expect the slot to be system
provided and for it to be app-provide on Core. However, the work on Core
Desktop means this idea is not always valid. A different approach is to
deny-connection based on the type of slot carrying snap, e.g for
`upower-observe` nowadays, we have:
upower-observe:
allow-installation:
slot-snap-type:
- app
- core
deny-auto-connection:
slot-snap-type:
- app
deny-connection:
slot-snap-type:
- app
Some interfaces only have implicit slots and should be auto-connected only on classic systems (eg, home). Eg:
slots:
implicit-auto-classic-slot:
allow-installation:
slot-snap-type:
- core
deny-auto-connection:
on-classic: false
Some interfaces with app-provided slots allow connections if some expectation
is met. This in turn is expressed by some kind of tag attribute on both plug
and slot (usually named after the interface) that must match, for example
`content`:
slots:
content:
allow-installation:
slot-snap-type:
- app
- gadget
allow-connection:
plug-attributes:
content: $SLOT(content)
allow-auto-connection:
plug-publisher-id:
- $SLOT_PUBLISHER_ID
plug-attributes:
content: $SLOT(content)
In these situations auto-connection is often granted by default if the
publisher also matches.
Some interfaces might need complex policies that mix many of these patterns, for example, shared-memory:
- allows auto-connection to the implicit slot if the plug set a `private`
attribute to `true` (default is false)
- supports app-provided slots but that are super-privileged to avoid naming
clashes
- uses a tag attribute to match plug and slots, and if the slot could be defined
lets same publisher plugs auto-connect
```
slots:
shared-memory:
allow-installation:
slot-snap-type:
- app
- gadget
- core
deny-installation:
slot-snap-type:
- app
- gadget
deny-auto-connection: true
plugs:
shared-memory:
allow-connection:
-
plug-attributes:
private: false
slot-attributes:
shared-memory: $PLUG(shared-memory)
-
plug-attributes:
private: true
slot-snap-type:
- core
allow-auto-connection:
-
plug-attributes:
private: false
slot-publisher-id:
- $PLUG_PUBLISHER_ID
slot-attributes:
shared-memory: $PLUG(shared-memory)
-
plug-attributes:
private: true
slot-snap-type:
- core
```
Note the combining of `allow-installation` and `deny-installation`
`slot-snap-type` constraints to make the slot super-privileged while accounting
for the slot snap type check done for `--dangerous` installations.
|