Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

para LDAP config is not picked up correctly #67

Closed
ensecoz opened this issue Jun 5, 2018 · 39 comments
Closed

para LDAP config is not picked up correctly #67

ensecoz opened this issue Jun 5, 2018 · 39 comments

Comments

@ensecoz
Copy link
Contributor

ensecoz commented Jun 5, 2018

I'm setting up server like this:

scoold server
  - application.conf
    - para.security.ldap.server_url = "ldap://test.com"
    - (...other)
para server
  - application.conf

at first try, everything is working but after adjust some of the configuration. I am not sure what is the root cause. now suddenly there is an error cannot authenticate with LDAP server localhost:8389 (which is the default)

my workaround is:

I have to also put the config inside the para (application.conf) too and now it is working again.

scoold server
  - application.conf
    - para.security.ldap.server_url = "ldap://test.com"
    - (...other)
para server
  - application.conf
    - para.security.ldap.server_url = "ldap://test.com"
    - (...other)
@albogdano
Copy link
Member

Are you mounting the config file with -v application.conf:applciation.conf?
Also, have you created a separate app for Scoold in Para?

The workaround you describe is setting LDAP configuration for the root app in Para, called "app:para".

@ensecoz
Copy link
Contributor Author

ensecoz commented Jun 5, 2018

yes, i have separated application.conf for both scoold and para

docker-compose.yml

 para:
     volumes:
       - type: volume
         source: paraData
         target: /para/data
       - type: bind
         source: ./para-application.conf
         target: /para/application.conf
     ...
   scoold:
     depends_on:
       - para
     volumes:
       - type: bind
         source: ./scoold-application.conf
         target: /scoold/application.conf
     ...

scoolid-application.conf

para.app_name = "my-scoold"
para.port = 8000
para.env = "production"
para.host_url = "https://myscoold"
para.endpoint = "http://para:8080"
para.access_key = "app:my-para"
...

para-application.conf

para.app_name = "my-para"
para.env = "production"
...

@albogdano
Copy link
Member

By creating a new app I meant calling para-cli new-app "scoold" --name "Scoold" with the keys for the root app. It's recommended to have a separate app namespace for Scoold.

@ensecoz
Copy link
Contributor Author

ensecoz commented Jun 6, 2018

@albogdano after I create new-app called app:scoold and use it. now the LDAP is not working anymore. Even though I put LDAP config in both application.conf (for scoold and para).

It try to connect to localhost:8389

@ensecoz
Copy link
Contributor Author

ensecoz commented Jun 6, 2018

@albogdano hey, i found out the problem now. when I ran the docker-compose, the scoold is starting but the para is not ready yet.

And later scoold is trying to connect para again but this time the setting is not loaded correctly.

my workaround is to restart the scoold container again and it is working as expected.

Later I can set TIMEOUT in the Dockerfile.

@albogdano
Copy link
Member

albogdano commented Jun 6, 2018

Edit scoold.env and set BOOT_SLEEP=10 for the Scoold container. This will tell Scoold to wait a bit longer for Para to start.

@keyanwb
Copy link

keyanwb commented Jun 7, 2018

@ensecoz Could you please share your ldap configurations which worked ? I am trying with my settings it doesn't work.

@ensecoz
Copy link
Contributor Author

ensecoz commented Jun 11, 2018

  • ensecoz.local is my domain
  • serviceUser is service user who has permission to query the AD
  • my setting is for AD so I have to put active_directory_domain
  • user_dn_pattern -> i think you can ignore this. I try to change this but nothing happen
para.security.ldap.server_url = "ldap://ensecoz.local:8389/"
para.security.ldap.base_dn = "DC=ensecoz,DC=local"
para.security.ldap.bind_dn = "CN=serviceUser,OU=Resources,DC=ensecoz,DC=local"
para.security.ldap.bind_pass = "xxxxyyyyzzzz"
para.security.ldap.user_search_base = "OU=Offices,DC=ensecoz,DC=local"
para.security.ldap.user_search_filter = "(&(objectClass=user)(sAMAccountName={1}))"
para.security.ldap.user_dn_pattern = "mail={0}"
para.security.ldap.password_attribute = ""
# set this only if you are connecting to Active Directory
para.security.ldap.active_directory_domain = "ensecoz.local"

