So you’ve brought something up that has certainly been ticking over in the back of my mind for a long time. I’m roughly 90% certain that the user DB interface is indeed the right extension point for hooking authentication systems into the system (And would be a better one than having to write a PAM module per authentication system - which often ends up just repeating the work of shuffling the username and password to the backend system over some custom protocol...)
> On 21 Jul 2025, at 12:39, Dominik George <n...@naturalnet.de> wrote: > > Hi, > > currently, the userdb system only allows querying for User Records and > Group Records, hence providing a modern replacement for NSS. > So note that currently there are only two ways that the User DB generically gets involved in user authentication: * pam_unix calls getpwnam and nss_systemd does the userdb lookups, or * you’re for some reason using my nss module[1] that I (initially) mostly created to solve problems I was having specific to NixOS (specifically: plugins can never work in the NSS shadow stack on NixOS) pam_systemd_home interacts with homed specifically and directly. (You probably know this, but its important to ensure everyone is on the same page) Whatever such a protocol ends up looking like, I think “you could replace pam_systemd_home with a generic UserDB PAM module and said generic protocol” is an important criterion > I would like to propose an addition to make it support authentication as > well. The additions to the io.systemd.UserDatabase Varlink interface > are: I don’t think there’s any strict necessity for it to be in the io.systemd.UserDatabase Interface. In many ways I think it would be better for any such methods in a separate interface (because it means when you get an `org.varlink.service.InterfaceNotFound` error back for a given userdb service you know you can just skip all advanced functionality For that service for the rest of the authentication sequence) > ```varlink > # Start the authentication process for a user > # > # Requires a username, and an optional authentication authToken > # (e.g. a password). > # > # The method can return: > # > # * a User Record object of the user that was authenticated > # * a conversation token for the AuthenticateContinue method > # * nothing at all (authentication finished and successful, but no details > provided) > method Authenticate( > userName : string, > authToken : ?string, > variables : []string, > client : ?string, > service : string > ) -> ( > convToken : ?int, > user : ?object > ) > > # Continue an ongogin conversation > # > # For example, the Authenticate method might have requested a conversation > # with the user, like in the OAuth Device Authorization Grant Flow where the > # user will need to open a web page on another device to log in. > # In such a case, the Authenticate method will return a matching error with > # a conversation token, and the client requesting authentication can later > # continue the process using this conversation token. > method AuthenticateContinue( > convToken : int, > authToken : ?string, > variables : []string, > client : ?string, > service : string > ) -> ( > user : ?object > ) > > # Cancel an ongogin authentication process for which a conversation token > # has been issued > method AuthenticateCancel( > convToken : int, > client : ?string, > service : string > ) > > # The authentication process was finished, but the authentication token was > invalid > error InvalidAuthToken(message: ?string) > > # An authentication token was not provided, but is required > # If a conversation token is issued, the client can use AuthenticateContinue. > # Otherwise, it has to restart the process. > error AuthTokenRequired(convToken: ?int, message: ?string) > > # A conversation with the user is required (e.g. ask a non-auth-token > question, > # open a website, etc.). > # Once a reply was acquired, the client must continue the process using > # AuthenticateContinue. > error ConvRequired(convToken: int, message: string) > > # The authentication is ongoing, but cannot complete right now. > # The service might be waiting for some backend to complete a task, > # or for the user to acknowledge a second factor in some external app > # or whatever. The client should ask for progress after the specified > # interval, but does not need to provide any new information. > error RetryRequired(convToken: int, interval: int, message: ?string) > > # An ongoging conversation timed out while waiting for the client to > # continue. > error ConvTimeout(message: ?string) > ``` > > The protocol is designed with PAM compatibility in mind, so it borrows > some of its terminology. However, it is tailored to asynchronous > authentication mechanisms, like various OAuth / OIDC flows. So a minor thing here: If you’re doing conversation tokens, I would be inclined to make them a string and exchange it at each step, so stateless applications can be supported. But I’m not sure if (for the auth step in particular, which is very much driven by the module) it would be better to use sd_varlink_push_fd to send a file descriptor across for a “reversed” varlink socket that the other end can use to drive the PAM conversation. A nice advantage here is that you also get automatic resource release by just closing the file descriptor. As described the interface proposed diverges a bunch from how pam_sm_authenticate and the conversation function work. As much as I dislike PAM, I don’t think you can get away form it; it's the lingua franca of Unix authentication after all. I think trying to fit OAuth token flows into “normal” PAM like this is probably more trouble than its worth. If you’re looking to do things beyond the basic text messages and prompts that PAM understands then I think we need to look towards GDM’s extensions [2]. They’re definitely imperfect but they’re the closest thing we have to a “modern” login extension to PAM right now. Anything that slots in at this extension point likely needs the ability to see any PAM environment variables (perhaps you pass them across by calling pam_getenvlist first) and likely set its own; and also the ability to (when feasible) return a token in the PAM_AUTHTOK item that e.g. can be used to establish disk encryption or Kerberos credentials (where appropriate/separate from the UserDB backend) I don’t see a need to return the user record here. To figure out which service you need to call to authenticate you already need to be in possession of it. > Concerning backwards compatibility, I propose that: > > * The userdbd multiplexer should try to call the Authenticate methods > on downstreams, and watch out for any MethodNotImplemented errors. > If a backend does not implement the Authenticate methods, the > multiplexer can try to authenticate against a hashedPassword in > the User Record itself. The multiplexer is completely uninvolved in login today (besides very indirectly i.e. through the nss_systemd compatibility shim). I don’t think this logic belongs in there. It belongs in whichever PAM module talks directly to the user DB. I agree about fallback to traditional behaviour on MethodNotImplemented Actually I think for the pam_acct extension point there’s a good argument for a backend being able to return a value which means “I have no objections - do lockout condition validation on the record as normal" > * Implementations that have been done before will continue to work, > because the proposal only contains additions, but no changes to > existing methods. > > Instead of making a new interface for this, I propose to add it to the > UserDatabase interface, so most code can jsut continue to work, and we > can build on top of the existing multiplexer and socket infrastructure > (however, this might also be possible while defining a second interface, > but I also want to re-use the error types from UserDatabase). > There’s really no reason why you can’t have multiple interfaces on a single socket and indeed Varlink is designed to support this (and I expect most UserDB implementations might supplement the normal interface with one of their own. My own certainly does) [1] https://github.com/erincandescent/pam_sduserdb/ [2] https://gitlab.gnome.org/GNOME/gdm/-/tree/main/pam-extensions