Skip to content

Commit 7b1dd21

Browse files
committed
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.
1 parent 2ebfe66 commit 7b1dd21

File tree

6 files changed

+179
-13
lines changed

6 files changed

+179
-13
lines changed

core/src/main/java/org/elasticsearch/script/ScriptContextRegistry.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ Collection<ScriptContext> scriptContexts() {
6464
/**
6565
* @return <tt>true</tt> if the provided {@link ScriptContext} is supported, <tt>false</tt> otherwise
6666
*/
67-
boolean isSupportedContext(ScriptContext scriptContext) {
68-
return scriptContexts.containsKey(scriptContext.getKey());
67+
boolean isSupportedContext(String scriptContext) {
68+
return scriptContexts.containsKey(scriptContext);
6969
}
7070

7171
//script contexts can be used in fine-grained settings, we need to be careful with what we allow here

core/src/main/java/org/elasticsearch/script/ScriptModes.java

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,19 @@
1919

2020
package org.elasticsearch.script;
2121

22+
import org.apache.lucene.util.SetOnce;
2223
import org.elasticsearch.common.settings.Setting;
2324
import org.elasticsearch.common.settings.Settings;
2425

26+
import java.util.ArrayList;
2527
import java.util.Collections;
2628
import java.util.HashMap;
29+
import java.util.HashSet;
30+
import java.util.List;
2731
import java.util.Map;
32+
import java.util.Set;
2833
import java.util.TreeMap;
34+
import java.util.function.Function;
2935

3036
/**
3137
* 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 {
3844

3945
final Map<String, Boolean> scriptEnabled;
4046

41-
ScriptModes(ScriptSettings scriptSettings, Settings settings) {
47+
private static final Setting<List<String>> TYPES_ALLOWED_SETTING =
48+
Setting.listSetting("script.types_allowed", Collections.emptyList(), Function.identity(), Setting.Property.NodeScope);
49+
private static final Setting<List<String>> CONTEXTS_ALLOWED_SETTING =
50+
Setting.listSetting("script.contexts_allowed", Collections.emptyList(), Function.identity(), Setting.Property.NodeScope);
51+
52+
private final Set<String> typesAllowed;
53+
private final Set<String> contextsAllowed;
54+
55+
ScriptModes(ScriptContextRegistry scriptContextRegistry, ScriptSettings scriptSettings, Settings settings) {
4256
HashMap<String, Boolean> scriptModes = new HashMap<>();
4357
for (Setting<Boolean> scriptModeSetting : scriptSettings.getScriptLanguageSettings()) {
4458
scriptModes.put(scriptModeSetting.getKey(), scriptModeSetting.get(settings));
4559
}
4660
this.scriptEnabled = Collections.unmodifiableMap(scriptModes);
61+
62+
typesAllowed = TYPES_ALLOWED_SETTING.exists(settings) ? new HashSet<>() : null;
63+
64+
if (typesAllowed != null) {
65+
for (String settingType : TYPES_ALLOWED_SETTING.get(settings)) {
66+
boolean found = false;
67+
68+
for (ScriptType scriptType : ScriptType.values()) {
69+
if (scriptType.getName().equals(settingType)) {
70+
found = true;
71+
typesAllowed.add(settingType);
72+
73+
break;
74+
}
75+
}
76+
77+
if (!found) {
78+
throw new IllegalArgumentException(
79+
"unknown script type [" + settingType + "] found in setting [" + TYPES_ALLOWED_SETTING.getKey() + "].");
80+
}
81+
}
82+
}
83+
84+
contextsAllowed = CONTEXTS_ALLOWED_SETTING.exists(settings) ? new HashSet<>() : null;
85+
86+
if (contextsAllowed != null) {
87+
for (String settingContext : CONTEXTS_ALLOWED_SETTING.get(settings)) {
88+
if (scriptContextRegistry.isSupportedContext(settingContext)) {
89+
contextsAllowed.add(settingContext);
90+
} else {
91+
throw new IllegalArgumentException(
92+
"unknown script context [" + settingContext + "] found in setting [" + CONTEXTS_ALLOWED_SETTING.getKey() + "].");
93+
}
94+
}
95+
}
4796
}
4897

4998
/**
@@ -60,6 +109,15 @@ public boolean getScriptEnabled(String lang, ScriptType scriptType, ScriptContex
60109
if (NativeScriptEngineService.NAME.equals(lang)) {
61110
return true;
62111
}
112+
113+
if (typesAllowed != null && typesAllowed.contains(scriptType.getName()) == false) {
114+
throw new IllegalArgumentException("[" + scriptType.getName() + "] scripts cannot be executed");
115+
}
116+
117+
if (contextsAllowed != null && contextsAllowed.contains(scriptContext.getKey()) == false) {
118+
throw new IllegalArgumentException("[" + scriptContext.getKey() + "] scripts cannot be executed");
119+
}
120+
63121
Boolean scriptMode = scriptEnabled.get(getKey(lang, scriptType, scriptContext));
64122
if (scriptMode == null) {
65123
throw new IllegalArgumentException("script mode not found for lang [" + lang + "], script_type [" + scriptType + "], operation [" + scriptContext.getKey() + "]");

core/src/main/java/org/elasticsearch/script/ScriptService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ public ScriptService(Settings settings, Environment env,
152152
this.scriptEnginesByLang = unmodifiableMap(enginesByLangBuilder);
153153
this.scriptEnginesByExt = unmodifiableMap(enginesByExtBuilder);
154154

155-
this.scriptModes = new ScriptModes(scriptSettings, settings);
155+
this.scriptModes = new ScriptModes(scriptContextRegistry, scriptSettings, settings);
156156

157157
// add file watcher for static scripts
158158
scriptsDirectory = env.scriptsFile();
@@ -511,7 +511,7 @@ private boolean isAnyScriptContextEnabled(String lang, ScriptType scriptType) {
511511

512512
private boolean canExecuteScript(String lang, ScriptType scriptType, ScriptContext scriptContext) {
513513
assert lang != null;
514-
if (scriptContextRegistry.isSupportedContext(scriptContext) == false) {
514+
if (scriptContextRegistry.isSupportedContext(scriptContext.getKey()) == false) {
515515
throw new IllegalArgumentException("script context [" + scriptContext.getKey() + "] not supported");
516516
}
517517
return scriptModes.getScriptEnabled(lang, scriptType, scriptContext);

core/src/test/java/org/elasticsearch/script/ScriptModesTests.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,14 @@ public void assertAllSettingsWereChecked() {
9797
}
9898

9999
public void testDefaultSettings() {
100-
this.scriptModes = new ScriptModes(scriptSettings, Settings.EMPTY);
100+
this.scriptModes = new ScriptModes(scriptContextRegistry, scriptSettings, Settings.EMPTY);
101101
assertScriptModesAllOps(true, ScriptType.FILE);
102102
assertScriptModesAllOps(false, ScriptType.STORED, ScriptType.INLINE);
103103
}
104104

105105
public void testMissingSetting() {
106106
assertAllSettingsWereChecked = false;
107-
this.scriptModes = new ScriptModes(scriptSettings, Settings.EMPTY);
107+
this.scriptModes = new ScriptModes(scriptContextRegistry, scriptSettings, Settings.EMPTY);
108108
try {
109109
scriptModes.getScriptEnabled("non_existing", randomFrom(ScriptType.values()), randomFrom(scriptContexts));
110110
fail("Expected IllegalArgumentException");
@@ -131,7 +131,7 @@ public void testScriptTypeGenericSettings() {
131131
builder.put("script" + "." + randomScriptTypes[i].getName(), randomScriptModes[i]);
132132
deprecated.add("script" + "." + randomScriptTypes[i].getName());
133133
}
134-
this.scriptModes = new ScriptModes(scriptSettings, builder.build());
134+
this.scriptModes = new ScriptModes(scriptContextRegistry, scriptSettings, builder.build());
135135

136136
for (int i = 0; i < randomInt; i++) {
137137
assertScriptModesAllOps(randomScriptModes[i], randomScriptTypes[i]);
@@ -167,7 +167,7 @@ public void testScriptContextGenericSettings() {
167167
builder.put("script" + "." + randomScriptContexts[i].getKey(), randomScriptModes[i]);
168168
deprecated.add("script" + "." + randomScriptContexts[i].getKey());
169169
}
170-
this.scriptModes = new ScriptModes(scriptSettings, builder.build());
170+
this.scriptModes = new ScriptModes(scriptContextRegistry, scriptSettings, builder.build());
171171

172172
for (int i = 0; i < randomInt; i++) {
173173
assertScriptModesAllTypes(randomScriptModes[i], randomScriptContexts[i]);
@@ -187,7 +187,7 @@ public void testConflictingScriptTypeAndOpGenericSettings() {
187187
.put("script.stored", "true")
188188
.put("script.inline", "true");
189189
//operations generic settings have precedence over script type generic settings
190-
this.scriptModes = new ScriptModes(scriptSettings, builder.build());
190+
this.scriptModes = new ScriptModes(scriptContextRegistry, scriptSettings, builder.build());
191191
assertScriptModesAllTypes(false, scriptContext);
192192
ScriptContext[] complementOf = complementOf(scriptContext);
193193
assertScriptModes(true, new ScriptType[]{ScriptType.FILE, ScriptType.STORED}, complementOf);

core/src/test/java/org/elasticsearch/script/ScriptServiceTests.java

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,69 @@ public void testInlineScriptCompiledOnceCache() throws IOException {
216216
assertThat(compiledScript1.compiled(), sameInstance(compiledScript2.compiled()));
217217
}
218218

219+
public void testAllowAllScriptTypeSettings() throws IOException {
220+
buildScriptService(Settings.EMPTY);
221+
222+
assertCompileAccepted("painless", "script", ScriptType.INLINE, ScriptContext.Standard.SEARCH);
223+
assertCompileAccepted("painless", "script", ScriptType.STORED, ScriptContext.Standard.SEARCH);
224+
}
225+
226+
public void testAllowAllScriptContextSettings() throws IOException {
227+
buildScriptService(Settings.EMPTY);
228+
229+
assertCompileAccepted("painless", "script", ScriptType.INLINE, ScriptContext.Standard.SEARCH);
230+
assertCompileAccepted("painless", "script", ScriptType.INLINE, ScriptContext.Standard.AGGS);
231+
assertCompileAccepted("painless", "script", ScriptType.INLINE, ScriptContext.Standard.UPDATE);
232+
assertCompileAccepted("painless", "script", ScriptType.INLINE, ScriptContext.Standard.INGEST);
233+
}
234+
235+
public void testAllowSomeScriptTypeSettings() throws IOException {
236+
Settings.Builder builder = Settings.builder();
237+
builder.put("script.types_allowed", "inline");
238+
builder.put("script.engine.painless.stored", false);
239+
buildScriptService(builder.build());
240+
241+
assertCompileAccepted("painless", "script", ScriptType.INLINE, ScriptContext.Standard.SEARCH);
242+
assertCompileRejected("painless", "script", ScriptType.STORED, ScriptContext.Standard.SEARCH);
243+
244+
assertSettingDeprecationsAndWarnings(
245+
ScriptSettingsTests.buildDeprecatedSettingsArray(scriptSettings, "script.engine.painless.stored"));
246+
}
247+
248+
public void testAllowSomeScriptContextSettings() throws IOException {
249+
Settings.Builder builder = Settings.builder();
250+
builder.put("script.contexts_allowed", "search, aggs");
251+
builder.put("script.update", false);
252+
buildScriptService(builder.build());
253+
254+
assertCompileAccepted("painless", "script", ScriptType.INLINE, ScriptContext.Standard.SEARCH);
255+
assertCompileAccepted("painless", "script", ScriptType.INLINE, ScriptContext.Standard.AGGS);
256+
assertCompileRejected("painless", "script", ScriptType.INLINE, ScriptContext.Standard.UPDATE);
257+
258+
assertSettingDeprecationsAndWarnings(
259+
ScriptSettingsTests.buildDeprecatedSettingsArray(scriptSettings, "script.update"));
260+
}
261+
262+
public void testAllowNoScriptTypeSettings() throws IOException {
263+
Settings.Builder builder = Settings.builder();
264+
builder.put("script.types_allowed", "");
265+
buildScriptService(builder.build());
266+
267+
assertCompileRejected("painless", "script", ScriptType.INLINE, ScriptContext.Standard.SEARCH);
268+
assertCompileRejected("painless", "script", ScriptType.STORED, ScriptContext.Standard.SEARCH);
269+
}
270+
271+
public void testAllowNoScriptContextSettings() throws IOException {
272+
Settings.Builder builder = Settings.builder();
273+
builder.put("script.contexts_allowed", "");
274+
buildScriptService(builder.build());
275+
276+
assertCompileRejected("painless", "script", ScriptType.INLINE, ScriptContext.Standard.SEARCH);
277+
assertCompileRejected("painless", "script", ScriptType.INLINE, ScriptContext.Standard.AGGS);
278+
assertCompileRejected("painless", "script", ScriptType.INLINE, ScriptContext.Standard.UPDATE);
279+
assertCompileRejected("painless", "script", ScriptType.INLINE, ScriptContext.Standard.INGEST);
280+
}
281+
219282
public void testDefaultBehaviourFineGrainedSettings() throws IOException {
220283
Settings.Builder builder = Settings.builder();
221284
//rarely inject the default settings, which have no effect
@@ -345,7 +408,7 @@ public void testCompileNonRegisteredContext() throws IOException {
345408
do {
346409
pluginName = randomAlphaOfLength(randomIntBetween(1, 10));
347410
unknownContext = randomAlphaOfLength(randomIntBetween(1, 30));
348-
} while(scriptContextRegistry.isSupportedContext(new ScriptContext.Plugin(pluginName, unknownContext)));
411+
} while(scriptContextRegistry.isSupportedContext(new ScriptContext.Plugin(pluginName, unknownContext).getKey()));
349412

350413
String type = scriptEngineService.getType();
351414
try {
@@ -491,8 +554,8 @@ private void assertCompileRejected(String lang, String script, ScriptType script
491554
try {
492555
scriptService.compile(new Script(scriptType, lang, script, Collections.emptyMap()), scriptContext);
493556
fail("compile should have been rejected for lang [" + lang + "], script_type [" + scriptType + "], scripted_op [" + scriptContext + "]");
494-
} catch(IllegalStateException e) {
495-
//all good
557+
} catch (IllegalArgumentException | IllegalStateException e) {
558+
// pass
496559
}
497560
}
498561

docs/reference/modules/scripting/security.asciidoc

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,51 @@ change from the defaults described above. You should be very, very careful
8888
when allowing more than the defaults. Any extra permissions weakens the total
8989
security of the Elasticsearch deployment.
9090

91+
[[allowed-script-types-setting]]
92+
[float]
93+
=== Allowed script types setting
94+
95+
By default all script types are allowed to be executed. This can be modified using the
96+
setting `script.allowed_types`. Only the types specified as part of the setting will be
97+
allowed to be executed.
98+
99+
[source,yaml]
100+
----
101+
script.allowed_types: inline <1>
102+
----
103+
<1> This will allow only inline scripts to be executed but not stored scripts
104+
(or any other types).
105+
106+
[[allowed-script-contexts-setting]]
107+
[float]
108+
=== Allowed script contexts setting
109+
110+
By default all script contexts are allowed to be executed. This can be modified using the
111+
setting `script.allowed_contexts`. Only the contexts specified as part of the setting will
112+
be allowed to be executed.
113+
114+
[source,yaml]
115+
----
116+
script.allowed_contexts: search, update <1>
117+
----
118+
<1> This will allow only search and update scripts to be executed but not
119+
aggs or plugin scripts (or any other contexts).
120+
121+
[[deprecated-script=settings]]
122+
[float]
123+
=== Deprecated script settings
124+
125+
The following settings have all been deprecated and will be removed in 6.0:
126+
127+
* <<security-script-source>>
128+
* <<security-script-context>>
129+
* <<security-script-fine>>
130+
131+
Use the following instead:
132+
133+
* <<allowed-script-types-setting>>
134+
* <<allowed-script-contexts-setting>>
135+
91136
[[security-script-source]]
92137
[float]
93138
=== Script source settings

0 commit comments

Comments
 (0)