Wednesday, April 20, 2016

Snappy interfaces, plugs, slots and connections

This is the second post in a series about snappy interfaces.

In the previous instalment we looked at the basics of interfaces. We used a snap containing the "links" text mode web browser and the "network" plug to get access to the network. We took advantage of the auto-connection feature of the "network" interface to simply install the snap and have it do the right thing.

Today we will focus on connections. A connection is simply an ordered tuple (plug, slot) where plug refers to a particular plug in a particular snap and slot refers to a particular slot in a particular (typically different) snap. Before we really talk about connections I need to explore plugs and slots with a little more depth.

Plugs and slots

As the most essential fact, both plugs and slots have a name and an interface name. In the examples so far this was somewhat shrouded because the plug name and plug interface name had the same value. Let's take all the simplifications away and look at the raw facts.

Recall the snapcraft.yaml file we looked at the last time:

name: links
version: 2.12-1
summary: Web browser running in text mode
description: |
    Links is a text mode WWW browser, similar to Lynx. It displays tables,
    frames, downloads on background, uses HTTP/1.1 keepalive connections.
apps:
    links:
        command: links
        plugs: [network, network-bind]
parts:
    links:
        plugin: nil
        stage-packages:
            - links

Let's focus on just the plug syntax:

apps:
    links:
        command: links
        plugs: [network, network-bind]

What you see above is an abbreviated form. Snappy has many abbreviated forms that make it look nice and are better as long as you understand what they mean. The fully expanded form, having the exact same semantics, looks like this:

apps:
    links:
        command: links
        plugs: [network, network-bind]
plugs:
    network:
        interface: network
    network-bind:
        interface: network-bind

Note that plug name is independent from plug interface name. As a convenience, when the plug name is the same as the interface name the mapping of plug name to interface name can be left out. As another convenience if the plug doesn't require any additional attributes (more on that later) the whole top-level declaration is optional. This is why we could get away with just plugs: [network, network-bind] defined in the app level.

The same is true of slots. Virtually every time we talk about plugs the exact same thing is true for slots. I will not mention that again unless the rule is broken.

Let's recap some of the key facts:
  1. Plugs and slots are objects that have several attributes
  2. Name uniquely identifies a plug or a slot within a particular snap.
  3. Interface name describes the "kind" of plug/slot.
Note that while a plug and a slot can have semi-arbitrary name defined by whoever is making the snap, all interface names are defined within snappy itself. You cannot create a new interface just by inventing a new name. I will describe how to create new interfaces in one of the future instalments.

Connections

So, returning to connections. A connection can be made between a plug and a slot of the same interface type. For example snappy would not allow a connection between the unity7 plug and the network slot. Snappy doesn't allow this. On IOT-like devices you can expect slots using names very different from the interface type they represent. Some made-up examples are "user-button", "led5" or "gpio-12". At the same time you can expect more manual, explicit connections to be established on the command line or through other mechanisms as there will be many possible candidates for a particular slot to form a connection.

Snappy maintains a persistent internal data structure called the state. This is used to record connections as they are established. When you reboot your snappy device the state is used to re-load all connections. Right now there is no difference but once we add support for hooks things will get more interesting. On each reboot the details of particular plugs or slots can be different. For example a hypothetical cheese snap can be connected to the same physical USB camera but due to way USB enumeration works some attributes of the slot that represents that camera might differ (e.g. the particular device that cheese can access can have a different path on the filesystem).

When connections are established the various systems that maintain snappy's security model must be informed. Each security system uses information about plugs, slots, apps, and connections to come up with a set of rules or profiles that encode what each application is or is not allowed to do. I will explore profiles in depth later, for now just remember that profiles are specific to an application. A given snap can have different security profiles for each of its applications based on how plugs and slots are bound to those applications.

Binding plugs and slots to app

So what does a binding look like? Let's say that links grew a bookmark manager app that can be started separately from links itself. Let's also assume that, for tighter security, we will not allow that application to talk to the network. This is how this could be encoded in snapcraft.yaml:

...
apps:
    links:
        command: links
        plugs: [network, network-bind]
    bookmarks:
        command: links-bookmark-editor (fake)
...

Note that only the links app refers to plugs, the bookmarks app does not. If a plug or slot is declared in a snap, but not associated with a specific application they will be implicitly bound to all apps. When a plug or slot is specified by one or more apps, as in the above example, it will be bound only to those applications. Compare that to the following code:

...
apps:
    links:
        command: links
    bookmarks:
        command: links-bookmark-editor (fake)
plugs:
    network:
        interface: network
    network-bind:
        interface: network-bind
...


Here both network and network-bind plugs are not referred from any application explicitly and thus they implicitly bind to all apps. Keep this in mind, the difference is subtle but the effective security is not.

Working with connections

Snappy allows users to change connections using two command line commands: snap connect and snap disconnect. Both of those respond to --help so make sure to read that if you are exploring.
Caveat: as of snapd 2.0.2 connect/disconnect only accept the fully-spelled-out form "snap connect snap:plug snap:slot". Other variants don't work yet, please avoid them for now.
For example, if you installed links as a snap earlier, you could now disconnect it from the network and network-bind slots and see what happens. Let's do so now:

Remember: the order for all of those operations is "snap:plug snap:slot"

$ sudo snap disconnect links:network ubuntu-core:network
$ sudo snap disconnect links:network-bind ubuntu-core:network-bind

After running the two commands links won't be able to use the system calls required for accessing the network and will effectively be offline.

At time of writing, links would not even start correctly, as it would be killed in the attempt to use particular network-related system calls, but I suspect that over time we will tweak the interfaces system to just put apps into an offline sandbox as if you had yanked the network cables and put enough tinfoil around your device to shield it from all signals. I would certainly love to be able to start an arbitrary app without letting it talk to the network.


If you inspect the output of "snap interfaces" you will see that it has changed slightly, the relevant parts now look like this:

Slot                 Plug
:network             -
:network-bind        -
-                    links:network
-                    links:network-bind

This output tells us that both plugs and slots are disconnected. To restore links to an usable state you can re-connect it with the following two commands:

$ sudo snap connect links:network ubuntu-core:network
$ sudo snap connect links:network-bind ubuntu-core:network-bind

That's it for today. Next time we will take a closer look at security systems and how they interact with interfaces internally. This will allow us to explore creating new interfaces for things that don't have one yet. 

As before please feel free to post your comment below. I will gladly answer all questions related to the interface system.

Special thanks to Jonathan Cave for proofreading and improvements!