This commit changes our behavior so that when we receive a request with an invalid/expired/wrong access token or API Key we do not fallback to authenticating as the anonymous user even if anonymous access is enabled for Elasticsearch.
This commit is contained in:
parent
295665b1ea
commit
4fc865e579
|
@ -460,6 +460,7 @@ public class AuthenticationService {
|
|||
* <ul>
|
||||
* <li>this is an initial request from a client without preemptive authentication, so we must return an authentication
|
||||
* challenge</li>
|
||||
* <li>this is a request that contained an Authorization Header that we can't validate </li>
|
||||
* <li>this is a request made internally within a node and there is a fallback user, which is typically the
|
||||
* {@link SystemUser}</li>
|
||||
* <li>anonymous access is enabled and this will be considered an anonymous request</li>
|
||||
|
@ -475,7 +476,7 @@ public class AuthenticationService {
|
|||
RealmRef authenticatedBy = new RealmRef("__fallback", "__fallback", nodeName);
|
||||
authentication = new Authentication(fallbackUser, authenticatedBy, null, Version.CURRENT, AuthenticationType.INTERNAL,
|
||||
Collections.emptyMap());
|
||||
} else if (isAnonymousUserEnabled) {
|
||||
} else if (isAnonymousUserEnabled && shouldFallbackToAnonymous()) {
|
||||
logger.trace("No valid credentials found in request [{}], using anonymous [{}]", request, anonymousUser.principal());
|
||||
RealmRef authenticatedBy = new RealmRef("__anonymous", "__anonymous", nodeName);
|
||||
authentication = new Authentication(anonymousUser, authenticatedBy, null, Version.CURRENT, AuthenticationType.ANONYMOUS,
|
||||
|
@ -499,6 +500,20 @@ public class AuthenticationService {
|
|||
action.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* When an API Key or an Elasticsearch Token Service token is used for authentication and authentication fails (as indicated by
|
||||
* a null AuthenticationToken) we should not fallback to the anonymous user.
|
||||
*/
|
||||
boolean shouldFallbackToAnonymous(){
|
||||
String header = threadContext.getHeader("Authorization");
|
||||
if (Strings.hasText(header) &&
|
||||
((header.regionMatches(true, 0, "Bearer ", 0, "Bearer ".length()) && header.length() > "Bearer ".length()) ||
|
||||
(header.regionMatches(true, 0, "ApiKey ", 0, "ApiKey ".length()) && header.length() > "ApiKey ".length()))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes the {@link User} that resulted from attempting to authenticate a token against the {@link Realms}. When the user is
|
||||
* {@code null}, authentication fails and does not proceed. When there is a user, the request is inspected to see if the run as
|
||||
|
|
|
@ -99,6 +99,7 @@ import java.util.concurrent.CountDownLatch;
|
|||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.elasticsearch.index.seqno.SequenceNumbers.UNASSIGNED_PRIMARY_TERM;
|
||||
import static org.elasticsearch.test.SecurityTestsUtils.assertAuthenticationException;
|
||||
import static org.elasticsearch.xpack.core.security.support.Exceptions.authenticationError;
|
||||
import static org.elasticsearch.xpack.security.authc.TokenServiceTests.mockGetTokenFromId;
|
||||
|
@ -744,6 +745,59 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void testWrongTokenDoesNotFallbackToAnonymous() {
|
||||
String username = randomBoolean() ? AnonymousUser.DEFAULT_ANONYMOUS_USERNAME : "user1";
|
||||
Settings.Builder builder = Settings.builder()
|
||||
.putList(AnonymousUser.ROLES_SETTING.getKey(), "r1", "r2", "r3");
|
||||
if (username.equals(AnonymousUser.DEFAULT_ANONYMOUS_USERNAME) == false) {
|
||||
builder.put(AnonymousUser.USERNAME_SETTING.getKey(), username);
|
||||
}
|
||||
Settings anonymousEnabledSettings = builder.build();
|
||||
final AnonymousUser anonymousUser = new AnonymousUser(anonymousEnabledSettings);
|
||||
service = new AuthenticationService(anonymousEnabledSettings, realms, auditTrail,
|
||||
new DefaultAuthenticationFailureHandler(Collections.emptyMap()), threadPool, anonymousUser, tokenService, apiKeyService);
|
||||
|
||||
try (ThreadContext.StoredContext ignore = threadContext.stashContext()) {
|
||||
final String reqId = AuditUtil.getOrGenerateRequestId(threadContext);
|
||||
threadContext.putHeader("Authorization", "Bearer thisisaninvalidtoken");
|
||||
ElasticsearchSecurityException e =
|
||||
expectThrows(ElasticsearchSecurityException.class, () -> authenticateBlocking("_action", message, null));
|
||||
verify(auditTrail).anonymousAccessDenied(reqId, "_action", message);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
assertAuthenticationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void testWrongApiKeyDoesNotFallbackToAnonymous() {
|
||||
String username = randomBoolean() ? AnonymousUser.DEFAULT_ANONYMOUS_USERNAME : "user1";
|
||||
Settings.Builder builder = Settings.builder()
|
||||
.putList(AnonymousUser.ROLES_SETTING.getKey(), "r1", "r2", "r3");
|
||||
if (username.equals(AnonymousUser.DEFAULT_ANONYMOUS_USERNAME) == false) {
|
||||
builder.put(AnonymousUser.USERNAME_SETTING.getKey(), username);
|
||||
}
|
||||
Settings anonymousEnabledSettings = builder.build();
|
||||
final AnonymousUser anonymousUser = new AnonymousUser(anonymousEnabledSettings);
|
||||
service = new AuthenticationService(anonymousEnabledSettings, realms, auditTrail,
|
||||
new DefaultAuthenticationFailureHandler(Collections.emptyMap()), threadPool, anonymousUser, tokenService, apiKeyService);
|
||||
doAnswer(invocationOnMock -> {
|
||||
final GetRequest request = (GetRequest) invocationOnMock.getArguments()[0];
|
||||
final ActionListener<GetResponse> listener = (ActionListener<GetResponse>) invocationOnMock.getArguments()[1];
|
||||
listener.onResponse(new GetResponse(new GetResult(request.index(), request.type(), request.id(),
|
||||
SequenceNumbers.UNASSIGNED_SEQ_NO, UNASSIGNED_PRIMARY_TERM, -1L, false, null,
|
||||
Collections.emptyMap(), Collections.emptyMap())));
|
||||
return Void.TYPE;
|
||||
}).when(client).get(any(GetRequest.class), any(ActionListener.class));
|
||||
try (ThreadContext.StoredContext ignore = threadContext.stashContext()) {
|
||||
final String reqId = AuditUtil.getOrGenerateRequestId(threadContext);
|
||||
threadContext.putHeader("Authorization", "ApiKey dGhpc2lzYW5pbnZhbGlkaWQ6dGhpc2lzYW5pbnZhbGlkc2VjcmV0");
|
||||
ElasticsearchSecurityException e =
|
||||
expectThrows(ElasticsearchSecurityException.class, () -> authenticateBlocking("_action", message, null));
|
||||
verify(auditTrail).anonymousAccessDenied(reqId, "_action", message);
|
||||
verifyNoMoreInteractions(auditTrail);
|
||||
assertAuthenticationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void testAnonymousUserRest() throws Exception {
|
||||
String username = randomBoolean() ? AnonymousUser.DEFAULT_ANONYMOUS_USERNAME : "user1";
|
||||
Settings.Builder builder = Settings.builder()
|
||||
|
|
Loading…
Reference in New Issue