@ijohnson, @jamesh, anything still missing for the implementation of the new cups
(printing) interface?
So just to summarize the state of this, it seems like there aren’t any other technical blockers identified by @jamesh with my proposal to share the socket that is inside the snap private directory of the cups
snap via a bind mount somewhere inside the client snaps that wish to print, is that correct? It sounds like the credentials/user wishing to print and queues issue is not actually an issue?
If that is correct, then the proposal as it stands seems to be the following:
- The
cups
snap shall still slot thecups
interface for client apps wishing to print to plug - The
cups
snap shall provide a basically emptycups-auto-connect-hack
(name TBD) content interface that client snaps wishing to print can plug (but note that client snaps wishing to print do not need to plug this - this content interface exists purely so we can getdefault-provider
working so that thecups
snap is auto-installed everywhere properly) - Client snaps can plug
cups
and they will get auto-connected to the slot exposed by thecups
snap - Client snaps can also plug the
cups-auto-connect-hack
content interface with thedefault-provider
set tocups
, such that when a user installs this snap (and they do not yet have thecups
snap installed), then thecups
snap will get automatically installed if it is not already installed. - The
cups
slot shall gain a new attribute (call itcups-socket
real name TBD) that thecups
snap can use to specify the location the unix socket that cupsd listens on that can be bind mounted by snapd into the mount namespace of the client snap that has acups
interface plug connected. This would be implemented with themount
backend internally in snapd interface code. - The
cups
interface on the slot side shall be bind mounted the cupsd unix socket identified by the slot sidecups-socket
attribute somewhere “standardized” which is not in the standard CUPS socket location that typical applications use (i.e./run/cups-snap/cups.socket
or some such). - Snaps wishing to only submit print jobs shall set (either through directly setting the environment stanza in the snapcraft.yaml, a snapcraft extension or just wrapper scripts) the environment variable
CUPS_SERVER
to the location agreed up on in 6. - Snaps wishing to do more than just submit print jobs and wishing to actually administrate and control printers etc, shall plug the
cups-control
slot, which can either be manually connected to the system, implicit slot or to the slot provided by thecups
snap. This interface would allow accessing the standard cups socket location/run/cups/cups.sock
so that snaps could talk directly to cupsd, whether that be the version in the snap, or a version installed on the host system through debs/rpms/etc. - The
cups
snap specifically shall operate in one of two modes, it shall either be a proxy, and just listen on the/var/snap/cups/common/cups.socket
socket (which is the source for the bind mount explained above and is the actual socket file that client snaps talk to) or it shall be the “real” cupsd and expose itself to unconfined apps on the host by listening also on/run/cups/cups.sock
for traditional non-snap apps to print to directly. This means that app snaps usingcups-control
and wishing to do printer administration may end up talking tocupsd
from thecups
snap in the special case where the onlycupsd
on the system is that of the snap.
Does all of that make sense? If so, then next steps here are to agree on:
- The source location that cupsd should listen on it’s socket to be bind mounted for client snaps just wishing to print (i.e. the proxying socket)
- The destination location that the cups proxying socket should be bind mounted to inside the client app snap wishing to print’s mount namespace - this will be the same location that we set CUPS_SERVER to.
- The name of the attribute for the
cups
interface slot mentioned in 3 above - The easiest mechanism to ensure that client snaps can get CUPS_SERVER set appropriately without much hassle - I think snapcraft extensions are the easy thing to do here, but maybe that is overkill
@ijohnson, thank you very much for your write-up.
Principally this is all correct, but I have the following remarks:
The ‘somewhere “standardized”’ in (6) I would say is /var/snap/cups/common/run/cups.sock
. The Snap’s CUPS is ALWAYS listening on this socket, independent whether it runs in proxy mode (when there is a classic CUPS installed) or in stand-alone mode. In stand-alone mode the Snap’s CUPS is listening on BOTH /var/snap/cups/common/run/cups.sock
AND /run/cups/cups.sock
, the latter giving access for unsnapped clients or administrative clients (which plug cups-control
).
The /var/snap/cups/common/run/cups.sock
can be bind-mounted to anywhere where a confined printing client (which plugs cups
) needs it.
in (2) you introduce an interface named "cups-auto-connect-hack (name TBD)
, a content interface to trigger the installation of the CUPS Snap. As we ALWAYS need the CUPS Snap to print from a confined app which plugs cups
I suggest that we join cups
and cups-auto-connect-hack
into one interface which is auto-connected when the confined app is installed and if the CUPS Snap is not installed the CUPS Snap gets installed as a default-provider
and in addition, the confined Snap gets access to the needed socket (and env variables if the socket cannot be bind mounted into teh standard one) and also gets all access needed (for example also D-Bus for CUPS notifications).
This directory needs to be somewhere that the client snap has access to, the client snap will not be cups
(well except in the case of cups test utilities, but that’s a special case and can easily be handled separately), so it will not have access to /var/snap/cups
. The directory you provided is the source of the bind mount, here I’m asking about the destination of the bind mount, i.e. somewhere in the foo
snap that firefox would use to do printing things, the foo
snap can only access /var/snap/foo/common
, it cannot access /var/snap/cups/common
, but using the bind mount we could put the cups snap socket inside the foo
snap somewhere like /var/cups-snap/cups.sock
that doesn’t conflict with anything else, and also the cups
interface provides the necessary policy snippets to allow foo
to access /var/cups-snap/cups.sock
.
Unfortunately, currently in snapd only the content interface is allowed to be used with default-provider
, however we may expand this policy, but expanding that policy I think will take a bit longer than the time to just change the cups
interface, so this content interface is a short-term workaround as we enable default-provider for other interfaces like cups
.
@ijohnson, thank you for the clarifications. The best would be if the bind-mount destination inside the confined Snaps which plug cups
is /run/cups/cups.sock
as a confined Snap cannot access the original /run/cups/cups.sock
anyway, but if this is not allowed as bind-mount destination we need to use something else. Would be somewhere under /var/snap/cups
or /var/snap
possible as these directories cannot get accessed by the confined Snap anyway? Or could it perhaps be /var/run/cups-snap/cups.sock`?
So for the interface we have to go with two now? One content inmterface to trigger the dependency installation of the CUPS Snap and one named cups
for all the rest? So then we have to go this way for now, and auto-connect both, to assure that if any confined Snap which prints gets installed that the CUPS Snap is also installed. We also need to add default-provider
support for all (or at least more) interface types to snapd as soon as possible.
The issue I saw with using /run/cups/cups.sock
as the bind mount destination for the cups socket originating from the cups
snap is that it excludes using cups-control
at the same time with the real socket at that location - this means that in order to give a snap using the cups
interface additional admin privileges, you would have to disconnect the cups
interface, whereas if the socket from cups that does proxying is bind mounted elsewhere, then you can have both sockets accessible at the same time.
I suppose maybe this is a disadvantage though, since client apps probably won’t have an easy way to switch which socket location they use, and at least with bind mounting the proxying socket in the same location, snap apps don’t need to change where they look for the socket if they want to switch between using the cups
and the cups-control
interface, and the way to switch is just disconnecting cups
and connecting cups-control
. In the case where cupsd on the host is actually coming from the cups
snap, then client snaps don’t need to disconnect cups
, as the cupsd from the cups
snap will be able to identify that the client end of the socket has the cups-control
interface connected.
I think I’ve been convinced and instead what we should do is a simpler version of above:
- The
cups
snap shall still slot thecups
interface for client apps wishing to print to plug - The
cups
snap shall provide a basically emptycups-auto-connect-hack
(name TBD) content interface that client snaps wishing to print can plug (but note that client snaps wishing to print do not need to plug this - this content interface exists purely so we can getdefault-provider
working so that thecups
snap is auto-installed everywhere properly) (eventually this content interface can go away when we supportdefault-provider
for thecups
interface) - Client snaps can plug
cups
and they will get auto-connected to the slot exposed by thecups
snap - Client snaps can also plug the
cups-auto-connect-hack
content interface with thedefault-provider
set tocups
, such that when a user installs this snap (and they do not yet have thecups
snap installed), then thecups
snap will get automatically installed if it is not already installed. - The
cups
slot shall gain a new attribute (call itcups-socket
real name TBD) that thecups
snap can use to specify the location the unix socket that cupsd listens on that can be bind mounted by snapd into the mount namespace of the client snap that has acups
interface plug connected. This would be implemented with themount
backend internally in snapd interface code. - The
cups
interface on the slot side shall perform a bind mount of the cupsd unix socket identified by the slot sidecups-socket
attribute to the standard location of the cups socket/run/cups/cups.sock
- Snaps wishing to do more than just submit print jobs and wishing to actually administrate and control printers etc, shall plug the
cups-control
slot, which can either be manually connected to the system, implicit slot or to the slot provided by thecups
snap. This interface would allow accessing the standard cups socket location/run/cups/cups.sock
(regardless of whether that socket location is a bind mount from thecups
interface or not) so that snaps could talk directly to cupsd, whether that be the version in the snap, or a version installed on the host system through debs/rpms/etc. - The
cups
snap specifically shall operate in one of two modes, it shall either be a proxy, and just listen on the/var/snap/cups/common/cups.socket
socket (which is the source for the bind mount explained above and is the actual socket file that client snaps talk to) or it shall be the “real” cupsd and expose itself to unconfined apps on the host by listening also on/run/cups/cups.sock
for traditional non-snap apps to print to directly. This means that app snaps usingcups-control
and wishing to do printer administration may end up talking tocupsd
from thecups
snap in the special case where the onlycupsd
on the system is that of the snap.
This means we just need the following change in snapd:
- the cups interface shall implement a bind mount from the location identified on the slot side of the interface to
/run/cups/cups.socket
inside the plug side snap’s mount namespace
And then the cups snap would need the following changes:
- expose the content interface slot that client snaps can use with default-provider as described above
- change the
cups
slot declaration to specify the attributecups-socket: $SNAP_COMMON/cups.socket
or some such
and client snaps need no changes except to add the content interface plug definition with default-provider: cups
for the best install experience.
@ijohnson, yes, this is excactly how everything should work!
So I will do the changes you asked for on the CUPS Snap. Would be great if you could post here the snapcraft.yaml
snippets which I have to add.
We also need to tell to Snap developers what exactly they have to plug/add to their snapcraft.yaml
in order to let their app print and also in order to let their app manage CUPS.
And we need to improve snapd so that who wants his app to print only plugs cups
and everyone who wants his app to admin plugs cups-control
and that’s it.
Sure, let me try and finish the snapd branch and then you can test with a snapd build to ensure it is all working end to end
@ijohnson, OK, thanks, let us go this way.
So could you please post here what I have to change in the snapcraft.yaml
of the CUPS Snap and what I have to add to the snapcraft.yaml
of a user application Snap, once for a Snap which only wants to print and once for a Snap which wants to manage CUPS.
Mmmh slight complication we need to enable from the mount backend mounting on top of socket files, currently this is not allowed by snap-update-ns, but I have a patch which will enable this as well.
Ah well we might actually have a problem with using /run/cups/cups.sock here since this causes mount namespace trespassing:
trespassing.go:224: DEBUG: trespassing violated "/run" while striving to "/run/cups/cups.sock"
since /run/ inside the snap’s mount namespace is shared with the host so if the file doesn’t already exist (i.e. cupsd is not running for whatever reason) then snap-update-ns attempts to create a “writable mimic” and this fails since creating the writable mimic would leak things to the host.
So I think we will actually need to go with a different directory that is not shared with the host, something like as I proposed before /var/cups-snap/cups.sock
perhaps… we still would need the patch to enable mounting on top of socket files however. I’ll make these changes in the morning and share a full set of example programs and snapcraft.yaml’s when I’ve got it working end to end
Yes, we should go this way for now. My only little concern is the /var/cups-snap/cups.sock
which does not actually fit into the FHS but probably this is the only way to go to avoid clashes with the application and to not be in /run
(/var/run
is linked to /run
, at least in Ubuntu).
The best would really be to offer the socket in the environment of the application on the original /run/cups/cups.sock
but if it is not possible for security reasons, we have to live with something like /var/cups-snap/cups.sock
.
Yeah it’s a bit unfortunate, since the typical top-level directory used for sockets, /run
can’t really be used for this purpose since the snapd mount namespace code explicitly forbids modifications to the snap’s mount namespace that “leak” from the snap’s mount namespace to the host, and thus we can’t use /run for this bind mount location. If there’s a better location that is more FHS compliant I’d be happy to use something else
As a client (user application) Snap cannot see the CUPS Snap’s /var/snap/cups/common/...
anyway, could it not simply bind-mount the CUPS Snap’s /var/snap/cups/common/run/cups.sock
into the client Snap’s /var/snap/cups/common/run/cups.sock
?
We technically could expose that, but we really don’t want to do that since that directory is $SNAP_COMMON for another snap and we don’t do that sort of sharing through interfaces - typically if one snap has something like a socket or other files that it wants to share and they exist in $SNAP_COMMON, they would be shared via the content interface as I initially suggested at the top of this post, however as @jamesh pointed out that can be brittle, so instead I am implementing a smaller subset of the content interface in a way that will not be quite so brittle, but doing so requires a choice of where to put the socket in the mount namespace of the client snap and putting it inside $SNAP_COMMON that corresponds to another snap is a very leaky abstraction.
But also to be clear if we went that route of just telling the client snaps to look at /var/snap/cups/common/run/cups.sock, we don’t even need a bind mount, that file will already be accessible, it is just denied via AppArmor since it is another snap’s data folder.
@ijohnson, so then /var/cups-snap/cups.sock
is perhaps really the best solution? Then we should go this way and assure that the cups
plug contains the setting of the CUPS_SERVER
environment variable to make the application print via /var/cups-snap/cups.sock
.
@ijohnson, anything new here? Please tell me when you have something for testing and tell me then, too, which changes I have to do in snapcraft.yaml
of the CUPS Snap and of a test client Snap for correctly defining the needed slots and plugs and whatever else is needed.
Sorry this will have to wait until I am back next week, I will post something when I get back though I think the changes are now somewhat simpler to implement on the snapd side.
Alright I have a PR up which implements what I planned, it has a bit of a bug in that I seem to need to provide read / write rules for both the source and the destination of the bind mount which kinda defeats the purpose of the bind mount and I’m not sure why…