Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
package org.elasticsearch.example;

import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.IndexComponentSelector;
import org.elasticsearch.cluster.metadata.IndexAbstraction;
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesResponse;
Expand All @@ -34,7 +35,6 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Supplier;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
Expand Down Expand Up @@ -117,19 +117,19 @@ public void loadAuthorizedIndices(
) {
if (isSuperuser(requestInfo.getAuthentication().getEffectiveSubject().getUser())) {
listener.onResponse(new AuthorizedIndices() {
public Supplier<Set<String>> all() {
public Set<String> all(IndexComponentSelector selector) {
return () -> indicesLookup.keySet();
}
public boolean check(String name) {
public boolean check(String name, IndexComponentSelector selector) {
return indicesLookup.containsKey(name);
}
});
} else {
listener.onResponse(new AuthorizedIndices() {
public Supplier<Set<String>> all() {
public Set<String> all(IndexComponentSelector selector) {
return () -> Set.of();
}
public boolean check(String name) {
public boolean check(String name, IndexComponentSelector selector) {
return false;
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,24 @@ public static IndexComponentSelector getByKey(String key) {
return KEY_REGISTRY.get(key);
}

/**
* Like {@link #getByKey(String)} but throws an exception if the key is not recognised.
* @return the selector if recognized. `null` input will return `DATA`.
* @throws IllegalArgumentException if the key was not recognised.
*/
public static IndexComponentSelector getByKeyOrThrow(@Nullable String key) {
if (key == null) {
return DATA;
}
IndexComponentSelector selector = getByKey(key);
if (selector == null) {
throw new IllegalArgumentException(
"Unknown key of index component selector [" + key + "], available options are: " + KEY_REGISTRY.keySet()
);
}
return selector;
}

public static IndexComponentSelector read(StreamInput in) throws IOException {
byte id = in.readByte();
if (in.getTransportVersion().onOrAfter(TransportVersions.REMOVE_ALL_APPLICABLE_SELECTOR_BACKPORT_8_19)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ default boolean isDataStreamRelated() {
return false;
}

/**
* @return whether this index abstraction is a failure index of a data stream
*/
default boolean isFailureIndexOfDataStream() {
return false;
}

/**
* An index abstraction type.
*/
Expand Down Expand Up @@ -183,6 +190,11 @@ public DataStream getParentDataStream() {
return dataStream;
}

@Override
public boolean isFailureIndexOfDataStream() {
return getParentDataStream() != null && getParentDataStream().isFailureStoreIndex(getName());
}

@Override
public boolean isHidden() {
return isHidden;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.BiPredicate;
import java.util.function.Function;

public class IndexAbstractionResolver {

Expand All @@ -37,8 +37,8 @@ public List<String> resolveIndexAbstractions(
Iterable<String> indices,
IndicesOptions indicesOptions,
Metadata metadata,
Supplier<Set<String>> allAuthorizedAndAvailable,
Predicate<String> isAuthorized,
Function<IndexComponentSelector, Set<String>> allAuthorizedAndAvailableBySelector,
BiPredicate<String, IndexComponentSelector> isAuthorized,
boolean includeDataStreams
) {
List<String> finalIndices = new ArrayList<>();
Expand All @@ -64,14 +64,15 @@ public List<String> resolveIndexAbstractions(
);
}
indexAbstraction = expressionAndSelector.v1();
IndexComponentSelector selector = IndexComponentSelector.getByKeyOrThrow(selectorString);

// we always need to check for date math expressions
indexAbstraction = IndexNameExpressionResolver.resolveDateMathExpression(indexAbstraction);

if (indicesOptions.expandWildcardExpressions() && Regex.isSimpleMatchPattern(indexAbstraction)) {
wildcardSeen = true;
Set<String> resolvedIndices = new HashSet<>();
for (String authorizedIndex : allAuthorizedAndAvailable.get()) {
for (String authorizedIndex : allAuthorizedAndAvailableBySelector.apply(selector)) {
if (Regex.simpleMatch(indexAbstraction, authorizedIndex)
&& isIndexVisible(
indexAbstraction,
Expand Down Expand Up @@ -102,7 +103,7 @@ && isIndexVisible(
resolveSelectorsAndCollect(indexAbstraction, selectorString, indicesOptions, resolvedIndices, metadata);
if (minus) {
finalIndices.removeAll(resolvedIndices);
} else if (indicesOptions.ignoreUnavailable() == false || isAuthorized.test(indexAbstraction)) {
} else if (indicesOptions.ignoreUnavailable() == false || isAuthorized.test(indexAbstraction, selector)) {
// Unauthorized names are considered unavailable, so if `ignoreUnavailable` is `true` they should be silently
// discarded from the `finalIndices` list. Other "ways of unavailable" must be handled by the action
// handler, see: https://github.com/elastic/elasticsearch/issues/90215
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.elasticsearch.common.util.CollectionUtils;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.core.Assertions;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.core.Predicates;
import org.elasticsearch.core.Tuple;
Expand Down Expand Up @@ -831,6 +832,14 @@ public static boolean hasSelectorSuffix(String expression) {
return expression.contains(SelectorResolver.SELECTOR_SEPARATOR);
}

public static boolean hasSelector(@Nullable String expression, IndexComponentSelector selector) {
Objects.requireNonNull(selector, "null selectors not supported");
if (expression == null) {
return false;
}
return expression.endsWith(SelectorResolver.SELECTOR_SEPARATOR + selector.getKey());
}

/**
* @return If the specified string is a selector expression then this method returns the base expression and its selector part.
*/
Expand All @@ -852,6 +861,14 @@ public static String combineSelectorExpression(String baseExpression, @Nullable
: (baseExpression + SelectorResolver.SELECTOR_SEPARATOR + selectorExpression);
}

public static void assertExpressionHasNullOrDataSelector(String expression) {
if (Assertions.ENABLED) {
var tuple = splitSelectorExpression(expression);
assert tuple.v2() == null || IndexComponentSelector.DATA.getKey().equals(tuple.v2())
: "Expected expression [" + expression + "] to have a data selector but found [" + tuple.v2() + "]";
}
}

/**
* Resolve an array of expressions to the set of indices and aliases that these expressions match.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import static org.elasticsearch.index.mapper.MapperService.SINGLE_MAPPING_NAME;
import static org.elasticsearch.indices.SystemIndices.EXTERNAL_SYSTEM_INDEX_ACCESS_CONTROL_HEADER_KEY;
Expand All @@ -47,7 +46,7 @@ public class IndexAbstractionResolverTests extends ESTestCase {
private String dateTimeIndexTomorrow;

// Only used when resolving wildcard expressions
private final Supplier<Set<String>> defaultMask = () -> Set.of("index1", "index2", "data-stream1");
private final Set<String> defaultMask = Set.of("index1", "index2", "data-stream1");

@Override
public void setUp() throws Exception {
Expand Down Expand Up @@ -208,13 +207,11 @@ public void testResolveIndexAbstractions() {

public void testIsIndexVisible() {
assertThat(isIndexVisible("index1", null), is(true));
assertThat(isIndexVisible("index1", "*"), is(true));
assertThat(isIndexVisible("index1", "data"), is(true));
assertThat(isIndexVisible("index1", "failures"), is(false)); // *
// * Indices don't have failure components so the failure component is not visible

assertThat(isIndexVisible("data-stream1", null), is(true));
assertThat(isIndexVisible("data-stream1", "*"), is(true));
assertThat(isIndexVisible("data-stream1", "data"), is(true));
assertThat(isIndexVisible("data-stream1", "failures"), is(true));
}
Expand Down Expand Up @@ -281,14 +278,14 @@ public void testIsNetNewSystemIndexVisible() {
indexAbstractionResolver = new IndexAbstractionResolver(indexNameExpressionResolver);

// this covers the GET * case -- with system access, you can see everything
assertThat(isIndexVisible("other", "*"), is(true));
assertThat(isIndexVisible(".foo", "*"), is(true));
assertThat(isIndexVisible(".bar", "*"), is(true));
assertThat(isIndexVisible("other", null), is(true));
assertThat(isIndexVisible(".foo", null), is(true));
assertThat(isIndexVisible(".bar", null), is(true));

// but if you don't ask for hidden and aliases, you won't see hidden indices or aliases, naturally
assertThat(isIndexVisible("other", "*", noHiddenNoAliases), is(true));
assertThat(isIndexVisible(".foo", "*", noHiddenNoAliases), is(false));
assertThat(isIndexVisible(".bar", "*", noHiddenNoAliases), is(false));
assertThat(isIndexVisible("other", null, noHiddenNoAliases), is(true));
assertThat(isIndexVisible(".foo", null, noHiddenNoAliases), is(false));
assertThat(isIndexVisible(".bar", null, noHiddenNoAliases), is(false));
}

{
Expand All @@ -298,14 +295,14 @@ public void testIsNetNewSystemIndexVisible() {
indexAbstractionResolver = new IndexAbstractionResolver(indexNameExpressionResolver);

// this covers the GET * case -- without system access, you can't see everything
assertThat(isIndexVisible("other", "*"), is(true));
assertThat(isIndexVisible(".foo", "*"), is(false));
assertThat(isIndexVisible(".bar", "*"), is(false));
assertThat(isIndexVisible("other", null), is(true));
assertThat(isIndexVisible(".foo", null), is(false));
assertThat(isIndexVisible(".bar", null), is(false));

// no difference here in the datastream case, you can't see these then, either
assertThat(isIndexVisible("other", "*", noHiddenNoAliases), is(true));
assertThat(isIndexVisible(".foo", "*", noHiddenNoAliases), is(false));
assertThat(isIndexVisible(".bar", "*", noHiddenNoAliases), is(false));
assertThat(isIndexVisible("other", null, noHiddenNoAliases), is(true));
assertThat(isIndexVisible(".foo", null, noHiddenNoAliases), is(false));
assertThat(isIndexVisible(".bar", null, noHiddenNoAliases), is(false));
}

{
Expand All @@ -316,14 +313,14 @@ public void testIsNetNewSystemIndexVisible() {
indexAbstractionResolver = new IndexAbstractionResolver(indexNameExpressionResolver);

// this covers the GET * case -- with product (only) access, you can't see everything
assertThat(isIndexVisible("other", "*"), is(true));
assertThat(isIndexVisible(".foo", "*"), is(false));
assertThat(isIndexVisible(".bar", "*"), is(false));
assertThat(isIndexVisible("other", null), is(true));
assertThat(isIndexVisible(".foo", null), is(false));
assertThat(isIndexVisible(".bar", null), is(false));

// no difference here in the datastream case, you can't see these then, either
assertThat(isIndexVisible("other", "*", noHiddenNoAliases), is(true));
assertThat(isIndexVisible(".foo", "*", noHiddenNoAliases), is(false));
assertThat(isIndexVisible(".bar", "*", noHiddenNoAliases), is(false));
assertThat(isIndexVisible("other", null, noHiddenNoAliases), is(true));
assertThat(isIndexVisible(".foo", null, noHiddenNoAliases), is(false));
assertThat(isIndexVisible(".bar", null, noHiddenNoAliases), is(false));
}
}

Expand All @@ -349,8 +346,15 @@ private List<String> resolveAbstractionsSelectorAllowed(List<String> expressions
return resolveAbstractions(expressions, IndicesOptions.strictExpandOpen(), defaultMask);
}

private List<String> resolveAbstractions(List<String> expressions, IndicesOptions indicesOptions, Supplier<Set<String>> mask) {
return indexAbstractionResolver.resolveIndexAbstractions(expressions, indicesOptions, metadata, mask, (idx) -> true, true);
private List<String> resolveAbstractions(List<String> expressions, IndicesOptions indicesOptions, Set<String> mask) {
return indexAbstractionResolver.resolveIndexAbstractions(
expressions,
indicesOptions,
metadata,
(ignored) -> mask,
(ignored, nothing) -> true,
true
);
}

private boolean isIndexVisible(String index, String selector) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.action.support.IndexComponentSelector;
import org.elasticsearch.cluster.metadata.DataStream;
import org.elasticsearch.cluster.metadata.IndexAbstraction;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
Expand All @@ -38,7 +41,6 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static org.elasticsearch.action.ValidateActions.addValidationError;
Expand Down Expand Up @@ -280,22 +282,23 @@ default AuthorizationInfo getAuthenticatedUserAuthorizationInfo() {
}

/**
* Used to retrieve index-like resources that the user has access to, for a specific access action type,
* Used to retrieve index-like resources that the user has access to, for a specific access action type and selector,
* at a specific point in time (for a fixed cluster state view).
* It can also be used to check if a specific resource name is authorized (access to the resource name
* can be authorized even if it doesn't exist).
*/
interface AuthorizedIndices {
/**
* Returns all the index-like resource names that are available and accessible for an action type by a user,
* Returns all the index-like resource names that are available and accessible for an action type and selector by a user,
* at a fixed point in time (for a single cluster state view).
* The result is cached and subsequent calls to this method are idempotent.
*/
Supplier<Set<String>> all();
Set<String> all(IndexComponentSelector selector);

/**
* Checks if an index-like resource name is authorized, for an action by a user. The resource might or might not exist.
*/
boolean check(String name);
boolean check(String name, IndexComponentSelector selector);
}

/**
Expand Down Expand Up @@ -365,6 +368,31 @@ public ActionRequestValidationException validate(ActionRequestValidationExceptio
&& application.length == 0) {
validationException = addValidationError("must specify at least one privilege", validationException);
}
if (index != null) {
// no need to validate failure-store related constraints if it's not enabled
if (DataStream.isFailureStoreFeatureFlagEnabled()) {
for (RoleDescriptor.IndicesPrivileges indexPrivilege : index) {
if (indexPrivilege.getIndices() != null
&& Arrays.stream(indexPrivilege.getIndices())
// best effort prevent users from attempting to check failure selectors
.anyMatch(idx -> IndexNameExpressionResolver.hasSelector(idx, IndexComponentSelector.FAILURES))) {
validationException = addValidationError(
// TODO adjust message once HasPrivileges check supports checking failure store privileges
"failures selector is not supported in index patterns",
validationException
);
}
if (indexPrivilege.getPrivileges() != null
&& Arrays.stream(indexPrivilege.getPrivileges())
.anyMatch(p -> "read_failure_store".equals(p) || "manage_failure_store".equals(p))) {
validationException = addValidationError(
"checking failure store privileges is not supported",
validationException
);
}
}
}
}
return validationException;
}

Expand Down
Loading