Interface request: "cups-control" on CUPS snap and including D-Bus

If you want to print (or do printer administration) from a snapped application you add a plug to the “cups-control” interface, to connect to the domain socket of CUPS. The slot is most probably provided by the classic emvironment with a Debian-package-based CUPS installed.
Some time a go, I started the developemt of a CUPS snap:


First thought of it was to allow printing in an all-snap distribution like Ubunru Core, but also to provide a distribution-independent implementation of a printing stack on OpenPrinting, for everyone being able to use a printinf stack composed from the newest components. I brought it even to a first test release in the Snap Store.
Now Ubuntu development is going to provide more and more components of the Desktop distribution to be provided in snaps. Also CUPS should come from a snap soon.
Therefore I have picked up the developemnt of the CUPS Snap again and done a lot of bug fixes and improvements for better reliability and usability of the snap.
Now I was hitting into some problems with the interface:

  1. A snapped application has a plug to the “cups-control” interface. This leads to a CUPS installed from Debian packages into the base system
  2. If there is no such CUPS but the CUPS snap installed instead, “cups-control” should point into the snapped CUPS, so the snapped CUPS needs to provide the “cups-control” slot. There needs to by some kind of automatic switch for which CUPS is actually installed.
  3. CUPS does not provide only a domain socket (/var/run/cups.sock in the case of CUPS in Debian packages) but also IP access via localhost:631 and also D-Bus services (AFAIK /org/cups/cupsd/Notifier and /com/redhat/PrinterSpooler at least). The interface needs to allow access to all of these channels for every snap with plug to “cups-control” but also to unsnapped (Debian-installed) applications.
  4. The “dbus” snap interface is not very well suited for CUPS as a client snap would need to connect to “cups-control” and two “dbus” interfaces, so 3 interfaces would need to get connected, therefore I like to have that all in one “cups-control” interface.

My request is to get the “cups-control” interface appropriately extended so that snapped applications have always full access to CUPS, independent whether CUPS comes from Debian packages or from the snap, and also that unsnapped applications have full access to CUPS, also to the D-Bus services.
Note that the CUPS snap will soon get renamed, from the current “printing-stack-snap” to “ipp-service-cups”.
I hope that this is possible to get implemented.

Till

My 2 pence on this is that from a clean slate we should have 2 interfaces for cups:

  • cups which allows an application to talk to the cups daemon over DBus, access the CUPS socket, etc.
  • cups-support which provides the accesses necessary to operate as the cups daemon

