Pārlūkot izejas kodu

Log the status of security on license change (#42488)

Whether security is enabled/disabled is dependent on the combination
of the node settings and the cluster license.

This commit adds a license state listener that logs when the license
change causes security to switch state (or to be initialised).

This is primarily useful for diagnosing cluster formation issues.
Tim Vernum 6 gadi atpakaļ
vecāks
revīzija
b0607ce344

+ 1 - 1
test/framework/src/main/java/org/elasticsearch/test/MockLogAppender.java

@@ -117,7 +117,7 @@ public class MockLogAppender extends AbstractAppender {
 
         @Override
         public void assertMatched() {
-            assertThat("expected to see " + name + " but did not", saw, equalTo(false));
+            assertThat("expected not to see " + name + " but did", saw, equalTo(false));
         }
     }
 

+ 2 - 0
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java

@@ -226,6 +226,7 @@ import org.elasticsearch.xpack.security.rest.action.user.RestHasPrivilegesAction
 import org.elasticsearch.xpack.security.rest.action.user.RestPutUserAction;
 import org.elasticsearch.xpack.security.rest.action.user.RestSetEnabledAction;
 import org.elasticsearch.xpack.security.support.SecurityIndexManager;
+import org.elasticsearch.xpack.security.support.SecurityStatusChangeListener;
 import org.elasticsearch.xpack.security.transport.SecurityHttpSettings;
 import org.elasticsearch.xpack.security.transport.SecurityServerTransportInterceptor;
 import org.elasticsearch.xpack.security.transport.filter.IPFilter;
@@ -446,6 +447,7 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
         // to keep things simple, just invalidate all cached entries on license change. this happens so rarely that the impact should be
         // minimal
         getLicenseState().addListener(allRolesStore::invalidateAll);
+        getLicenseState().addListener(new SecurityStatusChangeListener(getLicenseState()));
 
         final AuthenticationFailureHandler failureHandler = createAuthenticationFailureHandler(realms);
         authcService.set(new AuthenticationService(settings, realms, auditTrailService, failureHandler, threadPool,

+ 45 - 0
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecurityStatusChangeListener.java

@@ -0,0 +1,45 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+package org.elasticsearch.xpack.security.support;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.elasticsearch.license.LicenseStateListener;
+import org.elasticsearch.license.XPackLicenseState;
+
+import java.util.Objects;
+
+/**
+ * A listener for license state changes that provides log messages when a license change
+ * causes security to switch between enable and disabled (or vice versa).
+ */
+public class SecurityStatusChangeListener implements LicenseStateListener {
+
+    private final Logger logger;
+    private final XPackLicenseState licenseState;
+    private Boolean securityEnabled;
+
+    public SecurityStatusChangeListener(XPackLicenseState licenseState) {
+        this.logger = LogManager.getLogger(getClass());
+        this.licenseState = licenseState;
+        this.securityEnabled = null;
+    }
+
+    /**
+     * This listener will not be registered if security has been explicitly disabled, so we only need to account for dynamic changes due
+     * to changes in the applied license.
+     */
+    @Override
+    public synchronized void licenseStateChanged() {
+        final boolean newState = licenseState.isSecurityAvailable() && licenseState.isSecurityDisabledByLicenseDefaults() == false;
+        // old state might be null (undefined) so do Object comparison
+        if (Objects.equals(newState, securityEnabled) == false) {
+            logger.info("Active license is now [{}]; Security is {}", licenseState.getOperationMode(), newState ? "enabled" : "disabled");
+            this.securityEnabled = newState;
+        }
+    }
+}

+ 115 - 0
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/support/SecurityStatusChangeListenerTests.java

@@ -0,0 +1,115 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+package org.elasticsearch.xpack.security.support;
+
+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.test.ESTestCase;
+import org.elasticsearch.test.MockLogAppender;
+import org.junit.After;
+import org.junit.Before;
+import org.mockito.Mockito;
+
+import static org.mockito.Mockito.when;
+
+public class SecurityStatusChangeListenerTests extends ESTestCase {
+
+    private XPackLicenseState licenseState;
+    private SecurityStatusChangeListener listener;
+    private MockLogAppender logAppender;
+    private Logger listenerLogger;
+
+    @Before
+    public void setup() throws IllegalAccessException {
+        licenseState = Mockito.mock(XPackLicenseState.class);
+        when(licenseState.isSecurityAvailable()).thenReturn(true);
+
+        listener = new SecurityStatusChangeListener(licenseState);
+
+        logAppender = new MockLogAppender();
+        logAppender.start();
+        listenerLogger = LogManager.getLogger(listener.getClass());
+        Loggers.addAppender(listenerLogger, logAppender);
+    }
+
+    @After
+    public void cleanup() {
+        Loggers.removeAppender(listenerLogger, logAppender);
+        logAppender.stop();
+    }
+
+    public void testSecurityEnabledToDisabled() {
+        when(licenseState.isSecurityDisabledByLicenseDefaults()).thenReturn(false);
+
+        when(licenseState.getOperationMode()).thenReturn(License.OperationMode.GOLD);
+        logAppender.addExpectation(new MockLogAppender.SeenEventExpectation(
+            "initial change",
+            listener.getClass().getName(),
+            Level.INFO,
+            "Active license is now [GOLD]; Security is enabled"
+        ));
+        listener.licenseStateChanged();
+
+        when(licenseState.getOperationMode()).thenReturn(License.OperationMode.PLATINUM);
+        logAppender.addExpectation(new MockLogAppender.UnseenEventExpectation(
+            "no-op change",
+            listener.getClass().getName(),
+            Level.INFO,
+            "Active license is now [PLATINUM]; Security is enabled"
+        ));
+
+        when(licenseState.isSecurityDisabledByLicenseDefaults()).thenReturn(true);
+        when(licenseState.getOperationMode()).thenReturn(License.OperationMode.BASIC);
+        logAppender.addExpectation(new MockLogAppender.SeenEventExpectation(
+            "change to basic",
+            listener.getClass().getName(),
+            Level.INFO,
+            "Active license is now [BASIC]; Security is disabled"
+        ));
+        listener.licenseStateChanged();
+
+        logAppender.assertAllExpectationsMatched();
+    }
+
+    public void testSecurityDisabledToEnabled() {
+        when(licenseState.isSecurityDisabledByLicenseDefaults()).thenReturn(true);
+
+        when(licenseState.getOperationMode()).thenReturn(License.OperationMode.TRIAL);
+        logAppender.addExpectation(new MockLogAppender.SeenEventExpectation(
+            "initial change",
+            listener.getClass().getName(),
+            Level.INFO,
+            "Active license is now [TRIAL]; Security is disabled"
+        ));
+        listener.licenseStateChanged();
+
+        when(licenseState.getOperationMode()).thenReturn(License.OperationMode.BASIC);
+        logAppender.addExpectation(new MockLogAppender.UnseenEventExpectation(
+            "no-op change",
+            listener.getClass().getName(),
+            Level.INFO,
+            "Active license is now [BASIC]; Security is disabled"
+        ));
+
+        when(licenseState.isSecurityDisabledByLicenseDefaults()).thenReturn(false);
+        when(licenseState.getOperationMode()).thenReturn(License.OperationMode.PLATINUM);
+        logAppender.addExpectation(new MockLogAppender.SeenEventExpectation(
+            "change to platinum",
+            listener.getClass().getName(),
+            Level.INFO,
+            "Active license is now [PLATINUM]; Security is enabled"
+        ));
+        listener.licenseStateChanged();
+
+        logAppender.assertAllExpectationsMatched();
+    }
+
+}