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 intonssor 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
memberOfattributes 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-basedposixGroupclass, however, sincememberofrequires 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
memberofbe installed, as installingautogroupfails unless I included theolcAGmemberOfAdconfiguration attribute. It also seems to require DN-based membership, so likememberofit 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
autogroupable 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?