Skip to content

Commit

Permalink
Update imports for files refactored in core PR #8157 (opensearch-proj…
Browse files Browse the repository at this point in the history
…ect#3003)

* Update imports for files refactored in core PR #8157

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Update references to old packages in test files

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Get remaining bad imports in integrationTest

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Update log4j in bwc build.gradle

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Use versions.log4j

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Also reference guava version

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Update integtest.sh

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Update tests that expect certain amount of headers in a response

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Empty commit

Signed-off-by: Craig Perkins <cwperx@amazon.com>

---------

Signed-off-by: Craig Perkins <cwperx@amazon.com>
(cherry picked from commit 37aacdc)
  • Loading branch information
cwperks authored and DarshitChanpura committed Jul 19, 2023
1 parent e2a3a38 commit 543f5dc
Show file tree
Hide file tree
Showing 120 changed files with 6,172 additions and 137 deletions.
4 changes: 2 additions & 2 deletions bwc-test/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ repositories {
}

dependencies {
testImplementation 'com.google.guava:guava:30.0-jre'
testImplementation "com.google.guava:guava:${versions.guava}"
testImplementation "org.opensearch.test:framework:${opensearch_version}"
testImplementation 'org.apache.logging.log4j:log4j-core:2.17.1'
testImplementation "org.apache.logging.log4j:log4j-core:${versions.log4j}"
}

loggerUsageCheck.enabled = false
Expand Down
4 changes: 2 additions & 2 deletions scripts/integtest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function usage() {
echo -e "-v OPENSEARCH_VERSION\t, no defaults"
echo -e "-n SNAPSHOT\t, defaults to false"
echo -e "-m CLUSTER_NAME\t, defaults to docker-cluster"
echo -e "-u COMMON_UTILS_VERSION\t, defaults to 2.9.0.0-SNAPSHOT"
echo -e "-u COMMON_UTILS_VERSION\t, defaults to 3.0.0.0-SNAPSHOT"
echo "--------------------------------------------------------------------------"
}

Expand Down Expand Up @@ -101,7 +101,7 @@ then
fi
if [ -z "$COMMON_UTILS_VERSION" ]
then
COMMON_UTILS_VERSION="2.9.0.0-SNAPSHOT"
COMMON_UTILS_VERSION="3.0.0.0-SNAPSHOT"
fi

