Hi Guix, in the past few days I’ve been playing on and off with configuring a Guix System where accounts are authenticated against Active Directory via LDAP.
My findings so far: * NSCD *must* be extended with caches for “passwd” and “group” databases or else applications will make the lookup for user accounts directly and will not consult LDAP at all. * the default PAM rules provided by nslcd-service-type are either incomplete or incorrect, because of the many “unix-pam-service” defaults. * as a result awkward PAM configuration is required to make this work. * it’s a pity that “name-service-switch” is not a system service and has to be extended manually. … Or is this perhaps how we should treat PAM configuration as well? PAM is the mechanism to use if we want to allow applications to authenticate users that don’t have local accounts. For local accounts the “pam_unix.so” authentication module is used, and we say that it is required for authentication with “unix-pam-service”. “required” is an important word here, because that’s one of the keywords for the PAM module stack. Some terminology: * there are four resources: “auth” (for authentication of valid accounts), “session” (for setting up a user’s session once authenticated), “password” (for changing the user’s password), and “account” (to determine if a user account is valid). * there is a file in /etc/pam.d for each application that should use PAM, such as “/etc/pam.d/su” for “su”, “/etc/pam.d/sshd” for the SSH daemon, etc. * each of these files specifies a stack of PAM modules that should be consulted for each of the four resources. * each stack is evaluated from top to bottom. * each module can be “required”, “sufficient”, “optional”, or a “requisite”. The last point is confusing. Let’s only look at “required” and “sufficient”. Here’s a stack for authenticating a user: auth required pam_foo.so auth sufficient pam_bar.so auth sufficient pam_baz.so auth required pam_deny.so Stacks are evaluated top to bottom. First pam_foo.so is evaluated. It is “required” meaning that it must succeed for authentication to be successful. If it returns failure the *next module will be evaluated*, but no matter what happens authentication will *always fail*. This is important. A module that is “sufficient” will be evaluated. If it fails the next module will be tried. If it succeeds, however, no other module in the stack will be considered. Authentication will have succeeded. So in the above stack we need “pam_foo.so” and one of “pam_bar.so” or “pam_baz.so” to succeed. If “pam_bar.so” succeeds “pam_baz.so” will not be considered, nor will “pam_deny.so” be considered. If both “pam_bar.so” and “pam_baz.so” fail “pam_deny.so” is evaluated which will always return failure — so authentication will fail. As you can see, the order of modules in the stack is of significant importance. Even worse, modules can take arguments such as “try_first_pass” to use the same password that was provided in an earlier step. Mixing up the order of modules here could lead to unexpected, frustrating behaviour. Debugging this isn’t easy, because PAM isn’t very chatty even when “debug” is added as an argument to all modules. But back to the problem of authenticating users via LDAP. With our default unix-pam-service “pam_unix.so” (which checks that a user account exists locally and checks its password) is required, so LDAP authentication will always fail. I extended the pam-root-service-type with a service that matches on all pam_unix.so modules and makes them use the “sufficient” keyword instead to overcome this. This is problematic when nslcd-service-type is involved because it extends pam-root-service-type to add entries for pam_ldap.so. Instead of using a string “pam_ldap.so” it uses a G-expression to compute the absolute file name of “pam_ldap.so”. When extending the service and matching on PAM entries, however, we don’t have a string of the absolute file name to match on — we have a G-expression that is really awkward to match against. I worked around this (by lowering the G-expression first), but it’s ugly. And even then I still have the problem that I can’t control the order of PAM entries at all. Perhaps we should implement a different mechanism for specifying PAM entries for the system, perhaps similar to what the name-service-switch field does? I also recommend using “sufficient” as the default keyword for “pam_unix.so” and ending the stack with “required pam_deny.so”. This would make it easier to extend the stack without having to rewrite existing module entries. What do you think? -- Ricardo