Skip to content

Commit 6e9da22

Browse files
committed
Validate V2 templates more strictly
This commit changes the validation for V2 index and component templates to re-use the same validation that V1 templates used. This includes things like invalid template names, index patterns, etc. This also adds validation that template names do not contain `*`. Supercedes elastic#53970 Relates to elastic#53101
1 parent 022d3d7 commit 6e9da22

File tree

5 files changed

+100
-41
lines changed

5 files changed

+100
-41
lines changed

server/src/main/java/org/elasticsearch/action/admin/indices/template/post/TransportSimulateIndexTemplateAction.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import java.util.Collections;
5858
import java.util.HashMap;
5959
import java.util.List;
60+
import java.util.Locale;
6061
import java.util.Map;
6162
import java.util.Set;
6263
import java.util.function.Function;
@@ -106,7 +107,7 @@ protected void masterOperation(Task task, SimulateIndexTemplateRequest request,
106107
ClusterState simulateOnClusterState = state;
107108
if (request.getIndexTemplateRequest() != null) {
108109
// we'll "locally" add the template defined by the user in the cluster state (as if it existed in the system)
109-
String simulateTemplateToAdd = "simulate_new_template_" + UUIDs.randomBase64UUID();
110+
String simulateTemplateToAdd = "simulate_new_template_" + UUIDs.randomBase64UUID().toLowerCase(Locale.ROOT);
110111
simulateOnClusterState = indexTemplateService.addIndexTemplateV2(state, request.getIndexTemplateRequest().create(),
111112
simulateTemplateToAdd, request.getIndexTemplateRequest().indexTemplate());
112113
}

server/src/main/java/org/elasticsearch/cluster/metadata/MetadataCreateIndexService.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -981,7 +981,7 @@ private static List<String> validatePrivateSettingsNotExplicitlySet(Settings set
981981
for (final String key : settings.keySet()) {
982982
final Setting<?> setting = indexScopedSettings.get(key);
983983
if (setting == null) {
984-
assert indexScopedSettings.isPrivateSetting(key);
984+
assert indexScopedSettings.isPrivateSetting(key) : "expected [" + key + "] to be private but it was not";
985985
} else if (setting.isPrivateIndex()) {
986986
validationErrors.add("private index setting [" + key + "] can not be set explicitly");
987987
}

server/src/main/java/org/elasticsearch/cluster/metadata/MetadataIndexTemplateService.java

+88-30
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ ClusterState addComponentTemplate(final ClusterState currentState, final boolean
233233
final Template finalTemplate = new Template(finalSettings,
234234
stringMappings == null ? null : new CompressedXContent(stringMappings), template.template().aliases());
235235
final ComponentTemplate finalComponentTemplate = new ComponentTemplate(finalTemplate, template.version(), template.metadata());
236+
validate(name, finalComponentTemplate);
236237
logger.info("adding component template [{}]", name);
237238
return ClusterState.builder(currentState)
238239
.metadata(Metadata.builder(currentState.metadata()).put(name, finalComponentTemplate))
@@ -402,6 +403,7 @@ public ClusterState addIndexTemplateV2(final ClusterState currentState, final bo
402403
template.priority(), template.version(), template.metadata());
403404
}
404405

406+
validate(name, finalIndexTemplate);
405407
logger.info("adding index template [{}]", name);
406408
return ClusterState.builder(currentState)
407409
.metadata(Metadata.builder(currentState.metadata()).put(name, finalIndexTemplate))
@@ -922,70 +924,126 @@ private static void validateTemplate(Settings validateSettings, String mappings,
922924
}
923925
}
924926

925-
private void validate(PutRequest request) {
927+
private void validate(String name, ComponentTemplate template) {
928+
validate(name,
929+
template.template().settings(),
930+
Collections.emptyList(),
931+
Optional.ofNullable(template.template().aliases())
932+
.map(aliases -> aliases.values().stream()
933+
.map(aliasMeta -> {
934+
Alias a = new Alias(aliasMeta.alias());
935+
if (aliasMeta.filter() != null) {
936+
a.filter(aliasMeta.filter().string());
937+
}
938+
a.searchRouting(aliasMeta.searchRouting());
939+
a.indexRouting(aliasMeta.indexRouting());
940+
a.isHidden(aliasMeta.isHidden());
941+
a.writeIndex(aliasMeta.writeIndex());
942+
return a;
943+
})
944+
.collect(Collectors.toList()))
945+
.orElse(Collections.emptyList()));
946+
}
947+
948+
private void validate(String name, IndexTemplateV2 template) {
949+
Optional<Template> maybeTemplate = Optional.ofNullable(template.template());
950+
validate(name,
951+
maybeTemplate.map(Template::settings).orElse(Settings.EMPTY),
952+
template.indexPatterns(),
953+
maybeTemplate
954+
.map(Template::aliases)
955+
.map(aliasMap -> aliasMap.values().stream()
956+
.map(aliasMeta -> {
957+
Alias a = new Alias(aliasMeta.alias());
958+
if (aliasMeta.filter() != null) {
959+
a.filter(aliasMeta.filter().string());
960+
}
961+
a.searchRouting(aliasMeta.searchRouting());
962+
a.indexRouting(aliasMeta.indexRouting());
963+
a.isHidden(aliasMeta.isHidden());
964+
a.writeIndex(aliasMeta.writeIndex());
965+
return a;
966+
})
967+
.collect(Collectors.toList()))
968+
.orElse(Collections.emptyList()));
969+
}
970+
971+
private void validate(PutRequest putRequest) {
972+
validate(putRequest.name, putRequest.settings, putRequest.indexPatterns, putRequest.aliases);
973+
}
974+
975+
private void validate(String name, @Nullable Settings settings, List<String> indexPatterns, List<Alias> aliases) {
926976
List<String> validationErrors = new ArrayList<>();
927-
if (request.name.contains(" ")) {
977+
if (name.contains(" ")) {
928978
validationErrors.add("name must not contain a space");
929979
}
930-
if (request.name.contains(",")) {
980+
if (name.contains(",")) {
931981
validationErrors.add("name must not contain a ','");
932982
}
933-
if (request.name.contains("#")) {
983+
if (name.contains("#")) {
934984
validationErrors.add("name must not contain a '#'");
935985
}
936-
if (request.name.startsWith("_")) {
986+
if (name.contains("*")) {
987+
validationErrors.add("name must not contain a '*'");
988+
}
989+
if (name.startsWith("_")) {
937990
validationErrors.add("name must not start with '_'");
938991
}
939-
if (!request.name.toLowerCase(Locale.ROOT).equals(request.name)) {
992+
if (name.toLowerCase(Locale.ROOT).equals(name) == false) {
940993
validationErrors.add("name must be lower cased");
941994
}
942-
for(String indexPattern : request.indexPatterns) {
995+
for(String indexPattern : indexPatterns) {
943996
if (indexPattern.contains(" ")) {
944-
validationErrors.add("template must not contain a space");
997+
validationErrors.add("index_patterns [" + indexPattern + "] must not contain a space");
945998
}
946999
if (indexPattern.contains(",")) {
947-
validationErrors.add("template must not contain a ','");
1000+
validationErrors.add("index_pattern [" + indexPattern + "] must not contain a ','");
9481001
}
9491002
if (indexPattern.contains("#")) {
950-
validationErrors.add("template must not contain a '#'");
1003+
validationErrors.add("index_pattern [" + indexPattern + "] must not contain a '#'");
9511004
}
9521005
if (indexPattern.startsWith("_")) {
953-
validationErrors.add("template must not start with '_'");
1006+
validationErrors.add("index_pattern [" + indexPattern + "] must not start with '_'");
9541007
}
955-
if (!Strings.validFileNameExcludingAstrix(indexPattern)) {
956-
validationErrors.add("template must not contain the following characters " + Strings.INVALID_FILENAME_CHARS);
1008+
if (Strings.validFileNameExcludingAstrix(indexPattern) == false) {
1009+
validationErrors.add("index_pattern [" + indexPattern + "] must not contain the following characters " +
1010+
Strings.INVALID_FILENAME_CHARS);
9571011
}
9581012
}
9591013

960-
try {
961-
indexScopedSettings.validate(request.settings, true); // templates must be consistent with regards to dependencies
962-
} catch (IllegalArgumentException iae) {
963-
validationErrors.add(iae.getMessage());
964-
for (Throwable t : iae.getSuppressed()) {
965-
validationErrors.add(t.getMessage());
1014+
1015+
if (settings != null) {
1016+
try {
1017+
// templates must be consistent with regards to dependencies
1018+
indexScopedSettings.validate(settings, true);
1019+
} catch (IllegalArgumentException iae) {
1020+
validationErrors.add(iae.getMessage());
1021+
for (Throwable t : iae.getSuppressed()) {
1022+
validationErrors.add(t.getMessage());
1023+
}
9661024
}
1025+
List<String> indexSettingsValidation = metadataCreateIndexService.getIndexSettingsValidationErrors(settings, true);
1026+
validationErrors.addAll(indexSettingsValidation);
9671027
}
968-
List<String> indexSettingsValidation = metadataCreateIndexService.getIndexSettingsValidationErrors(request.settings, true);
969-
validationErrors.addAll(indexSettingsValidation);
9701028

971-
if (request.indexPatterns.stream().anyMatch(Regex::isMatchAllPattern)) {
972-
if (IndexMetadata.INDEX_HIDDEN_SETTING.exists(request.settings)) {
1029+
if (indexPatterns.stream().anyMatch(Regex::isMatchAllPattern)) {
1030+
if (settings != null && IndexMetadata.INDEX_HIDDEN_SETTING.exists(settings)) {
9731031
validationErrors.add("global templates may not specify the setting " + IndexMetadata.INDEX_HIDDEN_SETTING.getKey());
9741032
}
9751033
}
9761034

977-
if (!validationErrors.isEmpty()) {
1035+
if (validationErrors.size() > 0) {
9781036
ValidationException validationException = new ValidationException();
9791037
validationException.addValidationErrors(validationErrors);
980-
throw new InvalidIndexTemplateException(request.name, validationException.getMessage());
1038+
throw new InvalidIndexTemplateException(name, validationException.getMessage());
9811039
}
9821040

983-
for (Alias alias : request.aliases) {
984-
//we validate the alias only partially, as we don't know yet to which index it'll get applied to
1041+
for (Alias alias : aliases) {
1042+
// we validate the alias only partially, as we don't know yet to which index it'll get applied to
9851043
aliasValidator.validateAliasStandalone(alias);
986-
if (request.indexPatterns.contains(alias.name())) {
987-
throw new IllegalArgumentException("Alias [" + alias.name() +
988-
"] cannot be the same as any pattern in [" + String.join(", ", request.indexPatterns) + "]");
1044+
if (indexPatterns.contains(alias.name())) {
1045+
throw new IllegalArgumentException("alias [" + alias.name() +
1046+
"] cannot be the same as any pattern in [" + String.join(", ", indexPatterns) + "]");
9891047
}
9901048
}
9911049
}

server/src/test/java/org/elasticsearch/cluster/metadata/MetadataIndexTemplateServiceTests.java

+8-8
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ public void testIndexTemplateValidationAccumulatesValidationErrors() {
146146
assertEquals(throwables.size(), 1);
147147
assertThat(throwables.get(0), instanceOf(InvalidIndexTemplateException.class));
148148
assertThat(throwables.get(0).getMessage(), containsString("name must not contain a space"));
149-
assertThat(throwables.get(0).getMessage(), containsString("template must not start with '_'"));
149+
assertThat(throwables.get(0).getMessage(), containsString("index_pattern [_test_shards*] must not start with '_'"));
150150
assertThat(throwables.get(0).getMessage(),
151151
containsString("Failed to parse value [0] for setting [index.number_of_shards] must be >= 1"));
152152
}
@@ -159,7 +159,7 @@ public void testIndexTemplateWithAliasNameEqualToTemplatePattern() {
159159
List<Throwable> errors = putTemplate(xContentRegistry(), request);
160160
assertThat(errors.size(), equalTo(1));
161161
assertThat(errors.get(0), instanceOf(IllegalArgumentException.class));
162-
assertThat(errors.get(0).getMessage(), equalTo("Alias [foobar] cannot be the same as any pattern in [foo, foobar]"));
162+
assertThat(errors.get(0).getMessage(), equalTo("alias [foobar] cannot be the same as any pattern in [foo, foobar]"));
163163
}
164164

165165
public void testIndexTemplateWithValidateMapping() throws Exception {
@@ -309,15 +309,15 @@ public void testUpdateComponentTemplateWithIndexHiddenSetting() throws Exception
309309

310310
IndexTemplateV2 firstGlobalIndexTemplate =
311311
new IndexTemplateV2(List.of("*"), template, List.of("foo"), 1L, null, null);
312-
state = metadataIndexTemplateService.addIndexTemplateV2(state, true, "globalIndexTemplate1", firstGlobalIndexTemplate);
312+
state = metadataIndexTemplateService.addIndexTemplateV2(state, true, "globalindextemplate1", firstGlobalIndexTemplate);
313313

314314
IndexTemplateV2 secondGlobalIndexTemplate =
315315
new IndexTemplateV2(List.of("*"), template, List.of("foo"), 2L, null, null);
316-
state = metadataIndexTemplateService.addIndexTemplateV2(state, true, "globalIndexTemplate2", secondGlobalIndexTemplate);
316+
state = metadataIndexTemplateService.addIndexTemplateV2(state, true, "globalindextemplate2", secondGlobalIndexTemplate);
317317

318318
IndexTemplateV2 fooPatternIndexTemplate =
319319
new IndexTemplateV2(List.of("foo-*"), template, List.of("foo"), 3L, null, null);
320-
state = metadataIndexTemplateService.addIndexTemplateV2(state, true, "fooPatternIndexTemplate", fooPatternIndexTemplate);
320+
state = metadataIndexTemplateService.addIndexTemplateV2(state, true, "foopatternindextemplate", fooPatternIndexTemplate);
321321

322322
// update the component template to set the index.hidden setting
323323
Template templateWithIndexHiddenSetting = new Template(Settings.builder().put(IndexMetadata.SETTING_INDEX_HIDDEN, true).build(),
@@ -328,9 +328,9 @@ public void testUpdateComponentTemplateWithIndexHiddenSetting() throws Exception
328328
fail("expecting an exception as updating the component template would yield the global templates to include the index.hidden " +
329329
"setting");
330330
} catch (IllegalArgumentException e) {
331-
assertThat(e.getMessage(), containsStringIgnoringCase("globalIndexTemplate1"));
332-
assertThat(e.getMessage(), containsStringIgnoringCase("globalIndexTemplate2"));
333-
assertThat(e.getMessage(), not(containsStringIgnoringCase("fooPatternIndexTemplate")));
331+
assertThat(e.getMessage(), containsStringIgnoringCase("globalindextemplate1"));
332+
assertThat(e.getMessage(), containsStringIgnoringCase("globalindextemplate2"));
333+
assertThat(e.getMessage(), not(containsStringIgnoringCase("foopatternindextemplate")));
334334
}
335335
}
336336

server/src/test/java/org/elasticsearch/cluster/shards/ClusterShardLimitIT.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ public void testIndexCreationOverLimitFromTemplate() {
121121

122122
assertAcked(client().admin()
123123
.indices()
124-
.preparePutTemplate("should-fail*")
124+
.preparePutTemplate("should-fail")
125125
.setPatterns(Collections.singletonList("should-fail"))
126126
.setOrder(1)
127127
.setSettings(Settings.builder()

0 commit comments

Comments
 (0)