opends/resource/admin/abbreviations.xsl
@@ -23,6 +23,7 @@ ! ! ! Copyright 2008-2009 Sun Microsystems, Inc. ! Portions copyright 2011 ForgeRock AS ! --> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> @@ -53,7 +54,7 @@ or $value = 'sha384' or $value = 'sha512' or $value = 'tls' or $value = 'des' or $value = 'aes' or $value = 'rc4' or $value = 'db' or $value = 'snmp' or $value = 'qos' or $value = 'ecl' or $value = 'ecl' or $value = 'ttl' "/> </xsl:template> </xsl:stylesheet> opends/resource/schema/02-config.ldif
@@ -2642,12 +2642,7 @@ SINGLE-VALUE X-ORIGIN 'OpenDJ Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.25 NAME 'ds-cfg-cached-password-min-age' 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.26 NAME 'ds-cfg-cached-password-max-age' NAME 'ds-cfg-cached-password-ttl' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'OpenDJ Directory Server' ) @@ -4415,6 +4410,5 @@ ds-cfg-ssl-protocol $ ds-cfg-ssl-cipher-suite $ ds-cfg-cached-password-storage-scheme $ ds-cfg-cached-password-min-age $ ds-cfg-cached-password-max-age ) ds-cfg-cached-password-ttl ) X-ORIGIN 'OpenDJ Directory Server' ) opends/src/admin/defn/org/opends/server/admin/std/LDAPPassThroughAuthenticationPolicyConfiguration.xml
@@ -630,40 +630,7 @@ </adm:profile> </adm:property> <adm:property name="cached-password-min-age"> <adm:synopsis> Specifies the minimum length of time that a locally cached password must be used for authentication. </adm:synopsis> <adm:description> This property should be used in order to control the rate at which failed authentication attempts are forwarded to the remote LDAP server and, as a consequence, reduce the impact of a denial of service attack. An authentication attempt which fails because the provided password does not match the locally cached password will be retried against the remote LDAP service only if the cached password is older than the minimum age. Increasing the minimum age increases the period a user will have to wait before being able to authenticate using a new password. </adm:description> <adm:default-behavior> <adm:defined> <adm:value>10 seconds</adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> <adm:duration base-unit="s"/> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-cached-password-min-age</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="cached-password-max-age"> <adm:property name="cached-password-ttl"> <adm:synopsis> Specifies the maximum length of time that a locally cached password may be used for authentication before it is refreshed from the remote LDAP @@ -674,11 +641,10 @@ decreases the frequency that bind operations are delegated to the remote LDAP service, but increases the risk of users authenticating using stale passwords. Note that authentication attempts which fail because the provided password does not match the locally cached password will always be retried against the remote LDAP service, unless the cached password is newer than the minimum age. the remote LDAP service. </adm:description> <adm:default-behavior> <adm:defined> @@ -690,7 +656,7 @@ </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-cached-password-max-age</ldap:name> <ldap:name>ds-cfg-cached-password-ttl</ldap:name> </ldap:attribute> </adm:profile> </adm:property> opends/src/admin/messages/LDAPPassThroughAuthenticationPolicyCfgDefn.properties
@@ -6,13 +6,11 @@ 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. constraint.4.synopsis=The cached password storage scheme must be specified when password caching is enabled. property.cached-password-max-age.synopsis=Specifies the maximum length of time that a locally cached password may be used for authentication before it is refreshed from the remote LDAP service. property.cached-password-max-age.description=This property represents a cache timeout. Increasing the timeout period decreases the frequency that bind operations are delegated to the remote LDAP service, but increases the risk of users authenticating using stale passwords. Note that authentication attempts which fail because the provided password does not match the locally cached password will always be retried against the remote LDAP service, unless the cached password is newer than the minimum age. property.cached-password-min-age.synopsis=Specifies the minimum length of time that a locally cached password must be used for authentication. property.cached-password-min-age.description=This property should be used in order to control the rate at which failed authentication attempts are forwarded to the remote LDAP server and, as a consequence, reduce the impact of a denial of service attack. An authentication attempt which fails because the provided password does not match the locally cached password will be retried against the remote LDAP service only if the cached password is older than the minimum age. Increasing the minimum age increases the period a user will have to wait before being able to authenticate using a new password. property.cached-password-storage-scheme.synopsis=Specifies the name of a password storage scheme which should be used for encoding cached passwords. property.cached-password-storage-scheme.description=Changing the password storage scheme will cause all existing cached passwords to be discarded. property.cached-password-storage-scheme.syntax.aggregation.constraint-synopsis=The referenced password storage schemes must be enabled. property.cached-password-ttl.synopsis=Specifies the maximum length of time that a locally cached password may be used for authentication before it is refreshed from the remote LDAP service. property.cached-password-ttl.description=This property represents a cache timeout. Increasing the timeout period decreases the frequency that bind operations are delegated to the remote LDAP service, but increases the risk of users authenticating using stale passwords. Note that authentication attempts which fail because the provided password does not match the locally cached password will always be retried against the remote LDAP service. 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. opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java
@@ -1523,6 +1523,16 @@ * connection factories to determine when they are online. */ ScheduledExecutorService getScheduledExecutorService(); /** * Returns the current time in milli-seconds in order to perform cached * password expiration checks. * * @return The current time in milli-seconds. */ long getCurrentTimeMillis(); } @@ -2125,6 +2135,13 @@ return scheduler; } public long getCurrentTimeMillis() { return System.currentTimeMillis(); } }; opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyTestCase.java
@@ -54,6 +54,8 @@ import org.opends.server.protocols.asn1.ASN1; import org.opends.server.protocols.asn1.ASN1Writer; import org.opends.server.protocols.ldap.*; import org.opends.server.schema.DirectoryStringSyntax; import org.opends.server.schema.GeneralizedTimeSyntax; import org.opends.server.tools.LDAPReader; import org.opends.server.tools.LDAPWriter; import org.opends.server.types.*; @@ -430,6 +432,7 @@ private String mappedSearchBindPasswordEnvVar = null; private String mappedSearchBindPasswordFile = null; private String mappedSearchBindPasswordProperty = null; private boolean usePasswordCaching = false; @@ -710,6 +713,14 @@ MockPolicyCfg withUsePasswordCaching(final boolean usePasswordCaching) { this.usePasswordCaching = usePasswordCaching; return this; } /** * {@inheritDoc} */ @@ -743,7 +754,7 @@ /** * {@inheritDoc} */ public long getCachedPasswordMaxAge() public long getCachedPasswordTTL() { return 86400; } @@ -753,19 +764,9 @@ /** * {@inheritDoc} */ public long getCachedPasswordMinAge() { return 10; } /** * {@inheritDoc} */ public String getCachedPasswordStorageScheme() { return null; return "Salted SHA-1"; } @@ -775,7 +776,14 @@ */ public DN getCachedPasswordStorageSchemeDN() { return null; try { return DN.decode("cn=Salted SHA-1,cn=Password Storage Schemes,cn=config"); } catch (DirectoryException e) { throw new RuntimeException(e); } } @@ -785,7 +793,7 @@ */ public boolean isUsePasswordCaching() { return false; return usePasswordCaching; } } @@ -869,6 +877,7 @@ private final Queue<Event<?>> expectedEvents = new LinkedList<Event<?>>(); private final List<MockScheduledFuture> monitorRunnables = new LinkedList<MockScheduledFuture>(); private long currentTimeMS = System.currentTimeMillis(); // All methods unused excepted scheduleWithFixedDelay. private final ScheduledExecutorService mockScheduler = new ScheduledExecutorService() @@ -1050,6 +1059,16 @@ /** * {@inheritDoc} */ public long getCurrentTimeMillis() { return currentTimeMS; } void assertAllExpectedEventsReceived() { assertTrue(expectedEvents.isEmpty()); @@ -1082,6 +1101,14 @@ MockProvider withCurrentTime(final long currentTimeMS) { this.currentTimeMS = currentTimeMS; return this; } void runMonitorTasks() { for (final MockScheduledFuture task : monitorRunnables) @@ -4200,6 +4227,198 @@ /** * Returns test data for {@link #testPasswordCaching}. * * @return Test data for {@link #testPasswordCaching}. */ @DataProvider public Object[][] testPasswordCachingData() { // @formatter:off return new Object[][] { /* cacheState, matchesCache, matchesReal */ { "notPresent", false, false }, { "notPresent", false, true }, { "notPresent", true, false }, { "notPresent", true, true }, { "presentValid", false, false }, { "presentValid", false, true }, { "presentValid", true, false }, { "presentValid", true, true }, { "presentExpired", false, false }, { "presentExpired", false, true }, { "presentExpired", true, false }, { "presentExpired", true, true }, }; // @formatter:on } /** * Tests password caching functionality. * * @param cacheState * A string indicating the state of the cached password in the user's * entry. * @param matchesCache * {@code true} if the presented password should match the cached * password. * @param matchesReal * {@code true} if the presented password should match the real * password. * @throws Exception * If an unexpected exception occurred. */ @Test(enabled = false, dataProvider = "testPasswordCachingData") public void testPasswordCaching(String cacheState, boolean matchesCache, boolean matchesReal) throws Exception { // Create an empty test backend 'o=test' TestCaseUtils.initializeTestBackend(true); // Choose arbitrary date. final GregorianCalendar testTime = new GregorianCalendar(2010, 0, 1); final long testTimeMS = testTime.getTimeInMillis(); final String testCachedPassword = "{SSHA}N8QSu9kXHkODFxFtwtwVEqM5XMCfnSaq/5gWew=="; final boolean expectPTA; final boolean expectCacheUpdate; final boolean expectedBindResultIsSuccess; final Entry testUser; if (cacheState.equals("notPresent")) { expectPTA = true; expectCacheUpdate = matchesReal; expectedBindResultIsSuccess = matchesReal; testUser = TestCaseUtils.makeEntry( /* @formatter:off */ "dn: cn=test user,o=test", "objectClass: top", "objectClass: person", "sn: user", "cn: test user" /* @formatter:on */ ); } else if (cacheState.equals("presentValid")) { expectPTA = false; expectCacheUpdate = !matchesCache && matchesReal; expectedBindResultIsSuccess = matchesCache; // Create an entry whose cached password is 10s old. testUser = TestCaseUtils.makeEntry( /* @formatter:off */ "dn: cn=test user,o=test", "objectClass: top", "objectClass: person", "sn: user", "cn: test user", "ds-pta-cached-password: " + testCachedPassword, "ds-pta-cached-password-time: " + GeneralizedTimeSyntax.format(testTimeMS - 10000) /* @formatter:on */ ); } else { // presentExpired expectPTA = true; expectCacheUpdate = matchesReal; expectedBindResultIsSuccess = matchesReal; // Create an entry whose cached password is more than 1 day old. testUser = TestCaseUtils.makeEntry( /* @formatter:off */ "dn: cn=test user,o=test", "objectClass: top", "objectClass: person", "sn: user", "cn: test user", "ds-pta-cached-password: " + testCachedPassword, "ds-pta-cached-password-time: " + GeneralizedTimeSyntax.format(testTimeMS - 100000) /* @formatter:on */ ); } final String presentedPassword; if (matchesCache) { presentedPassword = "password"; } else { presentedPassword = "doesNotMatchCache"; } // Add the test entry. TestCaseUtils.addEntry(testUser); // Mock configuration. final LDAPPassThroughAuthenticationPolicyCfg cfg = mockCfg() .withPrimaryServer(phost1).withMappedAttribute("uid") .withBaseDN("o=ad").withUsePasswordCaching(true); // Create all the events. final MockProvider provider = new MockProvider(); if (expectPTA) { final GetLDAPConnectionFactoryEvent fe = new GetLDAPConnectionFactoryEvent( phost1, cfg); provider.expectEvent(fe); // Get connection then bind twice creating two pooled connections. final GetConnectionEvent ce = new GetConnectionEvent(fe); provider .expectEvent(ce) .expectEvent( new SimpleBindEvent(ce, "cn=test user,o=test", presentedPassword)) .expectEvent(new CloseEvent(ce)); } // Obtain policy and state. final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory( provider); assertTrue(factory.isConfigurationAcceptable(cfg, null)); final AuthenticationPolicy policy = factory.createAuthenticationPolicy(cfg); // Perform the authentication. final AuthenticationPolicyState state = policy .createAuthenticationPolicyState(userEntry); assertEquals(state.getAuthenticationPolicy(), policy); assertEquals(state.passwordMatches(ByteString.valueOf(presentedPassword)), expectedBindResultIsSuccess); state.finalizeStateAfterBind(); // Check that the password has been cached if needed. if (expectCacheUpdate) { Entry updatedTestUser = DirectoryServer.getEntry(DN .decode("cn=test user,o=test")); String newCachedPassword = updatedTestUser.getAttributeValue( DirectoryServer.getAttributeType("ds-pta-cached-password"), DirectoryStringSyntax.DECODER); assertFalse(newCachedPassword.equals(testCachedPassword)); String newCachedPasswordTime = updatedTestUser.getAttributeValue( DirectoryServer.getAttributeType("ds-pta-cached-password-time"), DirectoryStringSyntax.DECODER); assertEquals(newCachedPasswordTime, GeneralizedTimeSyntax.format(testTimeMS)); } // Tear down and check final state. policy.finalizeAuthenticationPolicy(); provider.assertAllExpectedEventsReceived(); } MockPolicyCfg mockCfg() { return new MockPolicyCfg();