Skip to content

Commit

Permalink
Merge pull request #837 from ParthKolekar/parthkolekar-allow-starttls
Browse files Browse the repository at this point in the history
Implement LDAP + StartTLS in CMAK
  • Loading branch information
patelh authored Dec 12, 2022
2 parents 65b0b11 + c83d8dc commit 30abfde
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 18 deletions.
32 changes: 21 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,20 +134,29 @@ LDAP support is through the basic authentication filter.
- basicAuthentication.iv="some-hex-string-representing-byte-array"
- basicAuthentication.secret="my-secret-string"

3. Configure LDAP/LDAPS authentication
3. Configure LDAP / LDAP + StartTLS / LDAPS authentication

_Note: LDAP is unencrypted and insecure. LDAPS is a commonly implemented
extension that implements an encryption layer in a manner similar to how
HTTPS adds encryption to an HTTP. LDAPS has not been documented, and the
specification is not formally defined anywhere. LDAP + StartTLS is the
currently recommended way to start an encrypted channel, and it upgrades
an existing LDAP connection to achieve this encryption._

- basicAuthentication.ldap.enabled=< Boolean flag to enable/disable ldap authentication >
- basicAuthentication.ldap.server=< fqdn of LDAP server>
- basicAuthentication.ldap.port=< port of LDAP server>
- basicAuthentication.ldap.username=< LDAP search username>
- basicAuthentication.ldap.password=< LDAP search password>
- basicAuthentication.ldap.search-base-dn=< LDAP search base>
- basicAuthentication.ldap.search-filter=< LDAP search filter>
- basicAuthentication.ldap.connection-pool-size=< number of connection to LDAP server>
- basicAuthentication.ldap.ssl=< Boolean flag to enable/disable LDAPS>
- basicAuthentication.ldap.server=< fqdn of LDAP server >
- basicAuthentication.ldap.port=< port of LDAP server (typically 389 for LDAP and LDAP + StartTLS and typically 636 for LDAPS) >
- basicAuthentication.ldap.username=< LDAP search username >
- basicAuthentication.ldap.password=< LDAP search password >
- basicAuthentication.ldap.search-base-dn=< LDAP search base >
- basicAuthentication.ldap.search-filter=< LDAP search filter >
- basicAuthentication.ldap.connection-pool-size=< maximum number of connection to LDAP server >
- basicAuthentication.ldap.ssl=< Boolean flag to enable/disable LDAPS (usually incompatible with StartTLS) >
- basicAuthentication.ldap.starttls=< Boolean flat to enable StartTLS (usually incompatible with SSL) >

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

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

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


Deployment
Expand Down
47 changes: 40 additions & 7 deletions app/controllers/BasicAuthenticationFilter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ package controllers
import java.nio.charset.StandardCharsets
import java.security.SecureRandom
import java.util.UUID

import akka.stream.Materializer
import com.typesafe.config.ConfigValueType
import com.unboundid.ldap.sdk._
import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest
import com.unboundid.util.ssl.{SSLUtil, TrustAllTrustManager}
import grizzled.slf4j.Logging

import javax.crypto.Mac
import javax.net.ssl
import org.apache.commons.codec.binary.Base64
Expand Down Expand Up @@ -143,19 +144,49 @@ case class LDAPAuthenticator(config: LDAPAuthenticationConfig)(implicit val mat:
private lazy val unauthorizedResult = Future successful Unauthorized.withHeaders(WWW_AUTHENTICATE -> realm)
private lazy val ldapConnectionPool: LDAPConnectionPool = {
val (address, port) = (config.address, config.port)

if (config.sslEnabled && config.startTLSEnabled) {
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")
}

val connection = if (config.sslEnabled) {
if (config.sslTrustAll) {
val sslUtil = new SSLUtil(null, new TrustAllTrustManager(true))
val sslSocketFactory = sslUtil.createSSLSocketFactory
new LDAPConnection(sslSocketFactory, address, port, config.username, config.password)
new LDAPConnection(sslSocketFactory, address, port)
} else {
val sslSocketFactory = ssl.SSLSocketFactory.getDefault
new LDAPConnection(sslSocketFactory, address, port, config.username, config.password)
new LDAPConnection(sslSocketFactory, address, port)
}
} else {
new LDAPConnection(address, port, config.username, config.password)
new LDAPConnection(address, port)
}

var startTLSPostConnectProcessor : StartTLSPostConnectProcessor = null
if (config.startTLSEnabled) {
if (config.sslTrustAll) {
val sslUtil = new SSLUtil(null, new TrustAllTrustManager(true))
val sslContext = sslUtil.createSSLContext
connection.processExtendedOperation(new StartTLSExtendedRequest(sslContext))
startTLSPostConnectProcessor = new StartTLSPostConnectProcessor(sslContext)
} else {
val sslContext = new SSLUtil().createSSLContext
connection.processExtendedOperation(new StartTLSExtendedRequest(sslContext))
startTLSPostConnectProcessor = new StartTLSPostConnectProcessor(sslContext)
}
}

try {
connection.bind(config.username, config.password)
} catch {
case e: LDAPException => {
connection.setDisconnectInfo(DisconnectType.BIND_FAILED, null, e)
connection.close()
logger.error(s"Bind failed with ldap server ${config.address}:${config.port}", e)
}
}
new LDAPConnectionPool(connection, config.connectionPoolSize)

new LDAPConnectionPool(connection, 1, config.connectionPoolSize, startTLSPostConnectProcessor)
}

def salt: Array[Byte] = config.salt
Expand Down Expand Up @@ -275,7 +306,8 @@ case class LDAPAuthenticationConfig(salt: Array[Byte]
, groupFilter: String
, connectionPoolSize: Int
, sslEnabled: Boolean
, sslTrustAll: Boolean) extends AuthenticationConfig
, sslTrustAll: Boolean
, startTLSEnabled: Boolean) extends AuthenticationConfig

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

BasicAuthenticationFilterConfiguration(
enabled,
LDAPAuth,
LDAPAuthenticationConfig(salt, iv, secret,
string("realm").getOrElse(defaultRealm),
server, port, username, password, searchDN, searchFilter, groupFilter, connectionPoolSize, sslEnabled, sslTrustAll
server, port, username, password, searchDN, searchFilter, groupFilter, connectionPoolSize, sslEnabled, sslTrustAll, startTLSEnabled
),
excluded
)
Expand Down
2 changes: 2 additions & 0 deletions conf/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ basicAuthentication.ldap.ssl=false
basicAuthentication.ldap.ssl=${?KAFKA_MANAGER_LDAP_SSL}
basicAuthentication.ldap.ssl-trust-all=false
basicAuthentication.ldap.ssl-trust-all=${?KAFKA_MANAGER_LDAP_SSL_TRUST_ALL}
basicAuthentication.ldap.starttls=false
basicAuthentication.ldap.starttls=${?KAFKA_MANAGER_LDAP_STARTTLS}

basicAuthentication.username="admin"
basicAuthentication.username=${?KAFKA_MANAGER_USERNAME}
Expand Down

0 comments on commit 30abfde

Please sign in to comment.