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

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.

Backport of: #42488
This commit is contained in:
Tim Vernum 2019-06-04 14:25:43 +10:00 committed by GitHub
parent 9035e61825
commit 8de3a88205
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 163 additions and 1 deletions

View File

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

View File

@ -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;
@ -461,6 +462,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,

View File

@ -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;
}
}
}

View File

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