Fix refresh remote JWKS logic (#42662)
This change ensures that: - We only attempt to refresh the remote JWKS when there is a signature related error only ( BadJWSException instead of the geric BadJOSEException ) - We do call OpenIDConnectAuthenticator#getUserClaims upon successful refresh. - We test this in OpenIdConnectAuthenticatorTests. Without this fix, when using the OpenID Connect realm with a remote JWKSet configured in `op.jwks_path`, the refresh would be triggered for most configuration errors ( i.e. wrong value for `op.issuer` ) and the kibana wouldn't get a response and timeout since `getUserClaims` wouldn't be called because `ReloadableJWKSource#reloadAsync` wouldn't call `onResponse` on the future.
This commit is contained in:
parent
5a76f46ac6
commit
7cabe8acc9
|
@ -12,6 +12,7 @@ import com.nimbusds.jose.jwk.JWKSelector;
|
|||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.BadJOSEException;
|
||||
import com.nimbusds.jose.proc.BadJWSException;
|
||||
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
import com.nimbusds.jose.util.IOUtils;
|
||||
|
@ -240,7 +241,7 @@ public class OpenIdConnectAuthenticator {
|
|||
}
|
||||
claimsListener.onResponse(enrichedVerifiedIdTokenClaims);
|
||||
}
|
||||
} catch (BadJOSEException e) {
|
||||
} catch (BadJWSException e) {
|
||||
// We only try to update the cached JWK set once if a remote source is used and
|
||||
// RSA or ECDSA is used for signatures
|
||||
if (shouldRetry
|
||||
|
@ -256,7 +257,7 @@ public class OpenIdConnectAuthenticator {
|
|||
} else {
|
||||
claimsListener.onFailure(new ElasticsearchSecurityException("Failed to parse or validate the ID Token", e));
|
||||
}
|
||||
} catch (com.nimbusds.oauth2.sdk.ParseException | ParseException | JOSEException e) {
|
||||
} catch (com.nimbusds.oauth2.sdk.ParseException | ParseException | BadJOSEException | JOSEException e) {
|
||||
claimsListener.onFailure(new ElasticsearchSecurityException("Failed to parse or validate the ID Token", e));
|
||||
}
|
||||
}
|
||||
|
@ -777,6 +778,7 @@ public class OpenIdConnectAuthenticator {
|
|||
StandardCharsets.UTF_8));
|
||||
reloadFutureRef.set(null);
|
||||
LOGGER.trace("Successfully refreshed and cached remote JWKSet");
|
||||
future.onResponse(null);
|
||||
} catch (IOException | ParseException e) {
|
||||
failed(e);
|
||||
}
|
||||
|
|
|
@ -88,6 +88,7 @@ public class OpenIdConnectAuthenticatorTests extends OpenIdConnectTestCase {
|
|||
private Settings globalSettings;
|
||||
private Environment env;
|
||||
private ThreadContext threadContext;
|
||||
private int callsToReloadJwk;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
|
@ -95,6 +96,7 @@ public class OpenIdConnectAuthenticatorTests extends OpenIdConnectTestCase {
|
|||
.put("xpack.security.authc.realms.oidc.oidc-realm.ssl.verification_mode", "certificate").build();
|
||||
env = TestEnvironment.newEnvironment(globalSettings);
|
||||
threadContext = new ThreadContext(globalSettings);
|
||||
callsToReloadJwk = 0;
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -278,6 +280,7 @@ public class OpenIdConnectAuthenticatorTests extends OpenIdConnectTestCase {
|
|||
authenticator.authenticate(token, future);
|
||||
JWTClaimsSet claimsSet = future.actionGet();
|
||||
assertThat(claimsSet.getSubject(), equalTo(subject));
|
||||
assertThat(callsToReloadJwk, equalTo(0));
|
||||
}
|
||||
|
||||
public void testImplicitFlowFailsWithExpiredToken() throws Exception {
|
||||
|
@ -317,6 +320,7 @@ public class OpenIdConnectAuthenticatorTests extends OpenIdConnectTestCase {
|
|||
assertThat(e.getMessage(), containsString("Failed to parse or validate the ID Token"));
|
||||
assertThat(e.getCause(), instanceOf(BadJWTException.class));
|
||||
assertThat(e.getCause().getMessage(), containsString("Expired JWT"));
|
||||
assertThat(callsToReloadJwk, equalTo(0));
|
||||
}
|
||||
|
||||
public void testImplicitFlowFailsNotYetIssuedToken() throws Exception {
|
||||
|
@ -356,6 +360,7 @@ public class OpenIdConnectAuthenticatorTests extends OpenIdConnectTestCase {
|
|||
assertThat(e.getMessage(), containsString("Failed to parse or validate the ID Token"));
|
||||
assertThat(e.getCause(), instanceOf(BadJWTException.class));
|
||||
assertThat(e.getCause().getMessage(), containsString("JWT issue time ahead of current time"));
|
||||
assertThat(callsToReloadJwk, equalTo(0));
|
||||
}
|
||||
|
||||
public void testImplicitFlowFailsInvalidIssuer() throws Exception {
|
||||
|
@ -394,6 +399,7 @@ public class OpenIdConnectAuthenticatorTests extends OpenIdConnectTestCase {
|
|||
assertThat(e.getMessage(), containsString("Failed to parse or validate the ID Token"));
|
||||
assertThat(e.getCause(), instanceOf(BadJWTException.class));
|
||||
assertThat(e.getCause().getMessage(), containsString("Unexpected JWT issuer"));
|
||||
assertThat(callsToReloadJwk, equalTo(0));
|
||||
}
|
||||
|
||||
public void testImplicitFlowFailsInvalidAudience() throws Exception {
|
||||
|
@ -432,6 +438,7 @@ public class OpenIdConnectAuthenticatorTests extends OpenIdConnectTestCase {
|
|||
assertThat(e.getMessage(), containsString("Failed to parse or validate the ID Token"));
|
||||
assertThat(e.getCause(), instanceOf(BadJWTException.class));
|
||||
assertThat(e.getCause().getMessage(), containsString("Unexpected JWT audience"));
|
||||
assertThat(callsToReloadJwk, equalTo(0));
|
||||
}
|
||||
|
||||
public void testAuthenticateImplicitFlowFailsWithForgedRsaIdToken() throws Exception {
|
||||
|
@ -456,6 +463,7 @@ public class OpenIdConnectAuthenticatorTests extends OpenIdConnectTestCase {
|
|||
assertThat(e.getMessage(), containsString("Failed to parse or validate the ID Token"));
|
||||
assertThat(e.getCause(), instanceOf(BadJWSException.class));
|
||||
assertThat(e.getCause().getMessage(), containsString("Signed JWT rejected: Invalid signature"));
|
||||
assertThat(callsToReloadJwk, equalTo(1));
|
||||
}
|
||||
|
||||
public void testAuthenticateImplicitFlowFailsWithForgedEcsdsaIdToken() throws Exception {
|
||||
|
@ -480,6 +488,7 @@ public class OpenIdConnectAuthenticatorTests extends OpenIdConnectTestCase {
|
|||
assertThat(e.getMessage(), containsString("Failed to parse or validate the ID Token"));
|
||||
assertThat(e.getCause(), instanceOf(BadJWSException.class));
|
||||
assertThat(e.getCause().getMessage(), containsString("Signed JWT rejected: Invalid signature"));
|
||||
assertThat(callsToReloadJwk, equalTo(1));
|
||||
}
|
||||
|
||||
public void testAuthenticateImplicitFlowFailsWithForgedHmacIdToken() throws Exception {
|
||||
|
@ -503,6 +512,7 @@ public class OpenIdConnectAuthenticatorTests extends OpenIdConnectTestCase {
|
|||
assertThat(e.getMessage(), containsString("Failed to parse or validate the ID Token"));
|
||||
assertThat(e.getCause(), instanceOf(BadJWSException.class));
|
||||
assertThat(e.getCause().getMessage(), containsString("Signed JWT rejected: Invalid signature"));
|
||||
assertThat(callsToReloadJwk, equalTo(0));
|
||||
}
|
||||
|
||||
public void testAuthenticateImplicitFlowFailsWithForgedAccessToken() throws Exception {
|
||||
|
@ -532,6 +542,7 @@ public class OpenIdConnectAuthenticatorTests extends OpenIdConnectTestCase {
|
|||
assertThat(e.getMessage(), containsString("Failed to verify access token"));
|
||||
assertThat(e.getCause(), instanceOf(InvalidHashException.class));
|
||||
assertThat(e.getCause().getMessage(), containsString("Access token hash (at_hash) mismatch"));
|
||||
assertThat(callsToReloadJwk, equalTo(0));
|
||||
}
|
||||
|
||||
public void testImplicitFlowFailsWithNoneAlgorithm() throws Exception {
|
||||
|
@ -569,6 +580,7 @@ public class OpenIdConnectAuthenticatorTests extends OpenIdConnectTestCase {
|
|||
assertThat(e.getMessage(), containsString("Failed to parse or validate the ID Token"));
|
||||
assertThat(e.getCause(), instanceOf(BadJOSEException.class));
|
||||
assertThat(e.getCause().getMessage(), containsString("Another algorithm expected, or no matching key(s) found"));
|
||||
assertThat(callsToReloadJwk, equalTo(0));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -599,6 +611,7 @@ public class OpenIdConnectAuthenticatorTests extends OpenIdConnectTestCase {
|
|||
assertThat(e.getMessage(), containsString("Failed to parse or validate the ID Token"));
|
||||
assertThat(e.getCause(), instanceOf(BadJOSEException.class));
|
||||
assertThat(e.getCause().getMessage(), containsString("Another algorithm expected, or no matching key(s) found"));
|
||||
assertThat(callsToReloadJwk, equalTo(0));
|
||||
}
|
||||
|
||||
public void testImplicitFlowFailsWithUnsignedJwt() throws Exception {
|
||||
|
@ -635,6 +648,7 @@ public class OpenIdConnectAuthenticatorTests extends OpenIdConnectTestCase {
|
|||
assertThat(e.getMessage(), containsString("Failed to parse or validate the ID Token"));
|
||||
assertThat(e.getCause(), instanceOf(BadJWTException.class));
|
||||
assertThat(e.getCause().getMessage(), containsString("Signed ID token expected"));
|
||||
assertThat(callsToReloadJwk, equalTo(0));
|
||||
}
|
||||
|
||||
public void testJsonObjectMerging() throws Exception {
|
||||
|
@ -832,6 +846,7 @@ public class OpenIdConnectAuthenticatorTests extends OpenIdConnectTestCase {
|
|||
Mockito.doAnswer(invocation -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
ActionListener<Void> listener = (ActionListener<Void>) invocation.getArguments()[0];
|
||||
callsToReloadJwk += 1;
|
||||
listener.onResponse(null);
|
||||
return null;
|
||||
}).when(jwkSource).triggerReload(any(ActionListener.class));
|
||||
|
|
Loading…
Reference in New Issue