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,7 @@
import java.nio.file.Path;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -54,7 +55,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 @@ -67,7 +68,8 @@
public HTTPJwtAuthenticator(final Settings settings, final Path configPath) {
super();

String signingKey = settings.get("signing_key");
List<String> signingKeys = settings.getAsList("signing_key");
stephen-crawford marked this conversation as resolved.
Show resolved Hide resolved

jwtUrlParameter = settings.get("jwt_url_parameter");
jwtHeaderName = settings.get("jwt_header", AUTHORIZATION);
isDefaultAuthHeader = AUTHORIZATION.equalsIgnoreCase(jwtHeaderName);
Expand All @@ -83,19 +85,23 @@
);
}

final JwtParserBuilder jwtParserBuilder = KeyUtils.createJwtParserBuilderFromSigningKey(signingKey, 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());
for (String key : signingKeys) {
JwtParser jwtParser;
final JwtParserBuilder jwtParserBuilder = KeyUtils.createJwtParserBuilderFromSigningKey(key, log);
if (jwtParserBuilder == null) {
jwtParser = null;

Check warning on line 92 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#L92

Added line #L92 was not covered by tests
} else {
if (requireIssuer != null) {
jwtParserBuilder.requireIssuer(requireIssuer);
}

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

Check warning on line 100 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#L100

Added line #L100 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 +126,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 +164,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 195 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#L193-L195

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

Check warning on line 198 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#L198

Added line #L198 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