Skip to content

Commit 52e3f44

Browse files
committed
refactoring of plugin
ldap context is created every login fixed caching of ldap users
1 parent d54c629 commit 52e3f44

File tree

57 files changed

+1214
-705
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+1214
-705
lines changed

README.adoc

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -75,22 +75,27 @@ The content of the configuration file is as follows:
7575
|service_password
7676
|Service user password
7777

78-
|ldap_naming_attribute
79-
|Use this setting if you need to change your naming attribute from the usual cn=
78+
|filter_template
79+
|template for searching in LDAP, explanation further in this readme, defaults to `(cn=%s)`
8080

8181
|auth_cache_enabled
8282
|relevant for Cassandra 3.11 and 4.0 plugins, defaults to `false`
8383

84+
|consistency_for_role
85+
|consistency level to use for retrieval of a role to check if it can log in - defaults to LOCAL_ONE
86+
8487
|auth_bcrypt_gensalt_log2_rounds
8588
|number of rounds to hash passwords
89+
90+
|load_ldap_service
91+
|defaults to false, if it is true, SPI mechanism will look on class path to load custom implementation of `LDAPUserRetriever`.
8692
|===
8793

8894

8995
## Configuration of Cassandra
9096

9197
If is *strongly* recommended to use `NetworkTopologyStrategy` for your `system_auth keyspace`.
9298

93-
9499
Please be sure that `system_auth` keyspace uses `NetworkTopologyStrategy` with number of replicas equal to number of nodes in DC. If it is not
95100
the case, you can alter your keyspace as follows:
96101

@@ -145,7 +150,15 @@ For fast testing there is Debian OpenLDAP Docker container
145150
The `ldap.configuration` file in the `conf` directory does not need to be changed, and with the above `docker run` it will work out of the box. You just
146151
have to put it in `$CASSANDRA_CONF` or set respective configuration property as described above.
147152

148-
## How It Works
153+
## Explanation of filter_template property
154+
155+
`filter_template` property is by default `(cn=%s)` where `%s` will be replaced by name you want to log in with.
156+
For example if you do `cqlsh -u myuserinldap`, a search filter for LDAP will be `(cn=myuserinldap)`. You
157+
may have a different search filter based on your need, a lot of people use e.g. SAM or something similar.
158+
If you try to log in with `cqlsh -u cn=myuserinldap`, there will be no replacement done and this will be
159+
used as a search filter instead.
160+
161+
## How it Works
149162

150163
LDAPAuthenticator currently supports plain text authorization requests only in the form of a username and password.
151164
This request is made to the LDAP server over plain text, so you should be using client encryption on the Cassandra
@@ -165,14 +178,8 @@ the operator still has a possibility of logging in via `cassandra` user as usual
165178
Users meant to be authenticated against the LDAP server will not be able to log in but all "normal" users will be able to
166179
login and the disruption of LDAP communication will not affect their ability to do so as they live in Cassandra natively.
167180

168-
In case there are two logins of same name (e.g. `admin` in LDAP and `admin` in C*),
169-
in order to distinguish them, if you want to login with LDAP user, you have to
170-
specify its full account name, e.g.
171-
172-
cqlsh -u cn=admin,dn=example,dn=org
173-
174-
In case a user specifies just `admin` as login name (or any other name, for that matter), it will try to
175-
authenticate against database first and if not successful against LDAP, adding all details (cn= etc. ...) to username automatically.
181+
In case a user specifies just `test` as login name (or any other name, for that matter), it will try to
182+
authenticate against database first and if not successful against LDAP using filter `filter_template` which defaults to `(cn=%s)`
176183

177184
It is possible to delete administration role (e.g. role `cassandra`) but if one does that, all administration operations are only able to
178185
be done via LDAP account. In case LDAP is down, the operator would not have any control over DB as `cassandra` is not present anymore.
@@ -184,21 +191,27 @@ If you delete `cassandra` user, there is suddenly not such user. You have to res
184191

185192
Where `dba` is _new_ superuser which is able to write to `system_auth.roles` and acts as Cassandra admin.
186193

194+
Upon login via LDAP user, this plugin will create a dummy role just to be able to play as a normal Cassandra role
195+
with all its permissions and so on. Passwords for LDAP users are not stored in Cassandra, obviously.
196+
197+
Credentials are cached for implementations for Cassandra 3.11 and 4.0 so that way we are not hitting LDAP server
198+
all the time when there is a lot of login attempts with same login name. An administrator can increase
199+
relevant validity settings in `cassandra.yaml` to increase these periods even more.
200+
187201
## SPI for LDAP server implementations (advanced)
188202

189203
In order to talk to a LDAP server, there is `DefaultLDAPServer` class in `base` module which all modules are using.
190204
However, it might not be enough - there is a lot of LDAP servers out there and their internals and configuration
191205
might render the default implementation incompatible. If you have special requirements, you might provide your
192-
own implementation by extending `DefaultLDAPServer` and overriding what is necessary. You might as well
193-
extend and implement `LDAPPasswordRetriever` class. `DefaultLDAPServer` just extends it.
206+
own implementation by implementing `LDAPUserRetriever`. You have to have `load_ldap_service` set to `true` as well.
194207

195208
To tell LDAP plugin to use your implementation, you need to create a file in `src/main/resources/META-INF/services`
196-
called `com.instaclustr.cassandra.ldap.auth.LDAPPasswordRetriever` and the content of that file needs to
209+
called `LDAPUserRetriever` and the content of that file needs to
197210
be just one line - the fully qualified class name (with package) of your custom implementation.
198211

