Browse Source

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

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.
Yang Wang 5 years ago
parent
commit
2e4394c173

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

@@ -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());

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

@@ -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")
@@ -274,18 +282,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();
-        });
-
-        ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, () -> future.actionGet());
-        assertThat(e.getMessage(), containsString("missing authentication credentials"));
-        verify(auditTrail).anonymousAccessDenied(reqId, "_action", transportRequest);
-        verifyNoMoreInteractions(auditTrail);
+        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"
+            ));
+
+            Mockito.doReturn(List.of(secondRealm)).when(realms).getUnlicensedRealms();
+            Mockito.doReturn(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 {