Skip to content

Commit

Permalink
Allow multiple signing keys to be provided
Browse files Browse the repository at this point in the history
Signed-off-by: Stephen Crawford <[email protected]>
  • Loading branch information
stephen-crawford committed Aug 20, 2024
1 parent a64bafe commit ecb73e1
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 204 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -101,45 +101,45 @@ public class JwtAuthenticationTests {
private static final String JWT_AUTH_HEADER = "jwt-auth";

private static final JwtAuthorizationHeaderFactory tokenFactory1 = new JwtAuthorizationHeaderFactory(
KEY_PAIR1.getPrivate(),
CLAIM_USERNAME,
CLAIM_ROLES,
JWT_AUTH_HEADER
KEY_PAIR1.getPrivate(),
CLAIM_USERNAME,
CLAIM_ROLES,
JWT_AUTH_HEADER
);

private static final JwtAuthorizationHeaderFactory tokenFactory2 = new JwtAuthorizationHeaderFactory(
KEY_PAIR2.getPrivate(),
CLAIM_USERNAME,
CLAIM_ROLES,
JWT_AUTH_HEADER
KEY_PAIR2.getPrivate(),
CLAIM_USERNAME,
CLAIM_ROLES,
JWT_AUTH_HEADER
);

public static final TestSecurityConfig.AuthcDomain JWT_AUTH_DOMAIN = new TestSecurityConfig.AuthcDomain(
"jwt",
BASIC_AUTH_DOMAIN_ORDER - 1
"jwt",
BASIC_AUTH_DOMAIN_ORDER - 1
).jwtHttpAuthenticator(
new JwtConfigBuilder().jwtHeader(JWT_AUTH_HEADER)
.signingKey(List.of(PUBLIC_KEY1, PUBLIC_KEY2))
.subjectKey(CLAIM_USERNAME)
.rolesKey(CLAIM_ROLES)
new JwtConfigBuilder().jwtHeader(JWT_AUTH_HEADER)
.signingKey(List.of(PUBLIC_KEY1, PUBLIC_KEY2))
.subjectKey(CLAIM_USERNAME)
.rolesKey(CLAIM_ROLES)
).backend("noop");
public static final String SONG_ID_1 = "song-id-01";

public static final Role DEPARTMENT_SONG_LISTENER_ROLE = new Role("department-song-listener-role").indexPermissions(
"indices:data/read/search"
"indices:data/read/search"
).on(DEPARTMENT_SONG_INDEX_PATTERN);

@ClassRule
public static final LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE)
.anonymousAuth(false)
.nodeSettings(
Map.of("plugins.security.restapi.roles_enabled", List.of("user_" + ADMIN_USER.getName() + "__" + ALL_ACCESS.getName()))
)
.authc(AUTHC_HTTPBASIC_INTERNAL)
.users(ADMIN_USER)
.roles(DEPARTMENT_SONG_LISTENER_ROLE)
.authc(JWT_AUTH_DOMAIN)
.build();
.anonymousAuth(false)
.nodeSettings(
Map.of("plugins.security.restapi.roles_enabled", List.of("user_" + ADMIN_USER.getName() + "__" + ALL_ACCESS.getName()))
)
.authc(AUTHC_HTTPBASIC_INTERNAL)
.users(ADMIN_USER)
.roles(DEPARTMENT_SONG_LISTENER_ROLE)
.authc(JWT_AUTH_DOMAIN)
.build();

@Rule
public LogsRule logsRule = new LogsRule("com.amazon.dlic.auth.http.jwt.HTTPJwtAuthenticator");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ public HTTPJwtAuthenticator(final Settings settings, final Path configPath) {

if (!jwtHeaderName.equals(AUTHORIZATION)) {
deprecationLog.deprecate(
"jwt_header",
"The 'jwt_header' setting will be removed in the next major version of OpenSearch. Consult https://github.com/opensearch-project/security/issues/3886 for more details."
"jwt_header",
"The 'jwt_header' setting will be removed in the next major version of OpenSearch. Consult https://github.com/opensearch-project/security/issues/3886 for more details."
);
}

Expand All @@ -108,7 +108,7 @@ public HTTPJwtAuthenticator(final Settings settings, final Path configPath) {
@Override
@SuppressWarnings("removal")
public AuthCredentials extractCredentials(final SecurityRequest request, final ThreadContext context)
throws OpenSearchSecurityException {
throws OpenSearchSecurityException {
final SecurityManager sm = System.getSecurityManager();

if (sm != null) {
Expand Down Expand Up @@ -147,9 +147,9 @@ private AuthCredentials extractCredentials0(final SecurityRequest request) {
if (jwtToken == null || jwtToken.length() == 0) {
if (log.isDebugEnabled()) {
log.debug(
"No JWT token found in '{}' {} header",
jwtUrlParameter == null ? jwtHeaderName : jwtUrlParameter,
jwtUrlParameter == null ? "header" : "url parameter"
"No JWT token found in '{}' {} header",
jwtUrlParameter == null ? jwtHeaderName : jwtUrlParameter,
jwtUrlParameter == null ? "header" : "url parameter"
);
}
return null;
Expand Down Expand Up @@ -216,7 +216,7 @@ private void assertValidAudienceClaim(Claims claims) throws BadJWTException {
@Override
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\""), "")
new SecurityResponse(HttpStatus.SC_UNAUTHORIZED, Map.of("WWW-Authenticate", "Bearer realm=\"OpenSearch Security\""), "")
);
}

Expand Down Expand Up @@ -245,10 +245,10 @@ protected String extractSubject(final Claims claims, final SecurityRequest reque
// We expect a String. If we find something else, convert to String but issue a warning
if (!(subjectObject instanceof String)) {
log.warn(
"Expected type String for roles in the JWT for subject_key {}, but value was '{}' ({}). Will convert this value to String.",
subjectKey,
subjectObject,
subjectObject.getClass()
"Expected type String for roles in the JWT for subject_key {}, but value was '{}' ({}). Will convert this value to String.",
subjectKey,
subjectObject,
subjectObject.getClass()
);
}
subject = String.valueOf(subjectObject);
Expand All @@ -266,8 +266,8 @@ protected String[] extractRoles(final Claims claims, final SecurityRequest reque
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
"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];
}
Expand All @@ -277,10 +277,10 @@ protected String[] extractRoles(final Claims claims, final SecurityRequest reque
// We expect a String or Collection. If we find something else, convert to String but issue a warning
if (!(rolesObject instanceof String) && !(rolesObject instanceof Collection<?>)) {
log.warn(
"Expected type String or Collection for roles in the JWT for roles_key {}, but value was '{}' ({}). Will convert this value to String.",
rolesKey,
rolesObject,
rolesObject.getClass()
"Expected type String or Collection for roles in the JWT for roles_key {}, but value was '{}' ({}). Will convert this value to String.",
rolesKey,
rolesObject,
rolesObject.getClass()
);
} else if (rolesObject instanceof Collection<?>) {
roles = ((Collection<String>) rolesObject).toArray(new String[0]);
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/org/opensearch/security/util/KeyUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ public JwtParserBuilder run() {
PublicKey key = null;

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

final byte[] decoded = Base64.getDecoder().decode(minimalKeyFormat);

Expand Down
Loading

0 comments on commit ecb73e1

Please sign in to comment.