USERNAME=`echo $CREDENTIAL | awk -F ':' '{print $1}'`
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2,719 changes: 2,719 additions & 0 deletions src/integrationTest/java/org/opensearch/security/SearchOperationTest.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
/*
* 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.io.IOException;
import java.security.KeyPair;
import java.util.Base64;
import java.util.List;
import java.util.Map;

import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.message.BasicHeader;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.opensearch.action.search.SearchRequest;
import org.opensearch.action.search.SearchResponse;
import org.opensearch.client.Client;
import org.opensearch.client.RestHighLevelClient;
import org.opensearch.test.framework.JwtConfigBuilder;
import org.opensearch.test.framework.TestSecurityConfig;
import org.opensearch.test.framework.TestSecurityConfig.Role;
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.log.LogsRule;

import static java.nio.charset.StandardCharsets.US_ASCII;
import static org.apache.http.HttpHeaders.AUTHORIZATION;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.opensearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE;
import static org.opensearch.client.RequestOptions.DEFAULT;
import static org.opensearch.core.rest.RestStatus.FORBIDDEN;
import static org.opensearch.security.Song.FIELD_TITLE;
import static org.opensearch.security.Song.QUERY_TITLE_MAGNUM_OPUS;
import static org.opensearch.security.Song.SONGS;
import static org.opensearch.security.Song.TITLE_MAGNUM_OPUS;
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;
import static org.opensearch.test.framework.cluster.SearchRequestFactory.queryStringQueryRequest;
import static org.opensearch.test.framework.matcher.ExceptionMatcherAssert.assertThatThrownBy;
import static org.opensearch.test.framework.matcher.OpenSearchExceptionMatchers.statusException;
import static org.opensearch.test.framework.matcher.SearchResponseMatchers.isSuccessfulSearchResponse;
import static org.opensearch.test.framework.matcher.SearchResponseMatchers.numberOfTotalHitsIsEqualTo;
import static org.opensearch.test.framework.matcher.SearchResponseMatchers.searchHitContainsFieldWithValue;
import static org.opensearch.test.framework.matcher.SearchResponseMatchers.searchHitsContainDocumentWithId;

@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class)
@ThreadLeakScope(ThreadLeakScope.Scope.NONE)
public class JwtAuthenticationTests {

public static final String CLAIM_USERNAME = "preferred-username";
public static final String CLAIM_ROLES = "backend-user-roles";

public static final String USER_SUPERHERO = "superhero";
public static final String USERNAME_ROOT = "root";
public static final String ROLE_ADMIN = "role_admin";
public static final String ROLE_DEVELOPER = "role_developer";
public static final String ROLE_QA = "role_qa";
public static final String ROLE_CTO = "role_cto";
public static final String ROLE_CEO = "role_ceo";
public static final String ROLE_VP = "role_vp";
public static final String POINTER_BACKEND_ROLES = "/backend_roles";
public static final String POINTER_USERNAME = "/user_name";

public static final String QA_DEPARTMENT = "qa-department";

public static final String CLAIM_DEPARTMENT = "department";

public static final String DEPARTMENT_SONG_INDEX_PATTERN = String.format("song_lyrics_${attr.jwt.%s}", CLAIM_DEPARTMENT);

public static final String QA_SONG_INDEX_NAME = String.format("song_lyrics_%s", QA_DEPARTMENT);

private static final KeyPair KEY_PAIR = Keys.keyPairFor(SignatureAlgorithm.RS256);
private static final String PUBLIC_KEY = new String(Base64.getEncoder().encode(KEY_PAIR.getPublic().getEncoded()), US_ASCII);

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

private static final String JWT_AUTH_HEADER = "jwt-auth";

private static final JwtAuthorizationHeaderFactory tokenFactory = new JwtAuthorizationHeaderFactory(
KEY_PAIR.getPrivate(),
CLAIM_USERNAME,
CLAIM_ROLES,
JWT_AUTH_HEADER
);

public static final TestSecurityConfig.AuthcDomain JWT_AUTH_DOMAIN = new TestSecurityConfig.AuthcDomain(
"jwt",
BASIC_AUTH_DOMAIN_ORDER - 1
).jwtHttpAuthenticator(
new JwtConfigBuilder().jwtHeader(JWT_AUTH_HEADER).signingKey(PUBLIC_KEY).subjectKey(CLAIM_USERNAME).rolesKey(CLAIM_ROLES)
).backend("noop");
public static final String SONG_ID_1 = "song-id-01";

public static final Role DEPARTMENT_SONG_LISTENER_ROLE = new Role("department-song-listener-role").indexPermissions(
"indices:data/read/search"
).on(DEPARTMENT_SONG_INDEX_PATTERN);

@ClassRule
public static final LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE)
.anonymousAuth(false)
.nodeSettings(
Map.of("plugins.security.restapi.roles_enabled", List.of("user_" + ADMIN_USER.getName() + "__" + ALL_ACCESS.getName()))
)
.authc(AUTHC_HTTPBASIC_INTERNAL)
.users(ADMIN_USER)
.roles(DEPARTMENT_SONG_LISTENER_ROLE)
.authc(JWT_AUTH_DOMAIN)
.build();

@Rule
public LogsRule logsRule = new LogsRule("com.amazon.dlic.auth.http.jwt.HTTPJwtAuthenticator");

@BeforeClass
public static void createTestData() {
try (Client client = cluster.getInternalNodeClient()) {
client.prepareIndex(QA_SONG_INDEX_NAME).setId(SONG_ID_1).setRefreshPolicy(IMMEDIATE).setSource(SONGS[0].asMap()).get();
}
try (TestRestClient client = cluster.getRestClient(ADMIN_USER)) {
client.createRoleMapping(ROLE_VP, DEPARTMENT_SONG_LISTENER_ROLE.getName());
}
}

@Test
public void shouldAuthenticateWithJwtToken_positive() {
try (TestRestClient client = cluster.getRestClient(tokenFactory.generateValidToken(USER_SUPERHERO))) {

HttpResponse response = client.getAuthInfo();

response.assertStatusCode(200);
String username = response.getTextFromJsonBody(POINTER_USERNAME);
assertThat(username, equalTo(USER_SUPERHERO));
}
}

@Test
public void shouldAuthenticateWithJwtToken_positiveWithAnotherUsername() {
try (TestRestClient client = cluster.getRestClient(tokenFactory.generateValidToken(USERNAME_ROOT))) {

HttpResponse response = client.getAuthInfo();

response.assertStatusCode(200);
String username = response.getTextFromJsonBody(POINTER_USERNAME);
assertThat(username, equalTo(USERNAME_ROOT));
}
}

@Test
public void shouldAuthenticateWithJwtToken_failureLackingUserName() {
try (TestRestClient client = cluster.getRestClient(tokenFactory.generateTokenWithoutPreferredUsername(USER_SUPERHERO))) {

HttpResponse response = client.getAuthInfo();

response.assertStatusCode(401);
logsRule.assertThatContainExactly("No subject found in JWT token");
}
}

@Test
public void shouldAuthenticateWithJwtToken_failureExpiredToken() {
try (TestRestClient client = cluster.getRestClient(tokenFactory.generateExpiredToken(USER_SUPERHERO))) {

HttpResponse response = client.getAuthInfo();

response.assertStatusCode(401);
logsRule.assertThatContainExactly("Invalid or expired JWT token.");
}
}

@Test
public void shouldAuthenticateWithJwtToken_failureIncorrectFormatOfToken() {
Header header = new BasicHeader(AUTHORIZATION, "not.a.token");
try (TestRestClient client = cluster.getRestClient(header)) {

HttpResponse response = client.getAuthInfo();

response.assertStatusCode(401);
logsRule.assertThatContainExactly(String.format("No JWT token found in '%s' header header", JWT_AUTH_HEADER));
}
}

@Test
public void shouldAuthenticateWithJwtToken_failureIncorrectSignature() {
KeyPair incorrectKeyPair = Keys.keyPairFor(SignatureAlgorithm.RS256);
Header header = tokenFactory.generateTokenSignedWithKey(incorrectKeyPair.getPrivate(), USER_SUPERHERO);
try (TestRestClient client = cluster.getRestClient(header)) {

HttpResponse response = client.getAuthInfo();

response.assertStatusCode(401);
logsRule.assertThatContainExactly("Invalid or expired JWT token.");
}
}

@Test
public void shouldReadRolesFromToken_positiveFirstRoleSet() {
Header header = tokenFactory.generateValidToken(USER_SUPERHERO, ROLE_ADMIN, ROLE_DEVELOPER, ROLE_QA);
try (TestRestClient client = cluster.getRestClient(header)) {

HttpResponse response = client.getAuthInfo();

response.assertStatusCode(200);
List<String> roles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES);
assertThat(roles, hasSize(3));
assertThat(roles, containsInAnyOrder(ROLE_ADMIN, ROLE_DEVELOPER, ROLE_QA));
}
}

@Test
public void shouldReadRolesFromToken_positiveSecondRoleSet() {
Header header = tokenFactory.generateValidToken(USER_SUPERHERO, ROLE_CTO, ROLE_CEO, ROLE_VP);
try (TestRestClient client = cluster.getRestClient(header)) {

HttpResponse response = client.getAuthInfo();

response.assertStatusCode(200);
List<String> roles = response.getTextArrayFromJsonBody(POINTER_BACKEND_ROLES);
assertThat(roles, hasSize(3));
assertThat(roles, containsInAnyOrder(ROLE_CTO, ROLE_CEO, ROLE_VP));
}
}

@Test
public void shouldExposeTokenClaimsAsUserAttributes_positive() throws IOException {
String[] roles = { ROLE_VP };
Map<String, Object> additionalClaims = Map.of(CLAIM_DEPARTMENT, QA_DEPARTMENT);
Header header = tokenFactory.generateValidTokenWithCustomClaims(USER_SUPERHERO, roles, additionalClaims);
try (RestHighLevelClient client = cluster.getRestHighLevelClient(List.of(header))) {
SearchRequest searchRequest = queryStringQueryRequest(QA_SONG_INDEX_NAME, QUERY_TITLE_MAGNUM_OPUS);

SearchResponse response = client.search(searchRequest, DEFAULT);

assertThat(response, isSuccessfulSearchResponse());
assertThat(response, numberOfTotalHitsIsEqualTo(1));
assertThat(response, searchHitsContainDocumentWithId(0, QA_SONG_INDEX_NAME, SONG_ID_1));
assertThat(response, searchHitContainsFieldWithValue(0, FIELD_TITLE, TITLE_MAGNUM_OPUS));
}
}

@Test
public void shouldExposeTokenClaimsAsUserAttributes_negative() throws IOException {
String[] roles = { ROLE_VP };
Map<String, Object> additionalClaims = Map.of(CLAIM_DEPARTMENT, "department-without-access-to-qa-song-index");
Header header = tokenFactory.generateValidTokenWithCustomClaims(USER_SUPERHERO, roles, additionalClaims);
try (RestHighLevelClient client = cluster.getRestHighLevelClient(List.of(header))) {
SearchRequest searchRequest = queryStringQueryRequest(QA_SONG_INDEX_NAME, QUERY_TITLE_MAGNUM_OPUS);

assertThatThrownBy(() -> client.search(searchRequest, DEFAULT), statusException(FORBIDDEN));
}
}
}
Loading

0 comments on commit 543f5dc

Please sign in to comment.