Today we will look at what makes an interface. This post might be a bit heavier on the programming side so feel free to skip over the code fragments if that is not your thing. Note that we will not build an actual interface just yet. The goal of this article is to set the stage for that to be meaningful, at least in part.
The Interface interface
From the point of view of snappy, an Interface is a bit of code with specific APIs. In go's terms it is an interface (note the lower case i). If you are familiar with other languages it is just a way to describe a class with a specific set of methods.In go, this is spelled out as:
type Interface interface { ... }
This can be read as "the go type Interface is an object with the following methods ..."
Interface name
At a very basic level each interface has a name.type Interface interface { Name() string ... }That is, having an arbitrary interface you call the Name method to obtain the name of that interface. Interface name must be unique and is something that other developers will refer to so plan ahead and pick a good, descriptive name. You cannot just change it later.
Validating plugs and slots
Two of the methods in an Interface are used to verify if a plug or slot definition is correct.type Interface interface { ... SanitizePlug(plug *Plug) error SanitizeSlot(slot *Slot) error ... }
Remember that plugs and slots can hold arbitrary attributes. A particular interface, say, one that allows access to a GPIO pin, can use an attribute to describe which particular pin is exposed. As an interface author you should check if the pin is specified correctly (e.g. that it is a number, that it has a sensible value, etc).
Both methods take an object to sanitize (a plug or a slot) and return an error if the object is incorrect. If you don't need to check anything just return nil and carry on.
Interfaces and snippets
Having a valid plug and slot, the main thing that interfaces do is to influence the various security systems. This is implemented as a set of four methods. Before I will spill the beans on the code I will explain this informally.
Small digression, when you see a security system below think of things like apparmor and seccomp. I will focus on security systems in a dedicated instalment. For now they are simply a part of the overall security story.
Each security system is responsible for setting up security for each app of each snap. By default all apps get the same treatment, there is nothing unique about any particular app. Interfaces allow to pass extra information to a particular security system to let a particular app do more than it could otherwise.
This extra information is exchanged as snippets. Snippets are just bits of untyped data, blobs, sequences of bytes. In practice all current snippets are just pieces of text that are easy to read and understand.
Interfaces can hand out snippets for each of the four distinct cases:
- the mere fact of having a plug of a given interface
- the fact of having a particular plug connected to a particular slot
- the mere fact of having a slot of a given interface
- the fact of having a particular slot connected to a particular plug
Typically most permissions will be based around a plug connected to a slot. Apps bound to the plug will be allowed to talk to a specific socket, to a specific DBus object, to a specific device on the system. All such permissions will be expressed through a snippet provided by case 2 in the list above.
For applications providing services to other snaps (e.g. bluez, network-manager, pulseaudio, mir, X11) the mere fact of having a slot will grant permissions to create the service (to configure network, manage GPUs, etc). Applications like this will use the mechanism provided by case 3 in the list above.
The meaning of the snippets is almost opaque to snappy. Snappy collects them, assembles them together and hands them over to security backends to process. At the end of the day they end up as various configuration files.
So how does the method definition look like? Like this:
type Interface interface { ... PermanentPlugSnippet(plug *Plug, securitySystem SecuritySystem) ([]byte, error) ConnectedPlugSnippet(plug *Plug, slot *Slot, securitySystem SecuritySystem) ([]byte, error) PermanentSlotSnippet(slot *Slot, securitySystem SecuritySystem) ([]byte, error) ConnectedSlotSnippet(plug *Plug, slot *Slot, securitySystem SecuritySystem) ([]byte, error) ... }Note that each method gets the name of the security system as an argument. A single interface can influence all security systems if that is required for it to operate correctly.
Connecting interfaces automatically
The one last thing an interface can do is say it wants to automatically connect plugs to viable slots under certain conditions. This is expressed as the following method:type Interface interface { ... AutoConnect() bool }
This feature was designed to let snappy automatically connect plugs in snaps being installed if there is a viable, unique slot on the OS snap that satisfies the interface requirements. If you recall, the OS snap exposes a number of slots for things like network, network-bind and so on. To make the user experience better, when a snap wants to use one of those interfaces the user does not have to connect them explicitly.
Please note that we're going to be conservative in what can be connected automatically. As a rule of thumb auto-connection is allowed if this is a reasonable thing to do and it is not a serious security risk (the interface doesn't hand out too much power).
The complete picture
You can check the complete interface code, with documentation, here. The key thing to take out of this whole article is that interfaces are bits of code that can validate plugs and slots and hand out security snippets.How this actually gets used and how the snippets should look like, that is for the next post.