Skip to content

Commit c83d8dc

Browse files
committed
Implement LDAP + StartTLS in CMAK
Signed-off-by: Parth Kolekar <[email protected]>
1 parent 8e16403 commit c83d8dc

File tree

3 files changed

+63
-18
lines changed

3 files changed

+63
-18
lines changed

README.md

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -134,20 +134,29 @@ LDAP support is through the basic authentication filter.
134134
- basicAuthentication.iv="some-hex-string-representing-byte-array"
135135
- basicAuthentication.secret="my-secret-string"
136136

137-
3. Configure LDAP/LDAPS authentication
137+
3. Configure LDAP / LDAP + StartTLS / LDAPS authentication
138+
139+
_Note: LDAP is unencrypted and insecure. LDAPS is a commonly implemented
140+
extension that implements an encryption layer in a manner similar to how
141+
HTTPS adds encryption to an HTTP. LDAPS has not been documented, and the
142+
specification is not formally defined anywhere. LDAP + StartTLS is the
143+
currently recommended way to start an encrypted channel, and it upgrades
144+
an existing LDAP connection to achieve this encryption._
145+
138146
- basicAuthentication.ldap.enabled=< Boolean flag to enable/disable ldap authentication >
139-
- basicAuthentication.ldap.server=< fqdn of LDAP server>
140-
- basicAuthentication.ldap.port=< port of LDAP server>
141-
- basicAuthentication.ldap.username=< LDAP search username>
142-
- basicAuthentication.ldap.password=< LDAP search password>
143-
- basicAuthentication.ldap.search-base-dn=< LDAP search base>
144-
- basicAuthentication.ldap.search-filter=< LDAP search filter>
145-
- basicAuthentication.ldap.connection-pool-size=< number of connection to LDAP server>
146-
- basicAuthentication.ldap.ssl=< Boolean flag to enable/disable LDAPS>
147+
- basicAuthentication.ldap.server=< fqdn of LDAP server >
148+
- basicAuthentication.ldap.port=< port of LDAP server (typically 389 for LDAP and LDAP + StartTLS and typically 636 for LDAPS) >
149+
- basicAuthentication.ldap.username=< LDAP search username >
150+
- basicAuthentication.ldap.password=< LDAP search password >
151+
- basicAuthentication.ldap.search-base-dn=< LDAP search base >
152+
- basicAuthentication.ldap.search-filter=< LDAP search filter >
153+
- basicAuthentication.ldap.connection-pool-size=< maximum number of connection to LDAP server >
154+
- basicAuthentication.ldap.ssl=< Boolean flag to enable/disable LDAPS (usually incompatible with StartTLS) >
155+
- basicAuthentication.ldap.starttls=< Boolean flat to enable StartTLS (usually incompatible with SSL) >
147156

148157
4. (Optional) Limit access to a specific LDAP Group
149-
- basicAuthentication.ldap.group-filter=< LDAP group filter>
150-
- basicAuthentication.ldap.ssl-trust-all=< Boolean flag to allow non-expired invalid certificates>
158+
- basicAuthentication.ldap.group-filter=< LDAP group filter >
159+
- basicAuthentication.ldap.ssl-trust-all=< Boolean flag to allow non-expired invalid certificates >
151160

152161
#### Example (Online LDAP Test Server):
153162

@@ -162,6 +171,7 @@ LDAP support is through the basic authentication filter.
162171
- basicAuthentication.ldap.connection-pool-size=10
163172
- basicAuthentication.ldap.ssl=false
164173
- basicAuthentication.ldap.ssl-trust-all=false
174+
- basicAuthetication.ldap.starttls=false
165175

166176

167177
Deployment

