Skip to content
Closed
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import org.opensearch.test.framework.cluster.TestRestClient;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.opensearch.security.api.PatchPayloadHelper.patch;
import static org.opensearch.security.api.PatchPayloadHelper.replaceOp;

Expand Down Expand Up @@ -93,23 +92,6 @@ private void verifyAuthInfoApi(final TestRestClient client) throws Exception {

}

@Test
public void flushCache() throws Exception {
withUser(NEW_USER, client -> {
forbidden(() -> client.get(apiPath("cache")));
forbidden(() -> client.postJson(apiPath("cache"), EMPTY_BODY));
forbidden(() -> client.putJson(apiPath("cache"), EMPTY_BODY));
forbidden(() -> client.delete(apiPath("cache")));
});
withUser(ADMIN_USER_NAME, localCluster.getAdminCertificate(), client -> {
notImplemented(() -> client.get(apiPath("cache")));
notImplemented(() -> client.postJson(apiPath("cache"), EMPTY_BODY));
notImplemented(() -> client.putJson(apiPath("cache"), EMPTY_BODY));
final var response = ok(() -> client.delete(apiPath("cache")));
assertThat(response.getBody(), response.getTextFromJsonBody("/message"), is("Cache flushed successfully."));
});
}

@Test
public void reloadSSLCertsNotAvailable() throws Exception {
withUser(NEW_USER, client -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* 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.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

package org.opensearch.security.api;

import org.junit.Test;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

public class FlushCacheApiIntegrationTest extends AbstractApiIntegrationTest {
private final static String TEST_USER = "testuser";

private String cachePath() {
return super.apiPath("cache");
}

private String cachePath(String user) {
return super.apiPath("cache", "user", user);
}

@Test
public void testFlushCache() throws Exception {
withUser(NEW_USER, client -> {
forbidden(() -> client.get(cachePath()));
forbidden(() -> client.postJson(cachePath(), EMPTY_BODY));
forbidden(() -> client.putJson(cachePath(), EMPTY_BODY));
forbidden(() -> client.delete(cachePath()));
forbidden(() -> client.delete(cachePath(TEST_USER)));
});
withUser(ADMIN_USER_NAME, localCluster.getAdminCertificate(), client -> {
notImplemented(() -> client.get(cachePath()));
notImplemented(() -> client.postJson(cachePath(), EMPTY_BODY));
notImplemented(() -> client.putJson(cachePath(), EMPTY_BODY));
final var deleteAllCacheResponse = ok(() -> client.delete(cachePath()));
assertThat(
deleteAllCacheResponse.getBody(),
deleteAllCacheResponse.getTextFromJsonBody("/message"),
is("Cache flushed successfully.")
);
final var deleteUserCacheResponse = ok(() -> client.delete(cachePath(TEST_USER)));
assertThat(
deleteUserCacheResponse.getBody(),
deleteAllCacheResponse.getTextFromJsonBody("/message"),
is("Cache invalidated for user: " + TEST_USER)
);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class DirectoryInformationTrees {

public static final String CN_GROUP_ADMIN = "admin";
public static final String CN_GROUP_CREW = "crew";
public static final String CN_GROUP_ENTERPRISE = "enterprise";
public static final String CN_GROUP_BRIDGE = "bridge";

public static final String USER_SEARCH = "(uid={0})";
Expand Down Expand Up @@ -120,4 +121,87 @@ class DirectoryInformationTrees {
.classes("groupofuniquenames", "top")
.buildRecord()
.buildLdif();

static final LdifData LDIF_DATA_UPDATED_BACKEND_ROLES = new LdifBuilder().root("o=test.org")
.dc("TEST")
.classes("top", "domain")
.newRecord(DN_PEOPLE_TEST_ORG)
.ou("people")
.classes("organizationalUnit", "top")
.newRecord(DN_OPEN_SEARCH_PEOPLE_TEST_ORG)
.classes("inetOrgPerson")
.cn("Open Search")
.sn("Search")
.uid(USER_OPENS)
.userPassword(PASSWORD_OPEN_SEARCH)
.mail("open.search@example.com")
.ou("Human Resources")
.newRecord(DN_CAPTAIN_SPOCK_PEOPLE_TEST_ORG)
.classes("inetOrgPerson")
.cn("Captain Spock")
.sn(USER_SPOCK)
.uid(USER_SPOCK)
.userPassword(PASSWORD_SPOCK)
.mail("spock@example.com")
.ou("Human Resources")
.newRecord(DN_KIRK_PEOPLE_TEST_ORG)
.classes("inetOrgPerson")
.cn("Kirk")
.sn("Kirk")
.uid(USER_KIRK)
.userPassword(PASSWORD_KIRK)
.mail("spock@example.com")
.ou("Human Resources")
.newRecord(DN_CHRISTPHER_PEOPLE_TEST_ORG)
.classes("inetOrgPerson")
.cn("Christpher")
.sn("Christpher")
.uid("christpher")
.userPassword(PASSWORD_CHRISTPHER)
.mail("christpher@example.com")
.ou("Human Resources")
.newRecord(DN_LEONARD_PEOPLE_TEST_ORG)
.classes("inetOrgPerson")
.cn("Leonard")
.sn("Leonard")
.uid(USER_LEONARD)
.userPassword(PASSWORD_LEONARD)
.mail("leonard@example.com")
.ou("Human Resources")
.newRecord(DN_JEAN_PEOPLE_TEST_ORG)
.classes("inetOrgPerson")
.cn("Jean")
.sn("Jean")
.uid(USER_JEAN)
.userPassword(PASSWORD_JEAN)
.mail("jean@example.com")
.ou("Human Resources")
.newRecord(DN_GROUPS_TEST_ORG)
.ou("groups")
.cn("groupsRoot")
.classes("groupofuniquenames", "top")
.newRecord("cn=admin,ou=groups,o=test.org")
.ou("groups")
.cn(CN_GROUP_ADMIN)
.uniqueMember(DN_KIRK_PEOPLE_TEST_ORG)
.classes("groupofuniquenames", "top")
.newRecord("cn=crew,ou=groups,o=test.org")
.ou("groups")
.cn(CN_GROUP_CREW)
.uniqueMember(DN_CAPTAIN_SPOCK_PEOPLE_TEST_ORG)
.uniqueMember(DN_CHRISTPHER_PEOPLE_TEST_ORG)
.uniqueMember(DN_BRIDGE_GROUPS_TEST_ORG)
.classes("groupofuniquenames", "top")
.newRecord("cn=enterprise,ou=groups,o=test.org")
.cn(CN_GROUP_ENTERPRISE)
.uniqueMember(DN_KIRK_PEOPLE_TEST_ORG)
.uniqueMember(DN_CAPTAIN_SPOCK_PEOPLE_TEST_ORG)
.classes("groupofuniquenames", "top")
.newRecord(DN_BRIDGE_GROUPS_TEST_ORG)
.ou("groups")
.cn(CN_GROUP_BRIDGE)
.uniqueMember(DN_JEAN_PEOPLE_TEST_ORG)
.classes("groupofuniquenames", "top")
.buildRecord()
.buildLdif();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
* 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.http;

import java.util.List;
import java.util.Map;

import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;

import org.opensearch.security.support.ConfigConstants;
import org.opensearch.test.framework.AuthorizationBackend;
import org.opensearch.test.framework.AuthzDomain;
import org.opensearch.test.framework.LdapAuthenticationConfigBuilder;
import org.opensearch.test.framework.LdapAuthorizationConfigBuilder;
import org.opensearch.test.framework.TestSecurityConfig;
import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain;
import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AuthenticationBackend;
import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.HttpAuthenticator;
import org.opensearch.test.framework.certificate.TestCertificates;
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.ldap.EmbeddedLDAPServer;
import org.opensearch.test.framework.log.LogsRule;

import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.opensearch.security.http.DirectoryInformationTrees.CN_GROUP_ADMIN;
import static org.opensearch.security.http.DirectoryInformationTrees.DN_GROUPS_TEST_ORG;
import static org.opensearch.security.http.DirectoryInformationTrees.DN_OPEN_SEARCH_PEOPLE_TEST_ORG;
import static org.opensearch.security.http.DirectoryInformationTrees.DN_PEOPLE_TEST_ORG;
import static org.opensearch.security.http.DirectoryInformationTrees.LDIF_DATA;
import static org.opensearch.security.http.DirectoryInformationTrees.LDIF_DATA_UPDATED_BACKEND_ROLES;
import static org.opensearch.security.http.DirectoryInformationTrees.PASSWORD_KIRK;
import static org.opensearch.security.http.DirectoryInformationTrees.PASSWORD_OPEN_SEARCH;
import static org.opensearch.security.http.DirectoryInformationTrees.PASSWORD_SPOCK;
import static org.opensearch.security.http.DirectoryInformationTrees.USERNAME_ATTRIBUTE;
import static org.opensearch.security.http.DirectoryInformationTrees.USER_KIRK;
import static org.opensearch.security.http.DirectoryInformationTrees.USER_SEARCH;
import static org.opensearch.security.http.DirectoryInformationTrees.USER_SPOCK;
import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED;
import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED;
import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL;
import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.BASIC_AUTH_DOMAIN_ORDER;
import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS;

/**
* Test uses plain (non TLS) connection between OpenSearch and LDAP server.
*/
@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class)
@ThreadLeakScope(ThreadLeakScope.Scope.NONE)
public class LdapAuthenticationCacheTest {

private static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS);

private static final TestCertificates TEST_CERTIFICATES = new TestCertificates();

public static final EmbeddedLDAPServer embeddedLDAPServer = new EmbeddedLDAPServer(
TEST_CERTIFICATES.getRootCertificateData(),
TEST_CERTIFICATES.getLdapCertificateData(),
LDIF_DATA
);

public static LocalCluster cluster = new LocalCluster.Builder().testCertificates(TEST_CERTIFICATES)
.clusterManager(ClusterManager.SINGLENODE)
.anonymousAuth(false)
.nodeSettings(
Map.of(
ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS + "." + ADMIN_USER.getName(),
List.of(USER_KIRK),
SECURITY_RESTAPI_ROLES_ENABLED,
List.of("user_" + ADMIN_USER.getName() + "__" + ALL_ACCESS.getName()),
SECURITY_RESTAPI_ADMIN_ENABLED,
true
)
)
.authc(
new AuthcDomain("ldap", BASIC_AUTH_DOMAIN_ORDER + 1, true).httpAuthenticator(new HttpAuthenticator("basic").challenge(false))
.backend(
new AuthenticationBackend("ldap").config(
() -> LdapAuthenticationConfigBuilder.config()
// this port is available when embeddedLDAPServer is already started, therefore Supplier interface is used to
// postpone
// execution of the code in this block.
.enableSsl(false)
.enableStartTls(false)
.hosts(List.of("localhost:" + embeddedLDAPServer.getLdapNonTlsPort()))
.bindDn(DN_OPEN_SEARCH_PEOPLE_TEST_ORG)
.password(PASSWORD_OPEN_SEARCH)
.userBase(DN_PEOPLE_TEST_ORG)
.userSearch(USER_SEARCH)
.usernameAttribute(USERNAME_ATTRIBUTE)
.build()
)
)
)
.authc(AUTHC_HTTPBASIC_INTERNAL)
.users(ADMIN_USER)
.rolesMapping(new TestSecurityConfig.RoleMapping(ALL_ACCESS.getName()).backendRoles(CN_GROUP_ADMIN))
.authz(
new AuthzDomain("ldap_roles").httpEnabled(true)
.authorizationBackend(
new AuthorizationBackend("ldap").config(
() -> new LdapAuthorizationConfigBuilder().hosts(List.of("localhost:" + embeddedLDAPServer.getLdapNonTlsPort()))
.enableSsl(false)
.bindDn(DN_OPEN_SEARCH_PEOPLE_TEST_ORG)
.password(PASSWORD_OPEN_SEARCH)
.userBase(DN_PEOPLE_TEST_ORG)
.userSearch(USER_SEARCH)
.usernameAttribute(USERNAME_ATTRIBUTE)
.roleBase(DN_GROUPS_TEST_ORG)
.roleSearch("(uniqueMember={0})")
.userRoleAttribute(null)
.userRoleName("disabled")
.roleName("cn")
.resolveNestedRoles(true)
.build()
)
)
)
.build();

@ClassRule
public static RuleChain ruleChain = RuleChain.outerRule(embeddedLDAPServer).around(cluster);

@Rule
public LogsRule logsRule = new LogsRule("com.amazon.dlic.auth.ldap.backend.LDAPAuthenticationBackend");

@Test
public void shouldAuthenticateUserWithLdap_positive() {
try (TestRestClient client = cluster.getRestClient(USER_SPOCK, PASSWORD_SPOCK)) {
TestRestClient.HttpResponse response = client.getAuthInfo();

response.assertStatusCode(200);

assertThat(response.getTextArrayFromJsonBody("/backend_roles"), contains("crew"));
assertThat(response.getTextArrayFromJsonBody("/backend_roles"), not(contains("enterprise")));
}

try (TestRestClient client = cluster.getRestClient(USER_KIRK, PASSWORD_KIRK)) {
TestRestClient.HttpResponse response = client.getAuthInfo();

response.assertStatusCode(200);

assertThat(response.getTextArrayFromJsonBody("/backend_roles"), contains("admin"));
assertThat(response.getTextArrayFromJsonBody("/backend_roles"), not(contains("enterprise")));
}

embeddedLDAPServer.loadLdifData(LDIF_DATA_UPDATED_BACKEND_ROLES);

try (TestRestClient client = cluster.getRestClient(ADMIN_USER)) {
TestRestClient.HttpResponse response = client.delete("_plugins/_security/api/cache/user/spock");

response.assertStatusCode(200);
}

try (TestRestClient client = cluster.getRestClient(USER_SPOCK, PASSWORD_SPOCK)) {
TestRestClient.HttpResponse response = client.getAuthInfo();

response.assertStatusCode(200);

assertThat(response.getTextArrayFromJsonBody("/backend_roles"), contains("enterprise", "crew"));
}

try (TestRestClient client = cluster.getRestClient(USER_KIRK, PASSWORD_KIRK)) {
TestRestClient.HttpResponse response = client.getAuthInfo();

response.assertStatusCode(200);

assertThat(response.getTextArrayFromJsonBody("/backend_roles"), contains("admin"));
assertThat(response.getTextArrayFromJsonBody("/backend_roles"), not(contains("enterprise")));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ protected void after() {
}
}

public void loadLdifData(LdifData ldifData) {
try {
server.loadLdifData(ldifData);
} catch (Exception e) {
throw new RuntimeException("Cannot reload LDIF data.", e);
}
}

public int getLdapNonTlsPort() {
return server.getLdapNonTlsPort();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,8 @@ public void stop() throws InterruptedException {
}
}

private void loadLdifData(LdifData ldifData) throws Exception {
public void loadLdifData(LdifData ldifData) throws Exception {
server.clear();
try (LDIFReader r = new LDIFReader(new BufferedReader(new StringReader(ldifData.getContent())))) {
Entry entry;
while ((entry = r.readEntry()) != null) {
Expand Down
Loading