diff --git a/src/integrationTest/java/org/opensearch/security/http/LdapTlsAuthenticationTest.java b/src/integrationTest/java/org/opensearch/security/http/LdapTlsAuthenticationTest.java index c37b5e24d5..d218f7c7a6 100644 --- a/src/integrationTest/java/org/opensearch/security/http/LdapTlsAuthenticationTest.java +++ b/src/integrationTest/java/org/opensearch/security/http/LdapTlsAuthenticationTest.java @@ -11,8 +11,10 @@ import java.io.IOException; import java.util.List; +import java.util.Map; import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.apache.hc.core5.http.message.BasicHeader; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; @@ -82,33 +84,40 @@ @ThreadLeakScope(ThreadLeakScope.Scope.NONE) public class LdapTlsAuthenticationTest { - public static final String SONG_INDEX_NAME = "song_lyrics"; + private static final String SONG_INDEX_NAME = "song_lyrics"; - public static final String PERSONAL_INDEX_NAME_SPOCK = "personal-" + USER_SPOCK; - public static final String PERSONAL_INDEX_NAME_KIRK = "personal-" + USER_KIRK; + private static final String HEADER_NAME_IMPERSONATE = "opendistro_security_impersonate_as"; - public static final String POINTER_BACKEND_ROLES = "/backend_roles"; - public static final String POINTER_ROLES = "/roles"; + private static final String PERSONAL_INDEX_NAME_SPOCK = "personal-" + USER_SPOCK; + private static final String PERSONAL_INDEX_NAME_KIRK = "personal-" + USER_KIRK; - public static final String SONG_ID_1 = "l0001"; - public static final String SONG_ID_2 = "l0002"; - public static final String SONG_ID_3 = "l0003"; + private static final String POINTER_BACKEND_ROLES = "/backend_roles"; + private static final String POINTER_ROLES = "/roles"; + private static final String POINTER_USERNAME = "/user_name"; + private static final String POINTER_ERROR_REASON = "/error/reason"; + + private static final String SONG_ID_1 = "l0001"; + private static final String SONG_ID_2 = "l0002"; + private static final String SONG_ID_3 = "l0003"; private static final User ADMIN_USER = new User("admin").roles(ALL_ACCESS); private static final TestCertificates TEST_CERTIFICATES = new TestCertificates(); - public static final Role ROLE_INDEX_ADMINISTRATOR = new Role("index_administrator").indexPermissions("*").on("*"); - public static final Role ROLE_PERSONAL_INDEX_ACCESS = new Role("personal_index_access").indexPermissions("*").on("personal-${attr.ldap.uid}"); - - public static final String POINTER_USERNAME = "/user_name"; + private static final Role ROLE_INDEX_ADMINISTRATOR = new Role("index_administrator").indexPermissions("*").on("*"); + private static final Role ROLE_PERSONAL_INDEX_ACCESS = new Role("personal_index_access").indexPermissions("*").on("personal-${attr.ldap.uid}"); private static final EmbeddedLDAPServer embeddedLDAPServer = new EmbeddedLDAPServer(TEST_CERTIFICATES.getRootCertificateData(), TEST_CERTIFICATES.getLdapCertificateData(), LDIF_DATA); + private static final Map USER_IMPERSONATION_CONFIGURATION = Map.of( + "plugins.security.authcz.rest_impersonation_user." + USER_KIRK, List.of(USER_SPOCK) + ); + private static final LocalCluster cluster = new LocalCluster.Builder() .testCertificates(TEST_CERTIFICATES) .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false) + .nodeSettings(USER_IMPERSONATION_CONFIGURATION) .authc(new AuthcDomain("ldap", BASIC_AUTH_DOMAIN_ORDER + 1, true) .httpAuthenticator(new HttpAuthenticator("basic").challenge(false)) .backend(new AuthenticationBackend("ldap") @@ -310,4 +319,66 @@ public void shouldResolveNestedGroups_negative() { assertThat(backendRoles, not(containsInAnyOrder(CN_GROUP_CREW))); } } + + @Test + public void shouldImpersonateUser_positive() { + try(TestRestClient client = cluster.getRestClient(USER_KIRK, PASSWORD_KIRK)){ + + HttpResponse response = client.getAuthInfo(new BasicHeader(HEADER_NAME_IMPERSONATE, USER_SPOCK)); + + response.assertStatusCode(200); + assertThat(response.getTextFromJsonBody(POINTER_USERNAME), equalTo(USER_SPOCK)); + List backendRoles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES); + assertThat(backendRoles, hasSize(1)); + assertThat(backendRoles, contains(CN_GROUP_CREW)); + } + } + + @Test + public void shouldImpersonateUser_negativeJean() { + try(TestRestClient client = cluster.getRestClient(USER_KIRK, PASSWORD_KIRK)){ + + HttpResponse response = client.getAuthInfo(new BasicHeader(HEADER_NAME_IMPERSONATE, USER_JEAN)); + + response.assertStatusCode(403); + String expectedMessage = String.format("'%s' is not allowed to impersonate as '%s'", USER_KIRK, USER_JEAN); + assertThat(response.getTextFromJsonBody(POINTER_ERROR_REASON), equalTo(expectedMessage)); + } + } + + @Test + public void shouldImpersonateUser_negativeKirk() { + try(TestRestClient client = cluster.getRestClient(USER_JEAN, PASSWORD_JEAN)){ + + HttpResponse response = client.getAuthInfo(new BasicHeader(HEADER_NAME_IMPERSONATE, USER_KIRK)); + + response.assertStatusCode(403); + String expectedMessage = String.format("'%s' is not allowed to impersonate as '%s'", USER_JEAN, USER_KIRK); + assertThat(response.getTextFromJsonBody(POINTER_ERROR_REASON), equalTo(expectedMessage)); + } + } + + @Test + public void shouldAccessImpersonatedUserPersonalIndex_positive() throws IOException { + BasicHeader impersonateHeader = new BasicHeader(HEADER_NAME_IMPERSONATE, USER_SPOCK); + try(RestHighLevelClient client = cluster.getRestHighLevelClient(USER_KIRK, PASSWORD_KIRK, impersonateHeader)){ + SearchRequest request = queryStringQueryRequest(PERSONAL_INDEX_NAME_SPOCK, "*"); + + SearchResponse searchResponse = client.search(request, DEFAULT); + + assertThat(searchResponse, isSuccessfulSearchResponse()); + assertThat(searchResponse, numberOfTotalHitsIsEqualTo(1)); + assertThat(searchResponse, searchHitsContainDocumentWithId(0, PERSONAL_INDEX_NAME_SPOCK, SONG_ID_2)); + } + } + + @Test + public void shouldAccessImpersonatedUserPersonalIndex_negative() throws IOException { + BasicHeader impersonateHeader = new BasicHeader(HEADER_NAME_IMPERSONATE, USER_SPOCK); + try(RestHighLevelClient client = cluster.getRestHighLevelClient(USER_KIRK, PASSWORD_KIRK, impersonateHeader)){ + SearchRequest request = queryStringQueryRequest(PERSONAL_INDEX_NAME_KIRK, "*"); + + assertThatThrownBy(() -> client.search(request, DEFAULT), statusException(FORBIDDEN)); + } + } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java index a48b49a513..9d3c0ba843 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/OpenSearchClientProvider.java @@ -100,7 +100,7 @@ default TestRestClient getRestClient(UserCredentialsHolder user, Header... heade return getRestClient(user.getName(), user.getPassword(), headers); } - default RestHighLevelClient getRestHighLevelClient(String username, String password) { + default RestHighLevelClient getRestHighLevelClient(String username, String password, Header... headers) { return getRestHighLevelClient(new UserCredentialsHolder() { @Override public String getName() { @@ -111,24 +111,27 @@ public String getName() { public String getPassword() { return password; } - }); + }, Arrays.asList(headers)); } + default RestHighLevelClient getRestHighLevelClient(UserCredentialsHolder user) { - + return getRestHighLevelClient(user, Collections.emptySet()); + } + + default RestHighLevelClient getRestHighLevelClient(UserCredentialsHolder user, Collection defaultHeaders) { + BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials(new AuthScope(null, -1), new UsernamePasswordCredentials(user.getName(), user.getPassword().toCharArray())); - return getRestHighLevelClient(credentialsProvider, Collections.emptySet()); + return getRestHighLevelClient(credentialsProvider, defaultHeaders); } default RestHighLevelClient getRestHighLevelClient(Collection defaultHeaders) { - - - return getRestHighLevelClient(null, defaultHeaders); + return getRestHighLevelClient((BasicCredentialsProvider)null, defaultHeaders); } - private RestHighLevelClient getRestHighLevelClient(BasicCredentialsProvider credentialsProvider, Collection defaultHeaders) { + default RestHighLevelClient getRestHighLevelClient(BasicCredentialsProvider credentialsProvider, Collection defaultHeaders) { RestClientBuilder.HttpClientConfigCallback configCallback = httpClientBuilder -> { TlsStrategy tlsStrategy = ClientTlsStrategyBuilder .create() @@ -152,6 +155,7 @@ public TlsDetails create(final SSLEngine sslEngine) { } httpClientBuilder.setDefaultHeaders(defaultHeaders); httpClientBuilder.setConnectionManager(cm); + httpClientBuilder.setDefaultHeaders(defaultHeaders); return httpClientBuilder; }; @@ -159,6 +163,7 @@ public TlsDetails create(final SSLEngine sslEngine) { RestClientBuilder builder = RestClient.builder(new HttpHost("https", httpAddress.getHostString(), httpAddress.getPort())) .setHttpClientConfigCallback(configCallback); + return new RestHighLevelClient(builder); } diff --git a/src/integrationTest/resources/log4j2-test.properties b/src/integrationTest/resources/log4j2-test.properties index 02254e3b98..22b9d0f0a1 100644 --- a/src/integrationTest/resources/log4j2-test.properties +++ b/src/integrationTest/resources/log4j2-test.properties @@ -29,8 +29,3 @@ logger.auditlogs.level = info logger.httpjwtauthenticator.name = com.amazon.dlic.auth.http.jwt.HTTPJwtAuthenticator logger.httpjwtauthenticator.level = debug logger.httpjwtauthenticator.appenderRef.capturing.ref = logCapturingAppender - -# com.amazon.dlic.auth.ldap.backend.LDAPAuthorizationBackend - -logger.ldap.name=com.amazon.dlic.auth.ldap.backend.LDAPAuthorizationBackend -logger.ldap.level=TRACE