Skip to content

Commit

Permalink
[8.2] Security plugin close realms (#87429) (#87443)
Browse files Browse the repository at this point in the history
* Security plugin close realms (#87429)

The `Closeable#close` method on `Realm`s was never called upon node
shutdown. This is a problem when realms (eg OIDC) create non-daemon
threads that expect to be stopped when the `close` method is invoked.
Specifically, it is a problem on Windows where the graceful shutdown is
implemented by terminatting all non-daemon threads (see `Bootstrap#stop`
and `Bootstrap#initializeNatives`).

Closes #86286

* IOUtils rename around
  • Loading branch information
albertzaharovits committed Jun 7, 2022
1 parent 7f6f419 commit de3f122
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 4 deletions.
6 changes: 6 additions & 0 deletions docs/changelog/87429.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 87429
summary: Security plugin close releasable realms
area: Security
type: bug
issues:
- 86286
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,36 @@
*/
package org.elasticsearch.xpack.security;

import org.elasticsearch.action.ActionListener;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.ResponseException;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.test.SecurityIntegTestCase;
import org.elasticsearch.test.SecuritySettingsSource;
import org.elasticsearch.test.SecuritySettingsSourceField;
import org.elasticsearch.xpack.core.security.SecurityExtension;
import org.elasticsearch.xpack.core.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.core.security.authc.AuthenticationToken;
import org.elasticsearch.xpack.core.security.authc.Realm;
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
import org.elasticsearch.xpack.core.security.authc.RealmSettings;
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
import org.elasticsearch.xpack.core.security.user.User;

import java.io.Closeable;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import static org.elasticsearch.rest.RestStatus.OK;
import static org.elasticsearch.rest.RestStatus.UNAUTHORIZED;
Expand All @@ -29,6 +49,33 @@ protected boolean addMockHttpTransport() {
return false; // enable http
}

@Override
protected Collection<Class<? extends Plugin>> nodePlugins() {
final List<Class<? extends Plugin>> plugins = new ArrayList<>(super.nodePlugins());
{
// replace the security plugin with the security plugin that contains a dummy realm
plugins.remove(LocalStateSecurity.class);
plugins.add(SecurityPluginTests.LocalStateWithDummyRealmAuthorizationEngineExtension.class);
}
return List.copyOf(plugins);
}

@Override
protected Class<?> xpackPluginClass() {
return SecurityPluginTests.LocalStateWithDummyRealmAuthorizationEngineExtension.class;
}

private static final AtomicBoolean REALM_CLOSE_FLAG = new AtomicBoolean(false);
private static final AtomicBoolean REALM_INIT_FLAG = new AtomicBoolean(false);

@Override
protected Settings nodeSettings(int nodeOrdinal, Settings otherSettings) {
final Settings.Builder settingsBuilder = Settings.builder();
settingsBuilder.put(super.nodeSettings(nodeOrdinal, otherSettings));
settingsBuilder.put("xpack.security.authc.realms.dummy.dummy10.order", "10");
return settingsBuilder.build();
}

public void testThatPluginIsLoaded() throws IOException {
try {
logger.info("executing unauthorized request to /_xpack info");
Expand All @@ -53,4 +100,80 @@ public void testThatPluginIsLoaded() throws IOException {
Response response = getRestClient().performRequest(request);
assertThat(response.getStatusLine().getStatusCode(), is(OK.getStatus()));
}

public void testThatPluginRealmIsLoadedAndClosed() throws IOException {
REALM_CLOSE_FLAG.set(false);
REALM_INIT_FLAG.set(false);
String nodeName = internalCluster().startNode();
assertThat(REALM_INIT_FLAG.get(), is(true));
internalCluster().stopNode(nodeName);
assertThat(REALM_CLOSE_FLAG.get(), is(true));
}

public static class LocalStateWithDummyRealmAuthorizationEngineExtension extends LocalStateSecurity {

public LocalStateWithDummyRealmAuthorizationEngineExtension(Settings settings, Path configPath) throws Exception {
super(settings, configPath);
}

@Override
protected List<SecurityExtension> securityExtensions() {
return List.of(new DummyRealmAuthorizationEngineExtension());
}

@Override
public List<Setting<?>> getSettings() {
ArrayList<Setting<?>> settings = new ArrayList<>();
settings.addAll(super.getSettings());
settings.addAll(RealmSettings.getStandardSettings(DummyRealm.TYPE));
return settings;
}
}

public static class DummyRealmAuthorizationEngineExtension implements SecurityExtension {

DummyRealmAuthorizationEngineExtension() {}

@Override
public Map<String, Realm.Factory> getRealms(SecurityComponents components) {
return Map.of(DummyRealm.TYPE, DummyRealm::new);
}
}

public static class DummyRealm extends Realm implements Closeable {

public static final String TYPE = "dummy";

public DummyRealm(RealmConfig config) {
super(config);
REALM_INIT_FLAG.set(true);
}

@Override
public boolean supports(AuthenticationToken token) {
return false;
}

@Override
public UsernamePasswordToken token(ThreadContext threadContext) {
return null;
}

@Override
public void authenticate(AuthenticationToken authToken, ActionListener<AuthenticationResult<User>> listener) {
listener.onResponse(AuthenticationResult.notHandled());
}

@Override
public void lookupUser(String username, ActionListener<User> listener) {
// Lookup (run-as) is not supported in this realm
listener.onResponse(null);
}

@Override
public void close() {
REALM_CLOSE_FLAG.set(true);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.logging.DeprecationCategory;
import org.elasticsearch.common.logging.DeprecationLogger;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.Maps;
import org.elasticsearch.common.util.concurrent.CountDown;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.env.Environment;
import org.elasticsearch.license.LicensedFeature;
import org.elasticsearch.license.XPackLicenseState;
Expand All @@ -32,6 +34,8 @@
import org.elasticsearch.xpack.security.Security;
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;

import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
Expand All @@ -50,7 +54,7 @@
/**
* Serves as a realms registry (also responsible for ordering the realms appropriately)
*/
public class Realms implements Iterable<Realm> {
public class Realms extends AbstractLifecycleComponent implements Iterable<Realm> {

private static final Logger logger = LogManager.getLogger(Realms.class);
private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(logger.getName());
Expand Down Expand Up @@ -339,6 +343,17 @@ public Map<String, Object> domainUsageStats() {
}
}

@Override
protected void doStart() {}

@Override
protected void doStop() {}

@Override
protected void doClose() throws IOException {
IOUtils.close(allConfiguredRealms.stream().filter(r -> r instanceof Closeable).map(r -> (Closeable) r).toList());
}

private void maybeAddBasicRealms(List<Realm> realms, List<RealmConfig> realmConfigs) throws Exception {
final Set<String> disabledBasicRealmTypes = findDisabledBasicRealmTypes(realmConfigs);
final Set<String> realmTypes = realms.stream().map(Realm::type).collect(Collectors.toUnmodifiableSet());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,13 +226,17 @@ protected void doAssertXPackIsInstalled() {
.map(p -> p.getClassname())
.collect(Collectors.toList());
assertThat(
"plugin [" + LocalStateSecurity.class.getName() + "] not found in [" + pluginNames + "]",
"plugin [" + xpackPluginClass().getName() + "] not found in [" + pluginNames + "]",
pluginNames,
hasItem(LocalStateSecurity.class.getName())
hasItem(xpackPluginClass().getName())
);
}
}

protected Class<?> xpackPluginClass() {
return LocalStateSecurity.class;
}

@Override
protected Settings nodeSettings(int nodeOrdinal, Settings otherSettings) {
Settings.Builder builder = Settings.builder().put(super.nodeSettings(nodeOrdinal, otherSettings));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.elasticsearch.xpack.core.action.XPackInfoFeatureAction;
import org.elasticsearch.xpack.core.action.XPackUsageFeatureAction;
import org.elasticsearch.xpack.core.action.XPackUsageResponse;
import org.elasticsearch.xpack.core.security.SecurityExtension;
import org.elasticsearch.xpack.core.ssl.SSLService;
import org.elasticsearch.xpack.ilm.IndexLifecycle;
import org.elasticsearch.xpack.monitoring.Monitoring;
Expand Down Expand Up @@ -97,7 +98,7 @@ protected XPackLicenseState getLicenseState() {
return thisVar.getLicenseState();
}
});
plugins.add(new Security(settings) {
plugins.add(new Security(settings, thisVar.securityExtensions()) {
@Override
protected SSLService getSslService() {
return thisVar.getSslService();
Expand All @@ -110,6 +111,10 @@ protected XPackLicenseState getLicenseState() {
});
}

protected List<SecurityExtension> securityExtensions() {
return List.of();
}

@Override
protected Class<? extends TransportAction<XPackUsageRequest, XPackUsageResponse>> getUsageAction() {
return SecurityTransportXPackUsageAction.class;
Expand Down

0 comments on commit de3f122

Please sign in to comment.