LDAP Sync with nested Groups

robin.tripp

New Member
Dec 14, 2020
1
0
1
Hello everyone,

I am currently trying to implement LDAP Sync for our Proxmox cluster.
Basically it's working fine, the only thing I don't get to sync are the user to group associations when a user is in a nested group.

This is the relevant content of my domains.cfg:
Code:
ad: example.local
    comment Active Directory authentication
    domain example.local
    server1 dc1.example.local
    bind_dn CN=Proxmox,OU=LDAP,OU=ServiceAccounts,DC=example,DC=local
    default 0
    filter (&(objectCategory=person)(objectClass=user)(memberOf:1.2.840.113556.1.4.1941:=CN=ProxmoxAdmin,OU=example Groups,DC=example,DC=local))
    group_classes group
    group_filter (&(objectCategory=group)(objectClass=group)(sAMAccountName=Proxmox*))
    group_name_attr cn
    secure 0
    server2 dc2.example.local
    sync-defaults-options full=1,purge=1,scope=both
    sync_attributes email=mail
    user_classes person

When running the sync, all Users in the group "ProxmoxAdmin" are created. Also all groups starting with "Proxmox*" are created.

Is it at all posssible to sync nested group associations? If so, can someone give me hint on how the group_filter should look like?

Thank you very much :)
 
Last edited:
I know this post is super old now, but it was still one of the first posts that came up in google about getting nested groups working. I’ll start by saying I got it working after 3 hours and this post led me to my breakthrough. This was achieved entirely in the GUI.

Other posts I came across were confused on what a nested group was. Basically it is a group within a group. A common use case is where users are a member of a security group to represent their role and the role group is a member of an asset group like “Proxmox_Admin.”

  1. Create a new realm
    1. Datacenter > Permissions > Realms click add
    2. Select Active Directory Server
  2. General:
    1. Realm: contoso
      1. like in ad.contoso.com
    2. Domain: ad.contoso.com
    3. Default: I don’t know what this means
    4. Server: DC1.ad.contoso.com
      1. I think the FQDN is best here
    5. Fallback Server: DC2.ad.contoso.com
    6. Everything else left at default
  3. Sync Options:
    1. Bind User: CN=SA_LDAP_RO,OU=Service Accounts,DC=ad,DC=contoso,DC=com
      1. I suggest to create a new user with the standard Domain Users membership
    2. Scope: Users and Groups
    3. User Filter: (memberOf:1.2.840.113556.1.4.1941:=CN=WEB_Proxmox_Admin,OU=WEB,OU=Rules,OU=Groups,DC=ad,DC=contoso,DC=com)
    4. Group Filter: (memberOf:1.2.840.113556.1.4.1941:=CN=WEB_Proxmox_Admin,OU=WEB,OU=Rules,OU=Groups,DC=ad,DC=contoso,DC=com)
      1. Yeah, both the User and Group filters are the same. Although, the object Category and Class could be added.
    5. I selected all the Remove Vanished Options
    6. All other fields not specified here were left empty or at their defaults.
  4. Click OK to save and then click sync on the new realm
    1. If it does not work check if your firewall is allowing 389 from your Proxmox box to your DC. I had that problem ;)
  5. Under Datacenter > Permissions > Groups you should now see a group. For me this was the nested group within the WEB_Proxmox_Admin group.
  6. Go to Datacenter > Permissions click add
    1. Select Group Permission
    2. Path: /
    3. Group: select the group synced from the previous steps
    4. Role: Administrator
  7. Celebrate
 
Last edited:
  • Like
Reactions: etheriault
I know this post is super old now, but it was still one of the first posts that came up in google about getting nested groups working. I’ll start by saying I got it working after 3 hours and this post led me to my breakthrough. This was achieved entirely in the GUI.

