Add New Security Script Settings (#24637)
Settings are simplified to allowed_types and allowed_contexts. If a setting is not specified the default is to enable all for that setting.
This commit is contained in:
parent
2e6dc04025
commit
43292979fd
|
@ -64,8 +64,8 @@ public final class ScriptContextRegistry {
|
|||
/**
|
||||
* @return <tt>true</tt> if the provided {@link ScriptContext} is supported, <tt>false</tt> otherwise
|
||||
*/
|
||||
boolean isSupportedContext(ScriptContext scriptContext) {
|
||||
return scriptContexts.containsKey(scriptContext.getKey());
|
||||
boolean isSupportedContext(String scriptContext) {
|
||||
return scriptContexts.containsKey(scriptContext);
|
||||
}
|
||||
|
||||
//script contexts can be used in fine-grained settings, we need to be careful with what we allow here
|
||||
|
|
|
@ -19,13 +19,19 @@
|
|||
|
||||
package org.elasticsearch.script;
|
||||
|
||||
import org.apache.lucene.util.SetOnce;
|
||||
import org.elasticsearch.common.settings.Setting;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Holds the boolean indicating the enabled mode for each of the different scripting languages available, each script source and each
|
||||
|
@ -38,12 +44,55 @@ public class ScriptModes {
|
|||
|
||||
final Map<String, Boolean> scriptEnabled;
|
||||
|
||||
ScriptModes(ScriptSettings scriptSettings, Settings settings) {
|
||||
private static final Setting<List<String>> TYPES_ALLOWED_SETTING =
|
||||
Setting.listSetting("script.types_allowed", Collections.emptyList(), Function.identity(), Setting.Property.NodeScope);
|
||||
private static final Setting<List<String>> CONTEXTS_ALLOWED_SETTING =
|
||||
Setting.listSetting("script.contexts_allowed", Collections.emptyList(), Function.identity(), Setting.Property.NodeScope);
|
||||
|
||||
private final Set<String> typesAllowed;
|
||||
private final Set<String> contextsAllowed;
|
||||
|
||||
ScriptModes(ScriptContextRegistry scriptContextRegistry, ScriptSettings scriptSettings, Settings settings) {
|
||||
HashMap<String, Boolean> scriptModes = new HashMap<>();
|
||||
for (Setting<Boolean> scriptModeSetting : scriptSettings.getScriptLanguageSettings()) {
|
||||
scriptModes.put(scriptModeSetting.getKey(), scriptModeSetting.get(settings));
|
||||
}
|
||||
this.scriptEnabled = Collections.unmodifiableMap(scriptModes);
|
||||
|
||||
typesAllowed = TYPES_ALLOWED_SETTING.exists(settings) ? new HashSet<>() : null;
|
||||
|
||||
if (typesAllowed != null) {
|
||||
for (String settingType : TYPES_ALLOWED_SETTING.get(settings)) {
|
||||
boolean found = false;
|
||||
|
||||
for (ScriptType scriptType : ScriptType.values()) {
|
||||
if (scriptType.getName().equals(settingType)) {
|
||||
found = true;
|
||||
typesAllowed.add(settingType);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
throw new IllegalArgumentException(
|
||||
"unknown script type [" + settingType + "] found in setting [" + TYPES_ALLOWED_SETTING.getKey() + "].");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contextsAllowed = CONTEXTS_ALLOWED_SETTING.exists(settings) ? new HashSet<>() : null;
|
||||
|
||||
if (contextsAllowed != null) {
|
||||
for (String settingContext : CONTEXTS_ALLOWED_SETTING.get(settings)) {
|
||||
if (scriptContextRegistry.isSupportedContext(settingContext)) {
|
||||
contextsAllowed.add(settingContext);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"unknown script context [" + settingContext + "] found in setting [" + CONTEXTS_ALLOWED_SETTING.getKey() + "].");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,6 +109,15 @@ public class ScriptModes {
|
|||
if (NativeScriptEngine.NAME.equals(lang)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typesAllowed != null && typesAllowed.contains(scriptType.getName()) == false) {
|
||||
throw new IllegalArgumentException("[" + scriptType.getName() + "] scripts cannot be executed");
|
||||
}
|
||||
|
||||
if (contextsAllowed != null && contextsAllowed.contains(scriptContext.getKey()) == false) {
|
||||
throw new IllegalArgumentException("[" + scriptContext.getKey() + "] scripts cannot be executed");
|
||||
}
|
||||
|
||||
Boolean scriptMode = scriptEnabled.get(getKey(lang, scriptType, scriptContext));
|
||||
if (scriptMode == null) {
|
||||
throw new IllegalArgumentException("script mode not found for lang [" + lang + "], script_type [" + scriptType + "], operation [" + scriptContext.getKey() + "]");
|
||||
|
|
|
@ -152,7 +152,7 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
|
|||
this.scriptEnginesByLang = unmodifiableMap(enginesByLangBuilder);
|
||||
this.scriptEnginesByExt = unmodifiableMap(enginesByExtBuilder);
|
||||
|
||||
this.scriptModes = new ScriptModes(scriptSettings, settings);
|
||||
this.scriptModes = new ScriptModes(scriptContextRegistry, scriptSettings, settings);
|
||||
|
||||
// add file watcher for static scripts
|
||||
scriptsDirectory = env.scriptsFile();
|
||||
|
@ -511,7 +511,7 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
|
|||
|
||||
private boolean canExecuteScript(String lang, ScriptType scriptType, ScriptContext scriptContext) {
|
||||
assert lang != null;
|
||||
if (scriptContextRegistry.isSupportedContext(scriptContext) == false) {
|
||||
if (scriptContextRegistry.isSupportedContext(scriptContext.getKey()) == false) {
|
||||
throw new IllegalArgumentException("script context [" + scriptContext.getKey() + "] not supported");
|
||||
}
|
||||
return scriptModes.getScriptEnabled(lang, scriptType, scriptContext);
|
||||
|
|
|
@ -97,14 +97,14 @@ public class ScriptModesTests extends ESTestCase {
|
|||
}
|
||||
|
||||
public void testDefaultSettings() {
|
||||
this.scriptModes = new ScriptModes(scriptSettings, Settings.EMPTY);
|
||||
this.scriptModes = new ScriptModes(scriptContextRegistry, scriptSettings, Settings.EMPTY);
|
||||
assertScriptModesAllOps(true, ScriptType.FILE);
|
||||
assertScriptModesAllOps(false, ScriptType.STORED, ScriptType.INLINE);
|
||||
}
|
||||
|
||||
public void testMissingSetting() {
|
||||
assertAllSettingsWereChecked = false;
|
||||
this.scriptModes = new ScriptModes(scriptSettings, Settings.EMPTY);
|
||||
this.scriptModes = new ScriptModes(scriptContextRegistry, scriptSettings, Settings.EMPTY);
|
||||
try {
|
||||
scriptModes.getScriptEnabled("non_existing", randomFrom(ScriptType.values()), randomFrom(scriptContexts));
|
||||
fail("Expected IllegalArgumentException");
|
||||
|
@ -131,7 +131,7 @@ public class ScriptModesTests extends ESTestCase {
|
|||
builder.put("script" + "." + randomScriptTypes[i].getName(), randomScriptModes[i]);
|
||||
deprecated.add("script" + "." + randomScriptTypes[i].getName());
|
||||
}
|
||||
this.scriptModes = new ScriptModes(scriptSettings, builder.build());
|
||||
this.scriptModes = new ScriptModes(scriptContextRegistry, scriptSettings, builder.build());
|
||||
|
||||
for (int i = 0; i < randomInt; i++) {
|
||||
assertScriptModesAllOps(randomScriptModes[i], randomScriptTypes[i]);
|
||||
|
@ -167,7 +167,7 @@ public class ScriptModesTests extends ESTestCase {
|
|||
builder.put("script" + "." + randomScriptContexts[i].getKey(), randomScriptModes[i]);
|
||||
deprecated.add("script" + "." + randomScriptContexts[i].getKey());
|
||||
}
|
||||
this.scriptModes = new ScriptModes(scriptSettings, builder.build());
|
||||
this.scriptModes = new ScriptModes(scriptContextRegistry, scriptSettings, builder.build());
|
||||
|
||||
for (int i = 0; i < randomInt; i++) {
|
||||
assertScriptModesAllTypes(randomScriptModes[i], randomScriptContexts[i]);
|
||||
|
@ -187,7 +187,7 @@ public class ScriptModesTests extends ESTestCase {
|
|||
.put("script.stored", "true")
|
||||
.put("script.inline", "true");
|
||||
//operations generic settings have precedence over script type generic settings
|
||||
this.scriptModes = new ScriptModes(scriptSettings, builder.build());
|
||||
this.scriptModes = new ScriptModes(scriptContextRegistry, scriptSettings, builder.build());
|
||||
assertScriptModesAllTypes(false, scriptContext);
|
||||
ScriptContext[] complementOf = complementOf(scriptContext);
|
||||
assertScriptModes(true, new ScriptType[]{ScriptType.FILE, ScriptType.STORED}, complementOf);
|
||||
|
|
|
@ -216,6 +216,69 @@ public class ScriptServiceTests extends ESTestCase {
|
|||
assertThat(compiledScript1.compiled(), sameInstance(compiledScript2.compiled()));
|
||||
}
|
||||
|
||||
public void testAllowAllScriptTypeSettings() throws IOException {
|
||||
buildScriptService(Settings.EMPTY);
|
||||
|
||||
assertCompileAccepted("painless", "script", ScriptType.INLINE, ScriptContext.Standard.SEARCH);
|
||||
assertCompileAccepted("painless", "script", ScriptType.STORED, ScriptContext.Standard.SEARCH);
|
||||
}
|
||||
|
||||
public void testAllowAllScriptContextSettings() throws IOException {
|
||||
buildScriptService(Settings.EMPTY);
|
||||
|
||||
assertCompileAccepted("painless", "script", ScriptType.INLINE, ScriptContext.Standard.SEARCH);
|
||||
assertCompileAccepted("painless", "script", ScriptType.INLINE, ScriptContext.Standard.AGGS);
|
||||
assertCompileAccepted("painless", "script", ScriptType.INLINE, ScriptContext.Standard.UPDATE);
|
||||
assertCompileAccepted("painless", "script", ScriptType.INLINE, ScriptContext.Standard.INGEST);
|
||||
}
|
||||
|
||||
public void testAllowSomeScriptTypeSettings() throws IOException {
|
||||
Settings.Builder builder = Settings.builder();
|
||||
builder.put("script.types_allowed", "inline");
|
||||
builder.put("script.engine.painless.stored", false);
|
||||
buildScriptService(builder.build());
|
||||
|
||||
assertCompileAccepted("painless", "script", ScriptType.INLINE, ScriptContext.Standard.SEARCH);
|
||||
assertCompileRejected("painless", "script", ScriptType.STORED, ScriptContext.Standard.SEARCH);
|
||||
|
||||
assertSettingDeprecationsAndWarnings(
|
||||
ScriptSettingsTests.buildDeprecatedSettingsArray(scriptSettings, "script.engine.painless.stored"));
|
||||
}
|
||||
|
||||
public void testAllowSomeScriptContextSettings() throws IOException {
|
||||
Settings.Builder builder = Settings.builder();
|
||||
builder.put("script.contexts_allowed", "search, aggs");
|
||||
builder.put("script.update", false);
|
||||
buildScriptService(builder.build());
|
||||
|
||||
assertCompileAccepted("painless", "script", ScriptType.INLINE, ScriptContext.Standard.SEARCH);
|
||||
assertCompileAccepted("painless", "script", ScriptType.INLINE, ScriptContext.Standard.AGGS);
|
||||
assertCompileRejected("painless", "script", ScriptType.INLINE, ScriptContext.Standard.UPDATE);
|
||||
|
||||
assertSettingDeprecationsAndWarnings(
|
||||
ScriptSettingsTests.buildDeprecatedSettingsArray(scriptSettings, "script.update"));
|
||||
}
|
||||
|
||||
public void testAllowNoScriptTypeSettings() throws IOException {
|
||||
Settings.Builder builder = Settings.builder();
|
||||
builder.put("script.types_allowed", "");
|
||||
buildScriptService(builder.build());
|
||||
|
||||
assertCompileRejected("painless", "script", ScriptType.INLINE, ScriptContext.Standard.SEARCH);
|
||||
assertCompileRejected("painless", "script", ScriptType.STORED, ScriptContext.Standard.SEARCH);
|
||||
}
|
||||
|
||||
public void testAllowNoScriptContextSettings() throws IOException {
|
||||
Settings.Builder builder = Settings.builder();
|
||||
builder.put("script.contexts_allowed", "");
|
||||
buildScriptService(builder.build());
|
||||
|
||||
assertCompileRejected("painless", "script", ScriptType.INLINE, ScriptContext.Standard.SEARCH);
|
||||
assertCompileRejected("painless", "script", ScriptType.INLINE, ScriptContext.Standard.AGGS);
|
||||
assertCompileRejected("painless", "script", ScriptType.INLINE, ScriptContext.Standard.UPDATE);
|
||||
assertCompileRejected("painless", "script", ScriptType.INLINE, ScriptContext.Standard.INGEST);
|
||||
}
|
||||
|
||||
public void testDefaultBehaviourFineGrainedSettings() throws IOException {
|
||||
Settings.Builder builder = Settings.builder();
|
||||
//rarely inject the default settings, which have no effect
|
||||
|
@ -345,7 +408,7 @@ public class ScriptServiceTests extends ESTestCase {
|
|||
do {
|
||||
pluginName = randomAlphaOfLength(randomIntBetween(1, 10));
|
||||
unknownContext = randomAlphaOfLength(randomIntBetween(1, 30));
|
||||
} while(scriptContextRegistry.isSupportedContext(new ScriptContext.Plugin(pluginName, unknownContext)));
|
||||
} while(scriptContextRegistry.isSupportedContext(new ScriptContext.Plugin(pluginName, unknownContext).getKey()));
|
||||
|
||||
String type = scriptEngine.getType();
|
||||
try {
|
||||
|
@ -491,8 +554,8 @@ public class ScriptServiceTests extends ESTestCase {
|
|||
try {
|
||||
scriptService.compile(new Script(scriptType, lang, script, Collections.emptyMap()), scriptContext);
|
||||
fail("compile should have been rejected for lang [" + lang + "], script_type [" + scriptType + "], scripted_op [" + scriptContext + "]");
|
||||
} catch(IllegalStateException e) {
|
||||
//all good
|
||||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,3 +12,8 @@ elasticsearch 5.0 and have now been removed. Use painless instead.
|
|||
milliseconds since epoch as a `long`. The same is true for
|
||||
`doc.some_date_field[some_number]`. Use `doc.some_date_field.value.millis` to
|
||||
fetch the milliseconds since epoch if you need it.
|
||||
|
||||
==== Script Settings
|
||||
|
||||
All of the existing scripting security settings have been deprecated. Instead
|
||||
they are replaced with `script.allowed_types` and `script.allowed_contexts`.
|
|
@ -66,3 +66,8 @@ and `http.tcp.blocking_server` settings are not recognized anymore.
|
|||
The `base` similarity is now ignored as coords and query normalization have
|
||||
been removed. If provided, this setting will be ignored and issue a
|
||||
deprecation warning.
|
||||
|
||||
==== Script Settings
|
||||
|
||||
All of the existing scripting security settings have been deprecated. Instead
|
||||
they are replaced with `script.allowed_types` and `script.allowed_contexts`.
|
|
@ -87,6 +87,51 @@ change from the defaults described above. You should be very, very careful
|
|||
when allowing more than the defaults. Any extra permissions weakens the total
|
||||
security of the Elasticsearch deployment.
|
||||
|
||||
[[allowed-script-types-setting]]
|
||||
[float]
|
||||
=== Allowed script types setting
|
||||
|
||||
By default all script types are allowed to be executed. This can be modified using the
|
||||
setting `script.allowed_types`. Only the types specified as part of the setting will be
|
||||
allowed to be executed.
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
script.allowed_types: inline <1>
|
||||
----
|
||||
<1> This will allow only inline scripts to be executed but not stored scripts
|
||||
(or any other types).
|
||||
|
||||
[[allowed-script-contexts-setting]]
|
||||
[float]
|
||||
=== Allowed script contexts setting
|
||||
|
||||
By default all script contexts are allowed to be executed. This can be modified using the
|
||||
setting `script.allowed_contexts`. Only the contexts specified as part of the setting will
|
||||
be allowed to be executed.
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
script.allowed_contexts: search, update <1>
|
||||
----
|
||||
<1> This will allow only search and update scripts to be executed but not
|
||||
aggs or plugin scripts (or any other contexts).
|
||||
|
||||
[[deprecated-script=settings]]
|
||||
[float]
|
||||
=== Deprecated script settings
|
||||
|
||||
The following settings have all been deprecated and will be removed in 6.0:
|
||||
|
||||
* <<security-script-source>>
|
||||
* <<security-script-context>>
|
||||
* <<security-script-fine>>
|
||||
|
||||
Use the following instead:
|
||||
|
||||
* <<allowed-script-types-setting>>
|
||||
* <<allowed-script-contexts-setting>>
|
||||
|
||||
[[security-script-source]]
|
||||
[float]
|
||||
=== Script source settings
|
||||
|
|
Loading…
Reference in New Issue