all messages for Guix-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
* LDAP authentication + Configuring PAM
@ 2019-08-22 14:03 Ricardo Wurmus
  2019-08-23 12:14 ` Ludovic Courtès
  0 siblings, 1 reply; 4+ messages in thread
From: Ricardo Wurmus @ 2019-08-22 14:03 UTC (permalink / raw)
  To: guix-devel

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

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: LDAP authentication + Configuring PAM
  2019-08-22 14:03 LDAP authentication + Configuring PAM Ricardo Wurmus
@ 2019-08-23 12:14 ` Ludovic Courtès
  2019-08-23 20:08   ` Ricardo Wurmus
  0 siblings, 1 reply; 4+ messages in thread
From: Ludovic Courtès @ 2019-08-23 12:14 UTC (permalink / raw)
  To: Ricardo Wurmus; +Cc: guix-devel

Heya!

Ricardo Wurmus <rekado@elephly.net> skribis:

> 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.

Nscd already has caches for those in our default configuration, doesn’t
it?

> * 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?

We should turn ‘name-service-switch’ into an extensible service, IMO.
It just happens to predate the service type graph.

> 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.

So you’re extending ‘pam-root-service-type’ with a “transformation”
procedure, right?

> 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.

Could you match against a <file-append> record?

> 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 could add a field in <pam-configuration> that would be
transformation procedure that takes the complete list of entries?

> Perhaps we should implement a different mechanism for specifying PAM
> entries for the system, perhaps similar to what the name-service-switch
> field does?

We could also allow users to specify

  (service pam-root-service-type (pam-configuration …))

in their ‘services’ field.  Would that help?

> 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.

Why not.

Tricky issues!  NixOS has lots of hard-coded cases instead of a generic
way to extend PAM settings:

  https://github.com/NixOS/nixpkgs/blob/release-19.03/nixos/modules/security/pam.nix#L304

From what you wrote, it may be that PAM configuration is simply not
“composable”, in the sense that you cannot assemble bits without viewing
the global picture.

Thanks,
Ludo’.

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: LDAP authentication + Configuring PAM
  2019-08-23 12:14 ` Ludovic Courtès
@ 2019-08-23 20:08   ` Ricardo Wurmus
  2019-09-03 12:14     ` Ludovic Courtès
  0 siblings, 1 reply; 4+ messages in thread
From: Ricardo Wurmus @ 2019-08-23 20:08 UTC (permalink / raw)
  To: Ludovic Courtès; +Cc: guix-devel


Hi Ludo,

> Ricardo Wurmus <rekado@elephly.net> skribis:
>
>> 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.
>
> Nscd already has caches for those in our default configuration, doesn’t
> it?

No, it only provides caches for “hosts” and “services” by default.  I
think this should be changed as it seems that NSCD will not use plugins
when it doesn’t also cache the resource.

>> * 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?
>
> We should turn ‘name-service-switch’ into an extensible service, IMO.
> It just happens to predate the service type graph.

I think so too.

>> 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.
>
> So you’re extending ‘pam-root-service-type’ with a “transformation”
> procedure, right?

I first wanted to respond that I did, but I forgot about
“pam-configuration” and its “transform” field.  (I didn’t find it in the
manual.)  Instead I used an extension procedure of the
pam-root-service-type.

Here’s the actual definition that I used.  It’s a bit long.

