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

[Feature/Extension] Restrict OBO token's usage for certain endpoints #3008

Merged
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.message.BasicHeader;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand Down Expand Up @@ -55,8 +56,15 @@ public class OnBehalfOfJwtAuthenticationTest {
private static final String encryptionKey = Base64.getEncoder().encodeToString("encryptionKey".getBytes(StandardCharsets.UTF_8));
public static final String ADMIN_USER_NAME = "admin";
public static final String DEFAULT_PASSWORD = "secret";
public static final String NEW_PASSWORD = "testPassword123!!";
public static final String OBO_TOKEN_REASON = "{\"reason\":\"Test generation\"}";
public static final String OBO_ENDPOINT_PREFIX = "_plugins/_security/api/user/onbehalfof";
public static final String OBO_REASON = "{\"reason\":\"Testing\", \"service\":\"self-issued\"}";
public static final String CURRENT_AND_NEW_PASSWORDS = "{ \"current_password\": \""
+ DEFAULT_PASSWORD
+ "\", \"password\": \""
+ NEW_PASSWORD
+ "\" }";

@ClassRule
public static final LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE)
Expand All @@ -76,60 +84,62 @@ public class OnBehalfOfJwtAuthenticationTest {

@Test
public void shouldAuthenticateWithOBOTokenEndPoint() {
Header adminOboAuthHeader;

try (TestRestClient client = cluster.getRestClient(ADMIN_USER_NAME, DEFAULT_PASSWORD)) {

client.assertCorrectCredentials(ADMIN_USER_NAME);

TestRestClient.HttpResponse response = client.postJson(OBO_ENDPOINT_PREFIX, OBO_TOKEN_REASON);
response.assertStatusCode(200);

Map<String, Object> oboEndPointResponse = response.getBodyAs(Map.class);
assertThat(oboEndPointResponse, allOf(aMapWithSize(3), hasKey("user"), hasKey("onBehalfOfToken"), hasKey("duration")));
String oboToken = generateOboToken(ADMIN_USER_NAME, DEFAULT_PASSWORD);
Header adminOboAuthHeader = new BasicHeader("Authorization", "Bearer " + oboToken);
authenticateWithOboToken(adminOboAuthHeader, ADMIN_USER_NAME, 200);
}

String encodedOboTokenStr = oboEndPointResponse.get("onBehalfOfToken").toString();
@Test
public void shouldNotAuthenticateWithATemperedOBOToken() {
String oboToken = generateOboToken(ADMIN_USER_NAME, DEFAULT_PASSWORD);
oboToken = oboToken.substring(0, oboToken.length() - 1); // tampering the token
Header adminOboAuthHeader = new BasicHeader("Authorization", "Bearer " + oboToken);
authenticateWithOboToken(adminOboAuthHeader, ADMIN_USER_NAME, 401);
}

adminOboAuthHeader = new BasicHeader("Authorization", "Bearer " + encodedOboTokenStr);
}
@Test
public void shouldNotAuthenticateForUsingOBOTokenToAccessOBOEndpoint() {
String oboToken = generateOboToken(ADMIN_USER_NAME, DEFAULT_PASSWORD);
Header adminOboAuthHeader = new BasicHeader("Authorization", "Bearer " + oboToken);

try (TestRestClient client = cluster.getRestClient(adminOboAuthHeader)) {

TestRestClient.HttpResponse response = client.getAuthInfo();
response.assertStatusCode(200);

String username = response.getTextFromJsonBody(POINTER_USERNAME);
assertThat(username, equalTo(ADMIN_USER_NAME));
TestRestClient.HttpResponse response = client.getOBOTokenFromOboEndpoint(OBO_REASON, adminOboAuthHeader);
response.assertStatusCode(401);
}
}

@Test
public void shouldNotAuthenticateWithATemperedOBOToken() {
Header adminOboAuthHeader;
public void shouldNotAuthenticateForUsingOBOTokenToAccessAccountEndpoint() {
String oboToken = generateOboToken(ADMIN_USER_NAME, DEFAULT_PASSWORD);
Header adminOboAuthHeader = new BasicHeader("Authorization", "Bearer " + oboToken);

try (TestRestClient client = cluster.getRestClient(ADMIN_USER_NAME, DEFAULT_PASSWORD)) {

client.assertCorrectCredentials(ADMIN_USER_NAME);
try (TestRestClient client = cluster.getRestClient(adminOboAuthHeader)) {
TestRestClient.HttpResponse response = client.changeInternalUserPassword(CURRENT_AND_NEW_PASSWORDS, adminOboAuthHeader);
response.assertStatusCode(401);
}
}

private String generateOboToken(String username, String password) {
try (TestRestClient client = cluster.getRestClient(username, password)) {
client.assertCorrectCredentials(username);
TestRestClient.HttpResponse response = client.postJson(OBO_ENDPOINT_PREFIX, OBO_TOKEN_REASON);
response.assertStatusCode(200);

Map<String, Object> oboEndPointResponse = response.getBodyAs(Map.class);
assertThat(oboEndPointResponse, allOf(aMapWithSize(3), hasKey("user"), hasKey("onBehalfOfToken"), hasKey("duration")));

String encodedOboTokenStr = oboEndPointResponse.get("onBehalfOfToken").toString();
StringBuilder stringBuilder = new StringBuilder(encodedOboTokenStr);
stringBuilder.deleteCharAt(encodedOboTokenStr.length() - 1);
String temperedOboTokenStr = stringBuilder.toString();

adminOboAuthHeader = new BasicHeader("Authorization", "Bearer " + temperedOboTokenStr);
return oboEndPointResponse.get("onBehalfOfToken").toString();
}
}

try (TestRestClient client = cluster.getRestClient(adminOboAuthHeader)) {

private void authenticateWithOboToken(Header authHeader, String expectedUsername, int expectedStatusCode) {
try (TestRestClient client = cluster.getRestClient(authHeader)) {
TestRestClient.HttpResponse response = client.getAuthInfo();
response.assertStatusCode(401);
response.getBody().contains("Unauthorized");
response.assertStatusCode(expectedStatusCode);
if (expectedStatusCode == 200) {
String username = response.getTextFromJsonBody(POINTER_USERNAME);
assertThat(username, equalTo(expectedUsername));
} else {
Assert.assertTrue(response.getBody().contains("Unauthorized"));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,26 @@ public HttpResponse getAuthInfo(Header... headers) {
return executeRequest(new HttpGet(getHttpServerUri() + "/_opendistro/_security/authinfo?pretty"), headers);
}

public HttpResponse getOBOTokenFromOboEndpoint(String jsonData, Header... headers) {
try {
HttpPost httpPost = new HttpPost(new URIBuilder(getHttpServerUri() + "/_plugins/_security/api/user/onbehalfof?pretty").build());
httpPost.setEntity(toStringEntity(jsonData));
return executeRequest(httpPost, mergeHeaders(CONTENT_TYPE_JSON, headers));
} catch (URISyntaxException ex) {
throw new RuntimeException("Incorrect URI syntax", ex);
}
}

public HttpResponse changeInternalUserPassword(String jsonData, Header... headers) {
try {
HttpPut httpPut = new HttpPut(new URIBuilder(getHttpServerUri() + "/_plugins/_security/api/account?pretty").build());
httpPut.setEntity(toStringEntity(jsonData));
return executeRequest(httpPut, mergeHeaders(CONTENT_TYPE_JSON, headers));
} catch (URISyntaxException ex) {
throw new RuntimeException("Incorrect URI syntax", ex);
}
}

public void assertCorrectCredentials(String expectedUserName) {
HttpResponse response = getAuthInfo();
assertThat(response, notNullValue());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import org.opensearch.client.node.NodeClient;
import org.opensearch.cluster.service.ClusterService;
import org.opensearch.common.settings.Settings;
import org.opensearch.common.transport.TransportAddress;
import org.opensearch.core.common.transport.TransportAddress;
import org.opensearch.core.xcontent.XContentBuilder;
import org.opensearch.rest.BaseRestHandler;
import org.opensearch.rest.BytesRestResponse;
Expand Down Expand Up @@ -72,7 +72,7 @@
this.vendor = null;
}
} else {
this.vendor = null;

Check warning on line 75 in src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java#L75

Added line #L75 was not covered by tests
}
}

Expand All @@ -95,72 +95,72 @@
protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
switch (request.method()) {
case POST:
return handlePost(request, client);

Check warning on line 98 in src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java#L98

Added line #L98 was not covered by tests
default:
throw new IllegalArgumentException(request.method() + " not supported");

Check warning on line 100 in src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java#L100

Added line #L100 was not covered by tests
}
}

private RestChannelConsumer handlePost(RestRequest request, NodeClient client) throws IOException {
return new RestChannelConsumer() {

Check warning on line 105 in src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java#L105

Added line #L105 was not covered by tests
@Override
public void accept(RestChannel channel) throws Exception {
final XContentBuilder builder = channel.newBuilder();

Check warning on line 108 in src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java#L108

Added line #L108 was not covered by tests
BytesRestResponse response;
try {
if (vendor == null) {
channel.sendResponse(

Check warning on line 112 in src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java#L112

Added line #L112 was not covered by tests
new BytesRestResponse(RestStatus.SERVICE_UNAVAILABLE, "on_behalf_of configuration is not being configured")
);
return;

Check warning on line 115 in src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java#L115

Added line #L115 was not covered by tests
}

final String clusterIdentifier = clusterService.getClusterName().value();

Check warning on line 118 in src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java#L118

Added line #L118 was not covered by tests

final Map<String, Object> requestBody = request.contentOrSourceParamParser().map();
final String reason = (String) requestBody.getOrDefault("reason", null);

Check warning on line 121 in src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java#L120-L121

Added lines #L120 - L121 were not covered by tests

final Integer tokenDuration = Optional.ofNullable(requestBody.get("duration"))
.map(value -> (String) value)
.map(Integer::parseInt)
.map(value -> Math.min(value, 10 * 60)) // Max duration is 10 minutes
.orElse(5 * 60); // Fallback to default of 5 minutes;

Check warning on line 127 in src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java#L123-L127

Added lines #L123 - L127 were not covered by tests

final String service = (String) requestBody.getOrDefault("service", "self-issued");
final User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER);
final TransportAddress caller = threadPool.getThreadContext()
.getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS);
Set<String> mappedRoles = mapRoles(user, caller);

Check warning on line 133 in src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java#L129-L133

Added lines #L129 - L133 were not covered by tests

builder.startObject();
builder.field("user", user.getName());

Check warning on line 136 in src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java#L135-L136

Added lines #L135 - L136 were not covered by tests

final String token = vendor.createJwt(

Check warning on line 138 in src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java#L138

Added line #L138 was not covered by tests
clusterIdentifier,
user.getName(),

Check warning on line 140 in src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java#L140

Added line #L140 was not covered by tests
service,
tokenDuration,
mappedRoles.stream().collect(Collectors.toList()),
user.getRoles().stream().collect(Collectors.toList())

Check warning on line 144 in src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java#L143-L144

Added lines #L143 - L144 were not covered by tests
);
builder.field("onBehalfOfToken", token);
builder.field("duration", tokenDuration + " seconds");
builder.endObject();

Check warning on line 148 in src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java#L146-L148

Added lines #L146 - L148 were not covered by tests

response = new BytesRestResponse(RestStatus.OK, builder);
} catch (final Exception exception) {
builder.startObject().field("error", exception.toString()).endObject();

Check warning on line 152 in src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java#L150-L152

Added lines #L150 - L152 were not covered by tests

response = new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, builder);
}
builder.close();
channel.sendResponse(response);
}

Check warning on line 158 in src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java#L154-L158

Added lines #L154 - L158 were not covered by tests
};
}

public Set<String> mapRoles(final User user, final TransportAddress caller) {
return this.configModel.mapSecurityRoles(user, caller);

Check warning on line 163 in src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfTokenAction.java#L163

Added line #L163 was not covered by tests
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@
JoseJwtProducer jwtProducer = new JoseJwtProducer();
try {
this.signingKey = createJwkFromSettings(settings);
} catch (Exception e) {
throw new RuntimeException(e);

Check warning on line 49 in src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java#L48-L49

Added lines #L48 - L49 were not covered by tests
}
this.jwtProducer = jwtProducer;
if (settings.get("encryption_key") == null) {
Expand Down Expand Up @@ -87,13 +87,13 @@
throw new Exception("Settings for key is missing. Please specify at least the option signing_key with a shared secret.");
}

JsonWebKey jwk = new JsonWebKey();

Check warning on line 90 in src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java#L90

Added line #L90 was not covered by tests

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

Check warning on line 94 in src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java#L93-L94

Added lines #L93 - L94 were not covered by tests

return jwk;

Check warning on line 96 in src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java#L96

Added line #L96 was not covered by tests
}
}

