Active Directory Nested Groups

I am trying to get active directory authentication working with nested groups. What I mean by that is that I have the following groups setup:

Sonar Users
  |- Developers
      |- User1
      |- User2
  |- SysAdmins
     |- User3
     |- User4

If I place User1/2/3/4 directly in the “Sonar Users” group everything works as expected. However If I try to place the “Developers” and “SysAdmins” group inside the “Sonar Users” group authentication won’t work.

How can I get nested ad groups working? Here’s what I have in my config file:

# Distinguished Name (DN) of the root node in LDAP from which to search for users (mandatory)
ldap.user.baseDn=DC=mydomain,DC=local

# LDAP user request. (default: (&(objectClass=inetOrgPerson)(uid={login})) )
ldap.user.request=(sAMAccountName={0})

# Attribute in LDAP defining the user’s real name. (default: cn)
ldap.user.realNameAttribute=cn

# Attribute in LDAP defining the user’s email. (default: mail)
ldap.user.emailAttribute=mail

# GROUP MAPPING

# Distinguished Name (DN) of the root node in LDAP from which to search for groups. (optional, default: empty)
ldap.group.baseDn=CN=SonarQube Users,OU=Groups,DC=domain,DC=local

# LDAP group request (default: (&(objectClass=groupOfUniqueNames)(uniqueMember={dn})) )
ldap.group.request=(&(objectClass=group)(member={dn}))

ldap.group.idAttribute=sAMAccountName

# Support Active Directory Nested Groups
ldap.windows.compatibilityMode = true

What I see on the web console is: Authentication failed.

What I see in the logs is:

