Snapcraft confinement & interfaces
by Igor Ljubuncic on 6 June 2019
Snaps. The final frontier. These are the voyages of the OSS Snapcraft. Its continuing mission, to provide snap users with simple, clear and effective tips and tricks on how to build and publish their applications.
In this tutorial, we are going to talk about confinement and interfaces – how to restrict what your snaps can do, and then fine-tune the restrictions to make your applications both secure and useful.
What are we going to do?
Our tasks for today will be:
- Overview of confinement levels and their use cases.
- Overview of interfaces, automatic and manual connections.
- Basic but practical examples.
Moreover, you should read the first two articles – Introduction to snapcraft and Parts & Plugins, to get a better sense of the content today. This will help you familiarize with the snapcraft ecosystem, the tools and commands, as well as several practical examples of how to build snaps.
By design, snaps are confined and limited in what they can do. This is an important feature that distinguishes snaps from software distributed using the traditional repository methods. The confinement allows for a high level of isolation and security, and prevents snaps from being affected by underlying system changes, affecting one another or affecting the system.
Different confinement levels describe what type of access the application will have once installed on the user’s system. Confinement levels can be treated as security filters that define what type of system resources the application can access outside the snap.
Confinement level is specified in the snapcraft.yaml file and will affect how your application behaves during runtime.
Devmode – This is a debug mode level used by developers as they iterate on the creation of their snap. This allows developers to troubleshoot applications, because they may behave differently when confined.
Strict – This confinement level uses Linux kernel security features to lock down the applications inside the snap. By default, a strictly confined application cannot access the network, the user’s home directory, any audio subsystems or webcams, and it cannot display any graphical output via X or Wayland.
Classic – This is a permissive level equivalent to the full system access that traditionally packaged applications have. Classic confinement is often used as a stop-gap measure to enable developers to publish applications that need more access than the current set of permissions allow. The classic level should be used only when required for functionality, as it lowers the security of the application.
Classically confined snaps are reviewed by the Snap Store reviewers team before they can be published. Snaps that use classic confinement may be rejected if they don’t meet the necessary requirements.
Confinement in action
We will shortly touch upon a practical example. Before we do that, let’s briefly focus on how confinement works and how it manifests when you run your snaps.
On the system level, the isolation of snaps is enforced via Discretionary Access Controls (DAC), Mandatory Access Control (MAC) via AppArmor, Seccomp kernel system call filtering (which limits the system calls a process may use), and cgroups device access controls for hardware assignment.
If a snap tries to access a resource that has not been explicitly granted, the access will be gated based on the confinement level specified in the snapcraft.yaml file:
- In the devmode, the access will be allowed, but there will be a notification of the error.
- In the strict mode, the access will be denied.
- In the classic mode, the access will be identical to system-level permissions.
As mentioned earlier, a strictly confined snap is considered untrusted, and it runs in a restricted sandbox. By design, untrusted applications:
- can freely access their own data
- cannot access other applications data
- cannot access non-application-specific user data
- cannot access privileged portions of the OS
- cannot access privileged system APIs
- may access sensitive APIs under some conditions
Strictly confined applications are not always functional with the default security policy. For example, a browser without network access or a media player without audio access do not serve their intended purpose.
|Access to network||N||Y||System|
|Access to home dir||N||Y||System|
|Access to audio||N||Y||System|
|Access to webcam||N||Y||System|
|Access to display||N||Y||System|
To that end, snap developers can use interfaces. These allow developers to expand on existing permissions and security policies and connect their applications to system resources. Interfaces are commonly used to enable a snap to access OpenGL acceleration, sound playback or recording, the network and the user’s $HOME directory. But which interfaces a snap requires, and provides, is very much dependent on the type of snap and its own requirements.
An interface consists of a connection between a slot and a plug. The slot is the provider of the interface while the plug is the consumer, and a slot can support multiple plug connections.
The Snap Connection
Interfaces can be automatically or manually connected. Some interfaces will be auto-connected. Others may not, especially if they have access to sensitive resources (like network control, for instance). Users have the option to manually control interfaces – connect and disconnect them.
In the first article, we briefly touched on the use of interfaces in the wethr example. The snapcraft.yaml file specifies the network plug for the wethr binary. Based on this definition, when the snap is installed, a security profile will be generated that grants the application access to network during runtime. This interface will be auto-connected so the user does not need to make any manual adjustments to have the snap work.
Now, let’s examine a different example
What do we have here?
We define an application that will have access to a range of resources, including the home directory, firewall, bluetooth, network, and even X11. Some of these interfaces will not be auto-connected. To that end, we need to examine what happens during runtime.
Automatic and manual interface connection
Once installed, you can check the list of interfaces your snap has by running:
snap interfaces “snap name”
Auto-connected interfaces will be shown with both the slot and the plug listed. Interfaces without connection will have an empty slot denoted by the dash character.
You can manually connect (or disconnect) interfaces by running:
snap connect “snap”:”plug interface” “snap”:”slot interface”
A slot and a plug can only be connected if they have the same interface name. For example, to connect FFmpeg’s ALSA plug to the system’s ALSA slot, you’d enter the following:
sudo snap connect ffmpeg:alsa :alsa
If you do not specify the slot interface, the default (system) one will be use, e.g.:
snap connect easy-openvpn:home
snap disconnect easy-openvpn:home
We can also examine what happens in the background. You can do this using the snappy-debug snap, which is designed to help developers troubleshoot common problems with their snaps. You can use it to help identify missing interfaces by reporting on application security failures. It will also make suggestions on how to improve the snap, perhaps by adding interfaces.
snap install snappy-debug
After the snappy-debug tool is installed, run it in a separate shell:
In a different command-line window, run your application and go through the expected behavior steps until you encounter an error. You can then consult the log for more details, which should highlight any issues with your snap. For instance, if you use a Firefox snap with the removable-media that isn’t auto-connected, and you try to save a file to a USB drive, you will see something like the error below:
= AppArmor =
Time: Oct 24 13:39:04
Log: apparmor="DENIED" operation="open" profile="snap.firefox.firefox" name="/etc/fstab" pid=25299 comm="firefox" requested_mask="r" denied_mask="r" fsuid=1000 ouid=0
File: /etc/fstab (read)
* adjust program to read necessary files from $SNAP, $SNAP_DATA, $SNAP_COMMON, $SNAP_USER_DATA or $SNAP_USER_COMMON
* add 'mount-observe' to 'plugs'
And that brings us to the end of this tutorial. Today, we focused on confinement and interfaces. The former is a method of restricting your application’s access to system resources using security policies. The latter is a method of allowing fine-tuned control of said resources.
We learned about the differences between confinement levels, the use of plugs and slots, and how to connect interfaces, both automatically and manually. Lastly, we reviewed some debugging tools that can help developers troubleshoot their applications. In the future, we will talk about hooks and health checks, as well as touch on simple recipes for how to create some common application types.
Thank you for reading. If you have any comments or suggestions, please join the forum for a friendly discussion.