Expand All @@ -105,13 +105,16 @@
List<String> roles,
List<String> backendRoles
) throws Exception {
String tokenIdentifier = "obo";
long timeMillis = timeProvider.getAsLong();
Instant now = Instant.ofEpochMilli(timeProvider.getAsLong());

jwtProducer.setSignatureProvider(JwsUtils.getSignatureProvider(signingKey));
JwtClaims jwtClaims = new JwtClaims();
JwtToken jwt = new JwtToken(jwtClaims);

jwtClaims.setProperty("typ", tokenIdentifier);

jwtClaims.setIssuer(issuer);

jwtClaims.setIssuedAt(timeMillis);
Expand All @@ -123,8 +126,8 @@
jwtClaims.setNotBefore(timeMillis);

if (expirySeconds == null) {
long expiryTime = timeProvider.getAsLong() + 300;
jwtClaims.setExpiryTime(expiryTime);

Check warning on line 130 in src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java#L129-L130

Added lines #L129 - L130 were not covered by tests
} else if (expirySeconds > 0) {
long expiryTime = timeProvider.getAsLong() + expirySeconds;
jwtClaims.setExpiryTime(expiryTime);
Expand All @@ -144,13 +147,13 @@
String encodedJwt = jwtProducer.processJwt(jwt);

if (logger.isDebugEnabled()) {
logger.debug(

Check warning on line 150 in src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java#L150

Added line #L150 was not covered by tests
"Created JWT: "
+ encodedJwt
+ "\n"
+ jsonMapReaderWriter.toJson(jwt.getJwsHeaders())

Check warning on line 154 in src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java#L154

Added line #L154 was not covered by tests
+ "\n"
+ JwtUtils.claimsToJson(jwt.getClaims())

Check warning on line 156 in src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java#L156

Added line #L156 was not covered by tests
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,6 @@ private boolean checkAndAuthenticateRequest(RestRequest request, RestChannel cha
);
}
}

return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.util.List;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

Expand All @@ -28,6 +29,7 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import org.opensearch.OpenSearchException;
import org.opensearch.OpenSearchSecurityException;
import org.opensearch.SpecialPermission;
import org.opensearch.common.settings.Settings;
Expand All @@ -36,16 +38,26 @@
import org.opensearch.rest.RestRequest;
import org.opensearch.security.auth.HTTPAuthenticator;
import org.opensearch.security.authtoken.jwt.EncryptionDecryptionUtil;
import org.opensearch.security.ssl.util.ExceptionUtils;
import org.opensearch.security.user.AuthCredentials;
import org.opensearch.security.util.keyUtil;

import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX;
import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX;

public class OnBehalfOfAuthenticator implements HTTPAuthenticator {

private static final String REGEX_PATH_PREFIX = "/(" + LEGACY_OPENDISTRO_PREFIX + "|" + PLUGINS_PREFIX + ")/" + "(.*)";
private static final Pattern PATTERN_PATH_PREFIX = Pattern.compile(REGEX_PATH_PREFIX);
private static final String ON_BEHALF_OF_SUFFIX = "api/user/onbehalfof";
private static final String ACCOUNT_SUFFIX = "api/account";

protected final Logger log = LogManager.getLogger(this.getClass());

private static final Pattern BEARER = Pattern.compile("^\\s*Bearer\\s.*", Pattern.CASE_INSENSITIVE);
private static final String BEARER_PREFIX = "bearer ";
private static final String SUBJECT_CLAIM = "sub";
private static final String TOKEN_TYPE_CLAIM = "typ";
private static final String TOKEN_TYPE = "obo";

private final JwtParser jwtParser;
private final String encryptionKey;
Expand Down Expand Up @@ -81,7 +93,7 @@
// Extracting roles based on the compatbility mode
String decryptedRoles = rolesClaim;
if (rolesObject == claims.get("er")) {
decryptedRoles = EncryptionDecryptionUtil.decrypt(encryptionKey, rolesClaim);

Check warning on line 96 in src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java#L96

Added line #L96 was not covered by tests
}
roles = Arrays.stream(decryptedRoles.split(",")).map(String::trim).collect(Collectors.toList());
}
Expand All @@ -95,21 +107,21 @@
return null;
}

Object backendRolesObject = claims.get("dbr");

Check warning on line 110 in src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java#L110

Added line #L110 was not covered by tests
String[] backendRoles;

if (backendRolesObject == null) {
log.warn("This is a malformed On-behalf-of Token");
backendRoles = new String[0];

Check warning on line 115 in src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java#L114-L115

Added lines #L114 - L115 were not covered by tests
} else {
final String backendRolesClaim = backendRolesObject.toString();

Check warning on line 117 in src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java#L117

Added line #L117 was not covered by tests

// Extracting roles based on the compatibility mode
String decryptedBackendRoles = backendRolesClaim;
backendRoles = Arrays.stream(decryptedBackendRoles.split(",")).map(String::trim).toArray(String[]::new);

Check warning on line 121 in src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java#L120-L121

Added lines #L120 - L121 were not covered by tests
}

return backendRoles;

Check warning on line 124 in src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java#L124

Added line #L124 was not covered by tests
}

