-
Notifications
You must be signed in to change notification settings - Fork 3k
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
LDAP Group Provider Support #8335
Conversation
Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: U-SLT\cjmakli.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I flagged a couple of minor things, but overall, lgtm!
{ | ||
this.client = requireNonNull(client, "client is null"); | ||
|
||
this.userBindSearchPatterns = ldapConfig.getUserBindSearchPatterns(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we perform a requireNonNull() check on ldapConfig before reaching this point? Or are we just letting the NPE happen in that case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe it's unnecessary because unlike the client, we're not keeping a reference, and we check the values afterwards.
} | ||
} | ||
} | ||
search.close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: This probably doesn't need to be closed explicitly, since it's managed by the try-with-resources.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated
Typo in PR description: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems good. In the PR description it'd be useful to provide a sentence or two about the proposed code changes in general terms (e.g. new classes, name changes, moving shared methods out LdapUtil
etc)
ImmutableSet.Builder<String> groupNames = ImmutableSet.builder(); | ||
if (search.hasMore()) { | ||
Attributes attributes = search.next().getAttributes(); | ||
Attribute memberOfAttribute = attributes.get("memberof"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is memberof
case sensitive? Elsewhere, on lines 124 and 151 you use memberOf
.
Also, could make this string a constant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For whatever reason, memberof
has to have a lowercase 'O' on line 122, and uppercase on line 151. If you don't it returns null.
@@ -49,16 +54,16 @@ | |||
import static javax.naming.Context.SECURITY_CREDENTIALS; | |||
import static javax.naming.Context.SECURITY_PRINCIPAL; | |||
|
|||
public class JdkLdapAuthenticatorClient | |||
implements LdapAuthenticatorClient | |||
public class JdkLdapClient |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason for renaming the class? Could add to PR description.
@@ -17,7 +17,7 @@ | |||
|
|||
import java.util.Set; | |||
|
|||
public interface LdapAuthenticatorClient | |||
public interface LdapClient |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason for renaming the interface? Could add to PR description.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added an explanation in the PR description. Essentially, Authenticator is a type of Trino plugin, but we're re-purposing the client for Group Provider plugin as well.
Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: U-SLT\cjmakli.
|
31115c8
to
3c43117
Compare
Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: U-SLT\cjmakli.
|
3c43117
to
8a77991
Compare
Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: U-SLT\cjmakli.
|
8a77991
to
01bce45
Compare
Updated |
Any chance to help in this PR to have it merged asap? |
throw new RuntimeException("Authentication error"); | ||
} | ||
} | ||
return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should return an empty set
return null; | |
return ImmutableSet.of(); |
@@ -110,17 +107,17 @@ public Principal createAuthenticatedPrincipal(String user, String password) | |||
private Principal authenticateWithUserBind(Credential credential) | |||
{ | |||
String user = credential.getUser(); | |||
if (containsSpecialCharacters(user)) { | |||
if (LdapUtil.containsSpecialCharacters(user)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use static imports from LdapUtil.
@@ -106,11 +111,44 @@ public boolean isGroupMember(String searchBase, String groupSearch, String conte | |||
} | |||
} | |||
|
|||
@Override | |||
public Set<String> lookupUserGroups(String searchBase, String searchFilter, String contextUserDistinguishedName, String contextPassword) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Would it be possible to avoid two LDAP queries while logging in and getting user groups in a separate call? Logging in is simply checking if a context can be created, and a similar context is later used for getting groups.
binder -> { | ||
configBinder(binder).bindConfig(LdapConfig.class); | ||
binder.bind(LdapGroupProvider.class).in(Scopes.SINGLETON); | ||
binder.bind(LdapClient.class).to(JdkLdapClient.class).in(Scopes.SINGLETON); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won't this fail at runtime if LdapClient
interface was already bound in LdapAuthenticatorFactory
? Maybe we need some @ForAuthentication
and @ForGroupProvider
annotations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is in separate guice context, so I think it is not an issue.
import java.util.Optional; | ||
import java.util.Set; | ||
|
||
public class TestLdapClient |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This mock LDAP implementation is great for unit testing, but it would be nice to have tests with an actual LDAP server. I don't think you would have detected the "memberof"
/"memberOf"
issue using only these tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Notice that we have SinglenodeLdap
or SinglenodeLdapBindDn
environments already created.
Basically to create product tests, you need to add a test io.trino.tests.product.jdbc.TestLdapTrinoJdbc
and add to the product test environment configuration for LDAP group provider.
@Override | ||
public String getName() | ||
{ | ||
return "ldap"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Starburst already has a group provider called "ldap", and this will cause conflicts while parsing configuration, since their config properties are different. Maybe use "ldap-group-provider", "ldap-query" or "ldap-trino"?
@@ -17,6 +17,7 @@ | |||
import io.trino.plugin.password.file.FileAuthenticatorFactory; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please improve commit message:
Introduce LDAP group provider
@@ -49,16 +54,16 @@ | |||
import static javax.naming.Context.SECURITY_CREDENTIALS; | |||
import static javax.naming.Context.SECURITY_PRINCIPAL; | |||
|
|||
public class JdkLdapAuthenticatorClient | |||
implements LdapAuthenticatorClient | |||
public class JdkLdapClient |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please follow: https://github.com/trinodb/trino/blob/master/DEVELOPMENT.md#git-merge-strategy
Mechanical changes (like refactoring and renaming) should be separated from logical and functional changes.
Please extract class rename into a separate commit.
Attributes attributes = search.next().getAttributes(); | ||
Attribute memberOfAttribute = attributes.get("memberof"); | ||
if (memberOfAttribute == null) { | ||
log.error("No memberOf attribute found... The ldap group provider requires the memberOf overlay to be enabled."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should fail instead of silenlty ignore this problem. It will some time to users to understand why ldap group provider does not return groups. Throwing an exception here could make the realize the problem sooner.
@@ -144,7 +141,7 @@ private Principal authenticateWithUserBind(Credential credential) | |||
private Principal authenticateWithBindDistinguishedName(Credential credential) | |||
{ | |||
String user = credential.getUser(); | |||
if (containsSpecialCharacters(user)) { | |||
if (LdapUtil.containsSpecialCharacters(user)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extraction of LdapUtil
can go as separate commit too.
@Override | ||
public Set<String> getGroups(String user) | ||
{ | ||
if (LdapUtil.containsSpecialCharacters(user)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
static import
String userDistinguishedName = LdapUtil.replaceUser(userBindSearchPattern, user); | ||
String searchBase = userBaseDistinguishedName.orElseThrow(); | ||
try { | ||
return client.lookupUserGroups(searchBase, userDistinguishedName, bindDistinguishedName.orElseThrow(), bindPassword.orElseThrow()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are breaking a loop in first iteration and so we are ignoring rest of userBindSearchPatterns
. Is this intentional? If so, why? Can you please add a comemnt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reminder to keep #8134 in mind. All patterns must be tried and only the last AccessDeniedException must be bubbled up.
binder -> { | ||
configBinder(binder).bindConfig(LdapConfig.class); | ||
binder.bind(LdapGroupProvider.class).in(Scopes.SINGLETON); | ||
binder.bind(LdapClient.class).to(JdkLdapClient.class).in(Scopes.SINGLETON); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is in separate guice context, so I think it is not an issue.
|
||
import com.google.common.base.CharMatcher; | ||
|
||
public class LdapUtil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
final
private static final CharMatcher SPECIAL_CHARACTERS = CharMatcher.anyOf(",=+<>#;*()\"\\\u0000"); | ||
private static final CharMatcher WHITESPACE = CharMatcher.anyOf(" \r"); | ||
|
||
private LdapUtil() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private LdapUtil() {}
import java.util.Optional; | ||
import java.util.Set; | ||
|
||
public class TestLdapClient |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Notice that we have SinglenodeLdap
or SinglenodeLdapBindDn
environments already created.
Basically to create product tests, you need to add a test io.trino.tests.product.jdbc.TestLdapTrinoJdbc
and add to the product test environment configuration for LDAP group provider.
@coryjmaklin how can we help to get this merged? |
@coryjmaklin Would you mind if we would take over this pull request? |
Sorry. I don't have time to work on it at present. If you want to take it over, by all means. |
@MiguelWeezardo @kokosing Let me know how can I help in this. I'm very interested to see this merged. |
@rimolive Thank you ❤️ Please open a new pull request based on this one with all the comments addressed (applied or responded). Please make sure to mark @coryjmaklin as a co-author of the commit that you are goin to create. Together with @MiguelWeezardo we will be happy to do a review and merge the code eventually. |
Anyone knows why am I getting this error in trino-main tests?
I'm using OpenJDK 11.0.13 to run maven build |
Looks like your JDK knows of a zone which isn't yet supported by joda-time version Trino uses.
|
@kokosing @MiguelWeezardo Please check updated PR: #10116 |
This PR adds the code necessary to reference LDAP groups in Trino ACLs.
Context
We're using file based ACLs to authorize users connected to Trino.
https://trino.io/docs/current/security/file-system-access-control.html
In the documentation, it's mentioned "For group-based rules to match, users need to be assigned to groups by a Group provider."
In other words, we have to create a group provider to specify the following:
There exists a trino-password-authenticators plugin that takes care of authenticating users using their LDAP credentials.
https://github.com/trinodb/trino/tree/master/plugin/trino-password-authenticators/src/main/java/io/trino/plugin/password/ldap
In the same plugin, there's already have a file based Group Provider.
https://github.com/trinodb/trino/blob/master/plugin/trino-password-authenticators/src/main/java/io/trino/plugin/password/PasswordAuthenticatorPlugin.java
There are open issues asking to add this as a feature.
#6824
#2919
Implementation
cn
) because it's required by the definitionTesting
Configure the memberOf overlay in your LDAP instance
Create a group-provider.properties with the following properties: