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

Expanding Authentication with SecurityRequest Abstraction #3430

Merged
merged 53 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
ab693c6
Removed http channel
peternied Sep 29, 2023
6abd939
Add request source from the netty channel
peternied Sep 29, 2023
da8d0cb
Plumb SecurityRequest into HTTPAuthenticator interface
peternied Sep 29, 2023
26c6b83
Fix all tests
peternied Sep 29, 2023
a825d83
Prevent build break for generalized testing associated with SecurityR…
peternied Sep 29, 2023
095d4e5
Spotless
peternied Sep 29, 2023
6580e7f
Disable createHeaderVerifier so builds can pass
peternied Sep 29, 2023
8c44278
Implement SecurityRequest gaps
peternied Sep 29, 2023
9b5fe42
Fix spotless
peternied Sep 29, 2023
9f369cb
Redo how reRequestAuthentication works
peternied Oct 1, 2023
46358a2
Nix that idea
peternied Oct 1, 2023
fa98ba9
Cleaning up some
peternied Oct 1, 2023
9844df9
Mild switch to RequestChannel
peternied Oct 2, 2023
cafe1c7
Handle unsupported scenarios
peternied Oct 2, 2023
2beb434
Tests compile again
peternied Oct 2, 2023
2ef4c3f
Fix spotless issues
peternied Oct 2, 2023
4970a49
Merge remote-tracking branch 'origin/main' into headerverifier
peternied Oct 2, 2023
87646d7
Fix merging from main changes
peternied Oct 2, 2023
547218b
Restore default build.gradle settings
peternied Oct 3, 2023
d44d277
Fix bug in authn/z stages of handler wrapper
peternied Oct 3, 2023
b960aac
Focus channel implementation
peternied Oct 3, 2023
4d9506c
Merge remote-tracking branch 'origin/main' into headerverifier
peternied Oct 3, 2023
3111665
Fix merge conflict related issues
peternied Oct 3, 2023
2aad250
Seperate read-only vs read-write scenarios
peternied Oct 3, 2023
9a915e7
Spotless
peternied Oct 3, 2023
e749a8e
more cleanup
peternied Oct 3, 2023
20c9ea7
Rename concrete classes
peternied Oct 3, 2023
22991e3
Maybe final changes batch
peternied Oct 3, 2023
828ef2b
Fix parameter consumption
peternied Oct 3, 2023
641c195
Fix incorrect error message
peternied Oct 3, 2023
74a925d
javadoc
peternied Oct 3, 2023
21e1030
Remove the Authenticatiion verifier
peternied Oct 3, 2023
09207c2
Debug statement
peternied Oct 4, 2023
659088d
Even more logging
peternied Oct 4, 2023
c019f02
Even more loogging
peternied Oct 4, 2023
e8e5155
Even more logging
peternied Oct 4, 2023
9ffa147
Add more logging
peternied Oct 4, 2023
613acab
move logging location
peternied Oct 4, 2023
2125f9f
Refactor to optional in progress
peternied Oct 4, 2023
edbe2be
Restore changes around passing unsent response
peternied Oct 4, 2023
33aa863
revert logging changes
peternied Oct 4, 2023
0c5a97d
Fix spotless
peternied Oct 4, 2023
2f58523
Fix exception name
peternied Oct 4, 2023
5dc3033
Copyright headers
peternied Oct 4, 2023
e323823
PR comments
peternied Oct 4, 2023
177ad11
Fix issue with saml tests
peternied Oct 5, 2023
1312d2c
Fine tunning cr pass
peternied Oct 5, 2023
28f620b
Fix bad auth realm
peternied Oct 5, 2023
38c34cb
Fix build break
peternied Oct 5, 2023
641130f
Clean up allow list systems
peternied Oct 5, 2023
7940233
Merge remote-tracking branch 'origin/main' into headerverifier
peternied Oct 5, 2023
8701359
switch from complete to queue requests
peternied Oct 5, 2023
e7b0916
Fix checkstyle issue
peternied Oct 5, 2023
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 @@ -11,17 +11,21 @@

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 org.apache.cxf.rs.security.jose.jwt.JwtClaims;
import org.apache.cxf.rs.security.jose.jwt.JwtToken;
import org.apache.hc.core5.http.HttpHeaders;
peternied marked this conversation as resolved.
Show resolved Hide resolved
import org.apache.http.HttpStatus;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

Expand All @@ -35,10 +39,10 @@
import org.opensearch.common.settings.Settings;
import org.opensearch.common.util.concurrent.ThreadContext;
import org.opensearch.core.common.Strings;
import org.opensearch.rest.BytesRestResponse;
import org.opensearch.rest.RestRequest;
import org.opensearch.core.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;

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

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 @@ -82,7 +86,8 @@

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

if (sm != null) {
Expand All @@ -99,7 +104,7 @@
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 @@ -140,18 +145,18 @@

}

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);

Check warning on line 156 in src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java#L156

Added line #L156 was not covered by tests
} else {
// just consume to avoid "contains unrecognized parameter"
request.param(jwtUrlParameter);
request.params().get(jwtUrlParameter);

Check warning on line 159 in src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java#L159

Added line #L159 was not covered by tests
}
}

Expand Down Expand Up @@ -235,10 +240,10 @@
protected abstract KeyProvider initKeyProvider(Settings settings, Path configPath) throws Exception;

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

Check warning on line 245 in src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java#L244-L245

Added lines #L244 - L245 were not covered by tests
);
}

public String getRequiredAudience() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,33 @@

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 io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.JwtParserBuilder;
import io.jsonwebtoken.security.WeakKeyException;
import org.apache.hc.core5.http.HttpHeaders;

import org.apache.http.HttpStatus;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

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.RestRequest;
import org.opensearch.core.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 org.opensearch.security.util.KeyUtils;

Expand All @@ -58,8 +62,8 @@

String signingKey = settings.get("signing_key");
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");
requireAudience = settings.get("required_audience");
Expand All @@ -83,7 +87,8 @@

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

if (sm != null) {
Expand All @@ -100,7 +105,7 @@
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 @@ -112,10 +117,10 @@
}

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

if (jwtToken == null || jwtToken.length() == 0) {
Expand Down Expand Up @@ -170,18 +175,18 @@
}

@Override
public BytesRestResponse reRequestAuthentication(RestRequest request, AuthCredentials credentials) {
final BytesRestResponse wwwAuthenticateResponse = new BytesRestResponse(RestStatus.UNAUTHORIZED, "");
wwwAuthenticateResponse.addHeader("WWW-Authenticate", "Bearer realm=\"OpenSearch Security\"");
return wwwAuthenticateResponse;
public Optional<SecurityResponse> reRequestAuthentication(final SecurityRequest channel, AuthCredentials creds) {
return Optional.of(
new SecurityResponse(HttpStatus.SC_UNAUTHORIZED, Map.of("WWW-Authenticate", "Bearer realm=\"OpenSearch Security\""), "")

Check warning on line 180 in src/main/java/com/amazon/dlic/auth/http/jwt/HTTPJwtAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/com/amazon/dlic/auth/http/jwt/HTTPJwtAuthenticator.java#L179-L180

Added lines #L179 - L180 were not covered by tests
);
}

@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 @@ -205,7 +210,7 @@
}

@SuppressWarnings("unchecked")
protected String[] extractRoles(final Claims claims, final RestRequest request) {
protected String[] extractRoles(final Claims claims, final SecurityRequest request) {
// no roles key specified
if (rolesKey == null) {
return new String[0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

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

import static org.apache.http.HttpStatus.SC_UNAUTHORIZED;

import java.io.Serializable;
import java.nio.file.Files;
import java.nio.file.Path;
Expand All @@ -22,13 +24,17 @@
import java.security.PrivilegedExceptionAction;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import javax.security.auth.Subject;
import javax.security.auth.login.LoginException;

import com.google.common.base.Strings;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.ietf.jgss.GSSContext;
Expand All @@ -48,15 +54,13 @@
import org.opensearch.common.xcontent.XContentFactory;
import org.opensearch.core.xcontent.XContentBuilder;
import org.opensearch.env.Environment;
import org.opensearch.rest.BytesRestResponse;
import org.opensearch.rest.RestRequest;
import org.opensearch.core.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;

public class HTTPSpnegoAuthenticator implements HTTPAuthenticator {

private static final String EMPTY_STRING = "";
private static final Oid[] KRB_OIDS = new Oid[] { KrbConstants.SPNEGO, KrbConstants.KRB5MECH };

protected final Logger log = LogManager.getLogger(this.getClass());
Expand Down Expand Up @@ -170,7 +174,7 @@

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

if (sm != null) {
Expand All @@ -187,7 +191,7 @@
return creds;
}

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

if (acceptorPrincipal == null || acceptorKeyTabPath == null) {
log.error("Missing acceptor principal or keytab configuration. Kerberos authentication will not work");
Expand Down Expand Up @@ -279,25 +283,22 @@
}

@Override
public BytesRestResponse reRequestAuthentication(RestRequest request, AuthCredentials credentials) {
final BytesRestResponse wwwAuthenticateResponse;
XContentBuilder response = getNegotiateResponseBody();

if (response != null) {
wwwAuthenticateResponse = new BytesRestResponse(RestStatus.UNAUTHORIZED, response);
} else {
wwwAuthenticateResponse = new BytesRestResponse(RestStatus.UNAUTHORIZED, EMPTY_STRING);
public Optional<SecurityResponse> reRequestAuthentication(final SecurityRequest request, AuthCredentials creds) {
final Map<String, String> headers = new HashMap<>();
String responseBody = "";
final String negotiateResponseBody = getNegotiateResponseBody();

Check warning on line 289 in src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java#L287-L289

Added lines #L287 - L289 were not covered by tests
if (negotiateResponseBody != null) {
responseBody = negotiateResponseBody;
headers.putAll(SecurityResponse.CONTENT_TYPE_APP_JSON);

Check warning on line 292 in src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java#L291-L292

Added lines #L291 - L292 were not covered by tests
}

if (credentials == null || credentials.getNativeCredentials() == null) {
wwwAuthenticateResponse.addHeader("WWW-Authenticate", "Negotiate");
if (creds == null || creds.getNativeCredentials() == null) {
headers.put("WWW-Authenticate", "Negotiate");

Check warning on line 296 in src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java#L296

Added line #L296 was not covered by tests
} else {
wwwAuthenticateResponse.addHeader(
"WWW-Authenticate",
"Negotiate " + Base64.getEncoder().encodeToString((byte[]) credentials.getNativeCredentials())
);
headers.put("WWW-Authenticate", "Negotiate " + Base64.getEncoder().encodeToString((byte[]) creds.getNativeCredentials()));

Check warning on line 298 in src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java#L298

Added line #L298 was not covered by tests
}
return wwwAuthenticateResponse;

return Optional.of(new SecurityResponse(SC_UNAUTHORIZED, headers, responseBody));

Check warning on line 301 in src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java#L301

Added line #L301 was not covered by tests
}

@Override
Expand Down Expand Up @@ -369,7 +370,7 @@
return null;
}

private XContentBuilder getNegotiateResponseBody() {
private String getNegotiateResponseBody() {
peternied marked this conversation as resolved.
Show resolved Hide resolved
try {
XContentBuilder negotiateResponseBody = XContentFactory.jsonBuilder();
negotiateResponseBody.startObject();
Expand All @@ -381,7 +382,7 @@
negotiateResponseBody.endObject();
negotiateResponseBody.endObject();
negotiateResponseBody.endObject();
return negotiateResponseBody;
return negotiateResponseBody.toString();

Check warning on line 385 in src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/com/amazon/dlic/auth/http/kerberos/HTTPSpnegoAuthenticator.java#L385

Added line #L385 was not covered by tests
} catch (Exception ex) {
log.error("Can't construct response body", ex);
return null;
Expand Down
Loading
Loading