@albogdano
Copy link
Member

albogdano commented Jun 11, 2018 via email

@weisjohn
Copy link
Contributor

I spent basically the whole day trying to make this work, because I didn't think to be watching the LDAP logs, but here's the config that works for me:

para.security.ldap.server_url = "ldap://ldaphost:389/"
para.security.ldap.base_dn = "DC=mycompany,DC=com"
para.security.ldap.bind_dn = "CN=admin,DC=mycompany,DC=com"
para.security.ldap.bind_pass = "1234"
para.security.ldap.user_search_base = "OU=people"
para.security.ldap.user_search_filter = "cn={0}"
para.security.ldap.user_dn_pattern = "CN={0},OU=people"

@weisjohn
Copy link
Contributor

NOTE: Based on observing the logs in my OpenLDAP instance, I don't believe the bind_dn and bind_pass settings are used when a user is successfully logging in. And I don't think they're being honored at all anyways, but I'm not positive about that.

@weisjohn
Copy link
Contributor

Also, I should not that these settings should be specified on the Scoold config, not the para config

@NaanProphet
Copy link

@weisjohn I too was initially having trouble binding to an LDAP server that didn't allow unauthenticated requests. I recreated the issue by spun up OpenDJ locally, had it create 2000 dummy users, and disable anonymous binds (special thanks to Mark Craig]:

$ dsconfig -p 4444 -h `hostname` -D "cn=Directory Manager" -w password set-global-configuration-prop -X -n --set reject-unauthenticated-requests:true
$ ldapsearch -p 389 -b dc=example,dc=com uid=dummyuser
SEARCH operation failed
Result Code:  53 (Unwilling to Perform)
Additional Information:  Rejecting the requested operation  because the connection has not been authenticated

This helped me recreate the issue locally, and by playing around with the LDAP config, I was able to get Scoold to bind and authenticate to LDAP just fine.

+1 in that the LDAP settings should be specified in Scoold config and not Para config too.

@tongueisthirsty
Copy link

@weisjohn @albogdano I'm also seeing an issue where bind_dn and bind_pass are not being picked up. The configs are set in both scoold and para. I verified this by using para-cli and checking the app-settings; if necessary I used the /v1/_settings endpoint to set the configs manually. (I seemed to had to do this for the root app (app:para)

When I try to login to Scoold or hit Para's ldap_auth handler directly Para is trying to bind with the user/pass I'm trying to login as, not the bind_dn or bind_pass.

@albogdano
Copy link
Member

albogdano commented Jan 29, 2019

@tongueisthirsty configuration for the root app is loaded from application.conf only, never from the object itself (i.e. /v1/settings is useless here). For normal (child) apps configuration is loaded from the app object or via /v1/settings. I hope this clarifies things...

This is the reason why I don't recommend using the root app for Scoold because it uses /v1/settings to modify the configuration stored on the backend.

@tongueisthirsty
Copy link

@albogdano I really appreciate the quick response and the extra clarity. Knowing that Im going to change some things around and run some more tests.

