-
Notifications
You must be signed in to change notification settings - Fork 283
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Tests related to brute force attack prevention.
Signed-off-by: Lukasz Soszynski <[email protected]>
- Loading branch information
1 parent
40e2e9c
commit 7970da3
Showing
12 changed files
with
662 additions
and
35 deletions.
There are no files selected for viewing
149 changes: 149 additions & 0 deletions
149
src/integrationTest/java/org/opensearch/security/IpBruteForceAttacksPreventionTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
*/ | ||
package org.opensearch.security; | ||
|
||
import java.util.concurrent.TimeUnit; | ||
|
||
import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; | ||
import org.junit.ClassRule; | ||
import org.junit.Test; | ||
import org.junit.runner.RunWith; | ||
|
||
import org.opensearch.test.framework.AuthFailureListeners; | ||
import org.opensearch.test.framework.RateLimiting; | ||
import org.opensearch.test.framework.TestSecurityConfig.User; | ||
import org.opensearch.test.framework.cluster.ClusterManager; | ||
import org.opensearch.test.framework.cluster.LocalCluster; | ||
import org.opensearch.test.framework.cluster.TestRestClient; | ||
import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; | ||
import org.opensearch.test.framework.cluster.TestRestClientConfiguration; | ||
|
||
import static org.apache.hc.core5.http.HttpStatus.SC_OK; | ||
import static org.apache.hc.core5.http.HttpStatus.SC_UNAUTHORIZED; | ||
import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL_WITHOUT_CHALLENGE; | ||
import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; | ||
import static org.opensearch.test.framework.cluster.TestRestClientConfiguration.userWithSourceIp; | ||
|
||
@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) | ||
@ThreadLeakScope(ThreadLeakScope.Scope.NONE) | ||
public class IpBruteForceAttacksPreventionTests { | ||
private static final User USER_1 = new User("simple-user-1").roles(ALL_ACCESS); | ||
private static final User USER_2 = new User("simple-user-2").roles(ALL_ACCESS); | ||
|
||
public static final int ALLOWED_TRIES = 3; | ||
public static final int TIME_WINDOW_SECONDS = 3; | ||
|
||
public static final String CLIENT_IP_2 = "127.0.0.2"; | ||
public static final String CLIENT_IP_3 = "127.0.0.3"; | ||
public static final String CLIENT_IP_4 = "127.0.0.4"; | ||
public static final String CLIENT_IP_5 = "127.0.0.5"; | ||
public static final String CLIENT_IP_6 = "127.0.0.6"; | ||
public static final String CLIENT_IP_7 = "127.0.0.7"; | ||
public static final String CLIENT_IP_8 = "127.0.0.8"; | ||
public static final String CLIENT_IP_9 = "127.0.0.9"; | ||
|
||
private static final AuthFailureListeners listener = new AuthFailureListeners() | ||
.addRateLimit(new RateLimiting("internal_authentication_backend_limiting").type("ip") | ||
.allowedTries(ALLOWED_TRIES).timeWindowSeconds(TIME_WINDOW_SECONDS).blockExpirySeconds(2).maxBlockedClients(500) | ||
.maxTrackedClients(500)); | ||
|
||
@ClassRule | ||
public static final LocalCluster cluster = new LocalCluster.Builder() | ||
.clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false).authFailureListeners(listener) | ||
.authc(AUTHC_HTTPBASIC_INTERNAL_WITHOUT_CHALLENGE).users(USER_1, USER_2).build(); | ||
|
||
@Test | ||
public void shouldAuthenticateUserWhenBlockadeIsNotActive() { | ||
try(TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_1, CLIENT_IP_2))) { | ||
|
||
HttpResponse response = client.getAuthInfo(); | ||
|
||
response.assertStatusCode(SC_OK); | ||
} | ||
} | ||
|
||
@Test | ||
public void shouldBlockIpAddress() { | ||
authenticateUserWithIncorrectPassword(CLIENT_IP_3, USER_2, ALLOWED_TRIES); | ||
try(TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_2, CLIENT_IP_3))) { | ||
|
||
HttpResponse response = client.getAuthInfo(); | ||
|
||
response.assertStatusCode(SC_UNAUTHORIZED); | ||
} | ||
} | ||
|
||
@Test | ||
public void shouldBlockUsersWhoUseTheSameIpAddress() { | ||
authenticateUserWithIncorrectPassword(CLIENT_IP_4, USER_1, ALLOWED_TRIES); | ||
try(TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_2, CLIENT_IP_4))) { | ||
|
||
HttpResponse response = client.getAuthInfo(); | ||
|
||
response.assertStatusCode(SC_UNAUTHORIZED); | ||
} | ||
} | ||
|
||
@Test | ||
public void testUserShouldBeAbleToAuthenticateFromAnotherNotBlockedIpAddress() { | ||
authenticateUserWithIncorrectPassword(CLIENT_IP_5, USER_1, ALLOWED_TRIES); | ||
try(TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_1, CLIENT_IP_6))) { | ||
HttpResponse response = client.getAuthInfo(); | ||
|
||
response.assertStatusCode(SC_OK); | ||
} | ||
} | ||
|
||
@Test | ||
public void shouldNotBlockIpWhenFailureAuthenticationCountIsLessThanAllowedTries() { | ||
authenticateUserWithIncorrectPassword(CLIENT_IP_7, USER_1, ALLOWED_TRIES - 1); | ||
try(TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_1, CLIENT_IP_7))) { | ||
|
||
HttpResponse response = client.getAuthInfo(); | ||
|
||
response.assertStatusCode(SC_OK); | ||
} | ||
} | ||
|
||
@Test | ||
public void shouldBlockIpWhenFailureAuthenticationCountIsGraterThanAllowedTries() { | ||
authenticateUserWithIncorrectPassword(CLIENT_IP_8, USER_1, ALLOWED_TRIES * 2); | ||
try(TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_1, CLIENT_IP_8))) { | ||
|
||
HttpResponse response = client.getAuthInfo(); | ||
|
||
response.assertStatusCode(SC_UNAUTHORIZED); | ||
} | ||
} | ||
|
||
@Test | ||
public void shouldReleaseIpAddressLock() throws InterruptedException { | ||
authenticateUserWithIncorrectPassword(CLIENT_IP_9, USER_1, ALLOWED_TRIES * 2); | ||
TimeUnit.SECONDS.sleep(TIME_WINDOW_SECONDS); | ||
try(TestRestClient client = cluster.createGenericClientRestClient(userWithSourceIp(USER_1, CLIENT_IP_9))) { | ||
|
||
HttpResponse response = client.getAuthInfo(); | ||
|
||
response.assertStatusCode(SC_OK); | ||
} | ||
} | ||
|
||
private static void authenticateUserWithIncorrectPassword(String sourceIpAddress, User user, int numberOfRequests) { | ||
var clientConfiguration = new TestRestClientConfiguration().username(user.getName()) | ||
.password("incorrect password").sourceInetAddress(sourceIpAddress); | ||
try(TestRestClient client = cluster.createGenericClientRestClient(clientConfiguration)) { | ||
for(int i = 0; i < numberOfRequests; ++i) { | ||
HttpResponse response = client.getAuthInfo(); | ||
|
||
response.assertStatusCode(SC_UNAUTHORIZED); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
116 changes: 116 additions & 0 deletions
116
src/integrationTest/java/org/opensearch/security/UserBruteForceAttacksPreventionTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
*/ | ||
package org.opensearch.security; | ||
|
||
import java.util.concurrent.TimeUnit; | ||
|
||
import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; | ||
import org.junit.ClassRule; | ||
import org.junit.Test; | ||
import org.junit.runner.RunWith; | ||
|
||
import org.opensearch.test.framework.AuthFailureListeners; | ||
import org.opensearch.test.framework.RateLimiting; | ||
import org.opensearch.test.framework.TestSecurityConfig.User; | ||
import org.opensearch.test.framework.cluster.ClusterManager; | ||
import org.opensearch.test.framework.cluster.LocalCluster; | ||
import org.opensearch.test.framework.cluster.TestRestClient; | ||
import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; | ||
|
||
import static org.apache.hc.core5.http.HttpStatus.SC_OK; | ||
import static org.apache.hc.core5.http.HttpStatus.SC_UNAUTHORIZED; | ||
import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; | ||
import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; | ||
|
||
@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) | ||
@ThreadLeakScope(ThreadLeakScope.Scope.NONE) | ||
public class UserBruteForceAttacksPreventionTests { | ||
|
||
private static final User USER_1 = new User("simple-user-1").roles(ALL_ACCESS); | ||
private static final User USER_2 = new User("simple-user-2").roles(ALL_ACCESS); | ||
private static final User USER_3 = new User("simple-user-3").roles(ALL_ACCESS); | ||
private static final User USER_4 = new User("simple-user-4").roles(ALL_ACCESS); | ||
private static final User USER_5 = new User("simple-user-5").roles(ALL_ACCESS); | ||
|
||
public static final int ALLOWED_TRIES = 3; | ||
public static final int TIME_WINDOW_SECONDS = 3; | ||
private static final AuthFailureListeners listener = new AuthFailureListeners() | ||
.addRateLimit(new RateLimiting("internal_authentication_backend_limiting").type("username").authenticationBackend("intern") | ||
.allowedTries(ALLOWED_TRIES).timeWindowSeconds(TIME_WINDOW_SECONDS).blockExpirySeconds(2).maxBlockedClients(500) | ||
.maxTrackedClients(500)); | ||
|
||
@ClassRule | ||
public static final LocalCluster cluster = new LocalCluster.Builder() | ||
.clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false).authFailureListeners(listener) | ||
.authc(AUTHC_HTTPBASIC_INTERNAL).users(USER_1, USER_2, USER_3, USER_4, USER_5).build(); | ||
|
||
@Test | ||
public void shouldAuthenticateUserWhenBlockadeIsNotActive() { | ||
try(TestRestClient client = cluster.getRestClient(USER_1)) { | ||
|
||
HttpResponse response = client.getAuthInfo(); | ||
|
||
response.assertStatusCode(SC_OK); | ||
} | ||
} | ||
|
||
@Test | ||
public void shouldBlockUserWhenNumberOfFailureLoginAttemptIsEqualToLimit() { | ||
authenticateUserWithIncorrectPassword(USER_2, ALLOWED_TRIES); | ||
try(TestRestClient client = cluster.getRestClient(USER_2)) { | ||
HttpResponse response = client.getAuthInfo(); | ||
|
||
response.assertStatusCode(SC_UNAUTHORIZED); | ||
} | ||
} | ||
|
||
@Test | ||
public void shouldBlockUserWhenNumberOfFailureLoginAttemptIsGraterThanLimit() { | ||
authenticateUserWithIncorrectPassword(USER_3, ALLOWED_TRIES * 2); | ||
try(TestRestClient client = cluster.getRestClient(USER_3)) { | ||
HttpResponse response = client.getAuthInfo(); | ||
|
||
response.assertStatusCode(SC_UNAUTHORIZED); | ||
} | ||
} | ||
|
||
@Test | ||
public void shouldNotBlockUserWhenNumberOfLoginAttemptIsBelowLimit() { | ||
authenticateUserWithIncorrectPassword(USER_4, ALLOWED_TRIES - 1); | ||
try(TestRestClient client = cluster.getRestClient(USER_4)) { | ||
HttpResponse response = client.getAuthInfo(); | ||
|
||
response.assertStatusCode(SC_OK); | ||
} | ||
} | ||
|
||
@Test | ||
public void shouldReleaseLock() throws InterruptedException { | ||
authenticateUserWithIncorrectPassword(USER_5, ALLOWED_TRIES); | ||
try(TestRestClient client = cluster.getRestClient(USER_5)) { | ||
HttpResponse response = client.getAuthInfo(); | ||
response.assertStatusCode(SC_UNAUTHORIZED); | ||
TimeUnit.SECONDS.sleep(TIME_WINDOW_SECONDS); | ||
|
||
response = client.getAuthInfo(); | ||
|
||
response.assertStatusCode(SC_OK); | ||
} | ||
} | ||
|
||
private static void authenticateUserWithIncorrectPassword(User user, int numberOfAttempts) { | ||
try(TestRestClient client = cluster.getRestClient(user.getName(), "incorrect password")) { | ||
for(int i = 0; i < numberOfAttempts; ++i) { | ||
HttpResponse response = client.getAuthInfo(); | ||
response.assertStatusCode(SC_UNAUTHORIZED); | ||
} | ||
} | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
src/integrationTest/java/org/opensearch/test/framework/AuthFailureListeners.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
*/ | ||
package org.opensearch.test.framework; | ||
|
||
import java.io.IOException; | ||
import java.util.LinkedHashMap; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
|
||
import org.opensearch.common.xcontent.ToXContentObject; | ||
import org.opensearch.common.xcontent.XContentBuilder; | ||
|
||
public class AuthFailureListeners implements ToXContentObject { | ||
|
||
private Map<String, RateLimiting> limits = new LinkedHashMap<>(); | ||
|
||
public AuthFailureListeners addRateLimit(RateLimiting rateLimiting) { | ||
Objects.requireNonNull(rateLimiting, "Rate limiting is required"); | ||
limits.put(rateLimiting.getName(), rateLimiting); | ||
return this; | ||
} | ||
|
||
@Override | ||
public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException { | ||
xContentBuilder.startObject(); | ||
for(Map.Entry<String, RateLimiting> entry : limits.entrySet()) { | ||
xContentBuilder.field(entry.getKey(), entry.getValue()); | ||
} | ||
xContentBuilder.endObject(); | ||
return xContentBuilder; | ||
} | ||
} |
Oops, something went wrong.