opends/resource/schema/02-config.ldif
@@ -2616,6 +2616,21 @@ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'OpenDJ Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.20 NAME 'ds-cfg-mapped-search-bind-password-property' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'OpenDJ Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.21 NAME 'ds-cfg-mapped-search-bind-password-environment-variable' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'OpenDJ Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.22 NAME 'ds-cfg-mapped-search-bind-password-file' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'OpenDJ Directory Server' ) objectClasses: ( 1.3.6.1.4.1.26027.1.2.1 NAME 'ds-cfg-access-control-handler' SUP top @@ -4367,6 +4382,9 @@ ds-cfg-mapped-attribute $ ds-cfg-mapped-search-bind-dn $ ds-cfg-mapped-search-bind-password $ ds-cfg-mapped-search-bind-password-property $ ds-cfg-mapped-search-bind-password-environment-variable $ ds-cfg-mapped-search-bind-password-file $ ds-cfg-mapped-search-base-dn $ ds-cfg-connection-timeout $ ds-cfg-trust-manager-provider $ opends/src/admin/defn/org/opends/server/admin/std/LDAPPassThroughAuthenticationPolicyConfiguration.xml
@@ -60,18 +60,37 @@ <adm:constraint> <adm:synopsis> One or more search base DNs must be specified when using the "mapped-search" mapping policies. "mapped-search" mapping policy. </adm:synopsis> <adm:condition> <adm:implies> <adm:or> <adm:contains property="mapping-policy" value="mapped-search" /> </adm:or> <adm:contains property="mapping-policy" value="mapped-search" /> <adm:is-present property="mapped-search-base-dn" /> </adm:implies> </adm:condition> </adm:constraint> <adm:constraint> <adm:synopsis> The mapped search bind password must be specified when using the "mapped-search" mapping policy and a mapped-search-bind-dn is defined. </adm:synopsis> <adm:condition> <adm:implies> <adm:and> <adm:contains property="mapping-policy" value="mapped-search" /> <adm:is-present property="mapped-search-bind-dn" /> </adm:and> <adm:or> <adm:is-present property="mapped-search-bind-password" /> <adm:is-present property="mapped-search-bind-password-property" /> <adm:is-present property="mapped-search-bind-password-environment-variable" /> <adm:is-present property="mapped-search-bind-password-file" /> </adm:or> </adm:implies> </adm:condition> </adm:constraint> <adm:profile name="ldap"> <ldap:object-class> <ldap:name>ds-cfg-ldap-pass-through-authentication-policy</ldap:name> @@ -330,9 +349,7 @@ user searches in the remote LDAP directory service. </adm:synopsis> <adm:default-behavior> <adm:alias> <adm:synopsis>Searches will be performed anonymously.</adm:synopsis> </adm:alias> <adm:undefined/> </adm:default-behavior> <adm:syntax> <adm:password /> @@ -344,6 +361,65 @@ </adm:profile> </adm:property> <adm:property name="mapped-search-bind-password-property"> <adm:synopsis> Specifies the name of a Java property containing the bind password which should be used to perform user searches in the remote LDAP directory service. </adm:synopsis> <adm:default-behavior> <adm:undefined/> </adm:default-behavior> <adm:syntax> <adm:string /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-mapped-search-bind-password-property</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="mapped-search-bind-password-environment-variable"> <adm:synopsis> Specifies the name of an environment variable containing the bind password which should be used to perform user searches in the remote LDAP directory service. </adm:synopsis> <adm:default-behavior> <adm:undefined/> </adm:default-behavior> <adm:syntax> <adm:string /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name> ds-cfg-mapped-search-bind-password-environment-variable </ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="mapped-search-bind-password-file"> <adm:synopsis> Specifies the name of a file containing the bind password which should be used to perform user searches in the remote LDAP directory service. </adm:synopsis> <adm:default-behavior> <adm:undefined/> </adm:default-behavior> <adm:syntax> <adm:string /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-mapped-search-bind-password-file</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="mapped-search-base-dn" multi-valued="true"> <adm:synopsis> Specifies the set of base DNs below which to search for users opends/src/admin/messages/LDAPPassThroughAuthenticationPolicyCfgDefn.properties
@@ -3,7 +3,8 @@ synopsis=An authentication policy for users whose credentials are managed by a remote LDAP directory service. description=Authentication attempts will be redirected to the remote LDAP directory service based on a combination of the criteria specified in this policy and the content of the user's entry in this directory server. constraint.1.synopsis=One or more mapped attributes must be specified when using the "mapped-bind" or "mapped-search" mapping policies. constraint.2.synopsis=One or more search base DNs must be specified when using the "mapped-search" mapping policies. constraint.2.synopsis=One or more search base DNs must be specified when using the "mapped-search" mapping policy. constraint.3.synopsis=The mapped search bind password must be specified when using the "mapped-search" mapping policy and a mapped-search-bind-dn is defined. property.connection-timeout.synopsis=Specifies the timeout used when connecting to remote LDAP director servers, performing SSL negotiation, and for individual search and bind requests. property.connection-timeout.description=If the timeout expires then the current operation will be aborted and retried against another LDAP server if one is available. property.java-class.synopsis=Specifies the fully-qualified name of the Java class which provides the LDAP Pass Through Authentication Policy implementation. @@ -14,7 +15,9 @@ property.mapped-search-bind-dn.synopsis=Specifies the bind DN which should be used to perform user searches in the remote LDAP directory service. property.mapped-search-bind-dn.default-behavior.alias.synopsis=Searches will be performed anonymously. property.mapped-search-bind-password.synopsis=Specifies the bind password which should be used to perform user searches in the remote LDAP directory service. property.mapped-search-bind-password.default-behavior.alias.synopsis=Searches will be performed anonymously. property.mapped-search-bind-password-environment-variable.synopsis=Specifies the name of an environment variable containing the bind password which should be used to perform user searches in the remote LDAP directory service. property.mapped-search-bind-password-file.synopsis=Specifies the name of a file containing the bind password which should be used to perform user searches in the remote LDAP directory service. property.mapped-search-bind-password-property.synopsis=Specifies the name of a Java property containing the bind password which should be used to perform user searches in the remote LDAP directory service. property.mapping-policy.synopsis=Specifies the mapping algorithm for obtaining the bind DN from the user's entry. property.mapping-policy.syntax.enumeration.value.mapped-bind.synopsis=Bind to the remote LDAP directory service using a DN obtained from an attribute in the user's entry. This policy will check each attribute named in the "mapped-attribute" property. If more than one attribute or value is present then the first one will be used. property.mapping-policy.syntax.enumeration.value.mapped-search.synopsis=Bind to the remote LDAP directory service using the DN of an entry obtained using a search against the remote LDAP directory service. The search filter will comprise of an equality matching filter whose attribute type is the "mapped-attribute" property, and whose assertion value is the attribute value obtained from the user's entry. If more than one attribute or value is present then the filter will be composed of multiple equality filters combined using a logical OR (union). opends/src/messages/messages/extension.properties
@@ -1498,3 +1498,22 @@ MILD_ERR_LDAP_PTA_INVALID_PORT_NUMBER_606=The configuration of LDAP PTA policy \ "%s" is invalid because the remote LDAP server address "%s" specifies a port \ number which is invalid. Port numbers should be greater than 0 and less than 65536 SEVERE_ERR_LDAP_PTA_PWD_PROPERTY_NOT_SET_607=The configuration of LDAP PTA policy \ "%s" is invalid because the Java property %s which should contain the mapped \ search bind password is not set SEVERE_ERR_LDAP_PTA_PWD_ENVAR_NOT_SET_608=The configuration of LDAP PTA policy \ "%s" is invalid because the environment variable %s which should contain the mapped \ search bind password is not set SEVERE_ERR_LDAP_PTA_PWD_NO_SUCH_FILE_609=The configuration of LDAP PTA policy \ "%s" is invalid because the file %s which should contain the mapped search \ bind password does not exist SEVERE_ERR_LDAP_PTA_PWD_FILE_CANNOT_READ_610=The configuration of LDAP PTA policy \ "%s" is invalid because the file %s which should contain the mapped search \ bind password cannot be read for the following reason: %s SEVERE_ERR_LDAP_PTA_PWD_FILE_EMPTY_611=The configuration of LDAP PTA policy \ "%s" is invalid because the file %s which should contain the mapped search \ bind password is empty SEVERE_ERR_LDAP_PTA_NO_PWD_613=The configuration of LDAP PTA policy \ "%s" is invalid because it does not specify the a means for obtaining the mapped \ search bind password opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java
@@ -32,9 +32,10 @@ import static org.opends.messages.ExtensionMessages.*; import static org.opends.server.loggers.debug.DebugLogger.debugEnabled; import static org.opends.server.protocols.ldap.LDAPConstants.*; import static org.opends.server.util.StaticUtils.getExceptionMessage; import static org.opends.server.util.StaticUtils.getFileForPath; import java.io.Closeable; import java.io.IOException; import java.io.*; import java.net.*; import java.util.*; import java.util.concurrent.*; @@ -47,6 +48,8 @@ import org.opends.messages.Message; import org.opends.server.admin.server.ConfigurationChangeListener; import org.opends.server.admin.std.meta. LDAPPassThroughAuthenticationPolicyCfgDefn.MappingPolicy; import org.opends.server.admin.std.server.*; import org.opends.server.api.*; import org.opends.server.config.ConfigException; @@ -69,7 +72,6 @@ { // TODO: handle password policy response controls? AD? // TODO: provide alternative cfg for search password. // TODO: custom aliveness pings // TODO: manage account lockout // TODO: cache password @@ -1926,6 +1928,22 @@ { this.cfg = cfg; // First obtain the mapped search password if needed, ignoring any errors // since these should have already been detected during configuration // validation. final String mappedSearchPassword; if (cfg.getMappingPolicy() == MappingPolicy.MAPPED_SEARCH && cfg.getMappedSearchBindDN() != null && !cfg.getMappedSearchBindDN().isNullDN()) { mappedSearchPassword = getMappedSearchBindPassword(cfg, new LinkedList<Message>()); } else { mappedSearchPassword = null; } // Use two pools per server: one for authentication (bind) and one for // searches. Even if the searches are performed anonymously we cannot use // the same pool, otherwise they will be performed as the most recently @@ -1947,7 +1965,7 @@ searchPool[index] = new ConnectionPool( new AuthenticatedConnectionFactory(factory, cfg.getMappedSearchBindDN(), cfg.getMappedSearchBindPassword())); mappedSearchPassword)); bindPool[index++] = new ConnectionPool(factory); } primarySearchLoadBalancer = new RoundRobinLoadBalancer(searchPool, @@ -1972,7 +1990,7 @@ searchPool[index] = new ConnectionPool( new AuthenticatedConnectionFactory(factory, cfg.getMappedSearchBindDN(), cfg.getMappedSearchBindPassword())); mappedSearchPassword)); bindPool[index++] = new ConnectionPool(factory); } final RoundRobinLoadBalancer secondarySearchLoadBalancer = @@ -2098,6 +2116,100 @@ //Get the search bind password performing mapped searches. // // We will offer several places to look for the password, and we will // do so in the following order: // // - In a specified Java property // - In a specified environment variable // - In a specified file on the server filesystem. // - As the value of a configuration attribute. // // In any case, the password must be in the clear. private static String getMappedSearchBindPassword( final LDAPPassThroughAuthenticationPolicyCfg cfg, final List<Message> unacceptableReasons) { String password = null; if (cfg.getMappedSearchBindPasswordProperty() != null) { String propertyName = cfg.getMappedSearchBindPasswordProperty(); password = System.getProperty(propertyName); if (password == null) { unacceptableReasons.add(ERR_LDAP_PTA_PWD_PROPERTY_NOT_SET.get( String.valueOf(cfg.dn()), String.valueOf(propertyName))); } } else if (cfg.getMappedSearchBindPasswordEnvironmentVariable() != null) { String envVarName = cfg.getMappedSearchBindPasswordEnvironmentVariable(); password = System.getenv(envVarName); if (password == null) { unacceptableReasons.add(ERR_LDAP_PTA_PWD_ENVAR_NOT_SET.get( String.valueOf(cfg.dn()), String.valueOf(envVarName))); } } else if (cfg.getMappedSearchBindPasswordFile() != null) { String fileName = cfg.getMappedSearchBindPasswordFile(); File passwordFile = getFileForPath(fileName); if (!passwordFile.exists()) { unacceptableReasons.add(ERR_LDAP_PTA_PWD_NO_SUCH_FILE.get( String.valueOf(cfg.dn()), String.valueOf(fileName))); } else { BufferedReader br = null; try { br = new BufferedReader(new FileReader(passwordFile)); password = br.readLine(); if (password == null) { unacceptableReasons.add(ERR_LDAP_PTA_PWD_FILE_EMPTY.get( String.valueOf(cfg.dn()), String.valueOf(fileName))); } } catch (IOException e) { unacceptableReasons.add(ERR_LDAP_PTA_PWD_FILE_CANNOT_READ.get( String.valueOf(cfg.dn()), String.valueOf(fileName), getExceptionMessage(e))); } finally { try { br.close(); } catch (Exception e) { // Ignored. } } } } else if (cfg.getMappedSearchBindPassword() != null) { password = cfg.getMappedSearchBindPassword(); } else { // Password wasn't defined anywhere. unacceptableReasons .add(ERR_LDAP_PTA_NO_PWD.get(String.valueOf(cfg.dn()))); } return password; } private static boolean isServerAddressValid( final LDAPPassThroughAuthenticationPolicyCfg configuration, final List<Message> unacceptableReasons, final String hostPort) @@ -2189,7 +2301,7 @@ */ @Override public boolean isConfigurationAcceptable( final LDAPPassThroughAuthenticationPolicyCfg configuration, final LDAPPassThroughAuthenticationPolicyCfg cfg, final List<Message> unacceptableReasons) { // Check that the port numbers are valid. We won't actually try and connect @@ -2197,18 +2309,29 @@ // capabilities). boolean configurationIsAcceptable = true; for (final String hostPort : configuration.getPrimaryRemoteLDAPServer()) for (final String hostPort : cfg.getPrimaryRemoteLDAPServer()) { configurationIsAcceptable &= isServerAddressValid(configuration, configurationIsAcceptable &= isServerAddressValid(cfg, unacceptableReasons, hostPort); } for (final String hostPort : configuration.getSecondaryRemoteLDAPServer()) for (final String hostPort : cfg.getSecondaryRemoteLDAPServer()) { configurationIsAcceptable &= isServerAddressValid(configuration, configurationIsAcceptable &= isServerAddressValid(cfg, unacceptableReasons, hostPort); } // Ensure that the search bind password is defined somewhere. if (cfg.getMappingPolicy() == MappingPolicy.MAPPED_SEARCH && cfg.getMappedSearchBindDN() != null && !cfg.getMappedSearchBindDN().isNullDN()) { if (getMappedSearchBindPassword(cfg, unacceptableReasons) == null) { configurationIsAcceptable = false; } } return configurationIsAcceptable; } } opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyTestCase.java
@@ -424,6 +424,12 @@ private final SortedSet<String> primaryServers = new TreeSet<String>(); private final SortedSet<String> secondaryServers = new TreeSet<String>(); private int timeoutMS = 0; // unlimited private DN mappedSearchBindDN = searchBindDN; private String mappedSearchBindPassword = "searchPassword"; private String mappedSearchBindPasswordEnvVar = null; private String mappedSearchBindPasswordFile = null; private String mappedSearchBindPasswordProperty = null; @@ -496,7 +502,7 @@ @Override public DN getMappedSearchBindDN() { return searchBindDN; return mappedSearchBindDN; } @@ -504,7 +510,7 @@ @Override public String getMappedSearchBindPassword() { return "searchPassword"; return mappedSearchBindPassword; } @@ -660,6 +666,76 @@ secondaryServers.add(hostPort); return this; } MockPolicyCfg withMappedSearchBindDN(final DN value) { this.mappedSearchBindDN = value; return this; } MockPolicyCfg withMappedSearchBindPassword(final String value) { this.mappedSearchBindPassword = value; return this; } MockPolicyCfg withMappedSearchBindPasswordEnvironmentVariable(final String value) { this.mappedSearchBindPasswordEnvVar = value; return this; } MockPolicyCfg withMappedSearchBindPasswordFile(final String value) { this.mappedSearchBindPasswordFile = value; return this; } MockPolicyCfg withMappedSearchBindPasswordProperty(final String value) { this.mappedSearchBindPasswordProperty = value; return this; } /** * {@inheritDoc} */ public String getMappedSearchBindPasswordEnvironmentVariable() { return mappedSearchBindPasswordEnvVar; } /** * {@inheritDoc} */ public String getMappedSearchBindPasswordFile() { return mappedSearchBindPasswordFile; } /** * {@inheritDoc} */ public String getMappedSearchBindPasswordProperty() { return mappedSearchBindPasswordProperty; } } @@ -2063,6 +2139,8 @@ // @formatter:off return new Object[][] { /* cfg, isValid */ // Test server configuration. { mockCfg().withPrimaryServer("test:1"), true }, { mockCfg().withPrimaryServer("test:65535"), true }, { mockCfg().withPrimaryServer("test:0"), false }, @@ -2073,6 +2151,17 @@ { mockCfg().withSecondaryServer("test:0"), false }, { mockCfg().withSecondaryServer("test:65536"), false }, { mockCfg().withSecondaryServer("test:1000000"), false }, // Test mapped search parameters. { mockCfg().withMappingPolicy(MappingPolicy.MAPPED_SEARCH), true }, { mockCfg().withMappingPolicy(MappingPolicy.MAPPED_SEARCH).withMappedSearchBindDN(null).withMappedSearchBindPassword(null), true }, { mockCfg().withMappingPolicy(MappingPolicy.MAPPED_SEARCH).withMappedSearchBindPassword(null), false }, { mockCfg().withMappingPolicy(MappingPolicy.MAPPED_SEARCH).withMappedSearchBindPasswordProperty("org.opendj.dummy.property"), false }, { mockCfg().withMappingPolicy(MappingPolicy.MAPPED_SEARCH).withMappedSearchBindPasswordProperty("java.version"), true }, { mockCfg().withMappingPolicy(MappingPolicy.MAPPED_SEARCH).withMappedSearchBindPasswordEnvironmentVariable("ORG_OPENDJ_DUMMY_ENVVAR"), false }, { mockCfg().withMappingPolicy(MappingPolicy.MAPPED_SEARCH).withMappedSearchBindPasswordFile("dummy_file.txt"), false }, { mockCfg().withMappingPolicy(MappingPolicy.MAPPED_SEARCH).withMappedSearchBindPasswordFile("config/admin-keystore.pin"), true }, }; // @formatter:on }