New interface: "cups" for all Snaps which print

After one year of hard design and development work in a great collaboration of the Canonical snapd Team, the Canonical Desktop Team, and the OpenPrinting project it is finally here:

The “cups” interface for easy and secure printing out of Snaps

This is for all the snappers under you who snap software which prints, like LibreOffice, Chrome/Chromium, Firefox, Thunderbird, DarkTable, RawTherapee, …

Before, you had to plug the cups-control interface for this, but as this interface does not only allow to list the available printers and their options and send print jobs but also create and modify queues and their permissions, read and delete anyone’s print jobs, … you either had to require the user to manually connect the interface or get explicit permission from the Snap Store team to auto-connect the interface.

As we at OpenPrinting want that printing “just works” for your users, this is a bad situation.

Therefore we have created the new cups interface which allows the Snap which plugs it only to list printers and the printer’s options, and to print (non-administrative CUPS tasks) but NOT to create and modify queues, or delete anyone’s jobs (administrative CUPS tasks). Due to this the interface gets connected automatically when your cups-plugging Snap gets loaded from the Snap Store, no special permissions required, no questions asked! Printing will “just work”.

What do you have to do?

  • If you are snapping a new application and it has print functionality, simply plug cups as described below.
  • If you are maintaining an application Snap and for just printing you have it plugging cups-control, switch to plugging cups as described below.
  • If you are creating or maintaining a Snap of a printer setup tool, you have to plug cups-control and live with the alternatives of manual connection or special permission of the Snap Store team.

How do I use it?

First, the new interface got added in snapd 2.55.3, the first stable release in the 2.55 series. This version is included in Ubuntu 22.04 LTS (Jammy Jellyfish) but also in the stable channel of the Snap Store, so probably it already landed on your system as an auto update.

Snapping an application with print functionality is easy, do not get turned away by the rather complicated inner workings. Especially your Snap will always talk with the CUPS Snap for all things printing, never with the system’s cupsd (it auto-installs the CUPS Snap as a dependency). This is for security. If the user has a conventional CUPS installation, the CUPS Snap works as a firewall only passing through the non-administrative requests and blocking the administrative ones and if the user uses the CUPS Snap as standard printing environment the CUPS Snap manages this by itself (see my next post here).

First, you start your snapping the same way as you are used to for applications without print functionality. Read about this part elsewhere.

Second, in snapcraft.yaml let each app with print functionality plug the cups interface:

apps:
  my-printing-application:
    command: my-printing-app
    plugs:
      - cups
      - network

or

apps:
  lp:
    command: usr/bin/lp
    plugs: [network, home, cups]
  lpstat:
    command: usr/bin/lpstat
    plugs: [network, avahi-observe, cups]

Third, add this line to snapcraft.yaml:

# We need snapd 2.55 or later to have the needed support for the
# `cups` interface in snapd
assumes: [snapd2.55]

Do not put this into any section, put it right after the header part for example. Simply copy and paste this blob of lines as you see it here.

These lines make your Snap require at least version 2.55 of snapd, as older versions do not have the cups interface. I assume that this triggers an auto-update of snapd if needed.

Forth, add the placeholder content interface to trigger the auto-installation of the CUPS Snap. Simply add the following lines to snapcraft.yaml:

# this is not used or needed for anything other than to trigger automatic
# installation of the cups snap via "default-provider: cups"
plugs:
  foo-install-cups:
    interface: content
    content: foo
    default-provider: cups
    target: $SNAP_DATA/foo

Do not put this into any section, too, put it again right after the header part for example. Simply copy and paste the whole blob of lines as you see it here. Nothing needs to get adapted to your particular Snap, nor you have to create any directories in your Snap for that to work.

Note: Having to add these lines is only a temporary workaround. The default-provider functionality is planned to be added to the cups interface in snapd in the coming months. When this has taken place, all what is needed to use the cups interface is plugging cups as described in the second step here and the assumes: [snapd2.55] line of the third step.

Fifth, build your Snap as you are used to, test it, and upload it into the Snap Store using your habitual method. If a user installs it, the CUPS Snap gets auto-installed and the cups interface auto-connected and printing out of the Snap “just works”.

DO NOT plug both cups and cups-control in the same Snap. This can mess up things, especially as CUPS’ Snap mediation (mechanism to block administrative tasks) works “per-Snap” and not “per-application-in-the-Snap”, meaning that if one of the apps in the Snap plugs cups-control CUPS assumes the whole Snap plugging cups-control and allows all apps in the Snap to do administrative operations.

See a complete example here.

Thanks

Thanks a lot to my colleagues in the Desktop and snapd teams of Canonical for the great collaboration and team work to get the cups interface designed and implemented: Michael Vogt, Ian Johnson, James Henstridge, Samuele Pedroni, Alex Murray, Alberto Mardegan

And also thanks to Michael Sweet for the helping with the smooth integration of the Snap mediation into the CUPS code.

6 Likes

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

Updated the instructions for using the cups interface as a line assumes: [snapd2.55] should be added to snapcraft.yaml due to the fact that the cups interface only got introduced in snapd 2.55.x.

Thanks, @sergiusens for the hint.

2 Likes

Opend a thread in the “doc” forum to request for the interface’s documentation be added to the snapcraft documentation. @degville is on it.

While starting to make use of the new cups printing interface in web browsers, replacing cups-control by cups there, as recommended here, it turned out that the CUPS Snap in proxy mode did not clone the print queues, at least if there are no network IPP printers around (observed by @osomon and @nteodosio, thanks). This was caused by bugs in the CUPS Snap itself (cups-proxyd did not do an update right after starting (fixed), CUPS Snap needs to plug cups-control to use D-Bus services of host system’s CUPS (fixed)) and a bug in snapd (erroneous restriction peer=(name=org.freedesktop.DBus,label=...) for CUPS D-Bus access, Pull request #11843 on snapd).

As soon as the fix for snapd is merged (snapd 2.56) the CUPS Snap as proxy for the cups interface will clone the system’s print queues correctly in all cases.

For now, at least when starting the CUPS Snap the cloned queues are updated, as well as on appearing or disapperaing of a network printer. So if a new print queue does not get cloned (not appear in print dialogs of snapped applications), either power-cycle an arbitrary printer in your local network or restart the CUPS Snap (sudo snap stop cups; sudo snap start cups).

Thanks, @jamesh for the pull request on snapd, and @mvo for approving this pull request.

3 Likes

Also @mardy and @alexmurray have approved the PR! Thank you very much!

Request for CUPS Snap auto-connection to system’s “cups-control” posted.

PR got merged! Thanks, @mvo!

The first application using the cups interface got uploaded to the Snap Store on May 17, onlyoffice-desktopeditors and after that, on June 13 FreeCAD. Congratulations to the developers of these applications and thanks to @roadmr from the Snap Store Team at Canonical for this valuable information.

1 Like

CUPS Snap auto-connection to system’s “cups-control” is working now.

Also thanks to @nteodosio (he initially discovered the problem) for re-testing to see that all is working now.