Warn about unlicensed realms if no auth token can be extracted (#61402) (#61419)

There are warnings about unlicense realms when user lookup fails. This PR adds
similar warnings for when no authentication token can be extracted from the request.
This commit is contained in:
Yang Wang 2020-08-22 00:04:45 +10:00 committed by GitHub
parent 1b3a002588
commit 0509465a9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 45 additions and 11 deletions

View File

@ -554,6 +554,13 @@ public class AuthenticationService {
*/
// pkg-private for tests
void handleNullToken() {
List<Realm> unlicensedRealms = realms.getUnlicensedRealms();
if (unlicensedRealms.isEmpty() == false) {
logger.warn("No authentication credential could be extracted using realms [{}]." +
" Realms [{}] were skipped because they are not permitted on the current license",
Strings.collectionToCommaDelimitedString(defaultOrderedRealmList),
Strings.collectionToCommaDelimitedString(unlicensedRealms));
}
final Authentication authentication;
if (fallbackUser != null) {
logger.trace("No valid credentials found in request [{}], using fallback [{}]", request, fallbackUser.principal());

View File

@ -5,6 +5,9 @@
*/
package org.elasticsearch.xpack.security.authc;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.util.ArrayUtil;
import org.apache.lucene.util.SetOnce;
import org.elasticsearch.ElasticsearchException;
@ -34,6 +37,7 @@ import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
@ -51,6 +55,7 @@ import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.test.ClusterServiceUtils;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.MockLogAppender;
import org.elasticsearch.test.rest.FakeRestRequest;
import org.elasticsearch.threadpool.FixedExecutorBuilder;
import org.elasticsearch.threadpool.TestThreadPool;
@ -84,6 +89,7 @@ import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
import org.junit.After;
import org.junit.Before;
import org.mockito.Mockito;
import java.io.IOException;
import java.net.InetAddress;
@ -181,9 +187,11 @@ public class AuthenticationServiceTests extends ESTestCase {
firstRealm = mock(Realm.class);
when(firstRealm.type()).thenReturn(FIRST_REALM_TYPE);
when(firstRealm.name()).thenReturn(FIRST_REALM_NAME);
when(firstRealm.toString()).thenReturn(FIRST_REALM_NAME + "/" + FIRST_REALM_TYPE);
secondRealm = mock(Realm.class);
when(secondRealm.type()).thenReturn(SECOND_REALM_TYPE);
when(secondRealm.name()).thenReturn(SECOND_REALM_NAME);
when(secondRealm.toString()).thenReturn(SECOND_REALM_NAME + "/" + SECOND_REALM_TYPE);
Settings settings = Settings.builder()
.put("path.home", createTempDir())
.put("node.name", "authc_test")
@ -275,18 +283,37 @@ public class AuthenticationServiceTests extends ESTestCase {
}
public void testTokenMissing() throws Exception {
final String reqId = AuditUtil.getOrGenerateRequestId(threadContext);
PlainActionFuture<Authentication> future = new PlainActionFuture<>();
Authenticator authenticator = service.createAuthenticator("_action", transportRequest, true, future);
authenticator.extractToken((token) -> {
assertThat(token, nullValue());
authenticator.handleNullToken();
});
final Logger unlicensedRealmsLogger = LogManager.getLogger(AuthenticationService.class);
final MockLogAppender mockAppender = new MockLogAppender();
mockAppender.start();
try {
Loggers.addAppender(unlicensedRealmsLogger, mockAppender);
mockAppender.addExpectation(new MockLogAppender.SeenEventExpectation(
"unlicensed realms",
AuthenticationService.class.getName(), Level.WARN,
"No authentication credential could be extracted using realms [file_realm/file]. " +
"Realms [second_realm/second] were skipped because they are not permitted on the current license"
));
ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, () -> future.actionGet());
assertThat(e.getMessage(), containsString("missing authentication credentials"));
verify(auditTrail).anonymousAccessDenied(reqId, "_action", transportRequest);
verifyNoMoreInteractions(auditTrail);
Mockito.doReturn(org.elasticsearch.common.collect.List.of(secondRealm)).when(realms).getUnlicensedRealms();
Mockito.doReturn(org.elasticsearch.common.collect.List.of(firstRealm)).when(realms).asList();
final String reqId = AuditUtil.getOrGenerateRequestId(threadContext);
PlainActionFuture<Authentication> future = new PlainActionFuture<>();
Authenticator authenticator = service.createAuthenticator("_action", transportRequest, true, future);
authenticator.extractToken((token) -> {
assertThat(token, nullValue());
authenticator.handleNullToken();
});
ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, () -> future.actionGet());
assertThat(e.getMessage(), containsString("missing authentication credentials"));
verify(auditTrail).anonymousAccessDenied(reqId, "_action", transportRequest);
verifyNoMoreInteractions(auditTrail);
mockAppender.assertAllExpectationsMatched();
} finally {
Loggers.removeAppender(unlicensedRealmsLogger, mockAppender);
mockAppender.stop();
}
}
public void testAuthenticateBothSupportSecondSucceeds() throws Exception {