However, I'd like to point out that I've been running PUTs against /v1/settings using the app:para signature and modifying the settings there - and then seeing those changes take place in my network caps (i'm using network caps to see how the requests are being built and sent to my ldap server so I can more intelligently make config changes).

@tongueisthirsty
Copy link

tongueisthirsty commented Jan 29, 2019

@albogdano I'm still seeing the same issue. Whatever ID I'm trying to login with is the ID used in the initial bind request - I would expect the bind_dn to be used here, but maybe I'm misunderstanding.

When I try to log in via the Scoold UI I see two bindrequests:
The first one is trying to bind with "cn="
The second one is trying to bind with "<ROOT>"

Here is an example config I have in both the para applications.conf and the scoold applications.conf:

para.security.ldap.bind_dn = "uid=scooldBindID,ou=United States,ou=Bind IDs,o=Org One"
para.security.ldap.user_dn_pattern = "cn={0}"
para.security.ldap.bind_pass = "xxxxxxxxxx"
para.security.ldap.server_url = "ldap://ldap.host.com:389/"

Can you clarify the /ldap_auth?username=un&password=pw handler? Is 'un' and 'pw' supposed to be the BIND ID/PW? Or the ID of the login object?

@albogdano
Copy link
Member

@tongueisthirsty The username and password in /ldap_auth?username=un&password=pw are different from bind_dn and bind_pass. So the bind_dn and bind_pass are not sent through the /ldap_auth filter. That filter is for receiving the login credentials of the actual Scoold user.

I recommend that you have a separate app for Scoold only and you put the LDAP configuration settings inside application.conf for Scoold only.

Here's an example working config for the ForumSys LDAP test server:

para.security.ldap.server_url = "ldap://ldap.forumsys.com:389"
para.security.ldap.base_dn = "dc=example,dc=com"
para.security.ldap.bind_dn = "cn=read-only-admin,dc=example,dc=com"
para.security.ldap.bind_pass = "password"
para.security.ldap.user_search_base = "ou=mathematicians,dc=example,dc=com"
para.security.ldap.user_search_filter = "(cn={0})"
para.security.ldap.user_dn_pattern = "uid={0}"
para.security.ldap.password_attribute = "userPassword"

@tongueisthirsty
Copy link

Gotcha @albogdano. I have a separate app for Scoold already (app:scoold) but I've been putting the configurations in both Scoold and Para. I'll make the appropriate changes and review the example you provided. Thank you.

@albogdano
Copy link
Member

@tongueisthirsty Basically each time you edit application.conf you should restart Scoold and on startup it will send the updated config settings back to Para where they are stored inside the app object with id app:scoold.

@tongueisthirsty
Copy link

@albogdano Do you have a suggestion on how I can configure Scoold so LDAP BINDS are only done via the ID? eg. no dn, no dc=springframework,dc=org, no uid={0},ou=people, no (cn={0}).

All I need passed to my LDAP server is the ID being logged in with however there doesn't appear to be a set of configurations to do that.

@albogdano
Copy link
Member

@tongueisthirsty Have you tried leaving those properties blank, i.e. para.security.ldap.base_dn = ""?

@tongueisthirsty
Copy link

@albogdano I apologize for being MIA for a couple days. I have tried that. That is when I start getting <ROOT> showing up in the network trace. I'm going to dig more into that as well as other ways to BIND (eg. full DN).

@bviktor
Copy link

bviktor commented Feb 13, 2019

I'm having a hard time configuring scoold for AD auth using the UPN. This one works for me:

para.security.ldap.user_search_filter = "(&(objectClass=user)(sAMAccountName={1}))"

As you probably guessed, this lets you in with user.name. But I want to log in with the full email address. So I tried with

para.security.ldap.user_search_filter = "(&(objectClass=user)(userPrincipalName={0}))"
para.security.ldap.user_search_filter = "(&(objectClass=user)(userPrincipalName={1}))"
para.security.ldap.user_search_filter = "(&(objectClass=user)(mail={0}))"
para.security.ldap.user_search_filter = "(&(objectClass=user)(mail={1}))"

None of them works. I'm afraid the @ might not be escaped correctly somehwere. Any ideas?

For the record, here's everything LDAP-related:

para.security.ldap.server_url = "ldap://dc2.ad.foobar.com:389/"
para.security.ldap.base_dn = "DC=ad,DC=foobar,DC=com"
para.security.ldap.bind_dn = "CN=ldap,OU=Helpers,OU=Foobar,DC=ad,DC=foobar,DC=com"
para.security.ldap.bind_pass = "***"
para.security.ldap.user_search_base = "OU=Users,OU=Foobar,DC=ad,DC=foobar,DC=com"
# WORKS para.security.ldap.user_search_filter = "(&(objectClass=user)(sAMAccountName={1}))"
para.security.ldap.user_search_filter = "(&(objectClass=user)(userPrincipalName={0}))"
#para.security.ldap.user_dn_pattern = "uid={0},ou=people"
#para.security.ldap.password_attribute = "userPassword"
# set this only if you are connecting to Active Directory
para.security.ldap.active_directory_domain = "ad.foobar.com"

@albogdano
Copy link
Member

albogdano commented Feb 14, 2019 via email

@bviktor
Copy link

bviktor commented Feb 14, 2019

We use LDAP auth happily with dozens of other services. No offense, but if I had to guess where the problem is, I think it's far more probable it's in Scoold/Para, and not in Spring. Yes, you "only" pass the parameters, and it's exactly the passing itself that always causes problems if escapes are not proper. But anyhow, I no longer think that improper escapes are the issue here.

Here's some tests:

test case bind dn's upn suffix user_search_filter active_directory_domain mail upn suffix result
1 / [email protected] ad.fooworks.com (&(objectClass=user)(userPrincipalName={0})) ad.fooworks.com [email protected] ad.fooworks.com OK
2 / [email protected] ad.fooworks.com (&(objectClass=user)(userPrincipalName={0})) ad.fooworks.com [email protected] foomotive.com Caused by: org.springframework.security.core.userdetails.UsernameNotFoundException: User [email protected] not found in directory.
2 / [email protected] ad.fooworks.com (&(objectClass=user)(userPrincipalName={0})) ad.fooworks.com [email protected] foomotive.com Caused by: org.springframework.security.ldap.authentication.ad.ActiveDirectoryAuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C09042A, comment: AcceptSecurityContext error, data 52e, v3839 ] at org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider.raiseExceptionForErrorCode(ActiveDirectoryLdapAuthenticationProvider.java:263) ... 90 common frames omitted Caused by: javax.naming.AuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C09042A, comment: AcceptSecurityContext error, data 52e, v3839 ]
3 / [email protected] ad.fooworks.com (&(objectClass=user)(userPrincipalName={0})) ad.fooworks.com (empty) ad.fooworks.com The AD doesn't have email attribute. Instead, it uses domain name for email address: [email protected]@ad.fooworks.com. errors: ['email' Please provide a valid email address; 'email' Please provide a valid email address].
4 / [email protected] ad.fooworks.com (&(objectClass=user)(userPrincipalName={1})) ad.fooworks.com (empty) ad.fooworks.com The AD doesn't have email attribute. Instead, it uses domain name for email address: [email protected]@ad.fooworks.com. errors: ['email' Please provide a valid email address; 'email' Please provide a valid email address].
5 / [email protected] ad.fooworks.com (&(objectClass=user)(userPrincipalName={0})) ad.fooworks.com [email protected] ad.fooworks.com OK
5 / [email protected] ad.fooworks.com (&(objectClass=user)(userPrincipalName={0})) ad.fooworks.com [email protected] ad.fooworks.com Caused by: org.springframework.security.ldap.authentication.ad.ActiveDirectoryAuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C09042A, comment: AcceptSecurityContext error, data 52e, v3839 ] at org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider.raiseExceptionForErrorCode(ActiveDirectoryLdapAuthenticationProvider.java:263) ... 90 common frames omitted Caused by: javax.naming.AuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C09042A, comment: AcceptSecurityContext error, data 52e, v3839 ]
6 / [email protected] ad.fooworks.com (&(objectClass=user)(userPrincipalName={0})) ad.fooworks.com [email protected] foomotive.com Caused by: org.springframework.security.core.userdetails.UsernameNotFoundException: User [email protected] not found in directory.
6 / [email protected] ad.fooworks.com (&(objectClass=user)(userPrincipalName={0})) ad.fooworks.com [email protected] foomotive.com Caused by: org.springframework.security.ldap.authentication.ad.ActiveDirectoryAuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C09042A, comment: AcceptSecurityContext error, data 52e, v3839 ] at org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider.raiseExceptionForErrorCode(ActiveDirectoryLdapAuthenticationProvider.java:263) ... 90 common frames omitted Caused by: javax.naming.AuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C09042A, comment: AcceptSecurityContext error, data 52e, v3839 ]
7 / [email protected] ad.fooworks.com (&(objectClass=user)(userPrincipalName={0})) foomotive.com [email protected] foomotive.com Caused by: org.springframework.security.ldap.authentication.ad.ActiveDirectoryAuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C09042A, comment: AcceptSecurityContext error, data 52e, v3839 ] at org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider.raiseExceptionForErrorCode(ActiveDirectoryLdapAuthenticationProvider.java:263) ... 90 common frames omitted Caused by: javax.naming.AuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C09042A, comment: AcceptSecurityContext error, data 52e, v3839 ]
7 / [email protected] ad.fooworks.com (&(objectClass=user)(userPrincipalName={0})) foomotive.com [email protected] foomotive.com Caused by: org.springframework.security.core.userdetails.UsernameNotFoundException: User [email protected] not found in directory.
8 / [email protected] foomotive.com (&(objectClass=user)(userPrincipalName={0})) foomotive.com [email protected] foomotive.com Caused by: org.springframework.security.ldap.authentication.ad.ActiveDirectoryAuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C09042A, comment: AcceptSecurityContext error, data 52e, v3839 ] at org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider.raiseExceptionForErrorCode(ActiveDirectoryLdapAuthenticationProvider.java:263) ... 90 common frames omitted Caused by: javax.naming.AuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C09042A, comment: AcceptSecurityContext error, data 52e, v3839 ]
8 / [email protected] foomotive.com (&(objectClass=user)(userPrincipalName={0})) foomotive.com [email protected] foomotive.com Caused by: org.springframework.security.core.userdetails.UsernameNotFoundException: User [email protected] not found in directory.
9 / [email protected] foomotive.com (&(objectClass=user)(userPrincipalName={0})) ad.fooworks.com [email protected] foomotive.com Caused by: org.springframework.security.core.userdetails.UsernameNotFoundException: User [email protected] not found in directory.
9 / [email protected] foomotive.com (&(objectClass=user)(userPrincipalName={0})) ad.fooworks.com [email protected] foomotive.com Caused by: org.springframework.security.ldap.authentication.ad.ActiveDirectoryAuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C09042A, comment: AcceptSecurityContext error, data 52e, v3839 ] at org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider.raiseExceptionForErrorCode(ActiveDirectoryLdapAuthenticationProvider.java:263) ... 90 common frames omitted Caused by: javax.naming.AuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C09042A, comment: AcceptSecurityContext error, data 52e, v3839 ]

