-
Notifications
You must be signed in to change notification settings - Fork 24.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mandate X-Pack REST handler installed #71061
Changes from 29 commits
a19e287
8bc8643
7fe071c
e6b1581
f707583
b7bb95b
b18a6be
cc5bce6
b1b00f0
d0f0252
a0fba39
065fb8e
75dc96b
0e99823
9fd1481
6e176b4
7651421
e3f28ff
b1b51fd
7ca2084
6bfc65c
80d75b3
356636d
e90511c
16dcd6c
5713478
4a8aa48
4fa2895
81b64d3
f729748
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -235,8 +235,6 @@ | |
import org.elasticsearch.common.inject.AbstractModule; | ||
import org.elasticsearch.common.inject.TypeLiteral; | ||
import org.elasticsearch.common.inject.multibindings.MapBinder; | ||
import org.elasticsearch.common.logging.DeprecationCategory; | ||
import org.elasticsearch.common.logging.DeprecationLogger; | ||
import org.elasticsearch.common.settings.ClusterSettings; | ||
import org.elasticsearch.common.settings.IndexScopedSettings; | ||
import org.elasticsearch.common.settings.Settings; | ||
|
@@ -403,7 +401,6 @@ | |
public class ActionModule extends AbstractModule { | ||
|
||
private static final Logger logger = LogManager.getLogger(ActionModule.class); | ||
private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(logger.getName()); | ||
|
||
private final Settings settings; | ||
private final IndexNameExpressionResolver indexNameExpressionResolver; | ||
|
@@ -444,17 +441,15 @@ public ActionModule(Settings settings, IndexNameExpressionResolver indexNameExpr | |
UnaryOperator<RestHandler> newRestWrapper = plugin.getRestHandlerWrapper(threadPool.getThreadContext()); | ||
if (newRestWrapper != null) { | ||
logger.debug("Using REST wrapper from plugin " + plugin.getClass().getName()); | ||
if (plugin.getClass().getCanonicalName() == null || | ||
plugin.getClass().getCanonicalName().startsWith("org.elasticsearch.xpack") == false) { | ||
throw new IllegalArgumentException("The " + plugin.getClass().getName() + " plugin tried to install a custom REST " + | ||
"wrapper. This functionality is not available anymore."); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Per #73329, there should be tests for this functionality in ActionModuleTests. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved it to ActionModuleTests |
||
if (restWrapper != null) { | ||
throw new IllegalArgumentException("Cannot have more than one plugin implementing a REST wrapper"); | ||
} | ||
restWrapper = newRestWrapper; | ||
if (restWrapper.getClass().getCanonicalName() == null || | ||
restWrapper.getClass().getCanonicalName().startsWith("org.elasticsearch") == false) { | ||
deprecationLogger.deprecate(DeprecationCategory.PLUGINS, "3rd_party_rest_deprecation", "The " + | ||
plugin.getClass().getName() + "plugin installs a custom REST wrapper. This functionality is deprecated and will " + | ||
"not be possible in Elasticsearch 8.0. If this plugin is intended to provide security features for Elasticsearch " + | ||
"then you should switch to using the built-in Elasticsearch features instead."); | ||
} | ||
} | ||
} | ||
mappingRequestValidators = new RequestValidators<>( | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -20,6 +20,7 @@ | |||||
import org.elasticsearch.common.settings.Settings; | ||||||
import org.elasticsearch.common.settings.SettingsFilter; | ||||||
import org.elasticsearch.common.settings.SettingsModule; | ||||||
import org.elasticsearch.common.util.concurrent.ThreadContext; | ||||||
import org.elasticsearch.indices.TestIndexNameExpressionResolver; | ||||||
import org.elasticsearch.plugins.ActionPlugin; | ||||||
import org.elasticsearch.plugins.ActionPlugin.ActionHandler; | ||||||
|
@@ -34,10 +35,13 @@ | |||||
import org.elasticsearch.threadpool.TestThreadPool; | ||||||
import org.elasticsearch.threadpool.ThreadPool; | ||||||
import org.elasticsearch.usage.UsageService; | ||||||
import org.hamcrest.Matchers; | ||||||
|
||||||
import java.io.IOException; | ||||||
import java.util.Arrays; | ||||||
import java.util.List; | ||||||
import java.util.function.Supplier; | ||||||
import java.util.function.UnaryOperator; | ||||||
|
||||||
import static java.util.Collections.emptyList; | ||||||
import static java.util.Collections.singletonList; | ||||||
|
@@ -194,4 +198,47 @@ public List<Route> routes() { | |||||
threadPool.shutdown(); | ||||||
} | ||||||
} | ||||||
|
||||||
public void test3rdPartyHandlerIsNotInstalled() { | ||||||
Settings settings = Settings.builder() | ||||||
.put("xpack.security.enabled", false) | ||||||
.put("path.home", createTempDir()) | ||||||
.build(); | ||||||
|
||||||
SettingsModule settingsModule = new SettingsModule(Settings.EMPTY); | ||||||
ThreadPool threadPool = new TestThreadPool(getTestName()); | ||||||
ActionPlugin secPlugin = new SecPlugin(); | ||||||
try { | ||||||
UsageService usageService = new UsageService(); | ||||||
|
||||||
Exception e = expectThrows(IllegalArgumentException.class, () -> | ||||||
new ActionModule(settingsModule.getSettings(), | ||||||
TestIndexNameExpressionResolver.newInstance(threadPool.getThreadContext()), | ||||||
settingsModule.getIndexScopedSettings(), settingsModule.getClusterSettings(), settingsModule.getSettingsFilter(), | ||||||
threadPool, Arrays.asList(secPlugin), null, null, usageService, null) | ||||||
); | ||||||
assertThat(e.getMessage(), Matchers.equalTo("The org.elasticsearch.action.ActionModuleTests$SecPlugin plugin tried to " + | ||||||
"install a custom REST wrapper. This functionality is not available anymore.")); | ||||||
} finally { | ||||||
threadPool.shutdown(); | ||||||
} | ||||||
} | ||||||
|
||||||
class FakeHandler implements RestHandler { | ||||||
@Override | ||||||
public List<Route> routes() { | ||||||
return singletonList(new Route(GET, "/_dummy")); | ||||||
} | ||||||
|
||||||
@Override | ||||||
public void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception { | ||||||
} | ||||||
} | ||||||
|
||||||
class SecPlugin implements ActionPlugin { | ||||||
@Override | ||||||
public UnaryOperator<RestHandler> getRestHandlerWrapper(ThreadContext threadContext) { | ||||||
return handler -> new FakeHandler(); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is fine - there's no need to change it, but it would have been fine to just make this a no-op operator.
Suggested change
We really only care that the plugin returns something. We don't care what it does. |
||||||
} | ||||||
}; | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,9 +6,13 @@ | |
*/ | ||
package org.elasticsearch.xpack.security; | ||
|
||
import org.apache.logging.log4j.Level; | ||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
import org.elasticsearch.ElasticsearchSecurityException; | ||
import org.elasticsearch.Version; | ||
import org.elasticsearch.action.ActionListener; | ||
import org.elasticsearch.action.ActionModule; | ||
import org.elasticsearch.client.Client; | ||
import org.elasticsearch.cluster.ClusterName; | ||
import org.elasticsearch.cluster.ClusterState; | ||
|
@@ -18,10 +22,12 @@ | |
import org.elasticsearch.cluster.node.DiscoveryNodes; | ||
import org.elasticsearch.cluster.service.ClusterService; | ||
import org.elasticsearch.common.Strings; | ||
import org.elasticsearch.common.logging.Loggers; | ||
import org.elasticsearch.common.network.NetworkModule; | ||
import org.elasticsearch.common.settings.ClusterSettings; | ||
import org.elasticsearch.common.settings.Setting; | ||
import org.elasticsearch.common.settings.Settings; | ||
import org.elasticsearch.common.settings.SettingsModule; | ||
import org.elasticsearch.core.TimeValue; | ||
import org.elasticsearch.common.util.concurrent.ThreadContext; | ||
import org.elasticsearch.env.Environment; | ||
|
@@ -34,9 +40,12 @@ | |
import org.elasticsearch.rest.RestRequest; | ||
import org.elasticsearch.script.ScriptService; | ||
import org.elasticsearch.test.ESTestCase; | ||
import org.elasticsearch.test.MockLogAppender; | ||
import org.elasticsearch.test.VersionUtils; | ||
import org.elasticsearch.test.rest.FakeRestRequest; | ||
import org.elasticsearch.threadpool.TestThreadPool; | ||
import org.elasticsearch.threadpool.ThreadPool; | ||
import org.elasticsearch.usage.UsageService; | ||
import org.elasticsearch.watcher.ResourceWatcherService; | ||
import org.elasticsearch.xpack.core.XPackField; | ||
import org.elasticsearch.xpack.core.XPackSettings; | ||
|
@@ -514,6 +523,71 @@ public void testLicenseUpdateFailureHandlerUpdate() throws Exception { | |
} | ||
} | ||
|
||
public void testSecurityPluginInstallsRestHandlerWrapperEvenIfSecurityIsDisabled() throws IllegalAccessException { | ||
Settings settings = Settings.builder() | ||
.put("xpack.security.enabled", false) | ||
.put("path.home", createTempDir()) | ||
.build(); | ||
SettingsModule settingsModule = new SettingsModule(Settings.EMPTY); | ||
ThreadPool threadPool = new TestThreadPool(getTestName()); | ||
|
||
try { | ||
UsageService usageService = new UsageService(); | ||
Security security = new Security(settings, null); | ||
|
||
ActionModule actionModule = new ActionModule(settingsModule.getSettings(), | ||
TestIndexNameExpressionResolver.newInstance(threadPool.getThreadContext()), | ||
settingsModule.getIndexScopedSettings(), settingsModule.getClusterSettings(), settingsModule.getSettingsFilter(), | ||
threadPool, Arrays.asList(security), null, null, usageService, null); | ||
actionModule.initRestHandlers(null); | ||
BigPandaToo marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this needed? |
||
|
||
assertTrue(security.getRestHandlerWrapper(threadPool.getThreadContext()) != null); | ||
|
||
} finally { | ||
threadPool.shutdown(); | ||
} | ||
|
||
} | ||
|
||
public void testSecurityRestHandlerWrapperCanBeInstalled() throws IllegalAccessException { | ||
final Logger amLogger = LogManager.getLogger(ActionModule.class); | ||
Loggers.setLevel(amLogger, Level.DEBUG); | ||
final MockLogAppender appender = new MockLogAppender(); | ||
Loggers.addAppender(amLogger, appender); | ||
appender.start(); | ||
|
||
Settings settings = Settings.builder() | ||
.put("xpack.security.enabled", false) | ||
.put("path.home", createTempDir()) | ||
.build(); | ||
SettingsModule settingsModule = new SettingsModule(Settings.EMPTY); | ||
ThreadPool threadPool = new TestThreadPool(getTestName()); | ||
|
||
try { | ||
UsageService usageService = new UsageService(); | ||
Security security = new Security(settings, null); | ||
|
||
// Verify Security rest wrapper is about to be installed | ||
// We will throw later if another wrapper is already installed | ||
appender.addExpectation(new MockLogAppender.SeenEventExpectation( | ||
"Security rest wrapper", ActionModule.class.getName(), Level.DEBUG, | ||
"Using REST wrapper from plugin org.elasticsearch.xpack.security.Security" | ||
)); | ||
|
||
ActionModule actionModule = new ActionModule(settingsModule.getSettings(), | ||
TestIndexNameExpressionResolver.newInstance(threadPool.getThreadContext()), | ||
settingsModule.getIndexScopedSettings(), settingsModule.getClusterSettings(), settingsModule.getSettingsFilter(), | ||
threadPool, Arrays.asList(security), null, null, usageService, null); | ||
actionModule.initRestHandlers(null); | ||
|
||
appender.assertAllExpectationsMatched(); | ||
} finally { | ||
threadPool.shutdown(); | ||
appender.stop(); | ||
Loggers.removeAppender(amLogger, appender); | ||
} | ||
} | ||
|
||
private void logAndFail(Exception e) { | ||
logger.error("unexpected exception", e); | ||
fail("unexpected exception " + e.getMessage()); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I probably missed the discussion about why we prefer
getCanonicalName
over justgetName
here. But it seems to me thatgetName
is more consistent since that is what we report in the error message (and also the debug log above)?