浏览代码

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.
Jack Conradson 8 年之前
父节点
当前提交
43292979fd

+ 2 - 2
core/src/main/java/org/elasticsearch/script/ScriptContextRegistry.java

@@ -64,8 +64,8 @@ public final class ScriptContextRegistry {
     /**
     /**
      * @return <tt>true</tt> if the provided {@link ScriptContext} is supported, <tt>false</tt> otherwise
      * @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
     //script contexts can be used in fine-grained settings, we need to be careful with what we allow here

+ 59 - 1
core/src/main/java/org/elasticsearch/script/ScriptModes.java

@@ -19,13 +19,19 @@
 
 
 package org.elasticsearch.script;
 package org.elasticsearch.script;
 
 
+import org.apache.lucene.util.SetOnce;
 import org.elasticsearch.common.settings.Setting;
 import org.elasticsearch.common.settings.Setting;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.settings.Settings;
 
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Map;
+import java.util.Set;
 import java.util.TreeMap;
 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
  * 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;
     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<>();
         HashMap<String, Boolean> scriptModes = new HashMap<>();
         for (Setting<Boolean> scriptModeSetting : scriptSettings.getScriptLanguageSettings()) {
         for (Setting<Boolean> scriptModeSetting : scriptSettings.getScriptLanguageSettings()) {
             scriptModes.put(scriptModeSetting.getKey(), scriptModeSetting.get(settings));
             scriptModes.put(scriptModeSetting.getKey(), scriptModeSetting.get(settings));
         }
         }
         this.scriptEnabled = Collections.unmodifiableMap(scriptModes);
         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)) {
         if (NativeScriptEngine.NAME.equals(lang)) {
             return true;
             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));
         Boolean scriptMode = scriptEnabled.get(getKey(lang, scriptType, scriptContext));
         if (scriptMode == null) {
         if (scriptMode == null) {
             throw new IllegalArgumentException("script mode not found for lang [" + lang + "], script_type [" + scriptType + "], operation [" + scriptContext.getKey() + "]");
             throw new IllegalArgumentException("script mode not found for lang [" + lang + "], script_type [" + scriptType + "], operation [" + scriptContext.getKey() + "]");

+ 2 - 2
core/src/main/java/org/elasticsearch/script/ScriptService.java

@@ -152,7 +152,7 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
         this.scriptEnginesByLang = unmodifiableMap(enginesByLangBuilder);
         this.scriptEnginesByLang = unmodifiableMap(enginesByLangBuilder);
         this.scriptEnginesByExt = unmodifiableMap(enginesByExtBuilder);
         this.scriptEnginesByExt = unmodifiableMap(enginesByExtBuilder);
 
 
-        this.scriptModes = new ScriptModes(scriptSettings, settings);
+        this.scriptModes = new ScriptModes(scriptContextRegistry, scriptSettings, settings);
 
 
         // add file watcher for static scripts
         // add file watcher for static scripts
         scriptsDirectory = env.scriptsFile();
         scriptsDirectory = env.scriptsFile();
@@ -511,7 +511,7 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
 
 
     private boolean canExecuteScript(String lang, ScriptType scriptType, ScriptContext scriptContext) {
     private boolean canExecuteScript(String lang, ScriptType scriptType, ScriptContext scriptContext) {
         assert lang != null;
         assert lang != null;
-        if (scriptContextRegistry.isSupportedContext(scriptContext) == false) {
+        if (scriptContextRegistry.isSupportedContext(scriptContext.getKey()) == false) {
             throw new IllegalArgumentException("script context [" + scriptContext.getKey() + "] not supported");
             throw new IllegalArgumentException("script context [" + scriptContext.getKey() + "] not supported");
         }
         }
         return scriptModes.getScriptEnabled(lang, scriptType, scriptContext);
         return scriptModes.getScriptEnabled(lang, scriptType, scriptContext);

+ 5 - 5
core/src/test/java/org/elasticsearch/script/ScriptModesTests.java

@@ -97,14 +97,14 @@ public class ScriptModesTests extends ESTestCase {
     }
     }
 
 
     public void testDefaultSettings() {
     public void testDefaultSettings() {
-        this.scriptModes = new ScriptModes(scriptSettings, Settings.EMPTY);
+        this.scriptModes = new ScriptModes(scriptContextRegistry, scriptSettings, Settings.EMPTY);
         assertScriptModesAllOps(true, ScriptType.FILE);
         assertScriptModesAllOps(true, ScriptType.FILE);
         assertScriptModesAllOps(false, ScriptType.STORED, ScriptType.INLINE);
         assertScriptModesAllOps(false, ScriptType.STORED, ScriptType.INLINE);
     }
     }
 
 
     public void testMissingSetting() {
     public void testMissingSetting() {
         assertAllSettingsWereChecked = false;
         assertAllSettingsWereChecked = false;
-        this.scriptModes = new ScriptModes(scriptSettings, Settings.EMPTY);
+        this.scriptModes = new ScriptModes(scriptContextRegistry, scriptSettings, Settings.EMPTY);
         try {
         try {
             scriptModes.getScriptEnabled("non_existing", randomFrom(ScriptType.values()), randomFrom(scriptContexts));
             scriptModes.getScriptEnabled("non_existing", randomFrom(ScriptType.values()), randomFrom(scriptContexts));
             fail("Expected IllegalArgumentException");
             fail("Expected IllegalArgumentException");
@@ -131,7 +131,7 @@ public class ScriptModesTests extends ESTestCase {
             builder.put("script" + "." + randomScriptTypes[i].getName(), randomScriptModes[i]);
             builder.put("script" + "." + randomScriptTypes[i].getName(), randomScriptModes[i]);
             deprecated.add("script" + "." + randomScriptTypes[i].getName());
             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++) {
         for (int i = 0; i < randomInt; i++) {
             assertScriptModesAllOps(randomScriptModes[i], randomScriptTypes[i]);
             assertScriptModesAllOps(randomScriptModes[i], randomScriptTypes[i]);
@@ -167,7 +167,7 @@ public class ScriptModesTests extends ESTestCase {
             builder.put("script" + "." + randomScriptContexts[i].getKey(), randomScriptModes[i]);
             builder.put("script" + "." + randomScriptContexts[i].getKey(), randomScriptModes[i]);
             deprecated.add("script" + "." + randomScriptContexts[i].getKey());
             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++) {
         for (int i = 0; i < randomInt; i++) {
             assertScriptModesAllTypes(randomScriptModes[i], randomScriptContexts[i]);
             assertScriptModesAllTypes(randomScriptModes[i], randomScriptContexts[i]);
@@ -187,7 +187,7 @@ public class ScriptModesTests extends ESTestCase {
                 .put("script.stored", "true")
                 .put("script.stored", "true")
                 .put("script.inline", "true");
                 .put("script.inline", "true");
         //operations generic settings have precedence over script type generic settings
         //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);
         assertScriptModesAllTypes(false, scriptContext);
         ScriptContext[] complementOf = complementOf(scriptContext);
         ScriptContext[] complementOf = complementOf(scriptContext);
         assertScriptModes(true, new ScriptType[]{ScriptType.FILE, ScriptType.STORED}, complementOf);
         assertScriptModes(true, new ScriptType[]{ScriptType.FILE, ScriptType.STORED}, complementOf);

+ 66 - 3
core/src/test/java/org/elasticsearch/script/ScriptServiceTests.java

@@ -216,6 +216,69 @@ public class ScriptServiceTests extends ESTestCase {
         assertThat(compiledScript1.compiled(), sameInstance(compiledScript2.compiled()));
         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 {
     public void testDefaultBehaviourFineGrainedSettings() throws IOException {
         Settings.Builder builder = Settings.builder();
         Settings.Builder builder = Settings.builder();
         //rarely inject the default settings, which have no effect
         //rarely inject the default settings, which have no effect
@@ -345,7 +408,7 @@ public class ScriptServiceTests extends ESTestCase {
         do {
         do {
             pluginName = randomAlphaOfLength(randomIntBetween(1, 10));
             pluginName = randomAlphaOfLength(randomIntBetween(1, 10));
             unknownContext = randomAlphaOfLength(randomIntBetween(1, 30));
             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();
         String type = scriptEngine.getType();
         try {
         try {
@@ -491,8 +554,8 @@ public class ScriptServiceTests extends ESTestCase {
         try {
         try {
             scriptService.compile(new Script(scriptType, lang, script, Collections.emptyMap()), scriptContext);
             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 + "]");
             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
         }
         }
     }
     }
 
 

+ 5 - 0
docs/reference/migration/migrate_6_0/scripting.asciidoc

@@ -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
 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
 `doc.some_date_field[some_number]`. Use `doc.some_date_field.value.millis` to
 fetch the milliseconds since epoch if you need it.
 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`.

+ 5 - 0
docs/reference/migration/migrate_6_0/settings.asciidoc

@@ -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
 The `base` similarity is now ignored as coords and query normalization have
 been removed. If provided, this setting will be ignored and issue a
 been removed. If provided, this setting will be ignored and issue a
 deprecation warning.
 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`.

+ 45 - 0
docs/reference/modules/scripting/security.asciidoc

@@ -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
 when allowing more than the defaults. Any extra permissions weakens the total
 security of the Elasticsearch deployment.
 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]]
 [[security-script-source]]
 [float]
 [float]
 === Script source settings
 === Script source settings