Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code coverage of OBO Authentication #3428

Merged
merged 25 commits into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,51 @@
import org.apache.cxf.rs.security.jose.jwk.JsonWebKey;
import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactConsumer;
import org.apache.cxf.rs.security.jose.jwt.JwtToken;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.opensearch.common.settings.Settings;
import org.opensearch.security.support.ConfigConstants;

import java.util.List;
import java.util.Optional;
import java.util.function.LongSupplier;

import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

public class JwtVendorTest {
private Appender mockAppender;
private ArgumentCaptor<LogEvent> logEventCaptor;

@Test
public void testJsonWebKeyPropertiesSetFromJwkSettings() throws Exception {
Settings settings = Settings.builder().put("jwt.key.key1", "value1").put("jwt.key.key2", "value2").build();

JsonWebKey jwk = JwtVendor.createJwkFromSettings(settings);

Assert.assertEquals("value1", jwk.getProperty("key1"));
Assert.assertEquals("value2", jwk.getProperty("key2"));
}

@Test
public void testJsonWebKeyPropertiesSetFromSettings() {
Settings jwkSettings = Settings.builder().put("key1", "value1").put("key2", "value2").build();

JsonWebKey jwk = new JsonWebKey();
for (String key : jwkSettings.keySet()) {
jwk.setProperty(key, jwkSettings.get(key));
}

Assert.assertEquals("value1", jwk.getProperty("key1"));
Assert.assertEquals("value2", jwk.getProperty("key2"));
}

@Test
public void testCreateJwkFromSettings() throws Exception {
Expand Down Expand Up @@ -124,7 +159,7 @@ public void testCreateJwtWithRoleSecurityMode() throws Exception {
}

@Test
public void testCreateJwtWithBadExpiry() {
public void testCreateJwtWithNegativeExpiry() {
String issuer = "cluster_0";
String subject = "admin";
String audience = "audience_0";
Expand All @@ -144,6 +179,32 @@ public void testCreateJwtWithBadExpiry() {
Assert.assertEquals("java.lang.Exception: The expiration time should be a positive integer", exception.getMessage());
}

@Test
public void testCreateJwtWithExceededExpiry() throws Exception {
String issuer = "cluster_0";
String subject = "admin";
String audience = "audience_0";
List<String> roles = List.of("IT", "HR");
List<String> backendRoles = List.of("Sales", "Support");
int expirySeconds = 900;
LongSupplier currentTime = () -> (long) 100;
String claimsEncryptionKey = RandomStringUtils.randomAlphanumeric(16);
Settings settings = Settings.builder().put("signing_key", "abc123").put("encryption_key", claimsEncryptionKey).build();
JwtVendor jwtVendor = new JwtVendor(settings, Optional.of(currentTime));

Throwable exception = Assert.assertThrows(RuntimeException.class, () -> {
try {
jwtVendor.createJwt(issuer, subject, audience, expirySeconds, roles, backendRoles, true);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
Assert.assertEquals(
"java.lang.Exception: The provided expiration time exceeds the maximum allowed duration of 600 seconds",
exception.getMessage()
);
}

@Test
public void testCreateJwtWithBadEncryptionKey() {
String issuer = "cluster_0";
Expand Down Expand Up @@ -184,4 +245,40 @@ public void testCreateJwtWithBadRoles() {
});
Assert.assertEquals("java.lang.Exception: Roles cannot be null", exception.getMessage());
}

@Test
public void testCreateJwtLogsCorrectly() throws Exception {
mockAppender = Mockito.mock(Appender.class);
logEventCaptor = ArgumentCaptor.forClass(LogEvent.class);
Mockito.when(mockAppender.getName()).thenReturn("MockAppender");
Mockito.when(mockAppender.isStarted()).thenReturn(true);
Logger logger = (Logger) LogManager.getLogger(JwtVendor.class);
logger.addAppender(mockAppender);
logger.setLevel(Level.DEBUG);

// Mock settings and other required dependencies
LongSupplier currentTime = () -> (long) 100;
String claimsEncryptionKey = RandomStringUtils.randomAlphanumeric(16);
Settings settings = Settings.builder().put("signing_key", "abc123").put("encryption_key", claimsEncryptionKey).build();

String issuer = "cluster_0";
String subject = "admin";
String audience = "audience_0";
List<String> roles = List.of("IT", "HR");
List<String> backendRoles = List.of("Sales", "Support");
int expirySeconds = 300;

JwtVendor jwtVendor = new JwtVendor(settings, Optional.of(currentTime));

jwtVendor.createJwt(issuer, subject, audience, expirySeconds, roles, backendRoles, false);

verify(mockAppender, times(1)).append(logEventCaptor.capture());

LogEvent logEvent = logEventCaptor.getValue();
String logMessage = logEvent.getMessage().getFormattedMessage();
Assert.assertTrue(logMessage.startsWith("Created JWT:"));

String[] parts = logMessage.split("\\.");
Assert.assertTrue(parts.length >= 3);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,7 @@
package org.opensearch.security.http;

import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
RyanL1997 marked this conversation as resolved.
Show resolved Hide resolved

import javax.crypto.SecretKey;

Expand All @@ -30,11 +26,21 @@
import org.junit.Assert;
import org.junit.Test;

import org.mockito.Mockito;
import org.opensearch.SpecialPermission;
import org.opensearch.common.settings.Settings;
import org.opensearch.rest.RestRequest;
import org.opensearch.security.authtoken.jwt.EncryptionDecryptionUtil;
import org.opensearch.security.filter.SecurityRequest;
import org.opensearch.security.filter.SecurityResponse;
import org.opensearch.security.user.AuthCredentials;
import org.opensearch.security.util.FakeRestRequest;

import static org.hamcrest.Matchers.equalTo;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.opensearch.rest.RestRequest.Method.POST;
import static org.opensearch.rest.RestRequest.Method.PUT;

public class OnBehalfOfAuthenticatorTest {
final static String clusterName = "cluster_0";
Expand All @@ -47,6 +53,23 @@ public class OnBehalfOfAuthenticatorTest {
final static String signingKeyB64Encoded = BaseEncoding.base64().encode(signingKey.getBytes(StandardCharsets.UTF_8));
final static SecretKey secretKey = Keys.hmacShaKeyFor(signingKeyB64Encoded.getBytes(StandardCharsets.UTF_8));

private static final String ON_BEHALF_OF_SUFFIX = "api/generateonbehalfoftoken";
private static final String ACCOUNT_SUFFIX = "api/account";

@Test
public void testReRequestAuthenticationReturnsEmptyOptional() {
OnBehalfOfAuthenticator authenticator = new OnBehalfOfAuthenticator(defaultSettings(), clusterName);
Optional<SecurityResponse> result = authenticator.reRequestAuthentication(null, null);
Assert.assertFalse(result.isPresent());
}

@Test
public void testGetTypeReturnsExpectedType() {
OnBehalfOfAuthenticator authenticator = new OnBehalfOfAuthenticator(defaultSettings(), clusterName);
String type = authenticator.getType();
Assert.assertEquals("onbehalfof_jwt", type);
}

@Test
public void testNoKey() {
Exception exception = Assert.assertThrows(
Expand Down Expand Up @@ -211,6 +234,24 @@ public void testBearerWrongPosition() throws Exception {
Assert.assertNull(credentials);
}

@Test
public void testSecurityManagerCheck() {
SecurityManager mockSecurityManager = mock(SecurityManager.class);
System.setSecurityManager(mockSecurityManager);

OnBehalfOfAuthenticator jwtAuth = new OnBehalfOfAuthenticator(defaultSettings(), clusterName);
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer someToken");

try {
jwtAuth.extractCredentials(new FakeRestRequest(headers, new HashMap<>()).asSecurityRequest(), null);
} finally {
System.setSecurityManager(null);
}

verify(mockSecurityManager, times(2)).checkPermission(any(SpecialPermission.class));
}

@Test
public void testBasicAuthHeader() throws Exception {
String jwsToken = Jwts.builder()
Expand All @@ -231,7 +272,7 @@ public void testBasicAuthHeader() throws Exception {
}

@Test
public void testRoles() throws Exception {
public void testPlainTextedRolesFromDrClaim() {

final AuthCredentials credentials = extractCredentialsFromJwtHeader(
signingKeyB64Encoded,
Expand All @@ -246,6 +287,42 @@ public void testRoles() throws Exception {
Assert.assertEquals(0, credentials.getBackendRoles().size());
}

@Test
public void testBackendRolesExtraction() {
String rolesString = "role1, role2 ,role3,role4 , role5";

final AuthCredentials credentials = extractCredentialsFromJwtHeader(
signingKeyB64Encoded,
claimsEncryptionKey,
Jwts.builder().setIssuer(clusterName).setSubject("Test User").setAudience("audience_0").claim("br", rolesString),
true
);

Assert.assertNotNull(credentials);

Set<String> expectedBackendRoles = new HashSet<>(Arrays.asList("role1", "role2", "role3", "role4", "role5"));
Set<String> actualBackendRoles = credentials.getBackendRoles();

Assert.assertTrue(actualBackendRoles.containsAll(expectedBackendRoles));
}

@Test
public void testRolesDecryptionFromErClaim() {
EncryptionDecryptionUtil util = new EncryptionDecryptionUtil(claimsEncryptionKey);
String encryptedRole = util.encrypt("admin,developer");

final AuthCredentials credentials = extractCredentialsFromJwtHeader(
signingKeyB64Encoded,
claimsEncryptionKey,
Jwts.builder().setIssuer(clusterName).setSubject("Test User").setAudience("audience_0").claim("er", encryptedRole),
true
);

Assert.assertNotNull(credentials);
List<String> expectedRoles = Arrays.asList("admin", "developer");
Assert.assertTrue(credentials.getSecurityRoles().containsAll(expectedRoles));
}

@Test
public void testNullClaim() throws Exception {

Expand Down Expand Up @@ -306,6 +383,19 @@ public void testWrongSubjectKey() throws Exception {
Assert.assertNull(credentials);
}

@Test
public void testMissingAudienceClaim() throws Exception {

final AuthCredentials credentials = extractCredentialsFromJwtHeader(
signingKeyB64Encoded,
claimsEncryptionKey,
Jwts.builder().setIssuer(clusterName).setSubject("Test User").claim("roles", "role1,role2"),
false
);

Assert.assertNull(credentials);
}

@Test
public void testExp() throws Exception {

Expand Down Expand Up @@ -378,6 +468,26 @@ public void testDifferentIssuer() throws Exception {
Assert.assertNull(credentials);
}

@Test
public void testExtractCredentialsForDisallowedRequest() {
OnBehalfOfAuthenticator jwtAuth = new OnBehalfOfAuthenticator(defaultSettings(), clusterName);

AuthCredentials credentials = testEndpoint(jwtAuth, ON_BEHALF_OF_SUFFIX, String.valueOf(POST));
Assert.assertNull(credentials);

credentials = testEndpoint(jwtAuth, ACCOUNT_SUFFIX, String.valueOf(PUT));
Assert.assertNull(credentials);
}

private AuthCredentials testEndpoint(OnBehalfOfAuthenticator jwtAuth, String endpoint, String httpMethod) {
SecurityRequest mockedRequest = Mockito.mock(SecurityRequest.class);
Mockito.when(mockedRequest.header(HttpHeaders.AUTHORIZATION)).thenReturn("Bearer someToken");
Mockito.when(mockedRequest.method()).thenReturn(RestRequest.Method.valueOf(httpMethod));
Mockito.when(mockedRequest.path()).thenReturn("/some_prefix/" + endpoint);

return jwtAuth.extractCredentials(mockedRequest, null);
}

/** extracts a default user credential from a request header */
private AuthCredentials extractCredentialsFromJwtHeader(
final String signingKeyB64Encoded,
Expand Down
Loading