@Override
Expand All @@ -118,7 +130,7 @@
final SecurityManager sm = System.getSecurityManager();

if (sm != null) {
sm.checkPermission(new SpecialPermission());

Check warning on line 133 in src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java#L133

Added line #L133 was not covered by tests
}

AuthCredentials creds = AccessController.doPrivileged(new PrivilegedAction<AuthCredentials>() {
Expand All @@ -138,15 +150,15 @@
}

if (jwtParser == null) {
log.error("Missing Signing Key. JWT authentication will not work");
return null;

Check warning on line 154 in src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java#L153-L154

Added lines #L153 - L154 were not covered by tests
}

String jwtToken = request.header(HttpHeaders.AUTHORIZATION);

if (jwtToken == null || jwtToken.length() == 0) {
if (log.isDebugEnabled()) {
log.debug("No JWT token found in '{}' header", HttpHeaders.AUTHORIZATION);

Check warning on line 161 in src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java#L161

Added line #L161 was not covered by tests
}
return null;
}
Expand All @@ -159,7 +171,7 @@
jwtToken = jwtToken.substring(jwtToken.toLowerCase().indexOf(BEARER_PREFIX) + BEARER_PREFIX.length());
} else {
if (log.isDebugEnabled()) {
log.debug("No Bearer scheme found in header");

Check warning on line 174 in src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java#L174

Added line #L174 was not covered by tests
}
}

Expand All @@ -168,6 +180,15 @@
}

try {
Matcher matcher = PATTERN_PATH_PREFIX.matcher(request.path());
final String suffix = matcher.matches() ? matcher.group(2) : null;
if (request.method() == RestRequest.Method.POST && ON_BEHALF_OF_SUFFIX.equals(suffix)
|| request.method() == RestRequest.Method.PUT && ACCOUNT_SUFFIX.equals(suffix)) {
final OpenSearchException exception = ExceptionUtils.invalidUsageOfOBOTokenException();
log.error(exception.toString());
return null;

Check warning on line 189 in src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java#L187-L189

Added lines #L187 - L189 were not covered by tests
}

final Claims claims = jwtParser.parseClaimsJws(jwtToken).getBody();

final String subject = claims.getSubject();
Expand All @@ -178,10 +199,16 @@

final String audience = claims.getAudience();
if (Objects.isNull(audience)) {
log.error("Valid jwt on behalf of token with no audience");
return null;

Check warning on line 203 in src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java#L202-L203

Added lines #L202 - L203 were not covered by tests
}

final String tokenType = claims.get(TOKEN_TYPE_CLAIM).toString();
if (!tokenType.equals(TOKEN_TYPE)) {
log.error("This toke is not verifying as an on-behalf-of token");
return null;

Check warning on line 209 in src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java#L208-L209

Added lines #L208 - L209 were not covered by tests
}

List<String> roles = extractSecurityRolesFromClaims(claims);
String[] backendRoles = extractBackendRolesFromClaims(claims);

Expand All @@ -193,13 +220,13 @@

return ac;

} catch (WeakKeyException e) {
log.error("Cannot authenticate user with JWT because of ", e);
return null;

Check warning on line 225 in src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java#L223-L225

Added lines #L223 - L225 were not covered by tests
} catch (Exception e) {
e.printStackTrace();
if (log.isDebugEnabled()) {
log.debug("Invalid or expired JWT token.", e);

Check warning on line 229 in src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java#L229

Added line #L229 was not covered by tests
}
return null;
}
Expand All @@ -207,12 +234,12 @@

@Override
public boolean reRequestAuthentication(final RestChannel channel, AuthCredentials creds) {
return false;

Check warning on line 237 in src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java#L237

Added line #L237 was not covered by tests
}

@Override
public String getType() {
return "onbehalfof_jwt";

Check warning on line 242 in src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java#L242

Added line #L242 was not covered by tests
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@
);
}

public static OpenSearchException invalidUsageOfOBOTokenException() {
return new OpenSearchException("On-Behalf-Of Token is not allowed to be used for accessing this endopoint.");

Check warning on line 68 in src/main/java/org/opensearch/security/ssl/util/ExceptionUtils.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/ssl/util/ExceptionUtils.java#L68

Added line #L68 was not covered by tests
}

public static OpenSearchException createTransportClientNoLongerSupportedException() {
return new OpenSearchException("Transport client authentication no longer supported.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public void testCreateJwtWithRoles() throws Exception {
JwsJwtCompactConsumer jwtConsumer = new JwsJwtCompactConsumer(encodedJwt);
JwtToken jwt = jwtConsumer.getJwtToken();

Assert.assertEquals("obo", jwt.getClaim("typ"));
Assert.assertEquals("cluster_0", jwt.getClaim("iss"));
Assert.assertEquals("admin", jwt.getClaim("sub"));
Assert.assertEquals("audience_0", jwt.getClaim("aud"));
Expand Down
Loading
Loading