refactor GroovySecurityTests into a unit test.

This was basically a resurrected form of the tests for the old sandbox.
We use it to check that groovy scripts some degree of additional containment.

The other scripting plugins (javascript, python) already have this as a unit test,
its much easier to debug any problems that way.

closes #14484
This commit is contained in:
Robert Muir 2015-11-03 13:39:17 -05:00
parent 64a01cfb05
commit 7e6008f0b9
1 changed files with 35 additions and 60 deletions

View File

@ -20,61 +20,44 @@
package org.elasticsearch.script.groovy; package org.elasticsearch.script.groovy;
import org.apache.lucene.util.Constants; import org.apache.lucene.util.Constants;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.ShardSearchFailure;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.script.CompiledScript;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptException; import org.elasticsearch.script.ScriptException;
import org.elasticsearch.script.ScriptService.ScriptType; import org.elasticsearch.script.ScriptService;
import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.test.ESIntegTestCase;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collection; import java.util.AbstractMap;
import java.util.Collections; import java.util.Collections;
import java.util.Locale; import java.util.HashMap;
import java.util.Map;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
/** /**
* Tests for the Groovy security permissions * Tests for the Groovy security permissions
*/ */
@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 0) public class GroovySecurityTests extends ESTestCase {
// TODO: refactor into unit test, or, proper REST test
public class GroovySecurityTests extends ESIntegTestCase { private GroovyScriptEngineService se;
@Override @Override
public void setUp() throws Exception { public void setUp() throws Exception {
super.setUp(); super.setUp();
se = new GroovyScriptEngineService(Settings.Builder.EMPTY_SETTINGS);
// otherwise will exit your VM and other bad stuff
assumeTrue("test requires security manager to be enabled", System.getSecurityManager() != null); assumeTrue("test requires security manager to be enabled", System.getSecurityManager() != null);
} }
@Override @Override
protected Collection<Class<? extends Plugin>> nodePlugins() { public void tearDown() throws Exception {
return Collections.singleton(GroovyPlugin.class); se.close();
super.tearDown();
} }
public void testEvilGroovyScripts() throws Exception { public void testEvilGroovyScripts() throws Exception {
int nodes = randomIntBetween(1, 3);
Settings nodeSettings = Settings.builder()
.put("script.inline", true)
.put("script.indexed", true)
.build();
internalCluster().startNodesAsync(nodes, nodeSettings).get();
client().admin().cluster().prepareHealth().setWaitForNodes(nodes + "").get();
client().prepareIndex("test", "doc", "1").setSource("foo", 5, "bar", "baz").setRefresh(true).get();
// Plain test // Plain test
assertSuccess(""); assertSuccess("");
// numeric field access // field access
assertSuccess("def foo = doc['foo'].value; if (foo == null) { return 5; }"); assertSuccess("def foo = doc['foo'].value; if (foo == null) { return 5; }");
// string field access
assertSuccess("def bar = doc['bar'].value; if (bar == null) { return 5; }");
// List // List
assertSuccess("def list = [doc['foo'].value, 3, 4]; def v = list.get(1); list.add(10)"); assertSuccess("def list = [doc['foo'].value, 3, 4]; def v = list.get(1); list.add(10)");
// Ranges // Ranges
@ -119,36 +102,28 @@ public class GroovySecurityTests extends ESIntegTestCase {
} }
} }
private void assertSuccess(String script) { /** runs a script */
logger.info("--> script: " + script); private void doTest(String script) {
SearchResponse resp = client() Map<String, Object> vars = new HashMap<String, Object>();
.prepareSearch("test") // we add a "mock document" containing a single field "foo" that returns 4 (abusing a jdk class with a getValue() method)
.setSource( vars.put("doc", Collections.singletonMap("foo", new AbstractMap.SimpleEntry<Object,Integer>(null, 4)));
new SearchSourceBuilder().query(QueryBuilders.matchAllQuery()).sort( se.executable(new CompiledScript(ScriptService.ScriptType.INLINE, "test", "js", se.compile(script)), vars).run();
SortBuilders.scriptSort(new Script(script + "; doc['foo'].value + 2", ScriptType.INLINE, "groovy", null),
"number"))).get();
assertNoFailures(resp);
assertEquals(1, resp.getHits().getTotalHits());
assertThat(resp.getHits().getAt(0).getSortValues(), equalTo(new Object[]{7.0}));
} }
/** asserts that a script runs without exception */
private void assertSuccess(String script) {
doTest(script);
}
/** asserts that a script triggers securityexception */
private void assertFailure(String script) { private void assertFailure(String script) {
logger.info("--> script: " + script); try {
SearchResponse resp = client() doTest(script);
.prepareSearch("test") fail("did not get expected exception");
.setSource( } catch (ScriptException expected) {
new SearchSourceBuilder().query(QueryBuilders.matchAllQuery()).sort( Throwable cause = expected.getCause();
SortBuilders.scriptSort(new Script(script + "; doc['foo'].value + 2", ScriptType.INLINE, "groovy", null), assertNotNull(cause);
"number"))).get(); assertTrue("unexpected exception: " + cause, cause instanceof SecurityException);
assertEquals(0, resp.getHits().getTotalHits());
ShardSearchFailure fails[] = resp.getShardFailures();
// TODO: GroovyScriptExecutionException needs work:
// fix it to preserve cause so we don't do this flaky string-check stuff
for (ShardSearchFailure fail : fails) {
assertThat(fail.getCause(), instanceOf(ScriptException.class));
assertTrue("unexpected exception" + fail.getCause(),
// different casing, depending on jvm impl...
fail.getCause().toString().toLowerCase(Locale.ROOT).contains("[access denied"));
} }
} }
} }