User authentication in snapd (pam mediation)

I am currently snapping the printing stack, consisting of CUPS and cups-filters. See the thread Snapping CUPS Printing Stack: Avahi support, system users/groups. It is already mostly working under strict confinement on both classic and Ubuntu Core systems.
The problem is that if one uses the web interface of the snap’s CUPS (http://localhost:10631/) one can browse the printer and job lists but not do any changes or enter the admin page. If one does so, the browser asks for user name and password, but entering the correct credentials the action is not performed, but instead, the browser asks for user name and password again.
As under Ubuntu we do not have a root password by default and snapd does not yet support the snap’s own system users and groups (CUPS normally uses the “lpadmin” group for admin access) I have used the “adm” group as temporary makeshift for the time being.
At least on a classic system, the first created user is in the “adm” group and it is no problem to add further users to this group.
So CUPS has to authenticate the user by checking whether username/password is OK and whether the user is in the “adm” group.
In the beginning I have compiled CUPS in the snap with all set to default, which made CUPS using PAM for this authentication task which failed and this is because snapd does not support PAM currently.
@jdstrand recommended me to compile without PAM and so I have built with the --disable-pam option for the configure script, making CUPS trying to read /etc/passwd and /var/lib/extrausers/shadow files which cannot be read under strict confinement.
I did not investigate which authentication methods CUPS uses, but I know that on standard desktop and server systems with Debian packages it uses PAM and have discovered now that it accesses the passwd and shadow files directly (probably using library functions) when disabling its PAM support.
CUPS only needs to read the passwd/group/shadow files, not to write/modify them.
So I would like to know what would be the best solution in snapd to support this kind of use case.

@niemeyer and @mvo - in summary, some existing applications use libpam (or access /etc/passwd and /etc/shadow directly) to authenticate users. Based on @till.kamppeter’s description, CUPS has a rudimentary role based access control mechanism in its web interface which checks if the user is in the configured SystemGroup. When using libpam, the users may be local system users or users configured via ldap, kerberos, winbind, etc.

It’s theoretically possible that snapd could proxy PAM requests, but the design would need to be researched. To spark the conversation with a ‘back of the napkin’ idea: one way would be to have the application use a libpam that would talk to the (new) out of process snapd authentication named socket where snapd checks if the connecting user is ‘root’ and a new interface is created that has an apparmor rule for using the socket. This way, on distributions without apparmor, there is no escalation of privileges (if you are root, you can read just read the shadow file) but on distros with even partial support, the apparmor rule for the named socket mediates the access. In this manner, the snap doesn’t have access to the user databases. Handling this transparently may be possible by bind mounting the snapd libpam over the libpam in the (core) runtime of the snap. The snapd libpam and the snapd proxy would need a thorough security review.

A simpler solution would be to create an authentication interface that simply allows access to PAM, the shadow file and any helpers. While this approach is simpler to implement within snapd, it has several disadvantages:

  • on classic distro where PAM is highly customized, we’ll need to allow all the accesses to all of the authentication mechanisms since libpam runs in process
  • classic distro (where /etc from the host is made available in the snap’s runtime) will be problematic on non-Ubuntu/Debian since Fedora, Arch, etc may configure PAM (which is /etc from the host) differently from what libpam (which is in the snap’s runtime via the core snap) might expect
  • furthermore, the host may have authentication mechanisms configured in /etc that aren’t available in the snap’s runtime (eg, libpam-ldap, etc), thus breaking the authentication
  • the new authentication interface is even more privileged since it will have access to shadow files, ldap passwords, etc
1 Like

The “simple” solution described in your answer can get arbitrarily complicated, so I would opt for something as described in the first half of your post.The application in the snap is either dynamically linked against a custom libpam or, if possible, the application in the snap runs under a PAM configuration where the only authentication method is a snapd PAM module which does the proxying.

1 Like

@jdstrand Both of these solutions present the same main problem: we’d need to offer the application inside the snap a libpam alternative that satisfies the exact ABI used when the application was compiled, forever, including any changes any distribution may have made. Distributions might already diverge today in ways that makes this impossible, but if we can pull it at all it would be a time bomb.

@till.kamppeter That approach looks more promising, but it’s still a bit tricky to pull off, because as far as can see the modules of libpam are shared libraries which get loaded into libpam’s process space, and almost certainly need direct access to its memory to work instead of being just a functional API. That implies we’re back to the same underlying issue, depending on the internal stability of libpam itself.

We may need to split the problem in two halves: a PAM module that is shipped inside the snap itself, and that communicates with snapd over a socket that offers a guaranteed-stable protocol inside the snap. snapd then takes the request and authenticates with the system itself, ending up in the system PAM modules.

If we do a good job on the PAM module that forwards the authentication request, we can submit this to the upstream project.

Coming back to the problem at hand in this topic, I suggest starting with something simple so we’re not blocked on that problem. We might just implement an interface that offers access to the passwd and shadow files of the system, and put that under tight control so it’s not generally exposed except in well known cases.

@niemeyer, is there someone who can relatively quickly develop such a simple passwd/shadow file access interface? Or is something like that already under development?
The authentication forwarding PAM module is also a good idea, especially it would serve more universally, also for other applications than CUPS.

@till.kamppeter It should be relatively straightforward. Should be easy for someone external to the core team to implement it, and we can also put on our backlog if nobody else picks it up first.

What is needed would be read-only access to certain files, like /etc/passwd, /etc/shadow, /etc/shadow-, /var/lib/extrausers/shadow, … of the system (not the snap). In my case CUPS has unsuccessfully tried to read /etc/shadow and /var/lib/extrausers/shadow when building without PAM support. Am right? Or would we need more?
The problems I see here are the following:

  1. These files can be different on different distributions, and also different on Snap-only distributions like Core and classic distributions.
  2. CUPS (or generally the app) in the snap does not read the files by itself but uses a library for that. So probably this library (I do not know which one) and of this library the incarnation in the snap, built for the distribution under which the snap was built, determines which files will be read.
  3. One would need to have a group where one can add users to so that one can do admin tasks with the web interface. See also here. Such a thing would especially be needed for Core where one does not always have a desktop.

Yes, that’s pretty much it. The detail is that it’s a different mount namespace, so the interface may need to correctly map the external file into it via a bind mount or similar.

For points 1 and 2, aren’t the passwd and shadow files pretty standard in terms of content and placement? For point 3, the devil is in the details, but Ubuntu Core may also have users added to it.

Now, two years later, is there a solution for this problem? It would be already great to have read-only access /etc/passwd, /etc/shadow, /etc/shadow-, /var/lib/extrausers/shadow, … as then password authentication for administrative commands and the web interface should work.
For adding an “lpadmin” group we would need a solution of Multiple users and groups in snaps. Is there any progress there?

For reading the /etc/passwd, etc. files have you tried using the system-files interface?

@ijohnson, thank you very much! It is working now!
Now I can start the snap on a classic system which is already running CUPS and so the snap’s CUPS starts on port 10631. Now If I run

lpadmin -h localhost:10631 -p authtest -E -v /dev/null

and enter my password if prompted, the snap’s CUPS gets a raw queue named authtest.

1 Like

Fix committed:

That’s great, glad to hear it.

Note that you will need store approval to use those interfaces, see Process for aliases, auto-connections and tracks.