Printing and managing printers from your Snap

Hi,

TL; DR: If you snap an application which prints, plug “cups” and not “cups-control” (update your Snaps), plug “cups-control” only if you want to manage CUPS.

some days ago I have put up the CUPS Snap in the Snap Store and also posted a Call for Testing here.

In this post I want to give simple examples how to correctly create (fully confined) Snaps which print via the installed CUPS (either the CUPS Snap or the Debian/Ubuntu package) or manage printers and jobs on the installed CUPS.

For this we simply make primitive sample Snaps from the command line tools which come with the DEB package of CUPS (binary package “cups-client”). The first one mimics a user application supposed to have print functionality, but has some CUPS admin functionality slipped in (Malicious intentions? Bad communication between upstream and the snapper of the app? …). Here is the snapcraft.yaml:

name: cups-user-app-test
base: core18 # The base snap is the execution environment for this snap
version: 0.1.0
summary: CUPS command line tools out of a Snap 
description: Testing interfaces for the CUPS Snap (no cups-control plugging, so admin tasks should fail)
grade: stable
confinement: strict

apps:
  lpinfo:
    command: usr/sbin/lpinfo
    plugs: [network, cups]
  lpadmin:
    command: usr/sbin/lpadmin
    plugs: [network, avahi-observe, home, cups]
  lpstat:
    command: usr/bin/lpstat
    plugs: [network, avahi-observe, cups]
  lpoptions:
    command: usr/bin/lpoptions
    plugs: [network, home, cups]
  lp:
    command: usr/bin/lp
    plugs: [network, home, cups]
  cancel:
    command: usr/bin/cancel
    plugs: [network, cups]
  lpmove:
    command: usr/sbin/lpmove
    plugs: [network, cups]
  cupsenable:
    command: usr/sbin/cupsenable
    plugs: [network, cups]
  cupsdisable:
    command: usr/sbin/cupsdisable
    plugs: [network, cups]
  cupsaccept:
    command: usr/sbin/cupsaccept
    plugs: [network, cups]
  cupsreject:
    command: usr/sbin/cupsreject
    plugs: [network, cups]
  accept:
    command: usr/sbin/accept
    plugs: [network, cups]
  reject:
    command: usr/sbin/reject
    plugs: [network, cups]
  cupsctl:
    command: usr/sbin/cupsctl
    plugs: [network, cups]

