Closed ensecoz closed 5 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".
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"
...
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.
@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
@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.
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.
@ensecoz Could you please share your ldap configurations which worked ? I am trying with my settings it doesn't work.
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"
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 .
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"
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.
Also, I should not that these settings should be specified on the Scoold config, not the para config
@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.
@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.
@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.
@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).
@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=
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?
@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"
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.
@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
.
@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.
@tongueisthirsty Have you tried leaving those properties blank, i.e. para.security.ldap.base_dn = ""
?
@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).
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"
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 .
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 | 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.
@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.
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.
@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.
@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.
@bviktor I think I finally fixed your issue after 1 year :smile: Just to clarify a few things:
para.security.ldap.bind_dn
is ignored for Active Directory auth requestspara.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
(&(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}))"
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:
joe@erudika.com
+ passwordjoe@some-other-domain.com
+ passwordjoe
+ passwordAs 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.
@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!
@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.
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: @.***>
@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
@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
@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};
}
}
@albogdano PR: https://github.com/Erudika/para/pull/259
@albogdano Another PR: https://github.com/Erudika/para/pull/266
I'm setting up server like this:
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.