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:
parent
64a01cfb05
commit
7e6008f0b9
|
@ -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"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue