Erudika / scoold

A Stack Overflow clone for teams (self-hosted or hosted)
https://scoold.com
Apache License 2.0
863 stars 239 forks source link

para LDAP config is not picked up correctly #67

Closed ensecoz closed 5 years ago

ensecoz commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

@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 commented 6 years ago

@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 commented 6 years ago

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 commented 6 years ago

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

ensecoz commented 6 years ago
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 commented 6 years ago

I don't have a working LDAP config for AD. Just remove most of the properties and only keep the URL and search filter properties.

On Mon, 11 Jun 2018, 06:31 ensecoz, notifications@github.com wrote:

  • 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=Service Accounts,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"

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Erudika/scoold/issues/67#issuecomment-396113262, or mute the thread https://github.com/notifications/unsubscribe-auth/AAVK-Vmae90Ygv_P1ceo_KfKnkMUqG-Oks5t7eSmgaJpZM4UaXwI .

weisjohn commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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

NaanProphet commented 6 years ago

@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 commented 5 years ago

@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 commented 5 years ago

@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 commented 5 years ago

@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 commented 5 years ago

@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 commented 5 years ago

@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 commented 5 years ago

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 commented 5 years ago

@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 commented 5 years ago

@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 commented 5 years ago

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

tongueisthirsty commented 5 years ago

@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 commented 5 years ago

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 commented 5 years ago

I'm not sure if I can help here. All LDAP settings are passes to Spring LDAP directly and should work for all LDAP servers. The problem is probably related to a misconfiguration of some other LDAP property.

On Wed, 13 Feb 2019, 22:09 bviktor <notifications@github.com wrote:

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?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Erudika/scoold/issues/67#issuecomment-463349032, or mute the thread https://github.com/notifications/unsubscribe-auth/AAVK-czxgdTlgz5bR71FQ3KOnIUg1YXjks5vNHDlgaJpZM4UaXwI .

bviktor commented 5 years ago

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 / testuser@ad.fooworks.com ad.fooworks.com (&(objectClass=user)(userPrincipalName={0})) ad.fooworks.com testuser@ad.fooworks.com ad.fooworks.com OK
2 / testuser@ad.fooworks.com ad.fooworks.com (&(objectClass=user)(userPrincipalName={0})) ad.fooworks.com testuser@ad.fooworks.com foomotive.com Caused by: org.springframework.security.core.userdetails.UsernameNotFoundException: User testuser@ad.fooworks.com not found in directory.
2 / testuser@foomotive.com ad.fooworks.com (&(objectClass=user)(userPrincipalName={0})) ad.fooworks.com testuser@ad.fooworks.com 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 / testuser@ad.fooworks.com 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: testuser@ad.fooworks.com@ad.fooworks.com. errors: ['email' Please provide a valid email address; 'email' Please provide a valid email address].
4 / testuser@ad.fooworks.com 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: testuser@ad.fooworks.com@ad.fooworks.com. errors: ['email' Please provide a valid email address; 'email' Please provide a valid email address].
5 / testuser@ad.fooworks.com ad.fooworks.com (&(objectClass=user)(userPrincipalName={0})) ad.fooworks.com testuser@foomotive.com ad.fooworks.com OK
5 / testuser@foomotive.com ad.fooworks.com (&(objectClass=user)(userPrincipalName={0})) ad.fooworks.com testuser@foomotive.com 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 / testuser@ad.fooworks.com ad.fooworks.com (&(objectClass=user)(userPrincipalName={0})) ad.fooworks.com testuser@foomotive.com foomotive.com Caused by: org.springframework.security.core.userdetails.UsernameNotFoundException: User testuser@ad.fooworks.com not found in directory.
6 / testuser@foomotive.com ad.fooworks.com (&(objectClass=user)(userPrincipalName={0})) ad.fooworks.com testuser@foomotive.com 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 / testuser@ad.fooworks.com ad.fooworks.com (&(objectClass=user)(userPrincipalName={0})) foomotive.com testuser@foomotive.com 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 / testuser@foomotive.com ad.fooworks.com (&(objectClass=user)(userPrincipalName={0})) foomotive.com testuser@foomotive.com foomotive.com Caused by: org.springframework.security.core.userdetails.UsernameNotFoundException: User testuser@foomotive.com not found in directory.
8 / testuser@ad.fooworks.com foomotive.com (&(objectClass=user)(userPrincipalName={0})) foomotive.com testuser@foomotive.com 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 / testuser@foomotive.com foomotive.com (&(objectClass=user)(userPrincipalName={0})) foomotive.com testuser@foomotive.com foomotive.com Caused by: org.springframework.security.core.userdetails.UsernameNotFoundException: User testuser@foomotive.com not found in directory.
9 / testuser@ad.fooworks.com foomotive.com (&(objectClass=user)(userPrincipalName={0})) ad.fooworks.com testuser@foomotive.com foomotive.com Caused by: org.springframework.security.core.userdetails.UsernameNotFoundException: User testuser@ad.fooworks.com not found in directory.
9 / testuser@foomotive.com foomotive.com (&(objectClass=user)(userPrincipalName={0})) ad.fooworks.com testuser@foomotive.com 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:

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 user.name@foomotive.com. 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 commented 5 years ago

@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 user@fooworks.com 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 commented 5 years ago

If your users try to login with user@fooworks.com 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 commented 5 years ago

@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 commented 5 years ago

@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 commented 4 years ago

@bviktor I think I finally fixed your issue after 1 year :smile: 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}))"
  4. 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 joe.smith@gmail.com then the login with that email will fail because a bind is not possible, and the LDAP search request will return no results.

mljohns89 commented 7 months ago

@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 commented 7 months ago

@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 commented 7 months ago

My ldap server requires authentication to query. So in this scenario, Para is the ldap client. Para needs to authenticate to the ldap server using the managerDn and managerPassword config in my snippet.

I am trying to figure out how (or if) Para has support for this?

In your testing, did you only try connecting to an ldap server with anonymous authentication?

On Sun, Mar 31, 2024, 3:06 PM Alex Bogdanovski @.***> wrote:

@mljohns89 https://github.com/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.

— Reply to this email directly, view it on GitHub https://github.com/Erudika/scoold/issues/67#issuecomment-2028879069, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABO5DKA7EEWVTOHWXUBWXJLY3BNEBAVCNFSM4FDJPQEKU5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TEMBSHA4DOOJQGY4Q . You are receiving this because you were mentioned.Message ID: @.***>

mljohns89 commented 7 months ago

@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 commented 7 months ago

@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 commented 7 months ago

@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 [alex@erudika.com]
 */
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 commented 7 months ago

@albogdano PR: https://github.com/Erudika/para/pull/259

mljohns89 commented 7 months ago

@albogdano Another PR: https://github.com/Erudika/para/pull/266