Other posts I came across were confused on what a nested group was. Basically it is a group within a group. A common use case is where users are a member of a security group to represent their role and the role group is a member of an asset group like “Proxmox_Admin.”

  1. Create a new realm
    1. Datacenter > Permissions > Realms click add
    2. Select Active Directory Server
  2. General:
    1. Realm: contoso
      1. like in ad.contoso.com
    2. Domain: ad.contoso.com
    3. Default: I don’t know what this means
    4. Server: DC1.ad.contoso.com
      1. I think the FQDN is best here
    5. Fallback Server: DC2.ad.contoso.com
    6. Everything else left at default
  3. Sync Options:
    1. Bind User: CN=SA_LDAP_RO,OU=Service Accounts,DC=ad,DC=contoso,DC=com
      1. I suggest to create a new user with the standard Domain Users membership
    2. Scope: Users and Groups
    3. User Filter: (memberOf:1.2.840.113556.1.4.1941:=CN=WEB_Proxmox_Admin,OU=WEB,OU=Rules,OU=Groups,DC=ad,DC=contoso,DC=com)
    4. Group Filter: (memberOf:1.2.840.113556.1.4.1941:=CN=WEB_Proxmox_Admin,OU=WEB,OU=Rules,OU=Groups,DC=ad,DC=contoso,DC=com)
      1. Yeah, both the User and Group filters are the same. Although, the object Category and Class could be added.
    5. I selected all the Remove Vanished Options
    6. All other fields not specified here were left empty or at their defaults.
  4. Click OK to save and then click sync on the new realm
    1. If it does not work check if your firewall is allowing 389 from your Proxmox box to your DC. I had that problem ;)
  5. Under Datacenter > Permissions > Groups you should now see a group. For me this was the nested group within the WEB_Proxmox_Admin group.
  6. Go to Datacenter > Permissions click add
    1. Select Group Permission
    2. Path: /
    3. Group: select the group synced from the previous steps
    4. Role: Administrator
  7. Celebrate
I have followed this and I have domain admins in the proxmox_admin group, but I think because domain admins has a space in it, it won't pop up. I was trying to create a group with no space, and just have domain admins part of that group, but doesn't work like I want it to. It seems it will pull the users, but if I have proxmox_admins as adminsitrator it just uses the user group and not the domain group. I log in, and I don't have admin privileges. I just can't get it to work.
 
I have followed this and I have domain admins in the proxmox_admin group, but I think because domain admins has a space in it, it won't pop up. I was trying to create a group with no space, and just have domain admins part of that group, but doesn't work like I want it to. It seems it will pull the users, but if I have proxmox_admins as adminsitrator it just uses the user group and not the domain group. I log in, and I don't have admin privileges. I just can't get it to work.
I highly suspect that this is due to the fact that MS AD groups by design contain spaces, which Proxmox does not accept.

See also this post:
https://forum.proxmox.com/threads/group-names-and-active-directory-sync.74072/#post-608534

And its corresponding bug / feature request (more bug than feature imho):
https://bugzilla.proxmox.com/show_bug.cgi?id=2929
 
We are using FreeIPA and have multiple groups (with nested child groups), which we'd like grant access to specific VMs. FreeIPA's memberOf attribute seems to behave the same as in Active Directory, but for our case we want to sync multiple groups so we can't apply a global filter for memberOf=proxmox-admins.

Our workaround was to modify the LDAP module so that for each group it performs an additional search for users filtered by those that are a memberOf the group. This syncs all of the groups incl. indirect members perfectly into Proxmox.

Code:
--- /usr/share/perl5/PVE/LDAP.pm    2023-12-08 14:01:16.581194701 +1030
+++ /usr/share/perl5/PVE/LDAP.pm    2024-01-10 16:35:43.515791568 +1030
@@ -241,6 +241,18 @@
         if (!scalar(@$members)) {
         $members = [$entry->get_value('uniqueMember')];
         }