2019.01.02 20:10:14 TRACE web[AWgQII7LjH452GK4AAA6][sql] time=0ms | sql=SELECT u.id as id, u.uuid as uuid, u.login as login, u.name as name, u.email as email, u.active as "active", u.scm_accounts as "scmAccounts", u.salt as "salt", u.crypted_password as "cryptedPassword", u.hash_method as "hashMethod", u.external_id as "externalId", u.external_login as "externalLogin", u.external_identity_provider as "externalIdentityProvider", u.user_local as "local", u.is_root as "root", u.onboarded as "onboarded", u.homepage_type as "homepageType", u.homepage_parameter as "homepageParameter", u.organization_uuid as organizationUuid, u.created_at as "createdAt", u.updated_at as "updatedAt" FROM users u WHERE u.login=? AND u.active=true | params=myusername
2019.01.02 20:10:14 DEBUG web[AWgQII7LjH452GK4AAA6][o.s.p.l.LdapUsersProvider] Requesting details for user myusername
2019.01.02 20:10:14 DEBUG web[AWgQII7LjH452GK4AAA6][o.s.p.l.LdapSearch] Search: LdapSearch{baseDn=DC=domain,DC=local, scope=subtree, request=(sAMAccountName={0}), parameters=[myusername], attributes=[mail, cn]}
2019.01.02 20:10:14 DEBUG web[AWgQII7LjH452GK4AAA6][o.s.p.l.LdapContextFactory] Initializing LDAP context {java.naming.provider.url=ldap://domain-ad1.domain.local:389, java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory, java.naming.security.principal=CN=sonarqube,OU=Special Accounts,OU=AWS,DC=domain,DC=local, com.sun.jndi.ldap.connect.pool=true, java.naming.security.authentication=simple, java.naming.referral=follow}
2019.01.02 20:10:25 TRACE web[][sql] time=0ms | sql=select id, data from notifications order by id asc limit ? | params=1
2019.01.02 20:11:25 TRACE web[][sql] time=1ms | sql=select id, data from notifications order by id asc limit ? | params=1
2019.01.02 20:12:25 TRACE web[][sql] time=1ms | sql=select id, data from notifications order by id asc limit ? | params=1
2019.01.02 20:12:32 TRACE web[][sql] time=1ms | sql=select uuid, doc_type as docType, doc_id as docId, doc_id_type as docIdType, doc_routing as docRouting, created_at as createdAt from es_queue where created_at <= ? order by created_at desc limit ? | params=1546459652929, 10000
2019.01.02 20:13:25 TRACE web[][sql] time=0ms | sql=select id, data from notifications order by id asc limit ? | params=1
2019.01.02 20:14:25 TRACE web[][sql] time=0ms | sql=select id, data from notifications order by id asc limit ? | params=1

Can anyone assist me?

Thanks!
Brad

1 Like

Hi Brad,

Don’t forget to mention which version of SonarQube you’re using and (if manually installed) relevant plugin version.

This property is deprecated since quite a while, and as far as I can remember was unrelated to nested groups behaviour. Don’t know where you got it from, but I believe you can (and should) remove it.

More generally regarding the support of nested groups, I had to dig this topic a year ago and here were the conclusions (long form so that you have all the context):

I confirm that SonarQube does not support nested (hierarchical) groups. All in all you cannot define (in AD) users in a group A that is part of another group B and expect that (in SonarQube) users, on top of joining group A, will also join group B. (To this respect, group B definition is of no use in SonarQube, unless users are directly attached to that group in AD).

Generally speaking the Group Mapping feature of the LDAP Plugin all boils down to this one request that is issued by the Plugin when user logs in: ldap.group.request . There’s no extra logic from an LDAP Plugin perspective, the rest of it is pure LDAP considerations. As far as I know simple LDAP requests search only within direct attributes (hence why ‘parent’ groups are not processed), however do note that this is a pure LDAP/AD consideration (independent from SonarQube), and I don’t have the LDAP/AD mastery to conclude on whether further search capabilities exists.

So all in all:

  • no specific support of nested group in LDAP Plugin
  • to know which groups a user belongs to, the LDAP Plugin looks into results of the ldap.group.request search query.
  • whether such query can return inherited information (e.g. nested groups) is an LDAP/AD consideration, and you would have to consult AD experts (independently of SonarQube) to see if there are specific capabilities there (also depending on your setup/configuration)

Hope that helps!

1 Like

I got this working with nested groups.

For future reference this is what worked for me. I’m using SonarCube 7.5.

(If you’re using an older or newer version it should go without saying your mileage may vary. Obviously also update these values to suit your particular environment)

#--------------------------------------------------------------------------------------------------
# LDAP CONFIGURATION

# Enable the LDAP feature
sonar.security.realm=LDAP

# Set to true when connecting to a LDAP server using a case-insensitive setup.
sonar.authenticator.downcase=true

# URL of the LDAP server. Note that if you are using ldaps, then you should install the server certificate into the Java truststore.
ldap.url=ldap://yourdc.yourdomain.local:389

# Bind DN is the username of an LDAP user to connect (or bind) with. Leave this blank for anonymous access to the LDAP directory (optional)
ldap.bindDn=CN=sonarqube,OU=Users,DC=yourdomain,DC=local

# Bind Password is the password of the user to connect with. Leave this blank for anonymous access to the LDAP directory (optional)
ldap.bindPassword=your-super-secret-password

# Possible values: simple | CRAM-MD5 | DIGEST-MD5 | GSSAPI See http://java.sun.com/products/jndi/tutorial/ldap/security/auth.html (default: simple)
ldap.authentication=simple

# Distinguished Name (DN) of the root node in LDAP from which to search for users (mandatory)
ldap.user.baseDn=DC=yourdomain,DC=local

# LDAP user request. (default: (&(objectClass=inetOrgPerson)(uid={login})) )
ldap.user.request=(sAMAccountName={0})

# Attribute in LDAP defining the user’s real name. (default: cn)
ldap.user.realNameAttribute=cn

# Attribute in LDAP defining the user’s email. (default: mail)
ldap.user.emailAttribute=mail

# GROUP MAPPING

# Distinguished Name (DN) of the root node in LDAP from which to search for groups. (optional, default: empty)
ldap.group.baseDn=CN=SonarQube Users,OU=Groups,DC=yourdomain,DC=local

# LDAP group request (default: (&(objectClass=groupOfUniqueNames)(uniqueMember={dn})) )
ldap.group.request=(&(objectClass=group)(member={dn}))

ldap.group.idAttribute=sAMAccountName

Hope this helps someone else!

Thanks
Brad

6 Likes

Brad, thanks for the update. I might be missing something here, but I can’t see the actual difference between the config you pasted in your initial post, and the config from your latest post where you say it’s resolved. Specifically ldap.user.baseDn, ldap.user.request, ldap.group.baseDn, ldap.group.request all seem to be identical.

Can you shed some more lights on what specific change got it to work with nested groups on your end?

Thanks for the clarifications, especially as this may help other AD users indeed.

To be entirely honest I’m not sure what fixed it. It may have been upgrading to 7.5, it may have been removing ldap.windows.compatibilityMode = true.

Or possibly another setting I adjusted which I didn’t include in my original post. I simply “fiddled” with it until I got it to work. Thus the reason for posting the entire config versus a snippit for others to reference.

thanks! helped me out! :slight_smile:

Hi,

Unfortunately, it doesn’t work for me. I would be happy if someone could help me.
SONARqube Version: 8.0.0.29455

I have defined several groups with different permissions. The authorization groups in the AD directly contain users and other groups that combine e.g. a Developer Team or Administrators.

With this structure we separate the right/rule groups from function/role groups. So we can query which role has access to which resource and vice versa.

My group structure:
| - Team1Projects_administer (administer only Team 1 Projects)
     | - User 1 (direct)
     | - Testing Team (Group contain all Team Members of testing)
| - Team2Projects_administer (administer only Team 2 Projects)
     | - User 2 (direct)
     | - Testing Team (Group contain all Team Members of testing)
| - Team2Projects_administer (administer only Team 3 Projects)
     | - User 3 (direct)
     | - Testing Team (Group contain all Team Members of testing)
| - BrowseAllProjects (browse all Projects of all Teams)
     | - Developer Team 1 (Group contain all Team Members of Dev Team 1)
     | - Developer Team 2 (Group contain all Team Members of Dev Team 2)
     | - Developer Team 3 (Group contain all Team Members of Dev Team 3)
| - Sysadmin
     | - User 4 (e.g. Sonarqube specialist)
     | - Domain Administrators

We don’t want to allow logins if you are not a member of one of the above mentioned groups (define it by OU was not an option). To enforce this, I have adapted the query of ‘ldap.user.request’ and restricted it to these groups. For this I used ‘LDAP_MATCHING_RULE_IN_CHAIN (1.2.840.113556.1.4.1941)’ in the request.

sonar.security.realm=LDAP
sonar.authenticator.downcase=true
ldap.url=ldap://contoso.local

ldap.bindDn=CN=FKT_SONAR_LDAP,OU=FKT,OU=Identities,DC=CONTOSO,DC=LOCAL
ldap.bindPassword=SUPER SECRET PASSWORD
ldap.authentication=simple

ldap.user.baseDn=DC=CONTOSO,DC=LOCAL
ldap.user.request=(&(objectClass=user)(sAMAccountName={login})(|
	(memberOf:1.2.840.113556.1.4.1941:=CN=BrowseAllProjects,OU=Groups,DC=CONTOSO,DC=LOCAL)
	(memberOf:1.2.840.113556.1.4.1941:=CN=Team1Projects_administer,OU=Groups,DC=CONTOSO,DC=LOCAL)
	(memberOf:1.2.840.113556.1.4.1941:=CN=Team2Projects_administer,OU=Groups,DC=CONTOSO,DC=LOCAL)
	(memberOf:1.2.840.113556.1.4.1941:=CN=Team3Projects_administer,OU=Groups,DC=CONTOSO,DC=LOCAL)
	(memberOf:1.2.840.113556.1.4.1941:=CN=Sysadmin,OU=Groups,DC=CONTOSO,DC=LOCAL)
	))

ldap.user.realNameAttribute=cn
ldap.user.emailAttribute=mail

ldap.group.baseDn=DC=CONTOSO,DC=LOCAL
ldap.group.request=(&(objectClass=group)(member={dn}))
ldap.group.idAttribute=sAMAccountName

sonar.log.level=DEBUG
sonar.forceAuthentication=true

Note: I have shortened the OUs and changed the group names to make it easier to read. But this should have no further effect.

The ldap.user.request works fine. Only members belonging to the above mentioned groups can log in. The nested groups also work for the login.
BUT the group membership in SONARqube does NOT work. Only direct members get the permissions.

How can I solve this now?

Hint: I saw in Brad Baker’s Config that he directly stored the CN of a group in ldap.group.baseDn. But this can’t work with multiple groups, I think. At least I don’t know how.

Hi,

after some back and forth, I’ve got it working now.
A good colleague has shown me my failure, why the group mapping did not work out.

Below my now working configuration. I have adjusted the config a little bit to make it shorter.

ldap.user.baseDn=DC=CONTOSO,DC=LOCAL
ldap.user.request=(&(objectClass=user)(sAMAccountName={login})(memberOf:1.2.840.113556.1.4.1941:=CN=ACL_APP.Sonarqube_login,OU=Application Rules,OU=Access,OU=Groups,OU=PROD,DC=CONTOSO,DC=LOCAL))
ldap.user.realNameAttribute=cn
ldap.user.emailAttribute=mail

ldap.group.baseDn=DC=CONTOSO,DC=LOCAL
ldap.group.request=(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={dn})(sAMAccountName=ACL_APP.Sonarqube*))
ldap.group.idAttribute=sAMAccountName

