[8.x] Reconcile differences between Stateful and Stateless dot-prefix validation (#114946) (#115057)

* Reconcile differences between Stateful and Stateless dot-prefix validation (#114946)

This commit makes the dot prefix deprecation match the existing changes to validation for the SLO
and SLA UIs.

Relates to #112571

(cherry picked from commit 5bf446ea2e)

* Update not to use getFirst

---------

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Lee Hinman 2024-10-21 13:25:05 -06:00 committed by GitHub
parent 8678c6d6da
commit e9072e9bc4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 137 additions and 4 deletions

View file

@ -56,6 +56,7 @@ public abstract class DotPrefixValidator<RequestType> implements MappedActionFil
*
* .elastic-connectors-* is used by enterprise search
* .ml-* is used by ML
* .slo-observability-* is used by Observability
*/
private static Set<String> IGNORED_INDEX_NAMES = Set.of(
".elastic-connectors-v1",
@ -63,7 +64,11 @@ public abstract class DotPrefixValidator<RequestType> implements MappedActionFil
".ml-state",
".ml-anomalies-unrelated"
);
private static Set<Pattern> IGNORED_INDEX_PATTERNS = Set.of(Pattern.compile("\\.ml-state-\\d+"));
private static Set<Pattern> IGNORED_INDEX_PATTERNS = Set.of(
Pattern.compile("\\.ml-state-\\d+"),
Pattern.compile("\\.slo-observability\\.sli-v\\d+.*"),
Pattern.compile("\\.slo-observability\\.summary-v\\d+.*")
);
DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(DotPrefixValidator.class);
@ -99,10 +104,11 @@ public abstract class DotPrefixValidator<RequestType> implements MappedActionFil
if (Strings.hasLength(index)) {
char c = getFirstChar(index);
if (c == '.') {
if (IGNORED_INDEX_NAMES.contains(index)) {
final String strippedName = stripDateMath(index);
if (IGNORED_INDEX_NAMES.contains(strippedName)) {
return;
}
if (IGNORED_INDEX_PATTERNS.stream().anyMatch(p -> p.matcher(index).matches())) {
if (IGNORED_INDEX_PATTERNS.stream().anyMatch(p -> p.matcher(strippedName).matches())) {
return;
}
deprecationLogger.warn(
@ -132,7 +138,18 @@ public abstract class DotPrefixValidator<RequestType> implements MappedActionFil
return c;
}
private boolean isInternalRequest() {
private static String stripDateMath(String index) {
char c = index.charAt(0);
if (c == '<') {
assert index.charAt(index.length() - 1) == '>'
: "expected index name with date math to start with < and end with >, how did this pass request validation? " + index;
return index.substring(1, index.length() - 1);
} else {
return index;
}
}
boolean isInternalRequest() {
final String actionOrigin = threadContext.getTransient(ThreadContext.ACTION_ORIGIN_TRANSIENT_NAME);
final boolean isSystemContext = threadContext.isSystemContext();
final boolean isInternalOrigin = Optional.ofNullable(actionOrigin).map(Strings::hasText).orElse(false);

View file

@ -0,0 +1,116 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
package org.elasticsearch.validation;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.junit.BeforeClass;
import java.util.HashSet;
import java.util.Set;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class DotPrefixValidatorTests extends ESTestCase {
private final OperatorValidator<?> opV = new OperatorValidator<>();
private final NonOperatorValidator<?> nonOpV = new NonOperatorValidator<>();
private static final Set<Setting<?>> settings;
private static ClusterService clusterService;
private static ClusterSettings clusterSettings;
static {
Set<Setting<?>> cSettings = new HashSet<>(ClusterSettings.BUILT_IN_CLUSTER_SETTINGS);
cSettings.add(DotPrefixValidator.VALIDATE_DOT_PREFIXES);
settings = cSettings;
}
@BeforeClass
public static void beforeClass() {
clusterService = mock(ClusterService.class);
clusterSettings = new ClusterSettings(Settings.EMPTY, Sets.newHashSet(DotPrefixValidator.VALIDATE_DOT_PREFIXES));
when(clusterService.getClusterSettings()).thenReturn(clusterSettings);
when(clusterService.getSettings()).thenReturn(Settings.EMPTY);
when(clusterService.threadPool()).thenReturn(mock(ThreadPool.class));
}
public void testValidation() {
nonOpV.validateIndices(Set.of("regular"));
opV.validateIndices(Set.of("regular"));
assertFails(Set.of(".regular"));
opV.validateIndices(Set.of(".regular"));
assertFails(Set.of("first", ".second"));
assertFails(Set.of("<.regular-{MM-yy-dd}>"));
// Test ignored names
nonOpV.validateIndices(Set.of(".elastic-connectors-v1"));
nonOpV.validateIndices(Set.of(".elastic-connectors-sync-jobs-v1"));
nonOpV.validateIndices(Set.of(".ml-state"));
nonOpV.validateIndices(Set.of(".ml-anomalies-unrelated"));
// Test ignored patterns
nonOpV.validateIndices(Set.of(".ml-state-21309"));
nonOpV.validateIndices(Set.of(">.ml-state-21309>"));
nonOpV.validateIndices(Set.of(".slo-observability.sli-v2"));
nonOpV.validateIndices(Set.of(".slo-observability.sli-v2.3"));
nonOpV.validateIndices(Set.of(".slo-observability.sli-v2.3-2024-01-01"));
nonOpV.validateIndices(Set.of("<.slo-observability.sli-v3.3.{2024-10-16||/M{yyyy-MM-dd|UTC}}>"));
nonOpV.validateIndices(Set.of(".slo-observability.summary-v2"));
nonOpV.validateIndices(Set.of(".slo-observability.summary-v2.3"));
nonOpV.validateIndices(Set.of(".slo-observability.summary-v2.3-2024-01-01"));
nonOpV.validateIndices(Set.of("<.slo-observability.summary-v3.3.{2024-10-16||/M{yyyy-MM-dd|UTC}}>"));
}
private void assertFails(Set<String> indices) {
nonOpV.validateIndices(indices);
assertWarnings(
"Index ["
+ indices.stream().filter(i -> i.startsWith(".") || i.startsWith("<.")).toList().get(0)
+ "] name begins with a dot (.), which is deprecated, and will not be allowed in a future Elasticsearch version."
);
}
private class NonOperatorValidator<R> extends DotPrefixValidator<R> {
private NonOperatorValidator() {
super(new ThreadContext(Settings.EMPTY), clusterService);
}
@Override
protected Set<String> getIndicesFromRequest(Object request) {
return Set.of();
}
@Override
public String actionName() {
return "";
}
@Override
boolean isInternalRequest() {
return false;
}
}
private class OperatorValidator<R> extends NonOperatorValidator<R> {
@Override
boolean isInternalRequest() {
return true;
}
}
}