From dd07d055fbf59649b6b7a1c113bebda659a412af Mon Sep 17 00:00:00 2001 From: "Francois @fanf42 Armand" Date: Fri, 25 Oct 2024 18:51:39 +0200 Subject: [PATCH] Fixes #25742: Add LDAP synchronous mode config param --- .../resources/configuration.properties.sample | 4 + .../bootstrap/liftweb/RudderConfig.scala | 17 +- .../InMemoryDsConnectionProvider.scala | 8 +- .../ldap/sdk/LDAPConnectionProvider.scala | 151 ++++++++++-------- 4 files changed, 112 insertions(+), 68 deletions(-) diff --git a/webapp/sources/rudder/rudder-web/src/main/resources/configuration.properties.sample b/webapp/sources/rudder/rudder-web/src/main/resources/configuration.properties.sample index a5bd6c7eacb..7405069b0cd 100644 --- a/webapp/sources/rudder/rudder-web/src/main/resources/configuration.properties.sample +++ b/webapp/sources/rudder/rudder-web/src/main/resources/configuration.properties.sample @@ -87,6 +87,10 @@ ldap.port=389 ldap.authdn=cn=manager,cn=rudder-configuration ldap.maxPoolSize=2 +# By default, Rudder use a synchronous mode for LDAP connection. Before 8.1.8, it used to be async. +# You can revert to async by using 'false' +ldap.useSynchronousMode = true + # # Password used to connect to the OpenLDAP server. # On a standard Rudder installation, the password is managed in diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/RudderConfig.scala b/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/RudderConfig.scala index bc1e7644375..8886cfe5154 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/RudderConfig.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/RudderConfig.scala @@ -467,6 +467,17 @@ object RudderParsedProperties { 2 } } + val LDAP_USE_SYNC_MODE: Boolean = { + try { + config.getBoolean("ldap.useSynchronousMode") + } catch { + case ex: ConfigException => + ApplicationLogger.info( + "Property 'ldap.useSynchronousMode' is missing or empty in rudder.configFile. Default to true." + ) + true + } + } val LDAP_CACHE_NODE_INFO_MIN_INTERVAL: Duration = { val x = { try { @@ -2559,7 +2570,8 @@ object RudderConfigInit { port = LDAP_PORT, authDn = LDAP_AUTHDN, authPw = LDAP_AUTHPW, - poolSize = LDAP_MAX_POOL_SIZE + poolSize = LDAP_MAX_POOL_SIZE, + useSynchronousMode = LDAP_USE_SYNC_MODE ) } lazy val roLDAPConnectionProvider = roLdap @@ -2569,7 +2581,8 @@ object RudderConfigInit { port = LDAP_PORT, authDn = LDAP_AUTHDN, authPw = LDAP_AUTHPW, - poolSize = LDAP_MAX_POOL_SIZE + poolSize = LDAP_MAX_POOL_SIZE, + useSynchronousMode = LDAP_USE_SYNC_MODE ) } diff --git a/webapp/sources/scala-ldap/src/main/scala/com/normation/ldap/listener/InMemoryDsConnectionProvider.scala b/webapp/sources/scala-ldap/src/main/scala/com/normation/ldap/listener/InMemoryDsConnectionProvider.scala index ae8541eb44e..d0e9fd7f444 100644 --- a/webapp/sources/scala-ldap/src/main/scala/com/normation/ldap/listener/InMemoryDsConnectionProvider.scala +++ b/webapp/sources/scala-ldap/src/main/scala/com/normation/ldap/listener/InMemoryDsConnectionProvider.scala @@ -26,6 +26,7 @@ import com.normation.ldap.sdk.syntax.UnboundidLDAPConnection import com.normation.zio.* import com.unboundid.ldap.listener.InMemoryDirectoryServer import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig +import com.unboundid.ldap.sdk.LDAPConnectionOptions import com.unboundid.ldap.sdk.schema.Schema import zio.* @@ -53,9 +54,10 @@ class InMemoryDsConnectionProvider[CON <: RoLDAPConnection]( bootstrapLDIFPaths foreach { path => server.importFromLDIF(false, path) } server.startListening - override def toConnectionString: String = "in-memory-ldap-connection" - override def semaphore: Semaphore = ZioRuntime.unsafeRun(Semaphore.make(1)) - override val connection: Ref[Option[CON]] = ZioRuntime.unsafeRun(Ref.make(Option.empty[CON])) + override def toConnectionString: String = "in-memory-ldap-connection" + override def semaphore: Semaphore = ZioRuntime.unsafeRun(Semaphore.make(1)) + override def customizeOption: LDAPConnectionOptions => LDAPConnectionOptions = identity + override val connection: Ref[Option[CON]] = ZioRuntime.unsafeRun(Ref.make(Option.empty[CON])) override def newUnboundidConnection: UnboundidLDAPConnection = server.getConnection diff --git a/webapp/sources/scala-ldap/src/main/scala/com/normation/ldap/sdk/LDAPConnectionProvider.scala b/webapp/sources/scala-ldap/src/main/scala/com/normation/ldap/sdk/LDAPConnectionProvider.scala index a1568493707..b3f0692e065 100644 --- a/webapp/sources/scala-ldap/src/main/scala/com/normation/ldap/sdk/LDAPConnectionProvider.scala +++ b/webapp/sources/scala-ldap/src/main/scala/com/normation/ldap/sdk/LDAPConnectionProvider.scala @@ -129,13 +129,14 @@ trait LDAPConnectionProvider[LDAP <: RoLDAPConnection] { * */ trait UnboundidConnectionProvider { + def customizeOption: LDAPConnectionOptions => LDAPConnectionOptions // for performance reason, this can't be wrapped in ZIO def newUnboundidConnection: UnboundidLDAPConnection def toConnectionString: String } object RudderLDAPConnectionOptions { - def apply(useSchemaInfos: Boolean): LDAPConnectionOptions = { + def apply(useSchemaInfos: Boolean, useSynchMode: Boolean): LDAPConnectionOptions = { val options = new LDAPConnectionOptions options.setUseSchema(useSchemaInfos) // In Rudder, some entries can grow quite big, see: https://www.rudder-project.org/redmine/issues/13256 @@ -145,18 +146,20 @@ object RudderLDAPConnectionOptions { if (options.getMaxMessageSize() == 20971520) { options.setMaxMessageSize(Int.MaxValue) } + options.setUseSynchronousMode(useSynchMode) options } } trait AnonymousConnection extends UnboundidConnectionProvider { - def host: String - def port: Int - def useSchemaInfos: Boolean + def host: String + def port: Int + def useSchemaInfos: Boolean + def useSynchronousMode: Boolean override def newUnboundidConnection: LDAPConnection = { - new UnboundidLDAPConnection(RudderLDAPConnectionOptions(useSchemaInfos), host, port) + new UnboundidLDAPConnection(RudderLDAPConnectionOptions(useSchemaInfos, useSynchronousMode), host, port) } override def toConnectionString: String = s"anonymous@ldap://${host}:${port}" @@ -167,14 +170,16 @@ trait AnonymousConnection extends UnboundidConnectionProvider { * use a simple login/password authentication to the server. */ trait SimpleAuthConnection extends UnboundidConnectionProvider { - def authDn: String - def authPw: String - def host: String - def port: Int - def useSchemaInfos: Boolean + def authDn: String + def authPw: String + def host: String + def port: Int + def useSchemaInfos: Boolean + def useSynchronousMode: Boolean override def newUnboundidConnection: LDAPConnection = { - new UnboundidLDAPConnection(RudderLDAPConnectionOptions(useSchemaInfos), host, port, authDn, authPw) + val options = customizeOption(RudderLDAPConnectionOptions(useSchemaInfos, useSynchronousMode)) + new UnboundidLDAPConnection(options, host, port, authDn, authPw) } override def toConnectionString: String = s"$authDn:*****@ldap://${host}:${port}" @@ -232,6 +237,14 @@ trait PooledConnectionProvider[LDAP <: RoLDAPConnection] extends LDAPConnectionP def ldifFileLogger: LDIFFileLogger def poolname: String + // special option for pooled connection + override def customizeOption: LDAPConnectionOptions => LDAPConnectionOptions = (options: LDAPConnectionOptions) => { + options.setUsePooledSchema(true) + options.setPooledSchemaTimeoutMillis(0) // our schema never change without a Rudder restart + options.setAbandonOnTimeout(true) // better abandoning on timeout to free the connection in the pool + options + } + // for performance reason, operation on pool can't be wrapped into ZIO protected lazy val pool: LDAPConnectionPool = { try { @@ -263,13 +276,15 @@ trait PooledConnectionProvider[LDAP <: RoLDAPConnection] extends LDAPConnectionP * with no pool management. */ class ROAnonymousConnectionProvider( - override val host: String = "localhost", - override val port: Int = 389, - override val ldifFileLogger: LDIFFileLogger = new DefaultLDIFFileLogger(), - override val useSchemaInfos: Boolean = false + override val host: String = "localhost", + override val port: Int = 389, + override val ldifFileLogger: LDIFFileLogger = new DefaultLDIFFileLogger(), + override val useSchemaInfos: Boolean = false, + override val useSynchronousMode: Boolean = true ) extends AnonymousConnection with OneConnectionProvider[RoLDAPConnection] { - override val semaphore: Semaphore = ZioRuntime.unsafeRun(Semaphore.make(1)) - override val connection: Ref[Option[RoLDAPConnection]] = ZioRuntime.unsafeRun(Ref.make(Option.empty[RoLDAPConnection])) + override val semaphore: Semaphore = ZioRuntime.unsafeRun(Semaphore.make(1)) + override val connection: Ref[Option[RoLDAPConnection]] = ZioRuntime.unsafeRun(Ref.make(Option.empty[RoLDAPConnection])) + override def customizeOption: LDAPConnectionOptions => LDAPConnectionOptions = identity def newConnection: IO[LDAPRudderError.BackendException, RoLDAPConnection] = { LDAPIOResult.attempt(new RoLDAPConnection(newUnboundidConnection, ldifFileLogger)) @@ -281,13 +296,15 @@ class ROAnonymousConnectionProvider( * with no pool management. */ class RWAnonymousConnectionProvider( - override val host: String = "localhost", - override val port: Int = 389, - override val ldifFileLogger: LDIFFileLogger = new DefaultLDIFFileLogger(), - override val useSchemaInfos: Boolean = false + override val host: String = "localhost", + override val port: Int = 389, + override val ldifFileLogger: LDIFFileLogger = new DefaultLDIFFileLogger(), + override val useSchemaInfos: Boolean = false, + override val useSynchronousMode: Boolean = true ) extends AnonymousConnection with OneConnectionProvider[RwLDAPConnection] { - override def semaphore: Semaphore = ZioRuntime.unsafeRun(Semaphore.make(1)) - override val connection: Ref[Option[RwLDAPConnection]] = ZioRuntime.unsafeRun(Ref.make(Option.empty[RwLDAPConnection])) + override def semaphore: Semaphore = ZioRuntime.unsafeRun(Semaphore.make(1)) + override val connection: Ref[Option[RwLDAPConnection]] = ZioRuntime.unsafeRun(Ref.make(Option.empty[RwLDAPConnection])) + override def customizeOption: LDAPConnectionOptions => LDAPConnectionOptions = identity def newConnection: IO[LDAPRudderError.BackendException, RwLDAPConnection] = { LDAPIOResult.attempt(new RwLDAPConnection(newUnboundidConnection, ldifFileLogger)) @@ -299,11 +316,12 @@ class RWAnonymousConnectionProvider( * connection provider */ class ROPooledAnonymousConnectionProvider( - override val host: String = "localhost", - override val port: Int = 389, - override val ldifFileLogger: LDIFFileLogger = new DefaultLDIFFileLogger(), - override val useSchemaInfos: Boolean = false, - override val poolSize: Int = 2 + override val host: String = "localhost", + override val port: Int = 389, + override val ldifFileLogger: LDIFFileLogger = new DefaultLDIFFileLogger(), + override val useSchemaInfos: Boolean = false, + override val poolSize: Int = 2, + override val useSynchronousMode: Boolean = true ) extends AnonymousConnection with PooledConnectionProvider[RoLDAPConnection] { override def poolname: String = s"rudder-anonymous-ro" def newConnection: IO[LDAPRudderError.BackendException, RoLDAPConnection] = { @@ -316,11 +334,12 @@ class ROPooledAnonymousConnectionProvider( * connection provider */ class RWPooledAnonymousConnectionProvider( - override val host: String = "localhost", - override val port: Int = 389, - override val ldifFileLogger: LDIFFileLogger = new DefaultLDIFFileLogger(), - override val useSchemaInfos: Boolean = false, - override val poolSize: Int = 2 + override val host: String = "localhost", + override val port: Int = 389, + override val ldifFileLogger: LDIFFileLogger = new DefaultLDIFFileLogger(), + override val useSchemaInfos: Boolean = false, + override val poolSize: Int = 2, + override val useSynchronousMode: Boolean = true ) extends AnonymousConnection with PooledConnectionProvider[RwLDAPConnection] { override def poolname: String = s"rudder-anonymous-rw" def newConnection: IO[LDAPRudderError.BackendException, RwLDAPConnection] = { @@ -334,15 +353,17 @@ class RWPooledAnonymousConnectionProvider( * management. */ class ROSimpleAuthConnectionProvider( - override val authDn: String, - override val authPw: String, - override val host: String = "localhost", - override val port: Int = 389, - override val ldifFileLogger: LDIFFileLogger = new DefaultLDIFFileLogger(), - override val useSchemaInfos: Boolean = false + override val authDn: String, + override val authPw: String, + override val host: String = "localhost", + override val port: Int = 389, + override val ldifFileLogger: LDIFFileLogger = new DefaultLDIFFileLogger(), + override val useSchemaInfos: Boolean = false, + override val useSynchronousMode: Boolean = true ) extends SimpleAuthConnection with OneConnectionProvider[RoLDAPConnection] { - override val semaphore: Semaphore = ZioRuntime.unsafeRun(Semaphore.make(1)) - override val connection: Ref[Option[RoLDAPConnection]] = ZioRuntime.unsafeRun(Ref.make(Option.empty[RoLDAPConnection])) + override val semaphore: Semaphore = ZioRuntime.unsafeRun(Semaphore.make(1)) + override val connection: Ref[Option[RoLDAPConnection]] = ZioRuntime.unsafeRun(Ref.make(Option.empty[RoLDAPConnection])) + override def customizeOption: LDAPConnectionOptions => LDAPConnectionOptions = identity def newConnection: IO[LDAPRudderError.BackendException, RoLDAPConnection] = { LDAPIOResult.attempt(new RoLDAPConnection(newUnboundidConnection, ldifFileLogger)) @@ -355,15 +376,17 @@ class ROSimpleAuthConnectionProvider( * management. */ class RWSimpleAuthConnectionProvider( - override val authDn: String, - override val authPw: String, - override val host: String = "localhost", - override val port: Int = 389, - override val ldifFileLogger: LDIFFileLogger = new DefaultLDIFFileLogger(), - override val useSchemaInfos: Boolean = false + override val authDn: String, + override val authPw: String, + override val host: String = "localhost", + override val port: Int = 389, + override val ldifFileLogger: LDIFFileLogger = new DefaultLDIFFileLogger(), + override val useSchemaInfos: Boolean = false, + override val useSynchronousMode: Boolean = true ) extends SimpleAuthConnection with OneConnectionProvider[RwLDAPConnection] { - override val semaphore: Semaphore = ZioRuntime.unsafeRun(Semaphore.make(1)) - override val connection: Ref[Option[RwLDAPConnection]] = ZioRuntime.unsafeRun(Ref.make(Option.empty[RwLDAPConnection])) + override val semaphore: Semaphore = ZioRuntime.unsafeRun(Semaphore.make(1)) + override val connection: Ref[Option[RwLDAPConnection]] = ZioRuntime.unsafeRun(Ref.make(Option.empty[RwLDAPConnection])) + override def customizeOption: LDAPConnectionOptions => LDAPConnectionOptions = identity def newConnection: IO[LDAPRudderError.BackendException, RwLDAPConnection] = { LDAPIOResult.attempt(new RwLDAPConnection(newUnboundidConnection, ldifFileLogger)) @@ -375,13 +398,14 @@ class RWSimpleAuthConnectionProvider( * with a simple login/pass connection */ class ROPooledSimpleAuthConnectionProvider( - override val authDn: String, - override val authPw: String, - override val host: String = "localhost", - override val port: Int = 389, - override val ldifFileLogger: LDIFFileLogger = new DefaultLDIFFileLogger(), - override val useSchemaInfos: Boolean = false, - override val poolSize: Int = 2 + override val authDn: String, + override val authPw: String, + override val host: String = "localhost", + override val port: Int = 389, + override val ldifFileLogger: LDIFFileLogger = new DefaultLDIFFileLogger(), + override val useSchemaInfos: Boolean = false, + override val poolSize: Int = 2, + override val useSynchronousMode: Boolean = true ) extends SimpleAuthConnection with PooledConnectionProvider[RoLDAPConnection] { override def poolname: String = s"rudder-authenticated-ro" def newConnection: IO[LDAPRudderError.BackendException, RoLDAPConnection] = { @@ -394,13 +418,14 @@ class ROPooledSimpleAuthConnectionProvider( * with a simple login/pass connection */ class RWPooledSimpleAuthConnectionProvider( - override val authDn: String, - override val authPw: String, - override val host: String = "localhost", - override val port: Int = 389, - override val ldifFileLogger: LDIFFileLogger = new DefaultLDIFFileLogger(), - override val useSchemaInfos: Boolean = false, - override val poolSize: Int = 2 + override val authDn: String, + override val authPw: String, + override val host: String = "localhost", + override val port: Int = 389, + override val ldifFileLogger: LDIFFileLogger = new DefaultLDIFFileLogger(), + override val useSchemaInfos: Boolean = false, + override val poolSize: Int = 2, + override val useSynchronousMode: Boolean = true ) extends SimpleAuthConnection with PooledConnectionProvider[RwLDAPConnection] { override def poolname: String = s"rudder-authenticated-rw"