What is the easiest way to set up composable POSIX groups for SSH & Samba authentication?
Background
I'm putting together what I would consider to be a fairly ordinary chunk of infrastructure, but have been running into so many problems that I can't help but wonder if there's an easier way.
I need to be able to do the following:
- Securely share files from a Linux server to several hundred users on OSX, Linux, and Windows machines with delegated authentication and group-based permissions.
- Permit group-based SSH access to dozens of Linux servers, with delegated authentication and group-based permissions.
- Be able to create groups composed of both other groups and users, preferably to an arbitrary depth.
- Be able to permit self-service for basic tasks (password changing and recovery, limited editing of one's own information, etc)
- Be able to migrate existing servers (running various flavors of Linux) with minimal configuration - so sticking to "standard" configuration wherever possible, especially client-side.
I'd like to be able to have the options to do the following:
- Support public key authentication in addition to password-based authentication
All of the above sounds to me like LDAP + Samba is the only practical way to go, especially since the previous implementation used FreeIPA/Samba. The team settled on using OpenLDAP with LDAP Account Manager to provide the authentication and directory services, but the implementation has been a nightmare.
OpenLDAP setup
My directory tree has the following structure:
- dc=example,dc=com
- ou=groups
- cn=groupA
- ...
- ou=policies
- cn=passwordDefault
- ...
- ou=services
- cn=service1
- ...
- ou=users (
- uid=user1
- ...
Users have the following classes:
- inetOrgPerson (structural)
- posixAccount
- shadowAccount
- sambaSamAccount
- ppolicyUser
- passwordSelfReset
- ldapPublicKey
- generalInformation
Users work great. Most of the Linux machines are using sshd -> libpam-ldap -> libnss-ldapd -> nscd -> nslcd
and coreutils -> libnss-ldapd -> nscd -> nslcd
, so getent passwd
and id [username]
work without any special configuration at all.
Services have the following classes:
- applicationProcess (structural)
- simpleSecurityObject
Services are just simple DN/pw entities for in-house services like Gitlab such that contact LDAP directly, so that we can turn off anonymous binding without cutting them off. They also work wonderfully. (Note - a few services actually need to exist as POSIX accounts, so they're configured as users for simplicity)
Policies are entities such as password policies, which fall outside the scope of this question.
Groups have been the real problem, and so I will discuss them in detail in the next section.
The Problem With Groups
No matter what I do, I cannot seem to create composable POSIX-compliant groups. In this context, I'm using "composable" to mean the following. Suppose these groups are configured - GroupA (User1, User2, User3), GroupB (User4, User5), and GroupC (User1, User5). Composable groups would allow the creation of a dynamic GroupD (GroupA, GroupC), with an effective membership of (User1, User2, User3, User5). Ideally, GroupD could also be defined as (GroupA, GroupC, User6). Also, ideally you could continue nesting groups more than one layer deep.
OpenLDAP and LAM have some tools that seem intended for this sort of thing, but I keep running into schema issues, design issues, implementation issues, or some combination of the three.
-
dynlist - This OpenLDAP overlay allows you to populate lists of attributes on-the-fly based on an LDAP filter. You can even have hybrid groups made up of both users and other groups! Unfortunately, the overlay only triggers when the entity it's attached to is being viewed directly. This means that
getent group [group]
(which looks at one or more groups directly) works, butgroups [user]
(which searches for a user's presence in groups) does not. This makes it impossible to SSH based on a dynamic group without writing your own mapping layer intonss
or other LDAP consumers. Also, probably for similar reasons, you can't create a dynlist group from other dynlist groups. -
memberof - This OpenLDAP overlay will automatically update
memberOf
attributes for users to correspond to the user's addition to and removal from groups. It seems to require using a group class other than the RFC2307-basedposixGroup
class, however, sincememberof
requires DN-based membership, rather than uid-based membership. It also doesn't actually help with managing the groups themselves. -
autogroup - This OpenLDAP overlay will automatically add or remove user DNs from a configurable attribute, corresponding to the user's presence or absence in the results of a configurable LDAP filter. This overlay seems to require that
memberof
be installed, as installingautogroup
fails unless I included theolcAGmemberOfAd
configuration attribute. It also seems to require DN-based membership, so likememberof
it needs a class other than RFC2307-basedposixGroup
I initially tried to implement static groups using the class posixGroup (structural)
, and dynamic groups with an additional labeledURIObject
class to attach a dynlist
filter. This basically worked, but I ran into dynlist
's shortcomings outlined above.
I then tried using the autogroup
overlay instead, but ran into the issue where it seemed to expect DN-based memberships. At this point, I felt I had to try to get away from posixGroup
as the structural class, as it seemed nothing was written to support it. LAM seems to prefer using a combination of groupOfNames
and RFC2307bis-posixGroup
, as it includes a feature to automatically sync member
attributes with memberUid
attributes when both are present on the same object. So I modified the posixAccount
schema that ships with OpenLDAP to make it auxiliary instead of structural, and made groups with the groupOfNames
(structural) and modified posixAccount
classes. Hacking apart OpenLDAP's objectClass
schemas to trick it with an RFC2307bis-esque class felt like Not The Right Thing To Do™, but OpenLDAP/LAM appeared to accept it. Doing this also allowed me to install memberof
successfully, which was nice.
autogroup
, however, only half works. While it will add users to dynamic groups (which are visible with groups [user]
), it won't remove them when they're removed from the base group. groupOfNames
also requires that its members
field have at least one user in it at all times, which is problematic for use with autogroup
. You have to set it up with an initial member
, but then can't remove the dummy member once autogroup
locks the member
attribute.
Config
cn=module{0},cn=config
dn: cn=module{0}
objectClass: olcModuleList
cn: module{0}
olcModulePath: /usr/lib/ldap
olcModuleLoad: {0}back_mdb
olcModuleLoad: {1}ppolicy
olcModuleLoad: {2}autogroup
olcModuleLoad: {3}memberof
olcOverlay={1}memberof,olcDatabase={1}mdb,cn=config
dn: olcOverlay={1}memberof
objectClass: top
objectClass: olcConfig
objectClass: olcOverlayConfig
objectClass: olcMemberOf
olcOverlay: {1}memberof
olcMemberOfDangling: ignore
olcMemberOfRefInt: TRUE
olcMemberOfGroupOC: groupOfNames
olcMemberOfMemberAD: member
olcMemberOfMemberOfAD: memberOf
olcOverlay{2}autogroup,olcDatabase={1}mdb,cn=config
olcOverlay: {2}autogroup
structuralObjectClass: olcAutomaticGroups
olcAGattrSet: {0}labeledURIObject labeledURI member
olcAGmemberOfAd: memberOf
The Questions
Most specific to least:
- Why is
autogroup
able to add users to dynamic groups, but not remove them? - Is there an easier way to implement this use-case?
- Am I using the wrong tools for the job? Would this use-case be significantly easier with a different LDAP implementation, some simple client-side configuration, or something non-LDAP altogether?
It turned out that the best approach here was to let the LDAP clients do the heavy lifting:
I set up groups as consisting structurally of groupOfNames
with RFC2307bis-posixGroup
attached. As my environment didn't support RFC2307bis-posixGroup
directly, I put together an LDIF to make OpenLDAP remove its existing definition of posixGroup
and replace it with an auxiliary copy:
DO NOT modify your configuration with this as-is! If you choose to go this route, locate your server's posixGroup
configuration, and swap it with an equivalent configuration with STRUCTURAL
changed to AUXILIARY
.
dn: cn={2}nis,cn=schema,cn=config
changetype: modify
delete: olcObjectClasses
olcObjectClasses: {2}( 1.3.6.1.1.1.2.2 NAME 'posixGroup' DESC 'Abstraction of a group of accounts' SUP top STRUCTURAL MUST ( cn $ gidNumber ) MAY ( userPassword $ memberUid $ description ) )
-
add: olcObjectClasses
olcObjectClasses: {2}( 1.3.6.1.1.1.2.2 NAME 'posixGroup' DESC 'Abstraction of a group of accounts' SUP top AUXILIARY MUST ( cn $ gidNumber ) MAY ( userPassword $ memberUid $ description ) )
The server could then pretend to support the RFC2307bis-posixGroup specification. From there, I was able to add memberOf and complete the OpenLDAP configuration.
The key was that modern versions of NSLCD and SSSD both contain flags permitting recursive group searching. Apparently this feature is called "nested" grouping, which is a bit confusing in the context of an LDAP directory structure that also permits groups to be nested inside each other. These features aren't about the LDAP structure, however, they're about being able to follow references to groupOfNames
inside of other groupOfNames
.
nslcd: nss_nested_groups yes|no
If this option is set, the member attribute of a group may point to another group. Members of nested groups are also returned in the higher level group and parent groups are returned when finding groups for a specific user. The default is not to perform extra searches for nested groups.
sssd: ldap_group_nesting_level (integer)
If ldap_schema is set to a schema format that supports nested groups (e.g. RFC2307bis), then this option controls how many levels of nesting SSSD will follow. This option has no effect on the RFC2307 schema. Default: 2
There are some caveats - Red Hat prefers that you use SSSD, so the version of nslcd in the Red Hat repository isn't new enough to support the feature. But this approach should fit most use cases. Ultimately, reconfiguring a flag on all the servers using sssd or nslcd was much easier than trying to get OpenLDAP to do "nested" grouping all on its own.
I can now reference groups in other groups in the LDAP tree, and getent group
will now list each group and a flattened list of all the users that are referenced in the group directly, or within referenced groups recursively.