The key adjustments here are:

ldap.user.request : I have defined a login group in the AD that contains all other right/rule groups. So I control who is allowed to login at all and don’t have to define additional groups in the config anymore.

ldap.group.baseDn : Now I can set to BaseDN. So I have no dependencies to any changes of the OUs or if groups are moved.

ldap.group.request : With the query ‘member:1.2.840.113556.1.4.1941:={dn}’ the members are recursively queried. With the query ‘sAMAccountName=ACL_APP.Sonarqube*’ I restrict the groups to be queried to the sonarqube groups. Of course, this only works with a standardized naming convention.

now i can happily start the weekend :grinning::smiley::grinning:

Hi All,

I was wundering about the {login} and {dn} pieces in the text.
Can image that {login} will be the text a user will type during login?
What about this {dn}? Where does this come from?
Is it polling all custom groups from Sonar? Is it querying all the groups a user is member of in AD?

Also I found out that removing a user from an AD group it will only be remove from the Sonar group when you log in with the removed user once. So basically it looks like Sonar is only making changes to Sonar groups based on the user who logs in.

The version we use is 8.3.0.34182

Hope you can help me out.

Cheerz! Sebastiaan

Hi Sebastian,
it is a few weeks ago, so i cant remember it 100% and have no time to check it out in depth.
Please have an additional look here: Delegating Authentication | SonarQube Docs

Yes, the {login} is the Username, whic is typed at Sonarqube login page.

This ist the part i cant remember 100%. So if u can explain this by testing it would be great.
Search for the [LDAP_MATCHING_RULE_IN_CHAIN]. I know my config works, would be nice to know again why :slight_smile:

PN me if you have some additional questions or write it down here for everyone

Hi,

As far as I understoot correctly from Joshua, the group DN is used when sonar runs the query for groups after the user is successfully authenticated in AD using the login info.

My guess is that it uses the attribute memberOf from the AD user account and starts looking for these groups using the DN. So the dn field value is most likely filled with the memberOf info.

Not sure if it works like this but it sounds great doesn’t it?

Cheerz Sebastiaan

Outlook voor iOS downloaden

True! :sweat_smile: :rofl: :joy:

And I think your description makes sense. I am nearly 100% sure that it works like this.

Do you have any problems with the implementation Sebastian? Or is it just the understanding?

Hi Grey,

It actually works like a charm.

It was a bit of a hassle to get it going but eventually all works fine.

So indeed it’s the understanding of what actually happens underneath.

Anyway. Thanks for helping out here.

image001.png

1 Like