Skip to content

Commit b5da668

Browse files
authored
Add tier preference to security index settings allowlist and update default tier preference (#111818)
This commit allows tier preference for the security system indices to be set using the Security Settings API, and adds validation to prevent using the `data_frozen` tier for security system indices. Also updates the default tier preference to `data_hot,data_content`.
1 parent ad0292c commit b5da668

File tree

7 files changed

+192
-32
lines changed

7 files changed

+192
-32
lines changed

docs/changelog/111818.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 111818
2+
summary: Add tier preference to security index settings allowlist
3+
area: Security
4+
type: enhancement
5+
issues: []

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/settings/UpdateSecuritySettingsAction.java

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
import org.elasticsearch.action.support.master.AcknowledgedRequest;
1515
import org.elasticsearch.action.support.master.AcknowledgedResponse;
1616
import org.elasticsearch.cluster.metadata.IndexMetadata;
17+
import org.elasticsearch.cluster.routing.allocation.DataTier;
1718
import org.elasticsearch.common.io.stream.StreamInput;
1819
import org.elasticsearch.common.io.stream.StreamOutput;
19-
import org.elasticsearch.common.util.set.Sets;
2020
import org.elasticsearch.core.TimeValue;
2121
import org.elasticsearch.core.UpdateForV9;
2222
import org.elasticsearch.xcontent.ConstructingObjectParser;
@@ -28,6 +28,8 @@
2828
import java.util.Map;
2929
import java.util.Objects;
3030
import java.util.Set;
31+
import java.util.function.BiFunction;
32+
import java.util.stream.Collectors;
3133

3234
import static org.elasticsearch.xcontent.ConstructingObjectParser.optionalConstructorArg;
3335

@@ -42,10 +44,35 @@ public class UpdateSecuritySettingsAction {
4244
public static final String TOKENS_INDEX_NAME = "security-tokens";
4345
public static final String PROFILES_INDEX_NAME = "security-profile";
4446

45-
public static final Set<String> ALLOWED_SETTING_KEYS = Set.of(
46-
IndexMetadata.SETTING_NUMBER_OF_REPLICAS,
47-
IndexMetadata.SETTING_AUTO_EXPAND_REPLICAS
48-
);
47+
/**
48+
* A map of allowed settings to validators for those settings. Values should take the value which is being assigned to the setting
49+
* and an existing {@link ActionRequestValidationException}, to which they should add if the value is disallowed.
50+
*/
51+
public static final Map<
52+
String,
53+
BiFunction<Object, ActionRequestValidationException, ActionRequestValidationException>> ALLOWED_SETTING_VALIDATORS = Map.of(
54+
IndexMetadata.SETTING_NUMBER_OF_REPLICAS,
55+
(it, ex) -> ex, // no additional validation
56+
IndexMetadata.SETTING_AUTO_EXPAND_REPLICAS,
57+
(it, ex) -> ex, // no additional validation
58+
DataTier.TIER_PREFERENCE,
59+
(it, ex) -> {
60+
Set<String> allowedTiers = Set.of(DataTier.DATA_CONTENT, DataTier.DATA_HOT, DataTier.DATA_WARM, DataTier.DATA_COLD);
61+
if (it instanceof String preference) {
62+
String disallowedTiers = DataTier.parseTierList(preference)
63+
.stream()
64+
.filter(tier -> allowedTiers.contains(tier) == false)
65+
.collect(Collectors.joining(","));
66+
if (disallowedTiers.isEmpty() == false) {
67+
return ValidateActions.addValidationError(
68+
"disallowed data tiers [" + disallowedTiers + "] found, allowed tiers are [" + String.join(",", allowedTiers),
69+
ex
70+
);
71+
}
72+
}
73+
return ex;
74+
}
75+
);
4976

5077
private UpdateSecuritySettingsAction() {/* no instances */}
5178

@@ -154,19 +181,26 @@ private static ActionRequestValidationException validateIndexSettings(
154181
String indexName,
155182
ActionRequestValidationException existingExceptions
156183
) {
157-
Set<String> forbiddenSettings = Sets.difference(indexSettings.keySet(), ALLOWED_SETTING_KEYS);
158-
if (forbiddenSettings.size() > 0) {
159-
return ValidateActions.addValidationError(
160-
"illegal settings for index ["
161-
+ indexName
162-
+ "]: "
163-
+ forbiddenSettings
164-
+ ", these settings may not be configured. Only the following settings may be configured for that index: "
165-
+ ALLOWED_SETTING_KEYS,
166-
existingExceptions
167-
);
184+
ActionRequestValidationException errors = existingExceptions;
185+
186+
for (Map.Entry<String, Object> entry : indexSettings.entrySet()) {
187+
String setting = entry.getKey();
188+
if (ALLOWED_SETTING_VALIDATORS.containsKey(setting)) {
189+
errors = ALLOWED_SETTING_VALIDATORS.get(setting).apply(entry.getValue(), errors);
190+
} else {
191+
errors = ValidateActions.addValidationError(
192+
"illegal setting for index ["
193+
+ indexName
194+
+ "]: ["
195+
+ setting
196+
+ "], this setting may not be configured. Only the following settings may be configured for that index: "
197+
+ ALLOWED_SETTING_VALIDATORS.keySet(),
198+
existingExceptions
199+
);
200+
}
168201
}
169-
return existingExceptions;
202+
203+
return errors;
170204
}
171205
}
172206
}

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/settings/UpdateSecuritySettingsActionTests.java

Lines changed: 80 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,19 @@
77

88
package org.elasticsearch.xpack.core.security.action.settings;
99

10+
import org.elasticsearch.action.ActionRequestValidationException;
11+
import org.elasticsearch.cluster.metadata.IndexMetadata;
12+
import org.elasticsearch.cluster.routing.allocation.DataTier;
1013
import org.elasticsearch.test.ESTestCase;
1114

1215
import java.util.Collections;
1316
import java.util.HashMap;
1417
import java.util.List;
1518
import java.util.Map;
19+
import java.util.function.Supplier;
1620
import java.util.regex.Pattern;
1721

18-
import static org.elasticsearch.xpack.core.security.action.settings.UpdateSecuritySettingsAction.ALLOWED_SETTING_KEYS;
22+
import static org.elasticsearch.xpack.core.security.action.settings.UpdateSecuritySettingsAction.ALLOWED_SETTING_VALIDATORS;
1923
import static org.elasticsearch.xpack.core.security.action.settings.UpdateSecuritySettingsAction.MAIN_INDEX_NAME;
2024
import static org.elasticsearch.xpack.core.security.action.settings.UpdateSecuritySettingsAction.PROFILES_INDEX_NAME;
2125
import static org.elasticsearch.xpack.core.security.action.settings.UpdateSecuritySettingsAction.TOKENS_INDEX_NAME;
@@ -27,6 +31,15 @@
2731

2832
public class UpdateSecuritySettingsActionTests extends ESTestCase {
2933

34+
static final Map<String, Supplier<String>> ALLOWED_SETTING_GENERATORS = Map.of(
35+
IndexMetadata.SETTING_NUMBER_OF_REPLICAS,
36+
() -> randomAlphaOfLength(5), // no additional validation
37+
IndexMetadata.SETTING_AUTO_EXPAND_REPLICAS,
38+
() -> randomAlphaOfLength(5), // no additional validation
39+
DataTier.TIER_PREFERENCE,
40+
() -> randomFrom(DataTier.DATA_CONTENT, DataTier.DATA_HOT, DataTier.DATA_WARM, DataTier.DATA_COLD)
41+
);
42+
3043
public void testValidateSettingsEmpty() {
3144
var req = new UpdateSecuritySettingsAction.Request(
3245
TEST_REQUEST_TIMEOUT,
@@ -43,9 +56,10 @@ public void testValidateSettingsEmpty() {
4356

4457
public void testAllowedSettingsOk() {
4558
Map<String, Object> allAllowedSettingsMap = new HashMap<>();
46-
for (String allowedSetting : ALLOWED_SETTING_KEYS) {
47-
Map<String, Object> allowedSettingMap = Map.of(allowedSetting, randomAlphaOfLength(5));
48-
allAllowedSettingsMap.put(allowedSetting, randomAlphaOfLength(5));
59+
for (String allowedSetting : ALLOWED_SETTING_VALIDATORS.keySet()) {
60+
String settingValue = ALLOWED_SETTING_GENERATORS.get(allowedSetting).get();
61+
Map<String, Object> allowedSettingMap = Map.of(allowedSetting, settingValue);
62+
allAllowedSettingsMap.put(allowedSetting, settingValue);
4963
var req = new UpdateSecuritySettingsAction.Request(
5064
TEST_REQUEST_TIMEOUT,
5165
TEST_REQUEST_TIMEOUT,
@@ -86,11 +100,12 @@ public void testAllowedSettingsOk() {
86100

87101
public void testDisallowedSettingsFailsValidation() {
88102
String disallowedSetting = "index."
89-
+ randomValueOtherThanMany((value) -> ALLOWED_SETTING_KEYS.contains("index." + value), () -> randomAlphaOfLength(5));
103+
+ randomValueOtherThanMany((value) -> ALLOWED_SETTING_VALIDATORS.containsKey("index." + value), () -> randomAlphaOfLength(5));
90104
Map<String, Object> disallowedSettingMap = Map.of(disallowedSetting, randomAlphaOfLength(5));
105+
String validSetting = randomFrom(ALLOWED_SETTING_VALIDATORS.keySet());
91106
Map<String, Object> validOrEmptySettingMap = randomFrom(
92107
Collections.emptyMap(),
93-
Map.of(randomFrom(ALLOWED_SETTING_KEYS), randomAlphaOfLength(5))
108+
Map.of(validSetting, ALLOWED_SETTING_GENERATORS.get(validSetting).get())
94109
);
95110
{
96111
var req = new UpdateSecuritySettingsAction.Request(
@@ -106,11 +121,11 @@ public void testDisallowedSettingsFailsValidation() {
106121
assertThat(
107122
errorMsg,
108123
matchesRegex(
109-
"illegal settings for index \\["
124+
"illegal setting for index \\["
110125
+ Pattern.quote(TOKENS_INDEX_NAME)
111126
+ "\\]: \\["
112127
+ disallowedSetting
113-
+ "\\], these settings may not be configured. Only the following settings may be configured for that index.*"
128+
+ "\\], this setting may not be configured. Only the following settings may be configured for that index.*"
114129
)
115130
);
116131
}
@@ -130,13 +145,13 @@ public void testDisallowedSettingsFailsValidation() {
130145
assertThat(
131146
errorMsg,
132147
matchesRegex(
133-
"illegal settings for index \\[("
148+
"illegal setting for index \\[("
134149
+ Pattern.quote(MAIN_INDEX_NAME)
135150
+ "|"
136151
+ Pattern.quote(PROFILES_INDEX_NAME)
137152
+ ")\\]: \\["
138153
+ disallowedSetting
139-
+ "\\], these settings may not be configured. Only the following settings may be configured for that index.*"
154+
+ "\\], this setting may not be configured. Only the following settings may be configured for that index.*"
140155
)
141156
);
142157
}
@@ -156,19 +171,72 @@ public void testDisallowedSettingsFailsValidation() {
156171
assertThat(
157172
errorMsg,
158173
matchesRegex(
159-
"illegal settings for index \\[("
174+
"illegal setting for index \\[("
160175
+ Pattern.quote(MAIN_INDEX_NAME)
161176
+ "|"
162177
+ Pattern.quote(TOKENS_INDEX_NAME)
163178
+ "|"
164179
+ Pattern.quote(PROFILES_INDEX_NAME)
165180
+ ")\\]: \\["
166181
+ disallowedSetting
167-
+ "\\], these settings may not be configured. Only the following settings may be configured for that index.*"
182+
+ "\\], this setting may not be configured. Only the following settings may be configured for that index.*"
168183
)
169184
);
170185
}
171186
}
172187
}
173188

189+
public void testSettingValuesAreValidated() {
190+
Map<String, Object> forbiddenSettingsMap = Map.of(DataTier.TIER_PREFERENCE, DataTier.DATA_FROZEN);
191+
String badTier = randomAlphaOfLength(5);
192+
Map<String, Object> badSettingsMap = Map.of(DataTier.TIER_PREFERENCE, badTier);
193+
Map<String, Object> allowedSettingMap = Map.of(
194+
DataTier.TIER_PREFERENCE,
195+
randomFrom(DataTier.DATA_HOT, DataTier.DATA_WARM, DataTier.DATA_CONTENT, DataTier.DATA_COLD)
196+
);
197+
{
198+
var req = new UpdateSecuritySettingsAction.Request(
199+
TEST_REQUEST_TIMEOUT,
200+
TEST_REQUEST_TIMEOUT,
201+
allowedSettingMap,
202+
Collections.emptyMap(),
203+
Collections.emptyMap()
204+
);
205+
assertThat(req.validate(), nullValue());
206+
}
207+
208+
{
209+
var req = new UpdateSecuritySettingsAction.Request(
210+
TEST_REQUEST_TIMEOUT,
211+
TEST_REQUEST_TIMEOUT,
212+
forbiddenSettingsMap,
213+
Collections.emptyMap(),
214+
Collections.emptyMap()
215+
);
216+
ActionRequestValidationException exception = req.validate();
217+
assertThat(exception, notNullValue());
218+
assertThat(exception.validationErrors(), hasSize(1));
219+
assertThat(
220+
exception.validationErrors().get(0),
221+
containsString("disallowed data tiers [" + DataTier.DATA_FROZEN + "] found, allowed tiers are ")
222+
);
223+
}
224+
225+
{
226+
var req = new UpdateSecuritySettingsAction.Request(
227+
TEST_REQUEST_TIMEOUT,
228+
TEST_REQUEST_TIMEOUT,
229+
badSettingsMap,
230+
Collections.emptyMap(),
231+
Collections.emptyMap()
232+
);
233+
var exception = req.validate();
234+
assertThat(exception, notNullValue());
235+
assertThat(exception.validationErrors(), hasSize(1));
236+
assertThat(
237+
exception.validationErrors().get(0),
238+
containsString("disallowed data tiers [" + badTier + "] found, allowed tiers are ")
239+
);
240+
}
241+
}
174242
}

x-pack/plugin/security/qa/security-basic/src/javaRestTest/java/org/elasticsearch/xpack/security/SecuritySettingsIT.java

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static org.elasticsearch.test.XContentTestUtils.createJsonMapView;
2020
import static org.hamcrest.Matchers.containsString;
2121
import static org.hamcrest.Matchers.equalTo;
22+
import static org.hamcrest.Matchers.nullValue;
2223

2324
public class SecuritySettingsIT extends SecurityInBasicRestTestCase {
2425

@@ -70,6 +71,54 @@ public void testBasicWorkflow() throws IOException {
7071
assertOK(getResp);
7172
final XContentTestUtils.JsonMapView mapView = createJsonMapView(getResp.getEntity().getContent());
7273
assertThat(mapView.get("security.index.auto_expand_replicas"), equalTo("0-all"));
74+
assertThat(mapView.get("security-profile.index.auto_expand_replicas"), equalTo("0-all"));
75+
}
76+
77+
public void testTierPreference() throws IOException {
78+
{
79+
Request req = new Request("PUT", "/_security/settings");
80+
req.setJsonEntity("""
81+
{
82+
"security": {
83+
"index.routing.allocation.include._tier_preference": "data_hot"
84+
},
85+
"security-profile": {
86+
"index.routing.allocation.include._tier_preference": "data_hot"
87+
}
88+
}
89+
""");
90+
Response resp = adminClient().performRequest(req);
91+
assertOK(resp);
92+
Request getRequest = new Request("GET", "/_security/settings");
93+
Response getResp = adminClient().performRequest(getRequest);
94+
assertOK(getResp);
95+
final XContentTestUtils.JsonMapView mapView = createJsonMapView(getResp.getEntity().getContent());
96+
assertThat(mapView.get("security.index.routing.allocation.include._tier_preference"), equalTo("data_hot"));
97+
assertThat(mapView.get("security-profile.index.routing.allocation.include._tier_preference"), equalTo("data_hot"));
98+
}
99+
100+
{
101+
Request req = new Request("PUT", "/_security/settings");
102+
req.setJsonEntity("""
103+
{
104+
"security": {
105+
"index.routing.allocation.include._tier_preference": null
106+
},
107+
"security-profile": {
108+
"index.routing.allocation.include._tier_preference": null
109+
}
110+
}
111+
""");
112+
Response resp = adminClient().performRequest(req);
113+
assertOK(resp);
114+
Request getRequest = new Request("GET", "/_security/settings");
115+
Response getResp = adminClient().performRequest(getRequest);
116+
assertOK(getResp);
117+
final XContentTestUtils.JsonMapView mapView = createJsonMapView(getResp.getEntity().getContent());
118+
assertThat(mapView.get("security.index.routing.allocation.include._tier_preference"), nullValue());
119+
assertThat(mapView.get("security-profile.index.routing.allocation.include._tier_preference"), nullValue());
120+
}
121+
73122
}
74123

75124
public void testNoUpdatesThrowsException() throws IOException {
@@ -85,7 +134,7 @@ public void testDisallowedSettingThrowsException() throws IOException {
85134
ResponseException ex = expectThrows(ResponseException.class, () -> adminClient().performRequest(req));
86135
assertThat(
87136
EntityUtils.toString(ex.getResponse().getEntity()),
88-
containsString("illegal settings for index [security]: " + "[index.max_ngram_diff], these settings may not be configured.")
137+
containsString("illegal setting for index [security]: " + "[index.max_ngram_diff], this setting may not be configured.")
89138
);
90139
}
91140

x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/profile/ProfileIntegTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public void testProfileIndexAutoCreation() {
133133
final Settings settings = getIndexResponse.getSettings().get(INTERNAL_SECURITY_PROFILE_INDEX_8);
134134
assertThat(settings.get("index.number_of_shards"), equalTo("1"));
135135
assertThat(settings.get("index.auto_expand_replicas"), equalTo("0-1"));
136-
assertThat(settings.get("index.routing.allocation.include._tier_preference"), equalTo("data_content"));
136+
assertThat(settings.get("index.routing.allocation.include._tier_preference"), equalTo("data_hot,data_content"));
137137

138138
final Map<String, Object> mappings = getIndexResponse.getMappings().get(INTERNAL_SECURITY_PROFILE_INDEX_8).getSourceAsMap();
139139

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/settings/TransportGetSecuritySettingsAction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ private static Settings getFilteredSettingsForIndex(String indexName, ClusterSta
8585
.map(IndexMetadata::getSettings)
8686
.map(settings -> {
8787
Settings.Builder builder = Settings.builder();
88-
for (String settingName : UpdateSecuritySettingsAction.ALLOWED_SETTING_KEYS) {
88+
for (String settingName : UpdateSecuritySettingsAction.ALLOWED_SETTING_VALIDATORS.keySet()) {
8989
if (settings.hasValue(settingName)) {
9090
builder.put(settingName, settings.get(settingName));
9191
}

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecuritySystemIndices.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.elasticsearch.client.internal.Client;
1414
import org.elasticsearch.cluster.metadata.IndexMetadata;
1515
import org.elasticsearch.cluster.node.DiscoveryNode;
16+
import org.elasticsearch.cluster.routing.allocation.DataTier;
1617
import org.elasticsearch.cluster.service.ClusterService;
1718
import org.elasticsearch.common.VersionId;
1819
import org.elasticsearch.common.settings.Settings;
@@ -154,6 +155,7 @@ private static Settings getMainIndexSettings() {
154155
return Settings.builder()
155156
.put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
156157
.put(IndexMetadata.SETTING_AUTO_EXPAND_REPLICAS, "0-1")
158+
.put(DataTier.TIER_PREFERENCE, "data_hot,data_content")
157159
.put(IndexMetadata.SETTING_PRIORITY, 1000)
158160
.put(IndexMetadata.INDEX_FORMAT_SETTING.getKey(), INTERNAL_MAIN_INDEX_FORMAT)
159161
.put("analysis.filter.email.type", "pattern_capture")
@@ -702,6 +704,7 @@ private static Settings getTokenIndexSettings() {
702704
return Settings.builder()
703705
.put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
704706
.put(IndexMetadata.SETTING_AUTO_EXPAND_REPLICAS, "0-1")
707+
.put(DataTier.TIER_PREFERENCE, "data_hot,data_content")
705708
.put(IndexMetadata.SETTING_PRIORITY, 1000)
706709
.put(IndexMetadata.INDEX_FORMAT_SETTING.getKey(), INTERNAL_TOKENS_INDEX_FORMAT)
707710
.build();
@@ -902,6 +905,7 @@ private static Settings getProfileIndexSettings(Settings settings) {
902905
final Settings.Builder settingsBuilder = Settings.builder()
903906
.put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
904907
.put(IndexMetadata.SETTING_AUTO_EXPAND_REPLICAS, "0-1")
908+
.put(DataTier.TIER_PREFERENCE, "data_hot,data_content")
905909
.put(IndexMetadata.SETTING_PRIORITY, 1000)
906910
.put(IndexMetadata.INDEX_FORMAT_SETTING.getKey(), INTERNAL_PROFILE_INDEX_FORMAT)
907911
.put("analysis.filter.email.type", "pattern_capture")

0 commit comments

Comments
 (0)