Skip to content

Commit

Permalink
Allow multiple signing keys to be provided
Browse files Browse the repository at this point in the history
Signed-off-by: Stephen Crawford <[email protected]>
  • Loading branch information
stephen-crawford committed Aug 16, 2024
1 parent 88d13d2 commit 17dcd76
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -90,25 +90,36 @@ public class JwtAuthenticationTests {

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);
private static final KeyPair KEY_PAIR1 = Keys.keyPairFor(SignatureAlgorithm.RS256);
private static final String PUBLIC_KEY1 = new String(Base64.getEncoder().encode(KEY_PAIR1.getPublic().getEncoded()), US_ASCII);

private static final KeyPair KEY_PAIR2 = Keys.keyPairFor(SignatureAlgorithm.RS256);
private static final String PUBLIC_KEY2 = new String(Base64.getEncoder().encode(KEY_PAIR2.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(),
private static final JwtAuthorizationHeaderFactory tokenFactory1 = new JwtAuthorizationHeaderFactory(
KEY_PAIR1.getPrivate(),
CLAIM_USERNAME,
CLAIM_ROLES,
JWT_AUTH_HEADER
);

private static final JwtAuthorizationHeaderFactory tokenFactory2 = new JwtAuthorizationHeaderFactory(
KEY_PAIR2.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)
new JwtConfigBuilder().jwtHeader(JWT_AUTH_HEADER).signingKey(List.of(PUBLIC_KEY1, PUBLIC_KEY2)).subjectKey(CLAIM_USERNAME).rolesKey(CLAIM_ROLES)
).backend("noop");
public static final String SONG_ID_1 = "song-id-01";

Expand Down Expand Up @@ -143,7 +154,7 @@ public static void createTestData() {

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

HttpResponse response = client.getAuthInfo();

Expand All @@ -155,7 +166,7 @@ public void shouldAuthenticateWithJwtToken_positive() {

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

HttpResponse response = client.getAuthInfo();

Expand All @@ -167,7 +178,7 @@ public void shouldAuthenticateWithJwtToken_positiveWithAnotherUsername() {

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

HttpResponse response = client.getAuthInfo();

Expand All @@ -178,7 +189,7 @@ public void shouldAuthenticateWithJwtToken_failureLackingUserName() {

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

HttpResponse response = client.getAuthInfo();

Expand All @@ -202,7 +213,7 @@ public void shouldAuthenticateWithJwtToken_failureIncorrectFormatOfToken() {
@Test
public void shouldAuthenticateWithJwtToken_failureIncorrectSignature() {
KeyPair incorrectKeyPair = Keys.keyPairFor(SignatureAlgorithm.RS256);
Header header = tokenFactory.generateTokenSignedWithKey(incorrectKeyPair.getPrivate(), USER_SUPERHERO);
Header header = tokenFactory1.generateTokenSignedWithKey(incorrectKeyPair.getPrivate(), USER_SUPERHERO);
try (TestRestClient client = cluster.getRestClient(header)) {

HttpResponse response = client.getAuthInfo();
Expand All @@ -214,7 +225,7 @@ public void shouldAuthenticateWithJwtToken_failureIncorrectSignature() {

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

HttpResponse response = client.getAuthInfo();
Expand All @@ -228,7 +239,7 @@ public void shouldReadRolesFromToken_positiveFirstRoleSet() {

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

HttpResponse response = client.getAuthInfo();
Expand All @@ -244,7 +255,7 @@ public void shouldReadRolesFromToken_positiveSecondRoleSet() {
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);
Header header = tokenFactory1.generateValidTokenWithCustomClaims(USER_SUPERHERO, roles, additionalClaims);
try (RestHighLevelClient client = cluster.getRestHighLevelClient(List.of(header))) {
SearchRequest searchRequest = queryStringQueryRequest(QA_SONG_INDEX_NAME, QUERY_TITLE_MAGNUM_OPUS);

Expand All @@ -261,11 +272,37 @@ public void shouldExposeTokenClaimsAsUserAttributes_positive() throws IOExceptio
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);
Header header = tokenFactory1.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));
}
}


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

HttpResponse response = client.getAuthInfo();

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

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

HttpResponse response = client.getAuthInfo();

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/
package org.opensearch.test.framework;

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

Expand All @@ -19,7 +20,7 @@
public class JwtConfigBuilder {
private String jwtHeader;
private String jwtUrlParameter;
private String signingKey;
private List<String> signingKeys;
private String subjectKey;
private String rolesKey;

Expand All @@ -33,8 +34,8 @@ public JwtConfigBuilder jwtUrlParameter(String jwtUrlParameter) {
return this;
}

public JwtConfigBuilder signingKey(String signingKey) {
this.signingKey = signingKey;
public JwtConfigBuilder signingKey(List<String> signingKeys) {
this.signingKeys = signingKeys;
return this;
}

Expand All @@ -50,10 +51,10 @@ public JwtConfigBuilder rolesKey(String rolesKey) {

public Map<String, Object> build() {
Builder<String, Object> builder = new Builder<>();
if (Objects.isNull(signingKey)) {
if (Objects.isNull(signingKeys)) {
throw new IllegalStateException("Signing key is required.");
}
builder.put("signing_key", signingKey);
builder.put("signing_key", signingKeys);
if (isNoneBlank(jwtHeader)) {
builder.put("jwt_header", jwtHeader);
}
Expand Down

0 comments on commit 17dcd76

Please sign in to comment.