Skip to content

Commit

Permalink
Log the status of security on license change
Browse files Browse the repository at this point in the history
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: elastic#42488
  • Loading branch information
tvernum committed May 31, 2019
1 parent f6779de commit f985866
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public UnseenEventExpectation(String name, String logger, Level level, String me

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@
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;
Expand Down Expand Up @@ -461,6 +462,7 @@ Collection<Object> createComponents(Client client, ThreadPool threadPool, Cluste
// 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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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();
}

}

0 comments on commit f985866

Please sign in to comment.