Case 4 doesn't even make any sense to me, since userPrincipalName={1} is supposed to trim the domain part:

The syntax for this allows either {0} (replaced with username@domain) or {1} (replaced with username only).

What's "domain" anyway? Is this the AD domain from settings? Is this the UPN suffix of the AD object? Or the part after @ in the login form? Or... ?

To me it seems there's at least 2 unjustified assumptions made in the code:

  • AD domain name is the same as the user's UPN suffix: wrong, the UPN suffix can be anything you want
  • the bind DN's UPN suffix is the same as that of the user trying to log in: again, wrong, you can specify the UPN suffix per-user

In our case, the domain name is ad.fooworks.com, the (non-test) bind DN's UPN suffix is ad.fooworks.com and it doesn't have an email address. Regular users' UPN suffix is foomotive.com. The mail attribute is also set to [email protected]. No, we're not gonna change them, and this is the case for pretty much any company that uses Office 365 since AD Connect relies on the UPN being the same as the O365 email address, and the number of AD DCs being authoritative DNS servers for the company's main domain (i.e. where they send mails from) should be around zero.

In other words, UPN/mail and AD domain name usually have nothing in common.

@albogdano
Copy link
Member

albogdano commented Feb 14, 2019

@bviktor I understand that you're having difficulties with the configuration but I'm not sure what I'm supposed to do about that. Have a look at line 57 in this source file ActiveDirectoryLdapAuthenticationProvider.java

If your users try to login with [email protected] then your the "domain" in question is "fooworks.com". Otherwise, if users login with just their username, e.g. user, then the code will try to guess the domain and will append the already assigned AD domain, which in your case is ad.fooworks.com. That's my understanding of the situation.

@bviktor
Copy link

bviktor commented Feb 14, 2019

If your users try to login with [email protected] then your the "domain" in question is "fooworks.com".

Yes. Yours. Not AD's. And that's why authentication fails with correct credentials. Plus the bind DN is a completely different entity, yet you try to auth them both with the same logic.

Also, the code "guesses" incorrectly, since it appends the AD domain even if the user enters an email address for login, but doesn't have a mail attribute. But does have a UPN.

The list goes on. Neither do I know what else to say to you. I made it crystal clear what the problem is. Auth fails, not because the credentials are bad, but because bind and lookup have incorrect logic. I even explained what the issue with the logic is.

Redmine, Jenkins, GitLab, Nexus, SVN, PostgreSQL, Cisco ASA, just to name a few. They all work correctly via LDAP, with the mail attribute, with a different domain than the AD domain. And Scoold doesn't. But according to you, the obvious conclusion to this is that our setup is bad, and Scoold is fine. Okay...

You know, I would happily accept an answer like "I don't care". Seriously, that'd be way better, coz then at least I wouldn't have spent around 6 hours so far testing this app inside out to demonstrate how broken it is. All in vain. You keep saying everything's fine, even though it clearly isn't fine at all. It's broken. Have you ever tested it in the mentioned setup? If not, why do you insist the issue is in my setup, or in Spring (used by millions), or anywhere else but in the front- and backend written by you? I mean that's quite some self-confidence for sure.

I've spent the better part of this week showing other professional programmers how to fix their code which I actually paid for, so that's hard to top, but this one's surely a close second.


If anyone ever figured out how to make users log in via LDAP with email login, please let me know. Until then, no Scoold for us.

@albogdano
Copy link
Member

@bviktor There's no "logic" in Scoold. Para handles LDAP requests and it has no logic either. Login credentials are forwarded to ActiveDirectoryLdapAuthenticationProvider which is part of Spring LDAP. I don't know what you're talking about here. If you point out to me the exact thing that needs fixing, I'd be happy to fix it.

I'm not saying it's your fault. I'm just saying it's very likely that your LDAP configuration needs to be tweaked because I've talked to other people about the same thing and eventually they all managed to get it working. I understand your frustration but please, let's try to keep the discussion positive and constructive. If you find a bug - I'll fix it. That's about all I can do for now.

@albogdano
Copy link
Member

@bviktor BTW, are you by any chance testing against https://paraio.com? I'm asking because EC2 crashed without notice and the service was down for a while.

albogdano added a commit to Erudika/para that referenced this issue Mar 2, 2020
…ogins would fail if the UPN suffix is different than the given domain, related to Erudika/scoold#67
@albogdano
Copy link
Member

@bviktor I think I finally fixed your issue after 1 year 😄 Just to clarify a few things:

  1. para.security.ldap.bind_dn is ignored for Active Directory auth requests
  2. para.security.ldap.active_directory_domain is in fact the UPN suffix - the bit that gets appended after @ if you just try to login with your username
  3. the default search filter for AD is (&(objectClass=user)(userPrincipalName={0})) which is not going to work for some people so, as you mentioned previously, they should use:
para.security.ldap.user_search_filter = "(&(objectClass=user)(sAMAccountName={1}))"
  1. the configuration properties for AD are only user_search_filter, base_dn, server_url and active_directory_domain - everything else is ignored so don't put it in the config file at all!

Here's a working LDAP configuration for AD:

para.security.ldap.user_search_filter = "(&(objectClass=user)(sAMAccountName={1}))"
para.security.ldap.base_dn = "ou=dev,dc=erudika,dc=com"
para.security.ldap.server_url = "ldap://192.168.123.70:389"
para.security.ldap.active_directory_domain = "erudika.com"

For the above configuration the following logins should work, given that a user joe exists:

As you can see the domain part is actually ignored because it is irrelevant. You cannot bind an AD user with their email. You can bind them based on their username a.k.a. sAMAccountName. If the user has an email address where the alias is the same as the sAMAccountName but the domain is different, then the login will succeed. If the user above has an email [email protected] then the login with that email will fail because a bind is not possible, and the LDAP search request will return no results.

@mljohns89
Copy link

@albogdano I am not sure if your com.erudika.para.server.security.LDAPAuthenticator class supports manager authentication or not. Meaning the ldap server requires authentication to even allow querying.

I made a small spring boot app to test authentication against the ldap server. This is the minimum configuration I needed to get it working. I will try and "translate" this to what you are doing in para LDAPAuthenticator, but wondering if this is something you've ran into before or have advice in the meantime?

@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
     auth.ldapAuthentication()
          .userSearchFilter("(blah)")
          .contextSource()
          .url("ldap://ldap.host.com:3456/DC=company,DC=com")
          //This is the critical config required
          .managerDn("username")
          .managerPassword("password");
}

I've only spent like 1-2 hrs getting this sample working and haven't looked too much into how/if it translates to bindDn/bindPassword. But hoping you might know something I don't? Thanks in advanced!

@albogdano
Copy link
Member

@mljohns89 Not really but you can configure LDAP authentication with a few system properties in Scoold.
What are you trying to integrate with Scoold and LDAP exactly? Scoold makes LDAP auth requests to Para which calls the LDAP server in turn.

@mljohns89
Copy link

mljohns89 commented Mar 31, 2024 via email

@mljohns89
Copy link

@albogdano I figured it out. I copied your LDAPAuthenticator and LDAPAuthenticationProvider into my code and played around until I got it working!

I will upload a gist or something and share with you. Essentially, I needed the userSearchBase to evaluate to null

@mljohns89
Copy link

mljohns89 commented Apr 1, 2024

@albogdano Disregard my previous comment...I must have done something dumb and got a false positive.
The issue is here: https://github.com/Erudika/para/blob/master/para-server/src/main/java/com/erudika/para/server/security/LDAPAuthenticator.java#L65

When the LDAP Server requires Authentication to query, I essentially need to pass the bindDn and bindPass in an AuthenticationSource wrapper.

I did a side-by-side with how Spring Security handles LDAP out of the box. What do you think about adding this nested class to LDAPAuthenticator?

class SimpleAuthenticationSource implements AuthenticationSource {

     private final String bindDn;
     private final String bindPass;

     SimpleAuthenticationSource(String bindDn, String bindPass) {
         this.bindDn = bindDn;
         this.bindPass = bindPass;
    }

   public String getPrincipal() {
         return this.bindDn;
   }
   public String getCredentials() {
         return this.bindPass;
   }
}

And then we would have to modify LDAPAuthenticator: https://github.com/Erudika/para/blob/master/para-server/src/main/java/com/erudika/para/server/security/LDAPAuthenticator.java#L65

if(bindAuthenticationRequired) {
   contextSource.setAuthenticationSource(new SimpleAuthenticationSource(bindDn, bindPass));
} else {
   contextSource.setAuthenticationSource(new SpringSecurityAuthenticationSource());
}

We'd have to add a new config property too: bindAuthenticationRequired

Let me know your thoughts and if you want me to open a PR

@mljohns89
Copy link

mljohns89 commented Apr 1, 2024

@albogdano OK for real now I found the problem!

You need to add a call to contextSource.afterPropertiesSet() in LDAPAuthenticator.

If you compare to the Jenkins LDAP Plugin (they are also using Spring LDAP library): https://github.com/jenkinsci/ldap-plugin/blob/master/src/main/java/jenkins/security/plugins/ldap/LDAPConfiguration.java#L613

I tested this out locally and it will setup the required Bind Authentication Context in Para.

Here's the full LDAPAuthenticator class:

/*
 * Copyright 2013-2022 Erudika. http://erudika.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * For issues and patches go to: https://github.com/erudika
 */
package com.erudika.para.server.security;

