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

[Backport 1.3] Expanding Authentication with SecurityRequest Abstraction (#3487) #3670

Merged
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -15,36 +15,40 @@

package com.amazon.dlic.auth.http.jwt;

import static org.apache.http.HttpHeaders.AUTHORIZATION;

import java.nio.file.Path;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.Map.Entry;
import java.util.regex.Pattern;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;

import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
import org.apache.cxf.rs.security.jose.jwt.JwtToken;
import org.apache.http.HttpHeaders;
import org.apache.logging.log4j.Logger;
import org.apache.http.HttpStatus;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.opensearch.OpenSearchSecurityException;
import org.opensearch.SpecialPermission;
import org.opensearch.common.Strings;
import org.opensearch.common.settings.Settings;
import org.opensearch.common.util.concurrent.ThreadContext;
import org.opensearch.rest.BytesRestResponse;
import org.opensearch.rest.RestChannel;
import org.opensearch.rest.RestRequest;
import org.opensearch.rest.RestStatus;

import com.amazon.dlic.auth.http.jwt.keybyoidc.AuthenticatorUnavailableException;
import com.amazon.dlic.auth.http.jwt.keybyoidc.BadCredentialsException;
import com.amazon.dlic.auth.http.jwt.keybyoidc.JwtVerifier;
import com.amazon.dlic.auth.http.jwt.keybyoidc.KeyProvider;

import org.opensearch.security.auth.HTTPAuthenticator;
import org.opensearch.security.filter.SecurityRequest;
import org.opensearch.security.filter.SecurityResponse;
import org.opensearch.security.user.AuthCredentials;

public abstract class AbstractHTTPJwtAuthenticator implements HTTPAuthenticator {
Expand All @@ -66,8 +70,8 @@ public abstract class AbstractHTTPJwtAuthenticator implements HTTPAuthenticator

public AbstractHTTPJwtAuthenticator(Settings settings, Path configPath) {
jwtUrlParameter = settings.get("jwt_url_parameter");
jwtHeaderName = settings.get("jwt_header", HttpHeaders.AUTHORIZATION);
isDefaultAuthHeader = HttpHeaders.AUTHORIZATION.equalsIgnoreCase(jwtHeaderName);
jwtHeaderName = settings.get("jwt_header", AUTHORIZATION);
isDefaultAuthHeader = AUTHORIZATION.equalsIgnoreCase(jwtHeaderName);
rolesKey = settings.get("roles_key");
subjectKey = settings.get("subject_key");
clockSkewToleranceSeconds = settings.getAsInt("jwt_clock_skew_tolerance_seconds", DEFAULT_CLOCK_SKEW_TOLERANCE_SECONDS);
Expand All @@ -83,8 +87,9 @@ public AbstractHTTPJwtAuthenticator(Settings settings, Path configPath) {
}

@Override
public AuthCredentials extractCredentials(RestRequest request, ThreadContext context)
throws OpenSearchSecurityException {
@SuppressWarnings("removal")
public AuthCredentials extractCredentials(final SecurityRequest request, final ThreadContext context)
throws OpenSearchSecurityException {
final SecurityManager sm = System.getSecurityManager();

if (sm != null) {
Expand All @@ -101,7 +106,7 @@ public AuthCredentials run() {
return creds;
}

private AuthCredentials extractCredentials0(final RestRequest request) throws OpenSearchSecurityException {
private AuthCredentials extractCredentials0(final SecurityRequest request) throws OpenSearchSecurityException {

String jwtString = getJwtTokenString(request);

Expand Down Expand Up @@ -142,18 +147,18 @@ private AuthCredentials extractCredentials0(final RestRequest request) throws Op

}

protected String getJwtTokenString(RestRequest request) {
protected String getJwtTokenString(SecurityRequest request) {
String jwtToken = request.header(jwtHeaderName);
if (isDefaultAuthHeader && jwtToken != null && BASIC.matcher(jwtToken).matches()) {
jwtToken = null;
}

if (jwtUrlParameter != null) {
if (jwtToken == null || jwtToken.isEmpty()) {
jwtToken = request.param(jwtUrlParameter);
jwtToken = request.params().get(jwtUrlParameter);
} else {
// just consume to avoid "contains unrecognized parameter"
request.param(jwtUrlParameter);
request.params().get(jwtUrlParameter);
}
}

Expand Down Expand Up @@ -234,11 +239,10 @@ public String[] extractRoles(JwtClaims claims) {
protected abstract KeyProvider initKeyProvider(Settings settings, Path configPath) throws Exception;

@Override
public boolean reRequestAuthentication(RestChannel channel, AuthCredentials authCredentials) {
final BytesRestResponse wwwAuthenticateResponse = new BytesRestResponse(RestStatus.UNAUTHORIZED, "");
wwwAuthenticateResponse.addHeader("WWW-Authenticate", "Bearer realm=\"OpenSearch Security\"");
channel.sendResponse(wwwAuthenticateResponse);
return true;
public Optional<SecurityResponse> reRequestAuthentication(final SecurityRequest request, AuthCredentials authCredentials) {
return Optional.of(
new SecurityResponse(HttpStatus.SC_UNAUTHORIZED, ImmutableMap.of("WWW-Authenticate", "Bearer realm=\"OpenSearch Security\""), "")
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

package com.amazon.dlic.auth.http.jwt;

import static org.apache.http.HttpHeaders.AUTHORIZATION;

import java.nio.file.Path;
import java.security.AccessController;
import java.security.Key;
Expand All @@ -25,24 +27,26 @@
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.Map.Entry;
import java.util.regex.Pattern;

import org.apache.http.HttpHeaders;
import org.apache.logging.log4j.Logger;
import org.apache.http.HttpStatus;
import org.apache.logging.log4j.LogManager;
import org.opensearch.OpenSearchSecurityException;
import org.opensearch.SpecialPermission;
import org.opensearch.common.settings.Settings;
import org.opensearch.common.util.concurrent.ThreadContext;
import org.opensearch.rest.BytesRestResponse;
import org.opensearch.rest.RestChannel;
import org.opensearch.rest.RestRequest;
import org.opensearch.rest.RestStatus;

import org.opensearch.security.auth.HTTPAuthenticator;
import org.opensearch.security.filter.SecurityRequest;
import org.opensearch.security.filter.SecurityResponse;
import org.opensearch.security.user.AuthCredentials;

import com.google.common.collect.ImmutableMap;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
Expand Down Expand Up @@ -106,16 +110,18 @@ public HTTPJwtAuthenticator(final Settings settings, final Path configPath) {
}

jwtUrlParameter = settings.get("jwt_url_parameter");
jwtHeaderName = settings.get("jwt_header", HttpHeaders.AUTHORIZATION);
isDefaultAuthHeader = HttpHeaders.AUTHORIZATION.equalsIgnoreCase(jwtHeaderName);
jwtHeaderName = settings.get("jwt_header", AUTHORIZATION);
isDefaultAuthHeader = AUTHORIZATION.equalsIgnoreCase(jwtHeaderName);
rolesKey = settings.get("roles_key");
subjectKey = settings.get("subject_key");
jwtParser = _jwtParser;
}


@Override
public AuthCredentials extractCredentials(RestRequest request, ThreadContext context) throws OpenSearchSecurityException {
@SuppressWarnings("removal")
public AuthCredentials extractCredentials(final SecurityRequest request, final ThreadContext context)
throws OpenSearchSecurityException {
final SecurityManager sm = System.getSecurityManager();

if (sm != null) {
Expand All @@ -132,7 +138,7 @@ public AuthCredentials run() {
return creds;
}

private AuthCredentials extractCredentials0(final RestRequest request) {
private AuthCredentials extractCredentials0(final SecurityRequest request) {
if (jwtParser == null) {
log.error("Missing Signing Key. JWT authentication will not work");
return null;
Expand All @@ -143,11 +149,11 @@ private AuthCredentials extractCredentials0(final RestRequest request) {
jwtToken = null;
}

if((jwtToken == null || jwtToken.isEmpty()) && jwtUrlParameter != null) {
jwtToken = request.param(jwtUrlParameter);
if ((jwtToken == null || jwtToken.isEmpty()) && jwtUrlParameter != null) {
jwtToken = request.params().get(jwtUrlParameter);
} else {
//just consume to avoid "contains unrecognized parameter"
request.param(jwtUrlParameter);
// just consume to avoid "contains unrecognized parameter"
request.params().get(jwtUrlParameter);
}

if (jwtToken == null || jwtToken.length() == 0) {
Expand Down Expand Up @@ -198,19 +204,18 @@ private AuthCredentials extractCredentials0(final RestRequest request) {
}

@Override
public boolean reRequestAuthentication(final RestChannel channel, AuthCredentials creds) {
final BytesRestResponse wwwAuthenticateResponse = new BytesRestResponse(RestStatus.UNAUTHORIZED,"");
wwwAuthenticateResponse.addHeader("WWW-Authenticate", "Bearer realm=\"OpenSearch Security\"");
channel.sendResponse(wwwAuthenticateResponse);
return true;
public Optional<SecurityResponse> reRequestAuthentication(final SecurityRequest channel, AuthCredentials creds) {
return Optional.of(
new SecurityResponse(HttpStatus.SC_UNAUTHORIZED, ImmutableMap.of("WWW-Authenticate", "Bearer realm=\"OpenSearch Security\""), "")
);
}

@Override
public String getType() {
return "jwt";
}

protected String extractSubject(final Claims claims, final RestRequest request) {
protected String extractSubject(final Claims claims, final SecurityRequest request) {
String subject = claims.getSubject();
if(subjectKey != null) {
// try to get roles from claims, first as Object to avoid having to catch the ExpectedTypeException
Expand All @@ -229,17 +234,20 @@ protected String extractSubject(final Claims claims, final RestRequest request)
}

@SuppressWarnings("unchecked")
protected String[] extractRoles(final Claims claims, final RestRequest request) {
// no roles key specified
if(rolesKey == null) {
return new String[0];
}
// try to get roles from claims, first as Object to avoid having to catch the ExpectedTypeException
final Object rolesObject = claims.get(rolesKey, Object.class);
if(rolesObject == null) {
log.warn("Failed to get roles from JWT claims with roles_key '{}'. Check if this key is correct and available in the JWT payload.", rolesKey);
return new String[0];
}
protected String[] extractRoles(final Claims claims, final SecurityRequest request) {
// no roles key specified
if (rolesKey == null) {
return new String[0];
}
// try to get roles from claims, first as Object to avoid having to catch the ExpectedTypeException
final Object rolesObject = claims.get(rolesKey, Object.class);
if (rolesObject == null) {
log.warn(
"Failed to get roles from JWT claims with roles_key '{}'. Check if this key is correct and available in the JWT payload.",
rolesKey
);
return new String[0];
}

String[] roles = String.valueOf(rolesObject).split(",");

Expand Down
Loading
Loading