The cups interface would be plugged by an application like LibreOffice that wishes to print. It would either connect to a slot provided by the core/system snap if and only if there is not an “official” cups snap, but if there is an “official” cups snap installed, the slot comes from that snap instead (this distinction is useful because we can have different accesses depending on if the snap is connecting to an unconfined cups daemon, or if it is connecting to a confined cups daemon that is packaged as a snap.

The cups-control interface would be plugged by only the confined ipp-service-cups snap, much like lxd-support is only plugged by the official lxd snap.

The only caveat I will add to this is that I seem to remember @jdstrand mentioning some time that the cups-control interface has some specific accesses that make it unsafe or unreasonably powerful and that we couldn’t properly mediate it, so some apps have been denied auto-connect for the cups-control interface. This might complicate my proposal above, meaning that we can’t have cups and instead need to have cups-control and all its unreasonably wide accesses for applications wanting to print using the official ipp-service-cups snap.

I don’t quite follow this, are you saying that installing a snap application that plugs cups-control triggers debian packages to be installed on the system?

I don’t know that this work is completely done, but there is work around a “greedy” snap declaration which allows a snap application to declare that it should connect to “all such slots”, but I think this could perhaps be extended to have some logic like connecting to “the system slot if it exists, or a specific slotting snap if that snap is installed”. See Plug/slot declaration rules: greedy plugs for the current state of things. There has been prior requests for this kind of behavior at least at https://github.com/snapcore/snapd/pull/7417 for example.

Does this just mean a network port? Is there a reason why the network interface is insufficient for this specific access?

This should be fine to add to the interfaces.

Rather than have apps plug cups-control and 2 dbus interfaces, we should probably just add the dbus rules to the cups interface I mentioned above, then applications only need to plug cups.

AFAIK, the store does not support renames, is there a significant user base here of the printing-stack-snap? If so you will probably want to put together a migration plan to get users to manually install the ipp-service-cups snap instead.

How is the cups-control interface exactly defined? Is this documented somewhere? Who provides the slot currently? What does @jdstrand tell what is too powerful on this interface?
Yes, I also think it could make sense to have 2 interfaces, a simple cups one which allows printing and checking and handling jobs via domain socket, localhost:631, and D-Bus, for the apps which want to print. and a cups-control or cups-support one for apps which want to create print queues, modify the CUPS configuration, …, which should get admin access (like a user in the lpadmin group in classic Ubuntu.
Somehow we need also automatic switching that the interfaces are provided by the ipp-service-cups package when it is installed.

  • A snapped application has a plug to the “cups-control” interface. This leads to a CUPS installed from Debian packages into the base system

I don’t quite follow this, are you saying that installing a snap application that plugs cups-control triggers debian packages to be installed on the system?

I do not mean here that the installation of Debian packages is triggered, I mean here that there are Debian packages of CUPS already present in the current standard installations of Ubuntu. Snapped apps have a plug to the cups-control interface and through this they access this CUPS which current;ly comes from Debian packages. If we replace these Debian packages by the ipp-service-cups snap, we need to assure that these applications print through the snapped CUPS.

This is exactly what I mean.

No, there is no real user base on printing-stack-snap. When I first loaded that into the Snap Store something like two years ago no one actually downloaded it and I did not get any feedback. So we can simply set up ipp-service-cups as a new package.
Till

The interface is defined in snapd source code here: https://github.com/snapcore/snapd/blob/master/interfaces/builtin/cups_control.go

I have met @ijohnson in person and talked about the issue. Similar to Docker and pulseaudio we should have a “cups-control” interface where the CUPS snap plugs to, to provide its services consisting of a domain socket for communicating with CUPS via IPP and D-Bus services to provide notifications. Applications which want to print should plug to the “cups” interface to receive access to CUPS’ domain socket and to CUPS’ D-Bus services.
There are following D-Bus services according to “audit” messages in syslog:

  • bus=“system” path="/org/cups/cupsd/Notifier" in
    terface=“org.cups.cupsd.Notifier”
  • bus=“system” path="/com/redhat/PrinterSpooler" interface=“com.redhat.PrinterSpooler”

here are 2 “audit” messages as example:

Mar  3 09:02:38 till-x1yoga kernel: [2843974.221894] audit: type=1107 audit(1583222558.216:901456): pid=1438 uid=104 auid=4294967295 ses=4294967295 msg='apparmor="DENIED" operation="dbus_signal"  bus="system" path="/com/redhat/PrinterSpooler" interface="com.redhat.PrinterSpooler" member="PrinterRemoved" mask="send" name="org.freedesktop.DBus" pid=676810 label="snap.printing-stack-snap.cupsd" peer_pid=4920 peer_label="unconfined"
Mar  3 09:02:38 till-x1yoga kernel: [2843974.221894]  exe="/usr/bin/dbus-daemon" sauid=104 hostname=? addr=? terminal=?'
Mar  3 09:02:38 till-x1yoga kernel: [2843974.224268] audit: type=1107 audit(1583222558.216:901457): pid=1438 uid=104 auid=4294967295 ses=4294967295 msg='apparmor="DENIED" operation="dbus_method_call"  bus="system" path="/org/freedesktop/ColorManager" interface="org.freedesktop.ColorManager" member="FindDeviceById" mask="send" name="org.freedesktop.ColorManager" pid=676810 label="snap.printing-stack-snap.cupsd" peer_pid=1924921 peer_label="unconfined"

.deb-based CUPS and snapped CUPS can run in parallel, both can be accessed by domain sockets, but by different ones, .deb-based CUPS uses /var/run/cups/cups.sock, snapped CUPS uses /var/snap/printing-stack-snap/current/var/run/cups.sock for now (subject to change, as snap will get renamed to “ipp-service-cups” and as the socket is accessed externally it will probably move to /var/snap/ipp-service-cups/common/.... The D-Bus service names are the same for both. I do not know how this gets handles. The .deb-based CUPS is always on port 631, the snap goes to port 631 by default but uses 10631 when 631 is already occupied (usually by an already present .deb
-based CUPS. Both have their cups-browsed to accompany them. If both are running, they exchange shared printers like usual local and remote CUPS servers and clients. So running both could be helpful for development.
In production one should run only one, preferably the snap (which will run on port 631 then).
It should be esily possible for an application user to print, especially on the snap.
Please correct me if I wrote up something wrong here.

@jdstrand, could you have a look into this concept? Thanks.

The CUPS socket allows configuring printers in addition to printing, which is why we named it cups-control rather than cups. The rationale for not auto-connecting is described here: Process for aliases, auto-connections and tracks (see "auto-connection request considerations)).

I took a look. Rather than giving my preliminary thoughts, someone can summarize our meeting notes here.

The summary of our meeting is as follows:

  1. we want to be able to support 3 different user experiences:

    1. snap consumers already plugging cups-control to continue to be able to print to cups via distro packaging
    2. snap consumers already plugging cups-control to be able to print to cups packaged as a snap
    3. non-snap consumers to be able to print to cups packaged as a snap

    Due to the above, the snap providing cups will bind to /run/cups.sock (where /var/run is a symlink to /run) and listen on the standard 631 network port. The snap providing cups may at its discretion implement a fallback mechanism for testing purposes that puts its unix socket in SNAP_DATA/SNAP_COMMON (optionally exporting via the content interface) and listen on 10631 (or wherever)

  2. the cups-control interface will continue to support implicitClassic for 1.1, above

  3. the cups-control interface will be modified to also support ‘slot-snap-type: app’ to support 1.2, above

  4. rather than implementing an additional interface for cupsd accesses, the snap providing cups will ‘slots: cups-control’ which will grant via PermanentSlot any cupsd-specific accesses. The snap providing cups will plugs existing interfaces like ‘network’, ‘network-bind’, ‘avahi-control’, etc as needed

  5. per @till.kamppeter, the DBus interface is only about monitoring and subscribing to notifications, not about administration and these will not change. For now (though this is subject to change in PR review), we will add the appropriate DBus accesses to cups-control (esp for cupsd to bind to the interfaces and respond to requests from unconfined processes; adding the accesses for plugging snaps to use is likely also fine since they can get this information from the admin portions of the socket APIs). This helps support 1.3, above

In addition to the above, we discussed what it would take to allow auto-connection for printing. Note that printing is only done through the socket (not the DBus APIs). Also, recall that cupsd’s decision on whether to allow admin functionality to the connecting user is based on group membership. With typical desktop applications on single-user installs, the uid of the process the snap is running as will already be in this group, which means that connected snaps will typically have the necessary group membership to configure printing on the system rather than just print (which is why it is named ‘cups-control’ instead of ‘printing’ or similar). To address this, we discussed that a patch could be developed similar to what was done for pulseaudio, where cupsd would:

  • look at the PEERCRED of the connecting client process to obtain the pid/etc of the process
  • in addition to cupsd’s normal group checks, it would ask snapd (eg, via snapd-glib or similar) to see if the connecting process is from a snap
  • make a decision based on this information. An initial implementation might simply deny access to admin functionality if the connecting process is a snap. A future implementation might have cupsd then ask if a specific interface is connected, and if so, allow the admin access (a suggestion would be to introduce a new ‘cups’ interface that allows access to the socket and only if cups-control is connected would admin access be allowed).

This patch should be upstreamed to Apple. @till.kamppeter said he would discuss patching cupsd with @jamesh and see if a PoC could be developed in time for an upcoming OpenPrinting summit. If they are amenable to the change, we can consider changes to snapd. In the meantime, distros could choose to distro-patch as desired.

@till.kamppeter - as a future improvement to the snap, I suggested perhaps looking into using the ‘snap_daemon’ user instead of running as root. See System usernames for details and caveats.

1 Like

While not for this cycle, I’ve added these changes to my TODO, hopefully for early next cycle.

@pedronis - in addition to the above, please note that as an aside and not tied to the cups-control changes, @till.kamppeter mentioned that he is not able to create a group that the snap can consistently use to check for group membership of connecting clients for cupsd’s decision wrt access to admin APIs over the unix socket. This is similar to lxd, docker and multipass (and the related multipass request). This request is slightly different since those use 660 perms with group and cupsd uses 666 and looks at the PEERCRED.

Note that there isn’t such an API in snapd at present. The PulseAudio module I put together does this by checking the AppArmor label of the process. Xdg-desktop-portal does the same, but will be switching to check cgroups plus a call out to snap routine portal-info (it was done this way to avoid putting too much complicated logic on the x-d-p side).

Either of these strategies are possible under strict confinement with an appropriate interface, but they are not exactly simple.

I wonder if a better approach (and one more likely to be accepted upstream) would be to implement polkit support into CUPS? This has a number of benefits:

  1. It will be useful on Linux systems not running snapd, so could gain support from other distros if Apple pushes back.

  2. Polkitd can make access decisions based on the user’s host system user names or group memberships that a confined cupsd can’t see. This would make it possible to implement passwordless access for lpadmin members if desired.

  3. If configured to require the user’s password (i.e. auth_admin or auth_admin_keep), then perhaps we don’t need the snap check: the polkit dialog will allow the user to cancel administrative actions before they are completed.

This would obviously require some further improvements to snapd: namely allowing snapped daemons to talk to polkitd:

I think it is also worth keeping in mind that we have a solution available to allow applications to print without connecting the cups-control interface, in the form of xdg-desktop-portal’s print portal. Applications using GTK 3’s printing APIs can automatically use this interface. I think it would be better to steer applications towards that interface rather than following the audio-playback/audio-record model we used for Pulse Audio where the daemon makes the policy decision.

There is also cups-pk-helper which could be added to the CUPS Snap.
I never have looked deeply into it and how it works but perhaps it is helpful.

cups-pk-helper does show that there is some demand for a polkit mediation. The main problem is that to use cups-pk-helper, the client application needs to be rewritten to use its D-Bus API rather than sending IPP requests over /run/cups/cups.sock.

What I am suggesting is have cupsd make the polkit calls itself, which would allow standard cups clients to take advantage. We managed to integrate polkit support into snapd’s “HTTP over unix domain sockets” API, so it should be possible to do the same with cupsd.

Yes, the polkit-in-cupsd is principally a good idea and could be useful also for other things than only snapping CUPS, what raises the chances that Apple accepts the patch. Also for Apple to accept the patch we should use conditionals to optionally build without PolicyKit/polkit and also a directive in cups-files.conf to turn on/off PolicyKit/polkit support.
One problem is that there is PolicyKit and polkit, where the latter s the newer AFAIK. Now the question is whether we need to support both or only one of them and if only one which one.
Now if one uses PolicyKit/polkit, does this mean that I as creator of the CUPS Snap can say which group of the host system is allowed to do admin? Or can we also have two interfaces with that, one print-only and one print-and-admin?
Can we perhaps move code of the cups-pk-helper mechanism into CUPS itself to integrate the functionality in cupsd and eliminate the need of cups-pk-helper for tools like GNOME Control Center or system-config-printer?

@jamesh, @jdstrand, WDYT is the better method? The one of pulseaudio or using PolicyKit/polkit?

PolicyKit and polkit are the same thing: not two competing projects.

For the cupsd side, it would need to be able to issue an org.freedesktop.PolkicyKit1.Authority.CheckAuthorization D-Bus method call to decide whether to process a particular HTTP request. From the sound of it there is already some level of D-Bus integration for the Avahi support, so hopefully this won’t be too invasive.

As far as configuring default access, there are two parts to configuring Polkit:

  1. .policy files provided by applications. These are XML files describing the various actions the application will use, associating human readable descriptions for display in dialogs and default policy (e.g. let anyone perform the action, only admin users, only admin users after they provide their password, etc). These are not intended to be edited by users or system administrators.

  2. policy configuration files: either key file .pkla files with Ubuntu’s version of polkit, or Javascript .rules files for newer releases. These are usually provided by the distribtuion or local system administrator to augment or override the defaults from the .policy file. They are more expressive, so could be used to e.g. allow an action to be called by lpadmin group members.

At present there is no support in snapd for daemons that delegate policy decisions to polkit. In the forum thread I linked to, I was suggesting we let a snap install .policy files (after validation). I’d be wary about installing pkla/rules files, since they are not usually the domain of applications. Also, the JS versions are pretty much impossible to validate.

If Ubuntu is going to ship cups as a snap though, it could easily ship policy configuration that affects the cups snap’s actions though.

Important is that the CUPS Snap can be installed on any Snap-supporting operating system, not only Ubuntu. So we must be careful with allowing the Snap to install files out of the sandbox (*.policy) or require certain files outside the sandbox (*.rules/*.pkla) this can restrict the Snap to Ubuntu and it also can be a similar situation as the (now deprecated) printer drivers being PPD files and filters which need to be in certain file system locations, a situation we want to avoid with the new printing and scanning architecture.
Perhaps we need to go the PulseAudio way.

I’ve often said (outside of this thread) that polkit integration would be a welcome addition to cupsd. It can then listen on its socket like normal and apply polkit policies to its APIs. Presumably, all things related to printing would be allowed and all things related to admin would require auth_admin/auth_admin_keep. This would be a great addition to cupsd irrespective of snapd.

With respect to snapd:

  1. it would be easy enough to allow the cupsd providing (slot) to talk to polkit via its PermanentSlotAppArmor policy
  2. there is no polkit daemon on Ubuntu Core devices
  3. the devil is in the details wrt the polkit policy. Today, access to admin functionality is limited to group membership. I strongly suspect that for a similar UX, distros (including Ubuntu) would recreate this under polkit and say “admin APIs are allowed if user is in the lpadmin group” to avoid bugs from users complaining about needing a password to configure some aspect of a printer when they never used to. Since single user desktop installs will default to this, we are back to where we started with snap applications running in the desktop environment are usually able to access admin APIs via the socket
  4. the polkit policy will likely need to allow non-console root processes without a password so snap daemons that plugs cups-control would have access to the admin APIs
  5. snapd doesn’t currently have a polkit backend for the snap to ship policy and put in place for the host’s polkit to use
  6. the patch to cupsd would be rather extensive in comparison to the ‘pulseaudio approach’ (even considering what @jamesh reminded us about that there is no API for querying snapd so cupsd would have to implement that)

Due to ‘2’ on Ubuntu Core devices and ‘4’ in general, cupsd would need the ‘pulseaudio approach’ anyway to mediate admin access for snaps

‘3’ is a real problem when accessing cupsd coming from a deb when that deb implements polkit policy that isn’t what we would want for snaps. The “pulseaudio approach” is likely needed for this as well. While snap-confine is in a position to drop “lpadmin” from the list of supplementary groups as part of startup, this is unlikely to scale out cross-distro since it is possible that different distros will use a different group for cupsd.

‘5’ requires not insignificant work to snapd (it would be generally useful though), but is inhibited by ‘2’ (we would want to consider retroactively adding polkit to UC16, UC18 and UC20 for this). As @till.kamppeter mentioned, that implementation would need to be careful to not step on existing host policy (though, in theory we could utilize the vendor or site policy mechanisms to override deb/rpm/ubuntu/distro policy…).

(It is also theoretically possible for the printing stack snap to ship polkitd, listening on something printing stack-specific in terms of DBus and have the snap have internal polkit policy that this snap-specific-polkitd would use and cupsd would query that. This still wouldn’t address ‘4’ and for systems with only the printing stack snap installed, UX regresses due to ‘3’. Not to mention, this is pretty terrible implementation-wise. :slight_smile: )

In short, I would welcome polkit integration for cupsd, but it is unlikely to be sufficient to address all mediation points for snaps accessing the admin APIs. We’ll still need a “pulseaudio approach” IME, but just like @till.kamppeter said for polkit, this would be an optional build feature.

@jdstrand, thanks, I think that the PulseAudio way is the better one then.

@jamesh, could you do the PoC you suggsted for patching CUPS with the PulseAudio-like solution, like suggested in @jdstrand’s meeting summary and your answer? Thanks.