Ver código fonte

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 anos atrás
pai
commit
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
      */
-    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

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

@@ -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() + "]");

+ 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.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);

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

@@ -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);

+ 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()));
     }
 
+    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
         }
     }
 

+ 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
 `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`.

+ 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
 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`.

+ 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
 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