import com.erudika.para.core.utils.Utils;
import java.util.Arrays;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.authentication.AbstractLdapAuthenticator;
import org.springframework.security.ldap.authentication.BindAuthenticator;
import org.springframework.security.ldap.authentication.LdapAuthenticator;
import org.springframework.security.ldap.authentication.PasswordComparisonAuthenticator;
import org.springframework.security.ldap.authentication.SpringSecurityAuthenticationSource;
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
import org.springframework.security.ldap.search.LdapUserSearch;

/**
 * LDAP authenticator for either bind-based or password comparison authentication.
 * @author Alex Bogdanovski [[email protected]]
 */
public final class LDAPAuthenticator implements LdapAuthenticator {

	private static final Logger logger = LoggerFactory.getLogger(LDAPAuthenticator.class);
	private AbstractLdapAuthenticator authenticator = null;

	/**
	 * Default constructor.
	 * @param ldapSettings LDAP config map for an app
	 */
	public LDAPAuthenticator(Map<String, String> ldapSettings) {
		if (ldapSettings != null && ldapSettings.containsKey("security.ldap.server_url")) {
			String serverUrl = ldapSettings.get("security.ldap.server_url");
			String baseDN = ldapSettings.get("security.ldap.base_dn");
			String bindDN = Utils.noSpaces(ldapSettings.get("security.ldap.bind_dn"), "%20");
			String bindPass = ldapSettings.get("security.ldap.bind_pass");
			String userSearchBase = ldapSettings.get("security.ldap.user_search_base");
			String userSearchFilter = ldapSettings.get("security.ldap.user_search_filter");
			String userDnPattern = ldapSettings.get("security.ldap.user_dn_pattern");
			String passAttribute = ldapSettings.get("security.ldap.password_attribute");
			boolean usePasswordComparison = ldapSettings.containsKey("security.ldap.compare_passwords");

			DefaultSpringSecurityContextSource contextSource =
					new DefaultSpringSecurityContextSource(Arrays.asList(serverUrl), baseDN);
			contextSource.setAuthenticationSource(new SpringSecurityAuthenticationSource());
			contextSource.setCacheEnvironmentProperties(false);
			if (!bindDN.isEmpty()) {
				// this is usually not required for authentication - leave blank
				contextSource.setUserDn(bindDN);
			}
			if (!bindPass.isEmpty()) {
				// this is usually not required for authentication - leave blank
				contextSource.setPassword(bindPass);
			}
                         contextSource.afterPropertiesSet();  //THIS IS THE NEW LINE
			LdapUserSearch userSearch = new FilterBasedLdapUserSearch(userSearchBase, userSearchFilter, contextSource);

			if (usePasswordComparison) {
				PasswordComparisonAuthenticator p = new PasswordComparisonAuthenticator(contextSource);
				p.setPasswordAttributeName(passAttribute);
				p.setUserDnPatterns(getUserDnPatterns(userDnPattern));
				p.setUserSearch(userSearch);
				authenticator = p;
			} else {
				BindAuthenticator b = new BindAuthenticator(contextSource);
				b.setUserDnPatterns(getUserDnPatterns(userDnPattern));
				b.setUserSearch(userSearch);
				authenticator = b;
			}
		}
	}

	@Override
	public DirContextOperations authenticate(Authentication authentication) {
		try {
			if (authenticator != null) {
				return authenticator.authenticate(authentication);
			}
		} catch (Exception e) {
			logger.warn("Failed to authenticate user with LDAP server: {}", e.getMessage());
		}
		throw new AuthenticationServiceException("LDAP user not found.");
	}

	private String[] getUserDnPatterns(String userDnPattern) {
		if (StringUtils.isBlank(userDnPattern)) {
			return new String[]{""};
		}
		if (userDnPattern.contains("|")) {
			return userDnPattern.split("\\|");
		}
		return new String[]{userDnPattern};
	}
}

@mljohns89
Copy link

@albogdano PR: Erudika/para#259

@mljohns89
Copy link

@albogdano Another PR: Erudika/para#266

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

8 participants