Skip to content

Commit c70f6d0

Browse files
committed
Add Groovy as a scripting language, add sandboxing for Groovy
Sandboxes the groovy scripting language with multiple configurable whitelists: `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. As well as a method blacklist: `script.groovy.sandbox.method_blacklist`: comma-separated list of methods that are never allowed to be invoked, regardless of target object. The sandbox can be entirely disabled by setting: `script.groovy.sandbox.enabled: false`
1 parent 12fd6ce commit c70f6d0

21 files changed

+738
-59
lines changed

dev-tools/tests.policy

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ grant {
2727
permission java.io.FilePermission "${junit4.childvm.cwd}", "read,execute,write";
2828
permission java.io.FilePermission "${junit4.childvm.cwd}${/}-", "read,execute,write,delete";
2929
permission java.io.FilePermission "${junit4.tempDir}${/}*", "read,execute,write,delete";
30-
30+
permission groovy.security.GroovyCodeSourcePermission "/groovy/script";
31+
3132
// Allow connecting to the internet anywhere
3233
permission java.net.SocketPermission "*", "accept,listen,connect,resolve";
33-
34+
3435
// Basic permissions needed for Lucene / Elasticsearch to work:
3536
permission java.util.PropertyPermission "*", "read,write";
3637
permission java.lang.reflect.ReflectPermission "*";

pom.xml

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -208,13 +208,6 @@
208208
<scope>compile</scope>
209209
</dependency>
210210

211-
<dependency>
212-
<groupId>org.mvel</groupId>
213-
<artifactId>mvel2</artifactId>
214-
<version>2.2.0.Final</version>
215-
<scope>compile</scope>
216-
</dependency>
217-
218211
<dependency>
219212
<groupId>com.fasterxml.jackson.core</groupId>
220213
<artifactId>jackson-core</artifactId>
@@ -265,6 +258,22 @@
265258
</dependency>
266259
<!-- END: dependencies that are shaded -->
267260

261+
<dependency>
262+
<groupId>org.mvel</groupId>
263+
<artifactId>mvel2</artifactId>
264+
<version>2.2.0.Final</version>
265+
<scope>compile</scope>
266+
<optional>true</optional>
267+
</dependency>
268+
269+
<dependency>
270+
<groupId>org.codehaus.groovy</groupId>
271+
<artifactId>groovy-all</artifactId>
272+
<version>2.3.2</version>
273+
<scope>compile</scope>
274+
<optional>true</optional>
275+
</dependency>
276+
268277
<dependency>
269278
<groupId>log4j</groupId>
270279
<artifactId>log4j</artifactId>
@@ -648,7 +657,6 @@
648657
<includes>
649658
<include>com.google.guava:guava</include>
650659
<include>com.carrotsearch:hppc</include>
651-
<include>org.mvel:mvel2</include>
652660
<include>com.fasterxml.jackson.core:jackson-core</include>
653661
<include>com.fasterxml.jackson.dataformat:jackson-dataformat-smile</include>
654662
<include>com.fasterxml.jackson.dataformat:jackson-dataformat-yaml</include>
@@ -674,10 +682,6 @@
674682
<pattern>jsr166e</pattern>
675683
<shadedPattern>org.elasticsearch.common.util.concurrent.jsr166e</shadedPattern>
676684
</relocation>
677-
<relocation>
678-
<pattern>org.mvel2</pattern>
679-
<shadedPattern>org.elasticsearch.common.mvel2</shadedPattern>
680-
</relocation>
681685
<relocation>
682686
<pattern>com.fasterxml.jackson</pattern>
683687
<shadedPattern>org.elasticsearch.common.jackson</shadedPattern>
@@ -870,7 +874,7 @@
870874
</data>
871875
<data>
872876
<src>${project.build.directory}/lib</src>
873-
<includes>lucene*, log4j*, jna*, spatial4j*, jts*</includes>
877+
<includes>lucene*, log4j*, jna*, spatial4j*, jts*, groovy*, mvel*</includes>
874878
<type>directory</type>
875879
<mapper>
876880
<type>perm</type>
@@ -1070,6 +1074,8 @@
10701074
<include>jna*</include>
10711075
<include>spatial4j*</include>
10721076
<include>jts*</include>
1077+
<include>groovy*</include>
1078+
<include>mvel*</include>
10731079
</includes>
10741080
</source>
10751081
<source>
@@ -1393,7 +1399,7 @@
13931399
<version>0.6.4.201312101107</version>
13941400
<scope>test</scope>
13951401
</dependency>
1396-
</dependencies>
1402+
</dependencies>
13971403
<build>
13981404
<plugins>
13991405
<plugin>
@@ -1418,7 +1424,7 @@
14181424
<id>default-check</id>
14191425
<goals>
14201426
<goal>check</goal>
1421-
</goals>
1427+
</goals>
14221428
</execution>
14231429
</executions>
14241430
<configuration>

src/main/assemblies/common-bin.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
<include>net.java.dev.jna:jna</include>
1010
<include>com.spatial4j:spatial4j</include>
1111
<include>com.vividsolutions:jts</include>
12+
<include>org.codehaus.groovy:groovy-all</include>
13+
<include>org.mvel:mvel2</include>
1214
</includes>
1315
</dependencySet>
1416
<dependencySet>

src/main/java/org/elasticsearch/script/ScriptModule.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.elasticsearch.common.inject.multibindings.Multibinder;
2828
import org.elasticsearch.common.logging.Loggers;
2929
import org.elasticsearch.common.settings.Settings;
30+
import org.elasticsearch.script.groovy.GroovyScriptEngineService;
3031
import org.elasticsearch.script.mustache.MustacheScriptEngineService;
3132
import org.elasticsearch.script.mvel.MvelScriptEngineService;
3233

@@ -77,16 +78,23 @@ protected void configure() {
7778

7879
Multibinder<ScriptEngineService> multibinder = Multibinder.newSetBinder(binder(), ScriptEngineService.class);
7980
multibinder.addBinding().to(NativeScriptEngineService.class);
81+
82+
try {
83+
multibinder.addBinding().to(GroovyScriptEngineService.class);
84+
} catch (Throwable t) {
85+
Loggers.getLogger(GroovyScriptEngineService.class).debug("failed to load groovy", t);
86+
}
87+
8088
try {
8189
multibinder.addBinding().to(MvelScriptEngineService.class);
8290
} catch (Throwable t) {
83-
// no MVEL
91+
Loggers.getLogger(MvelScriptEngineService.class).debug("failed to load mvel", t);
8492
}
8593

8694
try {
8795
multibinder.addBinding().to(MustacheScriptEngineService.class);
8896
} catch (Throwable t) {
89-
Loggers.getLogger(MustacheScriptEngineService.class).trace("failed to load mustache", t);
97+
Loggers.getLogger(MustacheScriptEngineService.class).debug("failed to load mustache", t);
9098
}
9199

92100
for (Class<? extends ScriptEngineService> scriptEngine : scriptEngines) {

src/main/java/org/elasticsearch/script/ScriptService.java

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import java.io.FileInputStream;
4545
import java.io.IOException;
4646
import java.io.InputStreamReader;
47+
import java.util.Locale;
4748
import java.util.Map;
4849
import java.util.Set;
4950
import java.util.concurrent.ConcurrentMap;
@@ -54,6 +55,10 @@
5455
*/
5556
public class ScriptService extends AbstractComponent {
5657

58+
public static final String DEFAULT_SCRIPTING_LANGUAGE_SETTING = "script.default_lang";
59+
public static final String DISABLE_DYNAMIC_SCRIPTING_SETTING = "script.disable_dynamic";
60+
public static final String DISABLE_DYNAMIC_SCRIPTING_DEFAULT = "sandbox";
61+
5762
private final String defaultLang;
5863

5964
private final ImmutableMap<String, ScriptEngineService> scriptEngines;
@@ -63,7 +68,38 @@ public class ScriptService extends AbstractComponent {
6368
private final Cache<CacheKey, CompiledScript> cache;
6469
private final File scriptsDirectory;
6570

66-
private final boolean disableDynamic;
71+
private final DynamicScriptDisabling dynamicScriptingDisabled;
72+
73+
/**
74+
* Enum defining the different dynamic settings for scripting, either
75+
* ONLY_DISK_ALLOWED (scripts must be placed on disk), EVERYTHING_ALLOWED
76+
* (all dynamic scripting is enabled), or SANDBOXED_ONLY (only sandboxed
77+
* scripting languages are allowed)
78+
*/
79+
enum DynamicScriptDisabling {
80+
EVERYTHING_ALLOWED,
81+
ONLY_DISK_ALLOWED,
82+
SANDBOXED_ONLY;
83+
84+
public static final DynamicScriptDisabling parse(String s) {
85+
switch (s.toLowerCase(Locale.ROOT)) {
86+
// true for "disable_dynamic" means only on-disk scripts are enabled
87+
case "true":
88+
case "all":
89+
return ONLY_DISK_ALLOWED;
90+
// false for "disable_dynamic" means all scripts are enabled
91+
case "false":
92+
case "none":
93+
return EVERYTHING_ALLOWED;
94+
// only sandboxed scripting is enabled
95+
case "sandbox":
96+
case "sandboxed":
97+
return SANDBOXED_ONLY;
98+
default:
99+
throw new ElasticsearchIllegalArgumentException("Unrecognized script allowance setting: [" + s + "]");
100+
}
101+
}
102+
}
67103

68104
@Inject
69105
public ScriptService(Settings settings, Environment env, Set<ScriptEngineService> scriptEngines,
@@ -74,8 +110,8 @@ public ScriptService(Settings settings, Environment env, Set<ScriptEngineService
74110
TimeValue cacheExpire = componentSettings.getAsTime("cache.expire", null);
75111
logger.debug("using script cache with max_size [{}], expire [{}]", cacheMaxSize, cacheExpire);
76112

77-
this.defaultLang = componentSettings.get("default_lang", "mvel");
78-
this.disableDynamic = componentSettings.getAsBoolean("disable_dynamic", true);
113+
this.defaultLang = settings.get(DEFAULT_SCRIPTING_LANGUAGE_SETTING, "mvel");
114+
this.dynamicScriptingDisabled = DynamicScriptDisabling.parse(settings.get(DISABLE_DYNAMIC_SCRIPTING_SETTING, DISABLE_DYNAMIC_SCRIPTING_DEFAULT));
79115

80116
CacheBuilder cacheBuilder = CacheBuilder.newBuilder();
81117
if (cacheMaxSize >= 0) {
@@ -130,7 +166,7 @@ public CompiledScript compile(String lang, String script) {
130166
lang = defaultLang;
131167
}
132168
if (!dynamicScriptEnabled(lang)) {
133-
throw new ScriptException("dynamic scripting disabled");
169+
throw new ScriptException("dynamic scripting for [" + lang + "] disabled");
134170
}
135171
CacheKey cacheKey = new CacheKey(lang, script);
136172
compiled = cache.getIfPresent(cacheKey);
@@ -180,12 +216,16 @@ private boolean dynamicScriptEnabled(String lang) {
180216
if (service == null) {
181217
throw new ElasticsearchIllegalArgumentException("script_lang not supported [" + lang + "]");
182218
}
183-
// Templating languages and native scripts are always allowed
184-
// "native" executions are registered through plugins
185-
if (service.sandboxed() || "native".equals(lang)) {
219+
220+
// Templating languages (mustache) and native scripts are always
221+
// allowed, "native" executions are registered through plugins
222+
if (this.dynamicScriptingDisabled == DynamicScriptDisabling.EVERYTHING_ALLOWED || "native".equals(lang) || "mustache".equals(lang)) {
186223
return true;
224+
} else if (this.dynamicScriptingDisabled == DynamicScriptDisabling.ONLY_DISK_ALLOWED) {
225+
return false;
226+
} else {
227+
return service.sandboxed();
187228
}
188-
return !disableDynamic;
189229
}
190230

191231
private class ScriptChangesListener extends FileChangesListener {

0 commit comments

Comments
 (0)