parts:
  cups-client:
    plugin: dump
    source: .
    stage-packages:
        - cups-client
        - libcups2
    prime:
        - usr/bin/*
        - usr/sbin/*
        - usr/lib/*

The apps here are simply CUPS’ command line tools, from the “cups-client” package pulled in by the stage packages. All apps only plug “cups” (the interface for printing) and not “cups-control” (the interface for managing CUPS). Build this Snap putting the snapcraft.yaml into an empty directory, switch into this directory, and issue the command

snapcraft snap

and then install it via

sudo snap install --dangerous cups-user-app-test_0.1.0_amd64.snap

As this Snap does not come from the Snap Store it is possible that the “cups” interface does not connect automatically. So if the following command examples do not work, do the command

sudo snap connect cups-user-app-test:cups cups:cups

first. On applications from the Snap Store this connection should happen automatically.

Perhaps also “avahi-observe” needs manual connection:

sudo snap connect cups-user-app-test:avahi-observe

Now you can do things like

cups-user-app-test.lpstat -t
cups-user-app-test.lp -d printer file.pdf

and the CUPS Snap does what you expect, showing available print queues and printing a job.

Now try

cups-user-app-test.lpadmin -p newprinter -E -v file:/dev/null
cups-user-app-test.lpinfo -v

These commands do administrative CUPS tasks, therefore you will simply get

Forbidden

as an answer and no queue created and no discovered printers listed.

This is how the security concept of the CUPS Snap in conjunction with the Snap Store protects against malicious applications. This Snap can print and provide the user with the information he needs for that, displaying available queues, jobs, options, … The user can even cancel his own jobs, but not the jobs of others.

Now we edit the snapcraft.yaml a little bit, switching all tools which do administrative tasks from the plug “cups” to “cups-control” and also change the name and description:

name: cups-admin-test
base: core18 # The base snap is the execution environment for this snap
version: 0.1.0
summary: CUPS command line tools out of a Snap 
description: Testing interfaces for the CUPS Snap (plugging cups-control, so admin tasks should work)
grade: stable
confinement: strict

apps:
  lpinfo:
    command: usr/sbin/lpinfo
    plugs: [network, cups-control]
  lpadmin:
    command: usr/sbin/lpadmin
    plugs: [network, avahi-observe, home, cups-control]
  lpstat:
    command: usr/bin/lpstat
    plugs: [network, avahi-observe, cups]
  lpoptions:
    command: usr/bin/lpoptions
    plugs: [network, home, cups]
  lp:
    command: usr/bin/lp
    plugs: [network, home, cups]
  cancel:
    command: usr/bin/cancel
    plugs: [network, cups-control]
  lpmove:
    command: usr/sbin/lpmove
    plugs: [network, cups-control]
  cupsenable:
    command: usr/sbin/cupsenable
    plugs: [network, cups-control]
  cupsdisable:
    command: usr/sbin/cupsdisable
    plugs: [network, cups-control]
  cupsaccept:
    command: usr/sbin/cupsaccept
    plugs: [network, cups-control]
  cupsreject:
    command: usr/sbin/cupsreject
    plugs: [network, cups-control]
  accept:
    command: usr/sbin/accept
    plugs: [network, cups-control]
  reject:
    command: usr/sbin/reject
    plugs: [network, cups-control]
  cupsctl:
    command: usr/sbin/cupsctl
    plugs: [network, cups-control]

parts:
  cups-client:
    plugin: dump
    source: .
    stage-packages:
        - cups-client
        - libcups2
    prime:
        - usr/bin/*
        - usr/sbin/*
        - usr/lib/*

This is how a printer setup tool (or some system manager which contains CUPS managing functionality) should get snapped.

Now let us build and install this one (put it also into a separate empty directory):

snapcraft snap
sudo snap install --dangerous cups-admin-test_0.1.0_amd64.snap
sudo snap connect cups-admin-test:avahi-observe
sudo snap connect cups-admin-test:cups cups:cups
sudo snap connect cups-admin-test:cups-control cups:cups-control

Note the additional command for connecting “cups-control”. Applications simply uploaded to the Snap Store will not connect this automatically without permission from the Snap Store team.

Important: If you set “cups-control” into the plugs list of only one of the apps, the whole Snap gets CUPS admin permission, also the apps which have “cups” in their plugs list.

Keep also in mind that you can print and list print queues and jobs also from a Snap which only plugs “cups-control”. “cups-control” gives access to everything, you need not to plug both “cups-control” and “cups” in the plug list of an app.

Now try

cups-admin-test.lpadmin -p newprinter -E -v file:/dev/null
cups-admin-test.lpinfo -v

and you get a CUPS queue and a list of discovered printers instead of Forbidden.

So if you are already maintaining a Snap with print functionality and it plugs “cups-control” (from good old times) change it to plugging “cups” to get automatic connection and less chance that your Snap does something unexpected. “cups-control” is only for the case that your Snap should manage CUPS.

Till

4 Likes

Note that I have renamed the slots of the CUPS Snap from printing and admin to cups and cups-control, to do it the standard way as slots have the same names as their respective plugs, as of the discussion here.

I have updated the sample command lines in my initial posting appropriately. If you have already installed the CUPS Snap, please update your CUPS Snap via

sudo snap refresh --edge cups

Thank you so much for your efforts, @till.kamppeter and everyone else involved. Quick question related to the examples above: is the cups interface still the right way forward for snaps that just want to send print jobs?

Context: I’m asking because this conversation appears to imply that this interface is still in flux to ensure it works well and securely on classic systems. My use case is comparedly straightforward: I’m snapping a headless application component that just needs to be able to initiate print jobs with lp. This will run on an all-snap system meaning it only runs cupsd from the CUPS snap. This setup currently works with just the cups interface connected.

I’m trying to figure out if this would break in the future though. If so, what would be your recommended way to move forward? For example, I could use the cups-control interface instead if it’s considered “more stable” and ensure it’s auto-connected through my all-snap-system’s gadget snap.

@DriesOeyen, first, the interface name cups will stay the one for this purpose, for printing via CUPS. The interface is not ready yet, still under development, at least the design seems to be finalized now.

If you make a Snap only for systems which are all-Snap and so never have a classic CUPS, then you do not need to worry much. Simply let it plug cups. The CUPS Snap listens on both the standard domaon socket and on it own alternative socket. A Snap plugging cups will always send jobs to one of these and on the completion of the interface implementation this will not change. The parts of the interface which are not yet implemented only concern confined application Snaps downloadable from the Snap Store to be installed on systems which have a classic CUPS.

1 Like

@till.kamppeter Terrific, thanks for confirming!

This is essentially what I understood based on the other thread I linked to, but since that discussion is very much focused on the implementation details I did want to ask explicitly from the perspective of a “consumer” of the interface such as the application I’m working on. Thank you! :slight_smile: