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

Allow multiple signing keys to be provided #4632

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import java.nio.file.Path;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -54,7 +56,7 @@
private static final Pattern BASIC = Pattern.compile("^\\s*Basic\\s.*", Pattern.CASE_INSENSITIVE);
private static final String BEARER = "bearer ";

private final JwtParser jwtParser;
private final List<JwtParser> jwtParsers = new ArrayList<>();
private final String jwtHeaderName;
private final boolean isDefaultAuthHeader;
private final String jwtUrlParameter;
Expand All @@ -68,6 +70,7 @@
super();

String signingKey = settings.get("signing_key");
stephen-crawford marked this conversation as resolved.
Show resolved Hide resolved
List<String> signingKeysSplit = new ArrayList<>();
jwtUrlParameter = settings.get("jwt_url_parameter");
jwtHeaderName = settings.get("jwt_header", AUTHORIZATION);
isDefaultAuthHeader = AUTHORIZATION.equalsIgnoreCase(jwtHeaderName);
Expand All @@ -83,19 +86,31 @@
);
}

final JwtParserBuilder jwtParserBuilder = KeyUtils.createJwtParserBuilderFromSigningKey(signingKey, log);
if (jwtParserBuilder == null) {
jwtParser = null;
boolean multipleKeys = signingKey != null && signingKey.contains(",");
if (multipleKeys) {
String[] keyArray = signingKey.replace(",\\s+", ",").split(","); // Remove spaces
signingKeysSplit.addAll(Arrays.asList(keyArray));
} else {
if (requireIssuer != null) {
jwtParserBuilder.requireIssuer(requireIssuer);
}
signingKeysSplit.add(signingKey);
}

final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new SpecialPermission());
for (String key : signingKeysSplit) {
JwtParser jwtParser;
final JwtParserBuilder jwtParserBuilder = KeyUtils.createJwtParserBuilderFromSigningKey(key, log);
if (jwtParserBuilder == null) {
jwtParser = null;
} else {
if (requireIssuer != null) {
jwtParserBuilder.requireIssuer(requireIssuer);
}

final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new SpecialPermission());

Check warning on line 109 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#L109

Added line #L109 was not covered by tests
}
jwtParser = AccessController.doPrivileged((PrivilegedAction<JwtParser>) jwtParserBuilder::build);
}
jwtParser = AccessController.doPrivileged((PrivilegedAction<JwtParser>) jwtParserBuilder::build);
jwtParsers.add(jwtParser);
}
}

Expand All @@ -120,7 +135,8 @@
}

private AuthCredentials extractCredentials0(final SecurityRequest request) {
if (jwtParser == null) {

if (jwtParsers.isEmpty() || jwtParsers.getFirst() == null) {
log.error("Missing Signing Key. JWT authentication will not work");
return null;
}
Expand Down Expand Up @@ -157,39 +173,43 @@
}
}

try {
final Claims claims = jwtParser.parseClaimsJws(jwtToken).getBody();
for (JwtParser jwtParser : jwtParsers) {
try {

if (!requiredAudience.isEmpty()) {
assertValidAudienceClaim(claims);
}
final Claims claims = jwtParser.parseClaimsJws(jwtToken).getBody();

final String subject = extractSubject(claims, request);
if (!requiredAudience.isEmpty()) {
assertValidAudienceClaim(claims);
}

if (subject == null) {
log.error("No subject found in JWT token");
return null;
}
final String subject = extractSubject(claims, request);

final String[] roles = extractRoles(claims, request);
if (subject == null) {
log.error("No subject found in JWT token");
return null;
}

final AuthCredentials ac = new AuthCredentials(subject, roles).markComplete();
final String[] roles = extractRoles(claims, request);

for (Entry<String, Object> claim : claims.entrySet()) {
ac.addAttribute("attr.jwt." + claim.getKey(), String.valueOf(claim.getValue()));
}
final AuthCredentials ac = new AuthCredentials(subject, roles).markComplete();

return ac;
for (Entry<String, Object> claim : claims.entrySet()) {
ac.addAttribute("attr.jwt." + claim.getKey(), String.valueOf(claim.getValue()));
}

} catch (WeakKeyException e) {
log.error("Cannot authenticate user with JWT because of ", e);
return null;
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug("Invalid or expired JWT token.", e);
return ac;

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

Check warning on line 204 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#L202-L204

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

Check warning on line 207 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#L207

Added line #L207 was not covered by tests
}
}
return null;
}
log.error("Failed to parse JWT token using any of the available parsers");
return null;
}

private void assertValidAudienceClaim(Claims claims) throws BadJWTException {
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/opensearch/security/util/KeyUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ public JwtParserBuilder run() {
PublicKey key = null;

final String minimalKeyFormat = signingKey.replace("-----BEGIN PUBLIC KEY-----\n", "")
.replace("-----END PUBLIC KEY-----", "");

.replace("-----END PUBLIC KEY-----", "")
.trim();
stephen-crawford marked this conversation as resolved.
Show resolved Hide resolved
final byte[] decoded = Base64.getDecoder().decode(minimalKeyFormat);

try {
Expand Down
Loading
Loading