app/controllers/BasicAuthenticationFilter.scala

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ package controllers
44
import java.nio.charset.StandardCharsets
55
import java.security.SecureRandom
66
import java.util.UUID
7-
87
import akka.stream.Materializer
98
import com.typesafe.config.ConfigValueType
109
import com.unboundid.ldap.sdk._
10+
import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest
1111
import com.unboundid.util.ssl.{SSLUtil, TrustAllTrustManager}
1212
import grizzled.slf4j.Logging
13+
1314
import javax.crypto.Mac
1415
import javax.net.ssl
1516
import org.apache.commons.codec.binary.Base64
@@ -143,19 +144,49 @@ case class LDAPAuthenticator(config: LDAPAuthenticationConfig)(implicit val mat:
143144
private lazy val unauthorizedResult = Future successful Unauthorized.withHeaders(WWW_AUTHENTICATE -> realm)
144145
private lazy val ldapConnectionPool: LDAPConnectionPool = {
145146
val (address, port) = (config.address, config.port)
147+
148+
if (config.sslEnabled && config.startTLSEnabled) {
149+
logger.error("SSL and StartTLS enabled together. Most LDAP Server implementations will not handle this as it initializes an encrypted context over an already encrypted channel")
150+
}
151+
146152
val connection = if (config.sslEnabled) {
147153
if (config.sslTrustAll) {
148154
val sslUtil = new SSLUtil(null, new TrustAllTrustManager(true))
149155
val sslSocketFactory = sslUtil.createSSLSocketFactory
150-
new LDAPConnection(sslSocketFactory, address, port, config.username, config.password)
156+
new LDAPConnection(sslSocketFactory, address, port)
151157
} else {
152158
val sslSocketFactory = ssl.SSLSocketFactory.getDefault
153-
new LDAPConnection(sslSocketFactory, address, port, config.username, config.password)
159+
new LDAPConnection(sslSocketFactory, address, port)
154160
}
155161
} else {
156-
new LDAPConnection(address, port, config.username, config.password)
162+
new LDAPConnection(address, port)
163+
}
164+
165+
var startTLSPostConnectProcessor : StartTLSPostConnectProcessor = null
166+
if (config.startTLSEnabled) {
167+
if (config.sslTrustAll) {
168+
val sslUtil = new SSLUtil(null, new TrustAllTrustManager(true))
169+
val sslContext = sslUtil.createSSLContext
170+
connection.processExtendedOperation(new StartTLSExtendedRequest(sslContext))
171+
startTLSPostConnectProcessor = new StartTLSPostConnectProcessor(sslContext)
172+
} else {
173+
val sslContext = new SSLUtil().createSSLContext
174+
connection.processExtendedOperation(new StartTLSExtendedRequest(sslContext))
175+
startTLSPostConnectProcessor = new StartTLSPostConnectProcessor(sslContext)
176+
}
177+
}
178+
179+
try {
180+
connection.bind(config.username, config.password)
181+
} catch {
182+
case e: LDAPException => {
183+
connection.setDisconnectInfo(DisconnectType.BIND_FAILED, null, e)
184+
connection.close()
185+
logger.error(s"Bind failed with ldap server ${config.address}:${config.port}", e)
186+
}
157187
}
158-
new LDAPConnectionPool(connection, config.connectionPoolSize)
188+
189+
new LDAPConnectionPool(connection, 1, config.connectionPoolSize, startTLSPostConnectProcessor)
159190
}
160191

161192
def salt: Array[Byte] = config.salt
@@ -275,7 +306,8 @@ case class LDAPAuthenticationConfig(salt: Array[Byte]
275306
, groupFilter: String
276307
, connectionPoolSize: Int
277308
, sslEnabled: Boolean
278-
, sslTrustAll: Boolean) extends AuthenticationConfig
309+
, sslTrustAll: Boolean
310+
, startTLSEnabled: Boolean) extends AuthenticationConfig
279311

280312
sealed trait AuthType[T <: AuthenticationConfig] {
281313
def getConfig(config: AuthenticationConfig): T
@@ -357,13 +389,14 @@ object BasicAuthenticationFilterConfiguration {
357389
val connectionPoolSize = int("ldap.connection-pool-size").getOrElse(10)
358390
val sslEnabled = boolean("ldap.ssl").getOrElse(false)
359391
val sslTrustAll = boolean("ldap.ssl-trust-all").getOrElse(false)
392+
val startTLSEnabled = boolean("ldap.starttls").getOrElse(false)
360393

361394
BasicAuthenticationFilterConfiguration(
362395
enabled,
363396
LDAPAuth,
364397
LDAPAuthenticationConfig(salt, iv, secret,
365398
string("realm").getOrElse(defaultRealm),
366-
server, port, username, password, searchDN, searchFilter, groupFilter, connectionPoolSize, sslEnabled, sslTrustAll
399+
server, port, username, password, searchDN, searchFilter, groupFilter, connectionPoolSize, sslEnabled, sslTrustAll, startTLSEnabled
367400
),
368401
excluded
369402
)

conf/application.conf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ basicAuthentication.ldap.ssl=false
6464
basicAuthentication.ldap.ssl=${?KAFKA_MANAGER_LDAP_SSL}
6565
basicAuthentication.ldap.ssl-trust-all=false
6666
basicAuthentication.ldap.ssl-trust-all=${?KAFKA_MANAGER_LDAP_SSL_TRUST_ALL}
67+
basicAuthentication.ldap.starttls=false
68+
basicAuthentication.ldap.starttls=${?KAFKA_MANAGER_LDAP_STARTTLS}
6769

6870
basicAuthentication.username="admin"
6971
basicAuthentication.username=${?KAFKA_MANAGER_USERNAME}

0 commit comments

Comments
 (0)