199212
After you build such plugin, the SPI mechanism upon plugin's initialisation during Cassandra node startup
200213
will pick up your custom LDAP server connection / authentication logic.
201214

202215
## Further Information
203-
- See blog by Kurt Greaves https://www.instaclustr.com/apache-cassandra-ldap-authentication/[Apache Cassandra LDAP Authentication]
204-
- Please see https://www.instaclustr.com/support/documentation/announcements/instaclustr-open-source-project-status/ for Instaclustr support status of this project
216+
- See blog by Stefan Miklosovic about https://www.instaclustr.com/the-instaclustr-ldap-plugin-for-cassandra/[Apache Cassandra LDAP Authentication]
217+
- Please see https://www.instaclustr.com/support/documentation/announcements/instaclustr-open-source-project-status/[Instaclustr support status] of this project

base/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
-->
1717

1818
<artifactId>cassandra-ldap-base</artifactId>
19-
<version>1.0.2</version>
19+
<version>1.1.0</version>
2020

2121
<name>Cassandra LDAP Authenticator common code</name>
2222
<description>Common code for Apache Cassandra LDAP plugin</description>

base/src/main/java/com/instaclustr/cassandra/ldap/User.java

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,16 @@
1717
*/
1818
package com.instaclustr.cassandra.ldap;
1919

20+
import java.util.StringJoiner;
21+
2022
import org.apache.commons.lang3.builder.HashCodeBuilder;
2123

2224
public class User
2325
{
2426

25-
private final String username;
27+
private String username;
2628

27-
private final String password;
29+
private String password;
2830

2931
private String ldapDN = null;
3032

@@ -44,6 +46,14 @@ public User(String username, String password)
4446
this.password = password;
4547
}
4648

49+
public void setUsername(final String username) {
50+
this.username = username;
51+
}
52+
53+
public void setPassword(final String password) {
54+
this.password = password;
55+
}
56+
4757
public String getLdapDN()
4858
{
4959
return ldapDN;
@@ -83,11 +93,36 @@ public boolean equals(Object obj)
8393

8494
final User other = (User) obj;
8595

86-
return this.getUsername().equals(other.getUsername());
96+
if (this.ldapDN != null && other.ldapDN != null)
97+
{
98+
return this.ldapDN.equals(other.ldapDN);
99+
}
100+
else if (this.username != null && other.username != null)
101+
{
102+
return this.username.equals(other.username);
103+
}
104+
105+
return false;
87106
}
88107

89108
public int hashCode()
90109
{
91-
return new HashCodeBuilder(19, 29).append(getUsername()).toHashCode();
110+
if (ldapDN != null)
111+
{
112+
return new HashCodeBuilder(19, 29).append(ldapDN).toHashCode();
113+
}
114+
115+
assert username != null;
116+
117+
return new HashCodeBuilder(19, 29).append(username).toHashCode();
118+
}
119+
120+
@Override
121+
public String toString() {
122+
return new StringJoiner(", ", User.class.getSimpleName() + "[", "]")
123+
.add("username='" + username + "'")
124+
.add("password=redacted")
125+
.add("ldapDN='" + ldapDN + "'")
126+
.toString();
92127
}
93128
}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@
3232
import org.slf4j.Logger;
3333
import org.slf4j.LoggerFactory;
3434

35-
public abstract class AbstractCassandraRolePasswordRetriever implements CassandraPasswordRetriever
35+
public abstract class AbstractCassandraUserRetriever implements CassandraUserRetriever
3636
{
3737

38-
private static final Logger logger = LoggerFactory.getLogger(AbstractCassandraRolePasswordRetriever.class);
38+
private static final Logger logger = LoggerFactory.getLogger(AbstractCassandraUserRetriever.class);
3939

4040
protected static final String LEGACY_CREDENTIALS_TABLE = "credentials";
4141
protected static final String AUTH_KEYSPACE = "system_auth";
@@ -47,7 +47,7 @@ public abstract class AbstractCassandraRolePasswordRetriever implements Cassandr
4747
protected boolean legacyTableExists;
4848

4949
@Override
50-
public String retrieveHashedPassword(User user)
50+
public User retrieve(User user)
5151
{
5252
try
5353
{
@@ -67,7 +67,7 @@ public String retrieveHashedPassword(User user)
6767
throw new NoSuchCredentialsException();
6868
}
6969

70-
return result.one().getString("salted_hash");
70+
return new User(user.getUsername(), result.one().getString("salted_hash"));
7171
} catch (NoSuchRoleException ex)
7272
{
7373
logger.trace(format("User %s does not exist in the Cassandra database.", user.getUsername()));

base/src/main/java/com/instaclustr/cassandra/ldap/auth/CassandraPasswordRetriever.java

Lines changed: 0 additions & 6 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
package com.instaclustr.cassandra.ldap.auth;
22

3-
import com.instaclustr.cassandra.ldap.User;
43
import org.apache.cassandra.exceptions.ConfigurationException;
54
import org.apache.cassandra.service.ClientState;
65

7-
public interface PasswordRetriever
6+
public interface CassandraUserRetriever extends UserRetriever
87
{
9-
108
void init(ClientState clientState) throws ConfigurationException;
119

12-
String retrieveHashedPassword(User user);
1310
}

0 commit comments

Comments
 (0)