Skip to content

Commit

Permalink
Add test that demonstrates flush cache for particular user
Browse files Browse the repository at this point in the history
Signed-off-by: Craig Perkins <cwperx@amazon.com>
  • Loading branch information
cwperks committed Dec 18, 2023
1 parent ae8b3c3 commit bafa9bf
Show file tree
Hide file tree
Showing 9 changed files with 382 additions and 68 deletions.
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,193 @@
/*
* 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.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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.RolesMapping;
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 Logger log = LogManager.getLogger(LdapAuthenticationCacheTest.class);

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 RolesMapping(ALL_ACCESS).backendRoles(CN_GROUP_ADMIN))
.authz(
new AuthzDomain("ldap_roles").httpEnabled(true)
.transportEnabled(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
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,12 @@ public class ConfigUpdateRequest extends BaseNodesRequest<ConfigUpdateRequest> {

private String[] configTypes;

private String[] entityNames;

public ConfigUpdateRequest(StreamInput in) throws IOException {
super(in);
this.configTypes = in.readStringArray();
this.entityNames = in.readOptionalStringArray();
}

public ConfigUpdateRequest() {
Expand All @@ -51,10 +54,17 @@ public ConfigUpdateRequest(String[] configTypes) {
setConfigTypes(configTypes);
}

public ConfigUpdateRequest(String configType, String[] entityNames) {
this();
setConfigTypes(new String[] { configType });
setEntityNames(entityNames);
}

@Override
public void writeTo(final StreamOutput out) throws IOException {
super.writeTo(out);
out.writeStringArray(configTypes);
out.writeOptionalStringArray(entityNames);
}

public String[] getConfigTypes() {
Expand All @@ -65,10 +75,20 @@ public void setConfigTypes(final String[] configTypes) {
this.configTypes = configTypes;
}

public String[] getEntityNames() {
return entityNames;
}

public void setEntityNames(final String[] entityNames) {
this.entityNames = entityNames;
}

@Override
public ActionRequestValidationException validate() {
if (configTypes == null || configTypes.length == 0) {
return new ActionRequestValidationException();
} else if (configTypes.length > 1 && (entityNames != null && entityNames.length > 1)) {
return new ActionRequestValidationException();
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
package org.opensearch.security.action.configupdate;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import org.apache.logging.log4j.LogManager;
Expand Down Expand Up @@ -125,8 +126,16 @@ protected ConfigUpdateResponse newResponse(

@Override
protected ConfigUpdateNodeResponse nodeOperation(final NodeConfigUpdateRequest request) {
configurationRepository.reloadConfiguration(CType.fromStringValues((request.request.getConfigTypes())));
backendRegistry.get().invalidateCache();
if (request.request.getConfigTypes() != null
&& request.request.getEntityNames() != null
&& request.request.getConfigTypes().length == 1
&& Arrays.asList(request.request.getConfigTypes()).contains(CType.INTERNALUSERS.toLCString())
&& request.request.getEntityNames().length > 0) {
backendRegistry.get().invalidateUserCache(request.request.getEntityNames());
} else {
configurationRepository.reloadConfiguration(CType.fromStringValues((request.request.getConfigTypes())));
backendRegistry.get().invalidateCache();
}
return new ConfigUpdateNodeResponse(clusterService.localNode(), request.request.getConfigTypes(), null);
}

Expand Down
Loading

0 comments on commit bafa9bf

Please sign in to comment.