New interface: "cups" for all Snaps which print

For the Technically Interested: How the “cups” snapd Interface Actually Works

NOTE: This post describes the rather complex inner workings of the cups interface in snapd. As a snapper you do not need to read this, all instructions you need are in my first post, above. You can simply skip to the further news and discussion in the following posts.

Originally posted in the OpenPrinting News March 2022

Snaps are file systems in isolated sandboxes. A Snap is not able to access the file system of another Snap (like the CUPS Snap for example) nor it is able to access the file system of the host system (like for example the Unix socket of a classically installed CUPS).

For exceptions from this isolation there are interfaces. They have well-defined permissions what a Snap can access in the outside world. Snaps of user applications, like LibreOffice, Darktable, … have so-called “plugs”: network, avahi-observer, …, and cups. These plugs are connected (“plugged”) to so-called “slots”, most of the host system but some also of other Snaps (usually servers/system daemons in a Snap, like the CUPS Snap).

The packager of a Snap can decide which interfaces the packaged application plugs and so he can have maximum security without losing functionality of the application. He uploads the Snap to the Snap Store and when a user installs it on his system, the interfaces usually get auto-connected and the user can use the application without hassle.

Now there are some interfaces through which you can do operations which are dangerous for the system. One of them for example is the cups-control interface, which snapd provided already from the beginning on and plugging it (to the system) allows your Snap not only to list the available printers and to print on them, but also to do any administrative tasks (creating and modifying queues, changing cupsd configuration, seeing and deleting other user’s jobs, …) on the system’s classically installed (RPM, DEB, …) CUPS. In classic systems you control this via the “lpadmin” group, but in the Snap world you do not have any system groups.

To avoid that arbitrary developers can upload Snaps to the Snap Store which plug such “dangerous” interfaces and abuse them, compromising the system’s security, only selected (not “dangerous”) interfaces get auto-connected when installing from the Snap Store, other interfaces need to get connected manually by the user (requires “root” access to the system), or the developer of the Snap has to request a special permission for auto-connection by the Snap Store team (they manage the list of which interfaces are “dangerous” or not). The Snap Store team reviews the application and at least 2 members have to be in favor of the auto-connection for it to get improved.

Now when I started to get printing into the Snap world several years ago, snapping CUPS but also looking into how snapped applications can print, I found out that the only way to print out of a Snap was through the “dangerous” cups-control interface. The snapper of an application had to plug this interface and request special permission from the Snap Store for the interface’s auto-connection on the user’s machines, or the user had to connect it manually, not really having printing “just work”. In addition, this could lead up to very many special permissions from the Snap Store team and raise the probability that a developer abuses his permission by modifying his application.

So I started developing, together with the snapd developer team, the new cups interface. All the development was accompanied by the snapcraft.io forum thread of the initial feature request which I started exactly one year ago, on March 18, 2021. There were two pull requests on the snapd code, the first got dropped and the second got merged around half a year after getting initially posted. After the merge, on February 7, 2022, I marked this thread as solved and started a second thread for the finalization.

Now let us see how this interface works:

An application with print functionality (like LibreOffice, Darktable, gedit, …) plugs the cups interface. In contrary to cups-control the slot is not in the host system, but in the CUPS Snap, always the CUPS Snap, even if the user uses a classically installed (RPM, DEB, source, …) CUPS on his system. This means that the CUPS Snap gets auto-installed (like a package dependency) during the installation of the application Snap when it is not already on the system.

The application Snap then mounts the CUPS Snap’s /var/snap/cups/common/run directory into its own file system’s /var/cups directory and has this way access to the Unix socket of the CUPS Snap’s CUPS daemon, as /var/cups/cups.sock. This interaction between the two Snaps is part of the cups interface. In addition, the cups interface sets the CUPS_SERVER environment variable in the application Snap’s sandbox to /var/cups/cups.sock so that the application uses the CUPS Snap’s CUPS.

If there is no classically installed CUPS on the system, the CUPS Snap is running in its standalone mode and is the system’s print environment, also for non-snapped, classically installed applications (for which it then creates a second socket as the usual /run/cups/cups.sock). So both the snapped and the unsnapped applications print happily via the snapped CUPS.

But what if there is a classic installation of CUPS on the user’s system, with lots of thoroughly manually created queues and even proprietary printer drivers which we cannot migrate into a CUPS-Snap-based system? In this case the CUPS Snap sees that there is a classic CUPS configuration and automatically starts in its proxy mode. This means the CUPS Snap’s CUPS daemon is a proxy for the system’s CUPS daemon. The CUPS Snap runs a helper daemon (named cups-proxyd) along with its CUPS daemon and cups-proxyd observes the system’s CUPS and clones all its queues, as filterless pass-through queues but with the same PPD files as the original queues, to have the same options and printer properties. Every change on the system’s classically installed CUPS is immediately synced into the Snap’s CUPS.

But why that complicated for the most common situation of having a classically installed CUPS on the system? Why running two CUPS daemons on one simple desktop machine? The Snap’s CUPS daemon in this configuration is a firewall for the system’s CUPS daemon, it protects it against administrative requests of the snapped application.

In the beginning we told that the cups interface blocks administrative requests, but we did not tell how. snapd does not understand IPP (Internet Printing Protocol) and so it cannot filter out the unwished requests from the communication between the application and the CUPS daemon. So who is best for understanding IPP? CUPS! So I have implemented the filtering in the CUPS daemon, the so-called Snap mediation. The CUPS daemon, if it is new enough, 2.4.x in general, also some 2.3.x in case of Debian or Ubuntu packages, checks on each administrative inquiry it receives whether it is from a confined Snap (only these you can upload into the Snap Store without special permission). If so, it checks whether the client Snap plugs cups-control and only then it accepts the request. Requests from unsnapped clients or classic Snaps are not blocked. As we are explaining the mechanisms of the cups interface, our client plugs cups and not cups-control and so its administrative requests get rejected and our CUPS is safe.

The CUPS Snap only exists with a Snap-mediating CUPS, at least at the time of launch of the cups interface with snapd 2.55, but actually already for many months, since I implemented the Snap mediation. This way with the cups interface forcing the application’s CUPS communication through the Snap’s CUPS we are for sure blocking the unwished requests. If we let the snapped application communicate directly with the system’s CUPS, we cannot be sure that the administrative requests are blocked, as classically installed CUPS daemons can be too old or the package maintainer could have opted for building CUPS without Snap mediation. We need to keep in mind that Snaps are distribution-independent packages, and each distribution’s classic CUPS packages can be different. Therefore we need the CUPS Snap as proxy.

This way we can safely install application Snaps. They plug cups and therefore can print but not mess up CUPS, whatever CUPS the user prefers to use. Developers can easily snap their applications with print functionality and upload them to the Snap Store, without questions asked and easily installable by the user, so that his printing “just works”.

2 Likes