Scripting: remove groovy sandbox
Groovy sandboxing was disabled by default from 1.4.3 on though since we found out that it could be worked around, so it makes little sense to keep it and maintain it. Closes #10156 Closes #10480
This commit is contained in:
parent
371bc5a6b3
commit
c914134355
|
@ -345,6 +345,11 @@ Deprecated script parameters `id`, `file`, and `scriptField` have been removed
|
|||
from all scriptable APIs. `script_id`, `script_file` and `script` should be used
|
||||
in their place.
|
||||
|
||||
=== Groovy scripts sandbox
|
||||
|
||||
The groovy sandbox and related settings have been removed. Groovy is now a non
|
||||
sandboxed scripting language, without any option to turn the sandbox on.
|
||||
|
||||
=== Plugins making use of scripts
|
||||
|
||||
Plugins that make use of scripts must register their own script context through
|
||||
|
|
|
@ -11,26 +11,11 @@ The scripting module uses by default http://groovy.codehaus.org/[groovy]
|
|||
scripting language with some extensions. Groovy is used since it is extremely
|
||||
fast and very simple to use.
|
||||
|
||||
.Groovy dynamic scripting disabled by default from v1.4.3
|
||||
.Groovy dynamic scripting off by default from v1.4.3
|
||||
[IMPORTANT]
|
||||
===================================================
|
||||
|
||||
Elasticsearch versions 1.3.0-1.3.7 and 1.4.0-1.4.2 have a vulnerability in the
|
||||
Groovy scripting engine. The vulnerability allows an attacker to construct
|
||||
Groovy scripts that escape the sandbox and execute shell commands as the user
|
||||
running the Elasticsearch Java VM.
|
||||
|
||||
If you are running a vulnerable version of Elasticsearch, you should either
|
||||
upgrade to at least v1.3.8 or v1.4.3, or disable dynamic Groovy scripts by
|
||||
adding this setting to the `config/elasticsearch.yml` file in all nodes in the
|
||||
cluster:
|
||||
|
||||
[source,yaml]
|
||||
-----------------------------------
|
||||
script.groovy.sandbox.enabled: false
|
||||
-----------------------------------
|
||||
|
||||
This will turn off the Groovy sandbox, thus preventing dynamic Groovy scripts
|
||||
Groovy dynamic scripting is off by default, preventing dynamic Groovy scripts
|
||||
from being accepted as part of a request or retrieved from the special
|
||||
`.scripts` index. You will still be able to use Groovy scripts stored in files
|
||||
in the `config/scripts/` directory on every node.
|
||||
|
@ -351,39 +336,6 @@ The default scripting language (assuming no `lang` parameter is provided) is
|
|||
`groovy`. In order to change it, set the `script.default_lang` to the
|
||||
appropriate language.
|
||||
|
||||
[float]
|
||||
=== Groovy Sandboxing
|
||||
|
||||
Elasticsearch sandboxes Groovy scripts that are compiled and executed in order
|
||||
to ensure they don't perform unwanted actions. There are a number of options
|
||||
that can be used for configuring this sandbox:
|
||||
|
||||
`script.groovy.sandbox.receiver_whitelist`::
|
||||
|
||||
Comma-separated list of string classes for objects that may have methods
|
||||
invoked.
|
||||
|
||||
`script.groovy.sandbox.package_whitelist`::
|
||||
|
||||
Comma-separated list of packages under which new objects may be constructed.
|
||||
|
||||
`script.groovy.sandbox.class_whitelist`::
|
||||
|
||||
Comma-separated list of classes that are allowed to be constructed.
|
||||
|
||||
`script.groovy.sandbox.method_blacklist`::
|
||||
|
||||
Comma-separated list of methods that are never allowed to be invoked,
|
||||
regardless of target object.
|
||||
|
||||
`script.groovy.sandbox.enabled`::
|
||||
|
||||
Flag to enable the sandbox (defaults to `false` meaning the sandbox is
|
||||
disabled).
|
||||
|
||||
When specifying whitelist or blacklist settings for the groovy sandbox, all
|
||||
options replace the current whitelist, they are not additive.
|
||||
|
||||
[float]
|
||||
=== Automatic Script Reloading
|
||||
|
||||
|
|
|
@ -33,7 +33,6 @@ import org.elasticsearch.indices.cache.filter.IndicesFilterCache;
|
|||
import org.elasticsearch.indices.recovery.RecoverySettings;
|
||||
import org.elasticsearch.indices.store.IndicesStore;
|
||||
import org.elasticsearch.indices.ttl.IndicesTTLService;
|
||||
import org.elasticsearch.script.groovy.GroovyScriptEngineService;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
|
||||
/**
|
||||
|
@ -101,7 +100,6 @@ public class ClusterDynamicSettingsModule extends AbstractModule {
|
|||
clusterDynamicSettings.addDynamicSetting(HierarchyCircuitBreakerService.FIELDDATA_CIRCUIT_BREAKER_OVERHEAD_SETTING, Validator.NON_NEGATIVE_DOUBLE);
|
||||
clusterDynamicSettings.addDynamicSetting(HierarchyCircuitBreakerService.REQUEST_CIRCUIT_BREAKER_LIMIT_SETTING, Validator.MEMORY_SIZE);
|
||||
clusterDynamicSettings.addDynamicSetting(HierarchyCircuitBreakerService.REQUEST_CIRCUIT_BREAKER_OVERHEAD_SETTING, Validator.NON_NEGATIVE_DOUBLE);
|
||||
clusterDynamicSettings.addDynamicSetting(GroovyScriptEngineService.GROOVY_SCRIPT_BLACKLIST_PATCH);
|
||||
}
|
||||
|
||||
public void addDynamicSettings(String... settings) {
|
||||
|
|
|
@ -58,7 +58,6 @@ import org.elasticsearch.common.xcontent.XContentParser;
|
|||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.index.query.TemplateQueryParser;
|
||||
import org.elasticsearch.node.settings.NodeSettingsService;
|
||||
import org.elasticsearch.script.groovy.GroovyScriptEngineService;
|
||||
import org.elasticsearch.search.lookup.SearchLookup;
|
||||
import org.elasticsearch.watcher.FileChangesListener;
|
||||
|
@ -100,7 +99,6 @@ public class ScriptService extends AbstractComponent implements Closeable {
|
|||
|
||||
private final Cache<CacheKey, CompiledScript> cache;
|
||||
private final Path scriptsDirectory;
|
||||
private final FileWatcher fileWatcher;
|
||||
|
||||
private final ScriptModes scriptModes;
|
||||
private final ScriptContextRegistry scriptContextRegistry;
|
||||
|
@ -114,7 +112,7 @@ public class ScriptService extends AbstractComponent implements Closeable {
|
|||
|
||||
@Inject
|
||||
public ScriptService(Settings settings, Environment env, Set<ScriptEngineService> scriptEngines,
|
||||
ResourceWatcherService resourceWatcherService, NodeSettingsService nodeSettingsService, ScriptContextRegistry scriptContextRegistry) throws IOException {
|
||||
ResourceWatcherService resourceWatcherService, ScriptContextRegistry scriptContextRegistry) throws IOException {
|
||||
super(settings);
|
||||
|
||||
if (Strings.hasLength(settings.get(DISABLE_DYNAMIC_SCRIPTING_SETTING))) {
|
||||
|
@ -159,7 +157,7 @@ public class ScriptService extends AbstractComponent implements Closeable {
|
|||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Using scripts directory [{}] ", scriptsDirectory);
|
||||
}
|
||||
this.fileWatcher = new FileWatcher(scriptsDirectory);
|
||||
FileWatcher fileWatcher = new FileWatcher(scriptsDirectory);
|
||||
fileWatcher.addListener(new ScriptChangesListener());
|
||||
|
||||
if (settings.getAsBoolean(SCRIPT_AUTO_RELOAD_ENABLED_SETTING, true)) {
|
||||
|
@ -169,7 +167,6 @@ public class ScriptService extends AbstractComponent implements Closeable {
|
|||
// automatic reload is disable just load scripts once
|
||||
fileWatcher.init();
|
||||
}
|
||||
nodeSettingsService.addListener(new ApplySettings());
|
||||
}
|
||||
|
||||
//This isn't set in the ctor because doing so creates a guice circular
|
||||
|
@ -183,21 +180,6 @@ public class ScriptService extends AbstractComponent implements Closeable {
|
|||
IOUtils.close(scriptEngines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear both the in memory and on disk compiled script caches. Files on
|
||||
* disk will be treated as if they are new and recompiled.
|
||||
* */
|
||||
public void clearCache() {
|
||||
logger.debug("clearing script cache");
|
||||
// Clear the in-memory script caches
|
||||
this.cache.invalidateAll();
|
||||
this.cache.cleanUp();
|
||||
// Clear the cache of on-disk scripts
|
||||
this.staticCache.clear();
|
||||
// Clear the file watcher's state so it re-compiles on-disk scripts
|
||||
this.fileWatcher.clearState();
|
||||
}
|
||||
|
||||
private ScriptEngineService getScriptEngineServiceForLang(String lang) {
|
||||
ScriptEngineService scriptEngineService = scriptEnginesByLang.get(lang);
|
||||
if (scriptEngineService == null) {
|
||||
|
@ -642,23 +624,4 @@ public class ScriptService extends AbstractComponent implements Closeable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ApplySettings implements NodeSettingsService.Listener {
|
||||
@Override
|
||||
public void onRefreshSettings(Settings settings) {
|
||||
GroovyScriptEngineService engine = (GroovyScriptEngineService) ScriptService.this.scriptEnginesByLang.get(GroovyScriptEngineService.NAME);
|
||||
if (engine != null) {
|
||||
String[] patches = settings.getAsArray(GroovyScriptEngineService.GROOVY_SCRIPT_BLACKLIST_PATCH, Strings.EMPTY_ARRAY);
|
||||
boolean blacklistChanged = engine.addToBlacklist(patches);
|
||||
if (blacklistChanged) {
|
||||
logger.info("adding {} to [{}], new blacklisted methods: {}", patches,
|
||||
GroovyScriptEngineService.GROOVY_SCRIPT_BLACKLIST_PATCH, engine.blacklistAdditions());
|
||||
engine.reloadConfig();
|
||||
// Because the GroovyScriptEngineService knows nothing about the
|
||||
// cache, we need to clear it here if the setting changes
|
||||
ScriptService.this.clearCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,170 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.script.groovy;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.ast.expr.*;
|
||||
import org.codehaus.groovy.control.customizers.SecureASTCustomizer;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.google.common.collect.Lists.newArrayList;
|
||||
|
||||
/**
|
||||
* Class used to determine whether a Groovy expression should be allowed.
|
||||
* During compilation, every expression is passed to the
|
||||
* <code>isAuthorized</code> method, which returns true to allow that method
|
||||
* and false to block it. Includes all of the sandbox-related whitelist and
|
||||
* blacklist options.
|
||||
*/
|
||||
public class GroovySandboxExpressionChecker implements SecureASTCustomizer.ExpressionChecker {
|
||||
|
||||
public static String GROOVY_SANDBOX_METHOD_BLACKLIST = "script.groovy.sandbox.method_blacklist";
|
||||
public static String GROOVY_SANDBOX_PACKAGE_WHITELIST = "script.groovy.sandbox.package_whitelist";
|
||||
public static String GROOVY_SANDBOX_CLASS_WHITELIST = "script.groovy.sandbox.class_whitelist";
|
||||
public static String GROOVY_SCRIPT_SANDBOX_RECEIVER_WHITELIST = "script.groovy.sandbox.receiver_whitelist";
|
||||
|
||||
private final Set<String> methodBlacklist;
|
||||
private final Set<String> additionalMethodBlacklist;
|
||||
private final Set<String> packageWhitelist;
|
||||
private final Set<String> classWhitelist;
|
||||
|
||||
public GroovySandboxExpressionChecker(Settings settings, Set<String> blacklistAdditions) {
|
||||
this.methodBlacklist = ImmutableSet.copyOf(settings.getAsArray(GROOVY_SANDBOX_METHOD_BLACKLIST, defaultMethodBlacklist, true));
|
||||
this.additionalMethodBlacklist = ImmutableSet.copyOf(blacklistAdditions);
|
||||
this.packageWhitelist = ImmutableSet.copyOf(settings.getAsArray(GROOVY_SANDBOX_PACKAGE_WHITELIST, defaultPackageWhitelist, true));
|
||||
this.classWhitelist = ImmutableSet.copyOf(settings.getAsArray(GROOVY_SANDBOX_CLASS_WHITELIST, defaultClassConstructionWhitelist, true));
|
||||
}
|
||||
|
||||
// Never allow calling these methods, regardless of the object type
|
||||
public static String[] defaultMethodBlacklist = new String[]{
|
||||
"getClass",
|
||||
"class",
|
||||
"forName",
|
||||
"wait",
|
||||
"notify",
|
||||
"notifyAll",
|
||||
"invokeMethod",
|
||||
"finalize"
|
||||
};
|
||||
|
||||
// Only instances of these classes in these packages can be instantiated
|
||||
public static String[] defaultPackageWhitelist = new String[] {"java.util", "java.lang", "org.joda.time"};
|
||||
|
||||
// Classes that are allowed to be constructed
|
||||
public static String[] defaultClassConstructionWhitelist = new String[]{
|
||||
java.util.Date.class.getName(),
|
||||
java.util.Map.class.getName(),
|
||||
java.util.List.class.getName(),
|
||||
java.util.Set.class.getName(),
|
||||
java.util.ArrayList.class.getName(),
|
||||
java.util.Arrays.class.getName(),
|
||||
java.util.HashMap.class.getName(),
|
||||
java.util.HashSet.class.getName(),
|
||||
java.util.UUID.class.getName(),
|
||||
java.math.BigDecimal.class.getName(),
|
||||
org.joda.time.DateTime.class.getName(),
|
||||
org.joda.time.DateTimeZone.class.getName()
|
||||
};
|
||||
|
||||
// Default whitelisted receiver classes for the Groovy sandbox
|
||||
private final static String[] defaultReceiverWhitelist = new String [] {
|
||||
groovy.util.GroovyCollections.class.getName(),
|
||||
java.lang.Math.class.getName(),
|
||||
java.lang.Integer.class.getName(), "[I", "[[I", "[[[I",
|
||||
java.lang.Float.class.getName(), "[F", "[[F", "[[[F",
|
||||
java.lang.Double.class.getName(), "[D", "[[D", "[[[D",
|
||||
java.lang.Long.class.getName(), "[J", "[[J", "[[[J",
|
||||
java.lang.Short.class.getName(), "[S", "[[S", "[[[S",
|
||||
java.lang.Character.class.getName(), "[C", "[[C", "[[[C",
|
||||
java.lang.Byte.class.getName(), "[B", "[[B", "[[[B",
|
||||
java.lang.Boolean.class.getName(), "[Z", "[[Z", "[[[Z",
|
||||
java.math.BigDecimal.class.getName(),
|
||||
java.util.Arrays.class.getName(),
|
||||
java.util.Date.class.getName(),
|
||||
java.util.List.class.getName(),
|
||||
java.util.Map.class.getName(),
|
||||
java.util.Set.class.getName(),
|
||||
java.lang.Object.class.getName(),
|
||||
org.joda.time.DateTime.class.getName(),
|
||||
org.joda.time.DateTimeUtils.class.getName(),
|
||||
org.joda.time.DateTimeZone.class.getName(),
|
||||
org.joda.time.Instant.class.getName()
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether the expression to be compiled is allowed
|
||||
*/
|
||||
@Override
|
||||
public boolean isAuthorized(Expression expression) {
|
||||
if (expression instanceof MethodPointerExpression) {
|
||||
return false;
|
||||
} else if (expression instanceof MethodCallExpression) {
|
||||
MethodCallExpression mce = (MethodCallExpression) expression;
|
||||
String methodName = mce.getMethodAsString();
|
||||
if (methodBlacklist.contains(methodName)) {
|
||||
return false;
|
||||
} else if (additionalMethodBlacklist.contains(methodName)) {
|
||||
return false;
|
||||
} else if (methodName == null && mce.getMethod() instanceof GStringExpression) {
|
||||
// We do not allow GStrings for method invocation, they are a security risk
|
||||
return false;
|
||||
}
|
||||
} else if (expression instanceof ConstructorCallExpression) {
|
||||
ConstructorCallExpression cce = (ConstructorCallExpression) expression;
|
||||
ClassNode type = cce.getType();
|
||||
if (!packageWhitelist.contains(type.getPackageName())) {
|
||||
return false;
|
||||
}
|
||||
if (!classWhitelist.contains(type.getName())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a customized ASTCustomizer that includes the whitelists and
|
||||
* expression checker.
|
||||
*/
|
||||
public static SecureASTCustomizer getSecureASTCustomizer(Settings settings, Set<String> blacklistAdditions) {
|
||||
SecureASTCustomizer scz = new SecureASTCustomizer();
|
||||
// Closures are allowed
|
||||
scz.setClosuresAllowed(true);
|
||||
// But defining methods is not
|
||||
scz.setMethodDefinitionAllowed(false);
|
||||
// Only allow the imports that we explicitly call out
|
||||
List<String> importWhitelist = new ArrayList<>();
|
||||
importWhitelist.addAll(ImmutableSet.copyOf(GroovySandboxExpressionChecker.defaultClassConstructionWhitelist));
|
||||
scz.setImportsWhitelist(importWhitelist);
|
||||
// Package definitions are not allowed
|
||||
scz.setPackageAllowed(false);
|
||||
// White-listed receivers of method calls
|
||||
String[] receiverWhitelist = settings.getAsArray(GROOVY_SCRIPT_SANDBOX_RECEIVER_WHITELIST, defaultReceiverWhitelist, true);
|
||||
scz.setReceiversWhiteList(newArrayList(receiverWhitelist));
|
||||
// Add the customized expression checker for finer-grained checking
|
||||
scz.addExpressionCheckers(new GroovySandboxExpressionChecker(settings, blacklistAdditions));
|
||||
return scz;
|
||||
}
|
||||
}
|
|
@ -22,9 +22,6 @@ package org.elasticsearch.script.groovy;
|
|||
import groovy.lang.Binding;
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import groovy.lang.Script;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import org.apache.lucene.index.LeafReaderContext;
|
||||
import org.apache.lucene.search.Scorer;
|
||||
import org.codehaus.groovy.ast.ClassCodeExpressionTransformer;
|
||||
|
@ -40,28 +37,18 @@ import org.codehaus.groovy.control.customizers.CompilationCustomizer;
|
|||
import org.codehaus.groovy.control.customizers.ImportCustomizer;
|
||||
import org.elasticsearch.ExceptionsHelper;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.logging.ESLogger;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.script.CompiledScript;
|
||||
import org.elasticsearch.script.ExecutableScript;
|
||||
import org.elasticsearch.script.LeafSearchScript;
|
||||
import org.elasticsearch.script.ScoreAccessor;
|
||||
import org.elasticsearch.script.ScriptEngineService;
|
||||
import org.elasticsearch.script.ScriptException;
|
||||
import org.elasticsearch.script.SearchScript;
|
||||
import org.elasticsearch.script.*;
|
||||
import org.elasticsearch.search.lookup.LeafSearchLookup;
|
||||
import org.elasticsearch.search.lookup.SearchLookup;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
|
@ -70,47 +57,17 @@ import java.util.concurrent.atomic.AtomicLong;
|
|||
public class GroovyScriptEngineService extends AbstractComponent implements ScriptEngineService {
|
||||
|
||||
public static final String NAME = "groovy";
|
||||
public static String GROOVY_SCRIPT_SANDBOX_ENABLED = "script.groovy.sandbox.enabled";
|
||||
public static String GROOVY_SCRIPT_BLACKLIST_PATCH = "script.groovy.sandbox.method_blacklist_patch";
|
||||
|
||||
private final AtomicLong counter = new AtomicLong();
|
||||
private final boolean sandboxed;
|
||||
private volatile GroovyClassLoader loader;
|
||||
private volatile Set<String> blacklistAdditions;
|
||||
private final GroovyClassLoader loader;
|
||||
|
||||
@Inject
|
||||
public GroovyScriptEngineService(Settings settings) {
|
||||
super(settings);
|
||||
this.sandboxed = settings.getAsBoolean(GROOVY_SCRIPT_SANDBOX_ENABLED, false);
|
||||
this.blacklistAdditions = ImmutableSet.copyOf(settings.getAsArray(GROOVY_SCRIPT_BLACKLIST_PATCH, Strings.EMPTY_ARRAY));
|
||||
reloadConfig();
|
||||
}
|
||||
|
||||
public Set<String> blacklistAdditions() {
|
||||
return this.blacklistAdditions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the additional blacklisted methods to the current blacklist,
|
||||
* returns true if the black list has changed
|
||||
*/
|
||||
public boolean addToBlacklist(String... additions) {
|
||||
Set<String> newBlackList = new HashSet<>(blacklistAdditions);
|
||||
Collections.addAll(newBlackList, additions);
|
||||
boolean changed = this.blacklistAdditions.equals(newBlackList) == false;
|
||||
this.blacklistAdditions = ImmutableSet.copyOf(newBlackList);
|
||||
return changed;
|
||||
}
|
||||
|
||||
public void reloadConfig() {
|
||||
ImportCustomizer imports = new ImportCustomizer();
|
||||
imports.addStarImports("org.joda.time");
|
||||
imports.addStaticStars("java.lang.Math");
|
||||
CompilerConfiguration config = new CompilerConfiguration();
|
||||
config.addCompilationCustomizers(imports);
|
||||
if (this.sandboxed) {
|
||||
config.addCompilationCustomizers(GroovySandboxExpressionChecker.getSecureASTCustomizer(settings, this.blacklistAdditions));
|
||||
}
|
||||
// Add BigDecimal -> Double transformer
|
||||
config.addCompilationCustomizers(new GroovyBigDecimalTransformer(CompilePhase.CONVERSION));
|
||||
this.loader = new GroovyClassLoader(settings.getClassLoader(), config);
|
||||
|
@ -148,7 +105,7 @@ public class GroovyScriptEngineService extends AbstractComponent implements Scri
|
|||
|
||||
@Override
|
||||
public boolean sandboxed() {
|
||||
return this.sandboxed;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -360,5 +317,4 @@ public class GroovyScriptEngineService extends AbstractComponent implements Scri
|
|||
return super.transform(newExpr);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,159 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.script;
|
||||
|
||||
import org.elasticsearch.ExceptionsHelper;
|
||||
import org.elasticsearch.action.search.SearchPhaseExecutionException;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.script.groovy.GroovyScriptEngineService;
|
||||
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
|
||||
/**
|
||||
* Tests for the Groovy scripting sandbox
|
||||
*/
|
||||
@ElasticsearchIntegrationTest.ClusterScope(scope = ElasticsearchIntegrationTest.Scope.TEST, numDataNodes = 0)
|
||||
public class GroovySandboxScriptTests extends ElasticsearchIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void testSandboxedGroovyScript() throws Exception {
|
||||
int nodes = randomIntBetween(1, 3);
|
||||
Settings nodeSettings = ImmutableSettings.builder()
|
||||
.put(GroovyScriptEngineService.GROOVY_SCRIPT_SANDBOX_ENABLED, true)
|
||||
.build();
|
||||
internalCluster().startNodesAsync(nodes, nodeSettings).get();
|
||||
client().admin().cluster().prepareHealth().setWaitForNodes(nodes + "").get();
|
||||
|
||||
client().prepareIndex("test", "doc", "1").setSource("foo", 5).setRefresh(true).get();
|
||||
|
||||
// Plain test
|
||||
testSuccess("");
|
||||
// List
|
||||
testSuccess("def list = [doc['foo'].value, 3, 4]; def v = list.get(1); list.add(10)");
|
||||
// Ranges
|
||||
testSuccess("def range = 1..doc['foo'].value; def v = range.get(0)");
|
||||
// Maps
|
||||
testSuccess("def v = doc['foo'].value; def m = [:]; m.put(\\\"value\\\", v)");
|
||||
// Times
|
||||
testSuccess("def t = Instant.now().getMillis()");
|
||||
// GroovyCollections
|
||||
testSuccess("def n = [1,2,3]; GroovyCollections.max(n)");
|
||||
|
||||
// Fail cases
|
||||
testFailure("pr = Runtime.getRuntime().exec(\\\"touch /tmp/gotcha\\\"); pr.waitFor()",
|
||||
"Method calls not allowed on [java.lang.Runtime]");
|
||||
|
||||
testFailure("d = new DateTime(); d.getClass().getDeclaredMethod(\\\"plus\\\").setAccessible(true)",
|
||||
"Expression [MethodCallExpression] is not allowed: d.getClass()");
|
||||
|
||||
testFailure("d = new DateTime(); d.\\\"${'get' + 'Class'}\\\"()." +
|
||||
"\\\"${'getDeclared' + 'Method'}\\\"(\\\"now\\\").\\\"${'set' + 'Accessible'}\\\"(false)",
|
||||
"Expression [MethodCallExpression] is not allowed: d.$(get + Class)().$(getDeclared + Method)(now).$(set + Accessible)(false)");
|
||||
|
||||
testFailure("Class.forName(\\\"DateTime\\\").getDeclaredMethod(\\\"plus\\\").setAccessible(true)",
|
||||
"Expression [MethodCallExpression] is not allowed: java.lang.Class.forName(DateTime)");
|
||||
|
||||
testFailure("Eval.me('2 + 2')", "Method calls not allowed on [groovy.util.Eval]");
|
||||
|
||||
testFailure("Eval.x(5, 'x + 2')", "Method calls not allowed on [groovy.util.Eval]");
|
||||
|
||||
testFailure("t = new java.util.concurrent.ThreadPoolExecutor(2, 2, 0L, TimeUnit.MILLISECONDS, " +
|
||||
"new java.util.concurrent.LinkedBlockingQueue<Runnable>()); t.execute({ println 5 })",
|
||||
"Expression [ConstructorCallExpression] is not allowed: new java.util.concurrent.ThreadPoolExecutor");
|
||||
|
||||
testFailure("d = new Date(); java.lang.reflect.Field f = Date.class.getDeclaredField(\\\"fastTime\\\");" +
|
||||
" f.setAccessible(true); f.get(\\\"fastTime\\\")",
|
||||
"Method calls not allowed on [java.lang.reflect.Field]");
|
||||
|
||||
testFailure("t = new Thread({ println 3 }); t.start(); t.join()",
|
||||
"Expression [ConstructorCallExpression] is not allowed: new java.lang.Thread");
|
||||
|
||||
testFailure("Thread.start({ println 4 })", "Method calls not allowed on [java.lang.Thread]");
|
||||
|
||||
testFailure("import java.util.concurrent.ThreadPoolExecutor;",
|
||||
"Importing [java.util.concurrent.ThreadPoolExecutor] is not allowed");
|
||||
|
||||
testFailure("s = new java.net.URL();", "Expression [ConstructorCallExpression] is not allowed: new java.net.URL()");
|
||||
|
||||
testFailure("def methodName = 'ex'; Runtime.\\\"${'get' + 'Runtime'}\\\"().\\\"${methodName}ec\\\"(\\\"touch /tmp/gotcha2\\\")",
|
||||
"Expression [MethodCallExpression] is not allowed: java.lang.Runtime.$(get + Runtime)().$methodNameec(touch /tmp/gotcha2)");
|
||||
|
||||
testFailure("def c = [doc['foo'].value, 3, 4].&size; c()",
|
||||
"Expression [MethodPointerExpression] is not allowed");
|
||||
|
||||
testFailure("[doc['foo'].value, 3, 4].invokeMethod([1,2],\\\"size\\\", new Object[0])",
|
||||
"Expression [MethodCallExpression] is not allowed: [doc[foo].value, 3, 4].invokeMethod([1, 2], size, [])");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDynamicBlacklist() throws Exception {
|
||||
int nodes = randomIntBetween(1, 3);
|
||||
Settings nodeSettings = ImmutableSettings.builder()
|
||||
.put(GroovyScriptEngineService.GROOVY_SCRIPT_SANDBOX_ENABLED, true)
|
||||
.build();
|
||||
internalCluster().startNodesAsync(nodes, nodeSettings).get();
|
||||
client().admin().cluster().prepareHealth().setWaitForNodes(nodes + "").get();
|
||||
|
||||
client().prepareIndex("test", "doc", "1").setSource("foo", 5).setRefresh(true).get();
|
||||
|
||||
testSuccess("[doc['foo'].value, 3, 4].isEmpty()");
|
||||
testSuccess("[doc['foo'].value, 3, 4].size()");
|
||||
|
||||
// Now we blacklist two methods, .isEmpty() and .size()
|
||||
Settings blacklistSettings = ImmutableSettings.builder()
|
||||
.put(GroovyScriptEngineService.GROOVY_SCRIPT_BLACKLIST_PATCH, "isEmpty,size")
|
||||
.build();
|
||||
|
||||
client().admin().cluster().prepareUpdateSettings().setTransientSettings(blacklistSettings).get();
|
||||
|
||||
testFailure("[doc['foo'].value, 3, 4].isEmpty()",
|
||||
"Expression [MethodCallExpression] is not allowed: [doc[foo].value, 3, 4].isEmpty()");
|
||||
testFailure("[doc['foo'].value, 3, 4].size()",
|
||||
"Expression [MethodCallExpression] is not allowed: [doc[foo].value, 3, 4].size()");
|
||||
}
|
||||
|
||||
public void testSuccess(String script) {
|
||||
logger.info("--> script: " + script);
|
||||
SearchResponse resp = client().prepareSearch("test")
|
||||
.setSource("{\"query\": {\"match_all\": {}}," +
|
||||
"\"sort\":{\"_script\": {\"script\": \""+ script +
|
||||
"; doc['foo'].value + 2\", \"type\": \"number\", \"lang\": \"groovy\"}}}").get();
|
||||
assertNoFailures(resp);
|
||||
assertThat(resp.getHits().getAt(0).getSortValues(), equalTo(new Object[]{7.0}));
|
||||
}
|
||||
|
||||
public void testFailure(String script, String failMessage) {
|
||||
logger.info("--> script: " + script);
|
||||
try {
|
||||
client().prepareSearch("test")
|
||||
.setSource("{\"query\": {\"match_all\": {}}," +
|
||||
"\"sort\":{\"_script\": {\"script\": \""+ script +
|
||||
"; doc['foo'].value + 2\", \"type\": \"number\", \"lang\": \"groovy\"}}}").get();
|
||||
fail("script: " + script + " failed to be caught be the sandbox!");
|
||||
} catch (SearchPhaseExecutionException e) {
|
||||
assertThat("script failed, but with incorrect message: " + e.toString(), e.toString().contains(failMessage), equalTo(true));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,7 +28,6 @@ import org.elasticsearch.common.settings.ImmutableSettings;
|
|||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.settings.SettingsModule;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.node.settings.NodeSettingsService;
|
||||
import org.elasticsearch.script.ScriptService.ScriptType;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
|
@ -81,7 +80,7 @@ public class NativeScriptTests extends ElasticsearchTestCase {
|
|||
nativeScriptFactoryMap.put("my", new MyNativeScriptFactory());
|
||||
Set<ScriptEngineService> scriptEngineServices = ImmutableSet.<ScriptEngineService>of(new NativeScriptEngineService(settings, nativeScriptFactoryMap));
|
||||
ScriptContextRegistry scriptContextRegistry = new ScriptContextRegistry(Lists.<ScriptContext.Plugin>newArrayList());
|
||||
ScriptService scriptService = new ScriptService(settings, environment, scriptEngineServices, resourceWatcherService, new NodeSettingsService(settings), scriptContextRegistry);
|
||||
ScriptService scriptService = new ScriptService(settings, environment, scriptEngineServices, resourceWatcherService, scriptContextRegistry);
|
||||
|
||||
for (ScriptContext scriptContext : scriptContextRegistry.scriptContexts()) {
|
||||
assertThat(scriptService.compile(new Script(NativeScriptEngineService.NAME, "my", ScriptType.INLINE, null), scriptContext), notNullValue());
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.script;
|
||||
|
||||
import org.elasticsearch.ExceptionsHelper;
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.script.groovy.GroovyScriptEngineService;
|
||||
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
|
||||
/**
|
||||
* Test that a system where the sandbox is disabled while dynamic scripting is
|
||||
* also disabled does not allow a script to be sent
|
||||
*/
|
||||
@ElasticsearchIntegrationTest.ClusterScope(scope=ElasticsearchIntegrationTest.Scope.SUITE)
|
||||
public class SandboxDisabledTests extends ElasticsearchIntegrationTest {
|
||||
|
||||
@Override
|
||||
protected Settings nodeSettings(int nodeOrdinal) {
|
||||
return ImmutableSettings.settingsBuilder().put(super.nodeSettings(nodeOrdinal))
|
||||
.put(GroovyScriptEngineService.GROOVY_SCRIPT_SANDBOX_ENABLED, false)
|
||||
.put("script.inline", false).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScriptingDisabledWhileSandboxDisabled() {
|
||||
client().prepareIndex("test", "doc", "1").setSource("foo", 5).setRefresh(true).get();
|
||||
try {
|
||||
client().prepareSearch("test")
|
||||
.setSource("{\"query\": {\"match_all\": {}}," +
|
||||
"\"sort\":{\"_script\": {\"script\": \"doc['foo'].value + 2\", \"type\": \"number\", \"lang\": \"groovy\"}}}").get();
|
||||
fail("shards should fail because the sandbox and dynamic scripting are disabled");
|
||||
} catch (Exception e) {
|
||||
assertThat(e.toString(), containsString("scripts of type [inline], operation [search] and lang [groovy] are disabled"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,7 +26,6 @@ import org.elasticsearch.common.io.Streams;
|
|||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.node.settings.NodeSettingsService;
|
||||
import org.elasticsearch.script.ScriptService.ScriptType;
|
||||
import org.elasticsearch.script.expression.ExpressionScriptEngineService;
|
||||
import org.elasticsearch.script.groovy.GroovyScriptEngineService;
|
||||
|
@ -103,7 +102,7 @@ public class ScriptServiceTests extends ElasticsearchTestCase {
|
|||
private void buildScriptService(Settings additionalSettings) throws IOException {
|
||||
Settings finalSettings = ImmutableSettings.builder().put(baseSettings).put(additionalSettings).build();
|
||||
Environment environment = new Environment(finalSettings);
|
||||
scriptService = new ScriptService(finalSettings, environment, scriptEngineServices, resourceWatcherService, new NodeSettingsService(finalSettings), scriptContextRegistry) {
|
||||
scriptService = new ScriptService(finalSettings, environment, scriptEngineServices, resourceWatcherService, scriptContextRegistry) {
|
||||
@Override
|
||||
String getScriptFromIndex(String scriptLang, String id) {
|
||||
//mock the script that gets retrieved from an index
|
||||
|
|
|
@ -26,7 +26,6 @@ import org.elasticsearch.common.settings.Settings;
|
|||
import org.elasticsearch.index.cache.filter.FilterCacheModule;
|
||||
import org.elasticsearch.index.cache.filter.FilterCacheModule.FilterCacheSettings;
|
||||
import org.elasticsearch.index.cache.filter.weighted.WeightedFilterCache;
|
||||
import org.elasticsearch.script.groovy.GroovyScriptEngineService;
|
||||
import org.elasticsearch.search.sort.SortOrder;
|
||||
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
||||
import org.junit.Test;
|
||||
|
@ -50,7 +49,6 @@ public class ScriptFilterSearchTests extends ElasticsearchIntegrationTest {
|
|||
@Override
|
||||
protected Settings nodeSettings(int nodeOrdinal) {
|
||||
return ImmutableSettings.settingsBuilder().put(super.nodeSettings(nodeOrdinal))
|
||||
.put(GroovyScriptEngineService.GROOVY_SCRIPT_SANDBOX_ENABLED, false)
|
||||
// aggressive filter caching so that we can assert on the number of iterations of the script filters
|
||||
.put(FilterCacheModule.FilterCacheSettings.FILTER_CACHE_TYPE, WeightedFilterCache.class)
|
||||
.put(FilterCacheSettings.FILTER_CACHE_EVERYTHING, true)
|
||||
|
|
|
@ -22,7 +22,6 @@ package org.elasticsearch.search.timeout;
|
|||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.script.groovy.GroovyScriptEngineService;
|
||||
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -38,7 +37,7 @@ public class SearchTimeoutTests extends ElasticsearchIntegrationTest {
|
|||
|
||||
@Override
|
||||
protected Settings nodeSettings(int nodeOrdinal) {
|
||||
return ImmutableSettings.settingsBuilder().put(super.nodeSettings(nodeOrdinal)).put(GroovyScriptEngineService.GROOVY_SCRIPT_SANDBOX_ENABLED, false).build();
|
||||
return ImmutableSettings.settingsBuilder().put(super.nodeSettings(nodeOrdinal)).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Reference in New Issue