Skip to content

Commit 11d2480

Browse files
Let realms gracefully terminate the authN chain (#55623)
AuthN realms are ordered as a chain so that the credentials of a given user are verified in succession. Upon the first successful verification, the user is authenticated. Realms do however have the option to cut short this iterative process, when the credentials don't verify and the user cannot exist in any other realm. This mechanism is currently used by the Reserved and the Kerberos realm. This commit improves the early termination operation by allowing realms to gracefully terminate authentication, as if the chain has been tried out completely. Previously, early termination resulted in an authentication error which varies the response body compared to the failed authentication outcome where no realm could verify the credentials successfully. Reserved users are hence denied authentication in exactly the same way as other users are when no realm can validate their credentials.
1 parent c8363f0 commit 11d2480

File tree

3 files changed

+49
-23
lines changed

3 files changed

+49
-23
lines changed

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -465,11 +465,15 @@ private void consumeToken(AuthenticationToken token) {
465465
// the user was not authenticated, call this so we can audit the correct event
466466
request.realmAuthenticationFailed(authenticationToken, realm.name());
467467
if (result.getStatus() == AuthenticationResult.Status.TERMINATE) {
468-
logger.info("Authentication of [{}] was terminated by realm [{}] - {}",
469-
authenticationToken.principal(), realm.name(), result.getMessage());
470-
Exception e = (result.getException() != null) ? result.getException()
471-
: Exceptions.authenticationError(result.getMessage());
472-
userListener.onFailure(e);
468+
if (result.getException() != null) {
469+
logger.info(new ParameterizedMessage(
470+
"Authentication of [{}] was terminated by realm [{}] - {}",
471+
authenticationToken.principal(), realm.name(), result.getMessage()), result.getException());
472+
} else {
473+
logger.info("Authentication of [{}] was terminated by realm [{}] - {}",
474+
authenticationToken.principal(), realm.name(), result.getMessage());
475+
}
476+
userListener.onFailure(result.getException());
473477
} else {
474478
if (result.getMessage() != null) {
475479
messages.put(realm, new Tuple<>(result.getMessage(), result.getException()));
@@ -491,7 +495,13 @@ private void consumeToken(AuthenticationToken token) {
491495
final IteratingActionListener<User, Realm> authenticatingListener =
492496
new IteratingActionListener<>(ContextPreservingActionListener.wrapPreservingContext(ActionListener.wrap(
493497
(user) -> consumeUser(user, messages),
494-
(e) -> listener.onFailure(request.exceptionProcessingRequest(e, token))), threadContext),
498+
(e) -> {
499+
if (e != null) {
500+
listener.onFailure(request.exceptionProcessingRequest(e, token));
501+
} else {
502+
listener.onFailure(request.authenticationFailed(token));
503+
}
504+
}), threadContext),
495505
realmAuthenticatingConsumer, realmsList, threadContext);
496506
try {
497507
authenticatingListener.run();

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,14 +1002,14 @@ public void testRealmSupportsMethodThrowingExceptionRest() throws Exception {
10021002
}
10031003
}
10041004

1005-
public void testRealmAuthenticateTerminatingAuthenticationProcess() throws Exception {
1005+
public void testRealmAuthenticateTerminateAuthenticationProcessWithException() {
10061006
final String reqId = AuditUtil.getOrGenerateRequestId(threadContext);
10071007
final AuthenticationToken token = mock(AuthenticationToken.class);
1008-
when(token.principal()).thenReturn(randomAlphaOfLength(5));
1008+
final String principal = randomAlphaOfLength(5);
1009+
when(token.principal()).thenReturn(principal);
10091010
when(secondRealm.token(threadContext)).thenReturn(token);
10101011
when(secondRealm.supports(token)).thenReturn(true);
1011-
final boolean terminateWithNoException = rarely();
1012-
final boolean throwElasticsearchSecurityException = (terminateWithNoException == false) && randomBoolean();
1012+
final boolean throwElasticsearchSecurityException = randomBoolean();
10131013
final boolean withAuthenticateHeader = throwElasticsearchSecurityException && randomBoolean();
10141014
Exception throwE = new Exception("general authentication error");
10151015
final String basicScheme = "Basic realm=\"" + XPackField.SECURITY + "\" charset=\"UTF-8\"";
@@ -1020,31 +1020,45 @@ public void testRealmAuthenticateTerminatingAuthenticationProcess() throws Excep
10201020
((ElasticsearchSecurityException) throwE).addHeader("WWW-Authenticate", selectedScheme);
10211021
}
10221022
}
1023-
mockAuthenticate(secondRealm, token, (terminateWithNoException) ? null : throwE, true);
1023+
mockAuthenticate(secondRealm, token, throwE, true);
10241024

10251025
ElasticsearchSecurityException e =
10261026
expectThrows(ElasticsearchSecurityException.class, () -> authenticateBlocking("_action", transportRequest, null));
1027-
if (terminateWithNoException) {
1028-
assertThat(e.getMessage(), is("terminate authc process"));
1029-
assertThat(e.getHeader("WWW-Authenticate"), contains(basicScheme));
1030-
} else {
1031-
if (throwElasticsearchSecurityException) {
1032-
assertThat(e.getMessage(), is("authentication error"));
1033-
if (withAuthenticateHeader) {
1034-
assertThat(e.getHeader("WWW-Authenticate"), contains(selectedScheme));
1035-
} else {
1036-
assertThat(e.getHeader("WWW-Authenticate"), contains(basicScheme));
1037-
}
1027+
if (throwElasticsearchSecurityException) {
1028+
assertThat(e.getMessage(), is("authentication error"));
1029+
if (withAuthenticateHeader) {
1030+
assertThat(e.getHeader("WWW-Authenticate"), contains(selectedScheme));
10381031
} else {
1039-
assertThat(e.getMessage(), is("error attempting to authenticate request"));
10401032
assertThat(e.getHeader("WWW-Authenticate"), contains(basicScheme));
10411033
}
1034+
} else {
1035+
assertThat(e.getMessage(), is("error attempting to authenticate request"));
1036+
assertThat(e.getHeader("WWW-Authenticate"), contains(basicScheme));
10421037
}
10431038
verify(auditTrail).authenticationFailed(reqId, secondRealm.name(), token, "_action", transportRequest);
10441039
verify(auditTrail).authenticationFailed(reqId, token, "_action", transportRequest);
10451040
verifyNoMoreInteractions(auditTrail);
10461041
}
10471042

1043+
public void testRealmAuthenticateGracefulTerminateAuthenticationProcess() {
1044+
final String reqId = AuditUtil.getOrGenerateRequestId(threadContext);
1045+
final AuthenticationToken token = mock(AuthenticationToken.class);
1046+
final String principal = randomAlphaOfLength(5);
1047+
when(token.principal()).thenReturn(principal);
1048+
when(firstRealm.token(threadContext)).thenReturn(token);
1049+
when(firstRealm.supports(token)).thenReturn(true);
1050+
final String basicScheme = "Basic realm=\"" + XPackField.SECURITY + "\" charset=\"UTF-8\"";
1051+
mockAuthenticate(firstRealm, token, null, true);
1052+
1053+
ElasticsearchSecurityException e =
1054+
expectThrows(ElasticsearchSecurityException.class, () -> authenticateBlocking("_action", transportRequest, null));
1055+
assertThat(e.getMessage(), is("unable to authenticate user [" + principal + "] for action [_action]"));
1056+
assertThat(e.getHeader("WWW-Authenticate"), contains(basicScheme));
1057+
verify(auditTrail).authenticationFailed(reqId, firstRealm.name(), token, "_action", transportRequest);
1058+
verify(auditTrail).authenticationFailed(reqId, token, "_action", transportRequest);
1059+
verifyNoMoreInteractions(auditTrail);
1060+
}
1061+
10481062
public void testRealmAuthenticateThrowingException() throws Exception {
10491063
AuthenticationToken token = mock(AuthenticationToken.class);
10501064
when(token.principal()).thenReturn(randomAlphaOfLength(5));

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealmTests.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,8 @@ private void assertFailedAuthentication(PlainActionFuture<AuthenticationResult>
306306
assertThat(result.getStatus(), is(AuthenticationResult.Status.TERMINATE));
307307
assertThat(result.getMessage(), containsString("failed to authenticate"));
308308
assertThat(result.getMessage(), containsString(principal));
309+
// exception must be null for graceful termination
310+
assertThat(result.getException(), is(nullValue()));
309311
}
310312

311313
@SuppressWarnings("unchecked")

0 commit comments

Comments
 (0)