Browse Source

Log warn when license does not permit auditing (#65498)

Security auditing only works on license levels GOLD or higher.
The license level can dynamically change while the node is running.
This PR logs a WARN message when license does not permit auditing,
but the static settings xpack.security.enabled and xpack.security.audit.enabled
are true. The log warn message is outputed at most every 30 mins.
Albert Zaharovits 4 years ago
parent
commit
5414ad02ed

+ 27 - 3
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrailService.java

@@ -5,6 +5,8 @@
  */
 package org.elasticsearch.xpack.security.audit;
 
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
 import org.elasticsearch.common.transport.TransportAddress;
 import org.elasticsearch.license.XPackLicenseState;
 import org.elasticsearch.license.XPackLicenseState.Feature;
@@ -16,14 +18,21 @@ import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine.Authoriza
 import org.elasticsearch.xpack.security.transport.filter.SecurityIpFilterRule;
 
 import java.net.InetAddress;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
 
 public class AuditTrailService {
 
+    private static final Logger logger = LogManager.getLogger(AuditTrailService.class);
+
     private static final AuditTrail NOOP_AUDIT_TRAIL = new NoopAuditTrail();
     private final CompositeAuditTrail compositeAuditTrail;
     private final XPackLicenseState licenseState;
+    private final Duration minLogPeriod = Duration.ofMinutes(30);
+    protected AtomicReference<Instant> nextLogInstantAtomic = new AtomicReference<>(Instant.EPOCH);
 
     public AuditTrailService(List<AuditTrail> auditTrails, XPackLicenseState licenseState) {
         this.compositeAuditTrail = new CompositeAuditTrail(Collections.unmodifiableList(auditTrails));
@@ -31,9 +40,13 @@ public class AuditTrailService {
     }
 
     public AuditTrail get() {
-        if (compositeAuditTrail.isEmpty() == false &&
-            licenseState.isSecurityEnabled() && licenseState.checkFeature(Feature.SECURITY_AUDITING)) {
-            return compositeAuditTrail;
+        if (compositeAuditTrail.isEmpty() == false && licenseState.isSecurityEnabled()) {
+            if (licenseState.checkFeature(Feature.SECURITY_AUDITING)) {
+                return compositeAuditTrail;
+            } else {
+                maybeLogAuditingDisabled();
+                return NOOP_AUDIT_TRAIL;
+            }
         } else {
             return NOOP_AUDIT_TRAIL;
         }
@@ -45,6 +58,17 @@ public class AuditTrailService {
         return compositeAuditTrail.auditTrails;
     }
 
+    private void maybeLogAuditingDisabled() {
+        Instant nowInstant = Instant.now();
+        Instant nextLogInstant = nextLogInstantAtomic.get();
+        if (nextLogInstant.isBefore(nowInstant)) {
+            if (nextLogInstantAtomic.compareAndSet(nextLogInstant, nowInstant.plus(minLogPeriod))) {
+                logger.warn("Auditing logging is DISABLED because the currently active license [" +
+                        licenseState.getOperationMode() + "] does not permit it");
+            }
+        }
+    }
+
     private static class NoopAuditTrail implements AuditTrail {
 
         @Override

+ 57 - 0
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/AuditTrailServiceTests.java

@@ -5,10 +5,16 @@
  */
 package org.elasticsearch.xpack.security.audit;
 
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.elasticsearch.common.logging.Loggers;
+import org.elasticsearch.license.License;
 import org.elasticsearch.license.XPackLicenseState;
 import org.elasticsearch.license.XPackLicenseState.Feature;
 import org.elasticsearch.rest.RestRequest;
 import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.test.MockLogAppender;
 import org.elasticsearch.transport.TransportRequest;
 import org.elasticsearch.xpack.core.security.authc.Authentication;
 import org.elasticsearch.xpack.core.security.authc.Authentication.RealmRef;
@@ -20,6 +26,8 @@ import org.elasticsearch.xpack.security.transport.filter.SecurityIpFilterRule;
 import org.junit.Before;
 
 import java.net.InetAddress;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -58,6 +66,55 @@ public class AuditTrailServiceTests extends ESTestCase {
         restRequest = mock(RestRequest.class);
     }
 
+    public void testLogWhenLicenseProhibitsAuditing() throws Exception {
+        MockLogAppender mockLogAppender = new MockLogAppender();
+        mockLogAppender.start();
+        Logger auditTrailServiceLogger = LogManager.getLogger(AuditTrailService.class);
+        Loggers.addAppender(auditTrailServiceLogger, mockLogAppender);
+        when(licenseState.getOperationMode()).thenReturn(randomFrom(License.OperationMode.values()));
+        if (isAuditingAllowed) {
+            mockLogAppender.addExpectation(new MockLogAppender.UnseenEventExpectation(
+                    "audit disabled because of license",
+                    AuditTrailService.class.getName(),
+                    Level.WARN,
+                    "Security auditing is DISABLED because the currently active license [" +
+                            licenseState.getOperationMode() + "] does not permit it"
+            ));
+        } else {
+            mockLogAppender.addExpectation(new MockLogAppender.SeenEventExpectation(
+                    "audit disabled because of license",
+                    AuditTrailService.class.getName(),
+                    Level.WARN,
+                    "Security auditing is DISABLED because the currently active license [" +
+                            licenseState.getOperationMode() + "] does not permit it"
+            ));
+        }
+        for (int i = 1; i <= randomIntBetween(2, 6); i++) {
+            service.get();
+        }
+        mockLogAppender.assertAllExpectationsMatched();
+        Loggers.removeAppender(auditTrailServiceLogger, mockLogAppender);
+    }
+
+    public void testNoLogRecentlyWhenLicenseProhibitsAuditing() throws Exception {
+        MockLogAppender mockLogAppender = new MockLogAppender();
+        mockLogAppender.start();
+        Logger auditTrailServiceLogger = LogManager.getLogger(AuditTrailService.class);
+        Loggers.addAppender(auditTrailServiceLogger, mockLogAppender);
+        service.nextLogInstantAtomic.set(randomFrom(Instant.now().minus(Duration.ofMinutes(5)), Instant.now()));
+        mockLogAppender.addExpectation(new MockLogAppender.UnseenEventExpectation(
+                "audit disabled because of license",
+                AuditTrailService.class.getName(),
+                Level.WARN,
+                "Security auditing is DISABLED because the currently active license [*] does not permit it"
+        ));
+        for (int i = 1; i <= randomIntBetween(2, 6); i++) {
+            service.get();
+        }
+        mockLogAppender.assertAllExpectationsMatched();
+        Loggers.removeAppender(auditTrailServiceLogger, mockLogAppender);
+    }
+
     public void testAuthenticationFailed() throws Exception {
         final String requestId = randomAlphaOfLengthBetween(6, 12);
         service.get().authenticationFailed(requestId, token, "_action", request);