+
+        # FreeIPA nested group hack
+            my @memberof_args = (
+                base     => $base_dn,
+                scope    => 'subtree',
+                filter   => '(memberOf=' . $group->{dn} . ')',
+                control  => [ $page ],
+                attrs    => [ 'dn' ],
+            );
+            $members=[map{$_->dn} $ldap->search(@memberof_args)->entries];
+            # end FreeIPA nested group hack
+
         $group->{members} = $members;
         if ($group_name_attr && (my $name = $entry->get_value($group_name_attr))) {
         $group->{name} = $name;

Interestingly Proxmox's LDAP code is already querying and saving a list of groups that each user is a (direct or indirect) memberOf, but never appears to use this complete data and instead queries all of the groups and the incomplete list of their direct 'member' s. https://github.com/proxmox/pve-comm...ec3bb646de12d0ce6481519f/src/PVE/LDAP.pm#L157
 
  • Like
Reactions: jose-pr
Any update on this? My AD testing shows nested groups membership is ignored. Or I am doing something wrong?
 
We are using FreeIPA and have multiple groups (with nested child groups), which we'd like grant access to specific VMs. FreeIPA's memberOf attribute seems to behave the same as in Active Directory, but for our case we want to sync multiple groups so we can't apply a global filter for memberOf=proxmox-admins.

Our workaround was to modify the LDAP module so that for each group it performs an additional search for users filtered by those that are a memberOf the group. This syncs all of the groups incl. indirect members perfectly into Proxmox.

Code:
--- /usr/share/perl5/PVE/LDAP.pm    2023-12-08 14:01:16.581194701 +1030
+++ /usr/share/perl5/PVE/LDAP.pm    2024-01-10 16:35:43.515791568 +1030
@@ -241,6 +241,18 @@
         if (!scalar(@$members)) {
         $members = [$entry->get_value('uniqueMember')];
         }
+
+        # FreeIPA nested group hack
+            my @memberof_args = (
+                base     => $base_dn,
+                scope    => 'subtree',
+                filter   => '(memberOf=' . $group->{dn} . ')',
+                control  => [ $page ],
+                attrs    => [ 'dn' ],
+            );
+            $members=[map{$_->dn} $ldap->search(@memberof_args)->entries];
+            # end FreeIPA nested group hack
+
         $group->{members} = $members;
         if ($group_name_attr && (my $name = $entry->get_value($group_name_attr))) {
         $group->{name} = $name;

Interestingly Proxmox's LDAP code is already querying and saving a list of groups that each user is a (direct or indirect) memberOf, but never appears to use this complete data and instead queries all of the groups and the incomplete list of their direct 'member' s. https://github.com/proxmox/pve-comm...ec3bb646de12d0ce6481519f/src/PVE/LDAP.pm#L157
Would this also work for AD nested groups?
 
We are using a different Approach (see patch for /usr/share/perl5/PVE/Auth/LDAP.pm)

This just adds every known group (that matches the naming criteria) from LDAP to the groups list (as before) and populates the members via the already discovered users and their already fetched memberOf attribute.

Unlike above, no extra searches are done.

This should also work with AD.

Diff:
--- LDAP.pm.orig    2023-11-29 08:26:03.158648539 +0100
+++ LDAP.pm    2023-11-29 08:25:45.182601935 +0100
@@ -335,30 +335,75 @@
 
     my $groups = PVE::LDAP::query_groups($ldap, $basedn, $classes, $filter, $attr);
 
+    my $user_name_attr = $config->{user_attr} // 'uid';
+    my $user_ldap_attribute_map = {
+        $user_name_attr => 'username',
+        enable => 'enable',
+        expire => 'expire',
+        firstname => 'firstname',
+        lastname => 'lastname',
+        email => 'email',
+        comment => 'comment',
+        keys => 'keys',
+    };
+
+    foreach my $user_attr (PVE::Tools::split_list($config->{sync_attributes})) {
+        my ($ours, $ldaps) = ($user_attr =~ m/^\s*(\w+)=(.*)\s*$/);
+        $user_ldap_attribute_map->{$ldaps} = $ours;
+    }
+
+    my $user_filter = $config->{filter};
+    my $user_basedn = $config->{base_dn};
+
+    $config->{user_classes} //= 'inetorgperson, posixaccount, person, user';
+    my $user_classes = [PVE::Tools::split_list($config->{user_classes})];
+    my $users = PVE::LDAP::query_users($ldap, $user_filter, [keys %$user_ldap_attribute_map], $user_basedn, $user_classes);
+
     my $ret = {};
 
     foreach my $group (@$groups) {
-    my $name = $group->{name};
-    if (!$name && $group->{dn} =~ m/^[^=]+=([^,]+),/){
-        $name = PVE::Tools::trim($1);
-    }
-    if ($name) {
-        $name .= "-$realm";
-
-        # we cannot sync groups that do not meet our criteria
-        eval { PVE::AccessControl::verify_groupname($name) };
-        if (my $err = $@) {
-        warn "$err";
-        next;
-        }
-
-        $ret->{$name} = { users => {} };
-        foreach my $member (@{$group->{members}}) {
-        if (my $user = $dnmap->{lc($member)}) {
-            $ret->{$name}->{users}->{$user} = 1;
-        }
-        }
-    }
+        my $name = $group->{name};
+        if (!$name && $group->{dn} =~ m/^[^=]+=([^,]+),/){
+            $name = PVE::Tools::trim($1);
+        }
+        if ($name) {
+          $name .= "-$realm";
+
+          # we cannot sync groups that do not meet our criteria
+          eval { PVE::AccessControl::verify_groupname($name) };
+          if (my $err = $@) {
+          warn "$err";
+          next;
+          }
+
+          $ret->{$name} = { users => {} };
+          foreach my $user (@$users) {
+          my $user_attributes = $user->{attributes};
+          my $userid = $user_attributes->{$user_name_attr}->[0];
+          my $username = "$userid\@$realm";
+         
+          # we cannot sync usernames that do not meet our criteria
+          eval { PVE::Auth::Plugin::verify_username($username) };
+          if (my $err = $@) {
+            warn "$err";
+            next;
+          }
+         
+          if (defined($user->{'groups'})) {
+            foreach my $group_dn(@{$user->{'groups'}}) {
+              if ($group_dn eq $group->{'dn'}) {
+                  $ret->{$name}->{users}->{$username} = 1;
+              }
+            }
+          }
+        }
+       
+          #foreach my $member (@{$group->{members}}) {
+          #    if (my $user = $dnmap->{lc($member)}) {
+          #        $ret->{$name}->{users}->{$user} = 1;
+          #    }
+          #}
+      }
     }
 
     return $ret;
 
Last edited:
We are using FreeIPA and have multiple groups (with nested child groups), which we'd like grant access to specific VMs. FreeIPA's memberOf attribute seems to behave the same as in Active Directory, but for our case we want to sync multiple groups so we can't apply a global filter for memberOf=proxmox-admins.

Our workaround was to modify the LDAP module so that for each group it performs an additional search for users filtered by those that are a memberOf the group. This syncs all of the groups incl. indirect members perfectly into Proxmox.

Code:
--- /usr/share/perl5/PVE/LDAP.pm    2023-12-08 14:01:16.581194701 +1030
+++ /usr/share/perl5/PVE/LDAP.pm    2024-01-10 16:35:43.515791568 +1030
@@ -241,6 +241,18 @@
         if (!scalar(@$members)) {
         $members = [$entry->get_value('uniqueMember')];
         }
+
+        # FreeIPA nested group hack
+            my @memberof_args = (
+                base     => $base_dn,
+                scope    => 'subtree',
+                filter   => '(memberOf=' . $group->{dn} . ')',
+                control  => [ $page ],
+                attrs    => [ 'dn' ],
+            );
+            $members=[map{$_->dn} $ldap->search(@memberof_args)->entries];
+            # end FreeIPA nested group hack
+
         $group->{members} = $members;
         if ($group_name_attr && (my $name = $entry->get_value($group_name_attr))) {
         $group->{name} = $name;

Interestingly Proxmox's LDAP code is already querying and saving a list of groups that each user is a (direct or indirect) memberOf, but never appears to use this complete data and instead queries all of the groups and the incomplete list of their direct 'member' s. https://github.com/proxmox/pve-comm...ec3bb646de12d0ce6481519f/src/PVE/LDAP.pm#L157

You mentioned you are using FreeIPA - this brings me question, it using PAM is not better idea. FreeIPA can be used with PAM - does group membership works using FreeIPA + PAM and logging into PVE using PAM instead?
 
I could also answer that, because we use FreeIPA, too: The pam provider works but you have to manually create the groups in pve to allow assignment of privileges. When using the ldap provider, the groups are synced automatically and you can also use non-posix groups.
We use pam for administrative access on the host itself but not on the webfrontend/api.
 
  • Like
Reactions: iwik

About

The Proxmox community has been around for many years and offers help and support for Proxmox VE, Proxmox Backup Server, and Proxmox Mail Gateway.
We think our community is one of the best thanks to people like you!

Get your subscription!

The Proxmox team works very hard to make sure you are running the best software and getting stable updates and security enhancements, as well as quick enterprise support. Tens of thousands of happy customers have a Proxmox subscription. Get yours easily in our online shop.

Buy now!