--8<---------------cut here---------------start------------->8---
(define nslcd-custom-pam-rules-service-type
  (let* ((my-pam-ldap-pam-service
          (lambda (config)
            "Return a PAM service for LDAP authentication."
            (define pam-ldap-module
              #~(string-append #$((@@ (gnu services authentication) nslcd-configuration-nss-pam-ldapd) config)
                               "/lib/security/pam_ldap.so"))
            (lambda (pam)
              (if (member (pam-service-name pam)
                          ((@@ (gnu services authentication) nslcd-configuration-pam-services) config))
                  (let ((sufficient
                         (pam-entry
                          (control "sufficient")
                          (module pam-ldap-module)
                          (arguments '("minimum_uid=1000" "try_first_pass" "ignore_unknown_user"))))
                        (auth
                         (pam-entry
                          (control "sufficient")
                          (module pam-ldap-module)
                          (arguments '("minimum_uid=1000" "try_first_pass"))))
                        (session-mkhomedir
                         (pam-entry
                          (control "required")
                          (module "pam_mkhomedir.so")
                          (arguments '("skel=/etc/skel"
                                       "umask=0022"
                                       "minimum_uid=1000"
                                       "debug"))))
                        (make-unix-sufficient
                         (lambda (entry)
                           (match (pam-entry-module entry)
                                  ("pam_unix.so"
                                   (pam-entry
                                    (inherit entry)
                                    (control "sufficient")))
                                  (_ entry))))
                        ;; This is really ugly.  We have to do this because
                        ;; pam-entry-module may return a G-expression and
                        ;; there's no easy way for us to match the module name
                        ;; any other way.
                        (ldap-entry?
                         (lambda (entry)
                           (match (pam-entry-module entry)
                                  ((? gexp? g)
                                   (match (lowered-gexp-sexp (with-store store (run-with-store store (lower-gexp g))))
                                          (('string-append _ tail . rest)
                                           (string-suffix? "pam_ldap.so" tail))))
                                  (_ #f)))))

                    ;; Replace any existing pam_ldap.so entry and make the unix "sufficient" instead of "required" so that
                    ;; LDAP authentication has a chance.
                    (pam-service
                     (inherit pam)
                     (auth
                      (cons auth
                            (filter (negate ldap-entry?)
                                    (map make-unix-sufficient (pam-service-auth pam)))))
                     (session
                      (cons session-mkhomedir
                            (filter (negate ldap-entry?)
                                    (map make-unix-sufficient (pam-service-session pam)))))
                     (account
                      (cons sufficient
                            (filter (negate ldap-entry?)
                                    (map make-unix-sufficient (pam-service-account pam)))))
                     (password
                      (cons sufficient
                            (filter (negate ldap-entry?)
                                    (map make-unix-sufficient (pam-service-account pam)))))))
                  pam))))
          (my-pam-ldap-pam-services
           (lambda (config)
             (list (my-pam-ldap-pam-service config)))))
    (service-type
     (name 'nslcd-custom-pam-rules)
     (description "Install PAM records to enable authentication with LDAP.")
     (extensions
      (list (service-extension pam-root-service-type
                               my-pam-ldap-pam-services))))))
--8<---------------cut here---------------end--------------->8---

I use it like this in the “services” field:

--8<---------------cut here---------------start------------->8---
                   ;; Run NSLCD and bind to Active Directory
                   (service nslcd-service-type
                            ldap-config)
                   (service nslcd-custom-pam-rules-service-type
                            ldap-config)
--8<---------------cut here---------------end--------------->8---

The both share the same LDAP configuration only for
nslcd-configuration-nss-pam-ldapd and nslcd-configuration-pam-services.

>> 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.
>
> Could you match against a <file-append> record?

Perhaps, I haven’t tried yet.

>> 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 could add a field in <pam-configuration> that would be
> transformation procedure that takes the complete list of entries?

I don’t know if that makes sense without a guaranteed order.

>> Perhaps we should implement a different mechanism for specifying PAM
>> entries for the system, perhaps similar to what the name-service-switch
>> field does?
>
> We could also allow users to specify
>
>   (service pam-root-service-type (pam-configuration …))
>
> in their ‘services’ field.  Would that help?

I think it would.  It would be annoying, though, that I would have to
essentially re-create the pam entries that would otherwise be provided
by other services.

I wonder if we could tag pam entries with the service that provided
them, so that one could more easily match on them and rearrange them as
needed in a finalizer procedure that is guaranteed to be invoked at the
very end, after all other extensions to the pam-root-service-type have
been evaluated.

I’m thinking of this as a big pile of pam entries that individual
services contribute to and that the user can re-arrange and perhaps
modify.

>> 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.
>
> Why not.

As far as I can tell this should not have any downsides.

> Tricky issues!  NixOS has lots of hard-coded cases instead of a generic
> way to extend PAM settings:
>
>   https://github.com/NixOS/nixpkgs/blob/release-19.03/nixos/modules/security/pam.nix#L304

I prefer the Guix way here.  It is more generic and more flexible.  It
just misses a few convenience procedures, in my opinion.

> From what you wrote, it may be that PAM configuration is simply not
> “composable”, in the sense that you cannot assemble bits without viewing
> the global picture.

I think individual services cannot generically extend the PAM
configuration, because they cannot know what order is correct with
respect to all other services that contribute to the configuration.  But
they *can* provide at least their own entries.

--
Ricardo

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: LDAP authentication + Configuring PAM
  2019-08-23 20:08   ` Ricardo Wurmus
@ 2019-09-03 12:14     ` Ludovic Courtès
  0 siblings, 0 replies; 4+ messages in thread
From: Ludovic Courtès @ 2019-09-03 12:14 UTC (permalink / raw)
  To: Ricardo Wurmus; +Cc: guix-devel

Hi!

Ricardo Wurmus <rekado@elephly.net> skribis:

>> Ricardo Wurmus <rekado@elephly.net> skribis:

[...]

>>> 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 could add a field in <pam-configuration> that would be
>> transformation procedure that takes the complete list of entries?
>
> I don’t know if that makes sense without a guaranteed order.

Precisely: that procedure would allow you to reorder the entries.

>>> 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.
>>
>> Why not.
>
> As far as I can tell this should not have any downsides.

Cool, feel free to push this change.

>> Tricky issues!  NixOS has lots of hard-coded cases instead of a generic
>> way to extend PAM settings:
>>
>>   https://github.com/NixOS/nixpkgs/blob/release-19.03/nixos/modules/security/pam.nix#L304
>
> I prefer the Guix way here.  It is more generic and more flexible.  It
> just misses a few convenience procedures, in my opinion.
>
>> From what you wrote, it may be that PAM configuration is simply not
>> “composable”, in the sense that you cannot assemble bits without viewing
>> the global picture.
>
> I think individual services cannot generically extend the PAM
> configuration, because they cannot know what order is correct with
> respect to all other services that contribute to the configuration.  But
> they *can* provide at least their own entries.

The ideal view is that services contribute bits here and there in the
service graph, such that separation of concern is guaranteed: you can
add a complex service to your ‘services’ field without knowing how it’s
implemented, and the service automatically tweaks other services as
needed and adds any services it depends on.

In the case of PAM, that may simply be impossible: each service can
contribute its own PAM entries, sure, but then, who’s in charge of
ensuring that the final list of entries is correctly ordered?  How does
one know what the “correct order” is?

My understanding is that determining the correct order needs to be done
by the admin, who has to be (1) well versed in PAM, and (2)
knowledgeable about all the services that add PAM entries.

If that really is the case, it’s very different from the ideal view
above.

Thanks,
Ludo’.

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2019-09-03 12:14 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-08-22 14:03 LDAP authentication + Configuring PAM Ricardo Wurmus
2019-08-23 12:14 ` Ludovic Courtès
2019-08-23 20:08   ` Ricardo Wurmus
2019-09-03 12:14     ` Ludovic Courtès

Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/guix.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.