mirror of
https://github.com/elastic/elasticsearch.git
synced 2025-06-28 01:22:26 -04:00
Failure Store Access Authorization (#123986)
This PR implements authorization logic for failure store access. It builds on https://github.com/elastic/elasticsearch/pull/122715. Access to the failure store is granted by two privileges: `read_failure_store` and `manage_failure_store`. Either of these privileges lets a user access a failure store via the `::failures` selector, as well as access its backing failure indices. `read_failure_store` grants read access (for example to search documents in a failure store), `manage_failure_store` grants access to write operations, such as rollover. Users with only `read` or `manage` on a data stream do not get failure store access. Vice versa, users with `read_failure_store` and `manage_failure_store` do not get access to regular data in a data stream. The PR implements this by making authorization logic selector-aware. It involves two main changes: 1. Index permission groups now compare the selector under which an index resource is accessed to the selector associated with the group. 2. The `AuthorizedIndices` interface likewise uses selectors to decide which indices to treat as authorized. This part of the change requires a sizable refactor and changes to the interface. The high-level behavior for selector-aware search is as follows: For a user with `read_failure_store` over data stream `logs`: - `POST /logs::failures/_search` returns the documents in the failure store. - `POST /logs/_search` returns a 403. - `POST /logs/_search?ignore_unavailable=true` and `POST /*/_search` return an empty result. Similarly, for a user with `read` over data stream `logs`: - `POST /logs::failures/_search` returns a 403. - `POST /logs/_search` returns documents in the data stream. - `POST /logs::failures/_search?ignore_unavailable=true` and `POST /*::failures/_search` return an empty result. A user with both `read` and `read_failure_store` over data stream `logs` gets access to both `POST /logs::failures/_search` and `POST /logs/_search`. The index privilege `all` automatically grants access to both data and the failures store, as well as all hypothetical future selectors. Resolves: ES-10873
This commit is contained in:
parent
54240d3854
commit
c58ac456b8
24 changed files with 3050 additions and 332 deletions
|
@ -10,6 +10,7 @@
|
|||
package org.elasticsearch.example;
|
||||
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.support.IndexComponentSelector;
|
||||
import org.elasticsearch.action.support.SubscribableListener;
|
||||
import org.elasticsearch.cluster.metadata.IndexAbstraction;
|
||||
import org.elasticsearch.cluster.metadata.ProjectMetadata;
|
||||
|
@ -35,7 +36,6 @@ import org.elasticsearch.xpack.core.security.user.User;
|
|||
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;
|
||||
|
@ -119,19 +119,19 @@ public class CustomAuthorizationEngine implements AuthorizationEngine {
|
|||
) {
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -72,6 +72,24 @@ public enum IndexComponentSelector implements Writeable {
|
|||
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)
|
||||
|
|
|
@ -100,6 +100,13 @@ public interface IndexAbstraction {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether this index abstraction is a failure index of a data stream
|
||||
*/
|
||||
default boolean isFailureIndexOfDataStream() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* An index abstraction type.
|
||||
*/
|
||||
|
@ -183,6 +190,11 @@ public interface IndexAbstraction {
|
|||
return dataStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFailureIndexOfDataStream() {
|
||||
return getParentDataStream() != null && getParentDataStream().isFailureStoreIndex(getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHidden() {
|
||||
return isHidden;
|
||||
|
|
|
@ -22,8 +22,8 @@ import java.util.ArrayList;
|
|||
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 {
|
||||
|
||||
|
@ -37,8 +37,8 @@ public class IndexAbstractionResolver {
|
|||
Iterable<String> indices,
|
||||
IndicesOptions indicesOptions,
|
||||
ProjectMetadata projectMetadata,
|
||||
Supplier<Set<String>> allAuthorizedAndAvailable,
|
||||
Predicate<String> isAuthorized,
|
||||
Function<IndexComponentSelector, Set<String>> allAuthorizedAndAvailableBySelector,
|
||||
BiPredicate<String, IndexComponentSelector> isAuthorized,
|
||||
boolean includeDataStreams
|
||||
) {
|
||||
List<String> finalIndices = new ArrayList<>();
|
||||
|
@ -64,6 +64,7 @@ public class IndexAbstractionResolver {
|
|||
);
|
||||
}
|
||||
indexAbstraction = expressionAndSelector.v1();
|
||||
IndexComponentSelector selector = IndexComponentSelector.getByKeyOrThrow(selectorString);
|
||||
|
||||
// we always need to check for date math expressions
|
||||
indexAbstraction = IndexNameExpressionResolver.resolveDateMathExpression(indexAbstraction);
|
||||
|
@ -71,7 +72,7 @@ public class IndexAbstractionResolver {
|
|||
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,
|
||||
|
@ -102,7 +103,7 @@ public class IndexAbstractionResolver {
|
|||
resolveSelectorsAndCollect(indexAbstraction, selectorString, indicesOptions, resolvedIndices, projectMetadata);
|
||||
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
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.elasticsearch.common.time.DateUtils;
|
|||
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;
|
||||
|
@ -1001,6 +1002,14 @@ public class IndexNameExpressionResolver {
|
|||
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.
|
||||
*/
|
||||
|
@ -1022,6 +1031,14 @@ public class IndexNameExpressionResolver {
|
|||
: (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.
|
||||
*/
|
||||
|
|
|
@ -27,7 +27,6 @@ import java.io.UncheckedIOException;
|
|||
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;
|
||||
|
@ -48,7 +47,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 {
|
||||
|
@ -215,13 +214,11 @@ public class IndexAbstractionResolverTests extends ESTestCase {
|
|||
|
||||
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));
|
||||
}
|
||||
|
@ -290,14 +287,14 @@ public class IndexAbstractionResolverTests extends ESTestCase {
|
|||
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));
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -311,14 +308,14 @@ public class IndexAbstractionResolverTests extends ESTestCase {
|
|||
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));
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -333,14 +330,14 @@ public class IndexAbstractionResolverTests extends ESTestCase {
|
|||
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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -366,8 +363,15 @@ public class IndexAbstractionResolverTests extends ESTestCase {
|
|||
return resolveAbstractions(expressions, IndicesOptions.strictExpandOpen(), defaultMask);
|
||||
}
|
||||
|
||||
private List<String> resolveAbstractions(List<String> expressions, IndicesOptions indicesOptions, Supplier<Set<String>> mask) {
|
||||
return indexAbstractionResolver.resolveIndexAbstractions(expressions, indicesOptions, projectMetadata, mask, (idx) -> true, true);
|
||||
private List<String> resolveAbstractions(List<String> expressions, IndicesOptions indicesOptions, Set<String> mask) {
|
||||
return indexAbstractionResolver.resolveIndexAbstractions(
|
||||
expressions,
|
||||
indicesOptions,
|
||||
projectMetadata,
|
||||
(ignored) -> mask,
|
||||
(ignored, nothing) -> true,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
private boolean isIndexVisible(String index, String selector) {
|
||||
|
|
|
@ -11,8 +11,11 @@ import org.elasticsearch.TransportVersion;
|
|||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.ActionRequestValidationException;
|
||||
import org.elasticsearch.action.IndicesRequest;
|
||||
import org.elasticsearch.action.support.IndexComponentSelector;
|
||||
import org.elasticsearch.action.support.SubscribableListener;
|
||||
import org.elasticsearch.cluster.metadata.DataStream;
|
||||
import org.elasticsearch.cluster.metadata.IndexAbstraction;
|
||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||
import org.elasticsearch.cluster.metadata.ProjectMetadata;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
|
@ -39,7 +42,6 @@ import java.util.List;
|
|||
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;
|
||||
|
@ -281,22 +283,23 @@ public interface AuthorizationEngine {
|
|||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -366,6 +369,31 @@ public interface AuthorizationEngine {
|
|||
&& 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.core.security.authz.accesscontrol;
|
||||
|
||||
import org.elasticsearch.action.support.IndexComponentSelector;
|
||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.util.CachedSupplier;
|
||||
import org.elasticsearch.common.util.Maps;
|
||||
import org.elasticsearch.common.util.set.Sets;
|
||||
import org.elasticsearch.core.Nullable;
|
||||
import org.elasticsearch.core.Tuple;
|
||||
import org.elasticsearch.xpack.core.security.authz.IndicesAndAliasesResolverField;
|
||||
import org.elasticsearch.xpack.core.security.authz.permission.DocumentPermissions;
|
||||
import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissions;
|
||||
|
@ -56,13 +56,15 @@ public class IndicesAccessControl {
|
|||
}
|
||||
|
||||
/**
|
||||
* @return The document and field permissions for an index if exist, otherwise <code>null</code> is returned.
|
||||
* @return The document and field permissions for an index if they exist, otherwise <code>null</code> is returned.
|
||||
* If <code>null</code> is being returned this means that there are no field or document level restrictions.
|
||||
*/
|
||||
@Nullable
|
||||
public IndexAccessControl getIndexPermissions(String index) {
|
||||
Tuple<String, String> indexAndSelector = IndexNameExpressionResolver.splitSelectorExpression(index);
|
||||
return this.getAllIndexPermissions().get(indexAndSelector.v1());
|
||||
assert false == IndexNameExpressionResolver.hasSelectorSuffix(index)
|
||||
|| IndexNameExpressionResolver.hasSelector(index, IndexComponentSelector.FAILURES)
|
||||
: "index name [" + index + "] cannot have explicit selector other than ::failures";
|
||||
return getAllIndexPermissions().get(index);
|
||||
}
|
||||
|
||||
public boolean hasIndexPermissions(String index) {
|
||||
|
|
|
@ -142,17 +142,33 @@ public final class IndicesPermission {
|
|||
}
|
||||
|
||||
private IsResourceAuthorizedPredicate buildIndexMatcherPredicateForAction(String action) {
|
||||
final Set<String> ordinaryIndices = new HashSet<>();
|
||||
final Set<String> restrictedIndices = new HashSet<>();
|
||||
final Set<String> dataAccessOrdinaryIndices = new HashSet<>();
|
||||
final Set<String> failuresAccessOrdinaryIndices = new HashSet<>();
|
||||
final Set<String> dataAccessRestrictedIndices = new HashSet<>();
|
||||
final Set<String> failuresAccessRestrictedIndices = new HashSet<>();
|
||||
final Set<String> grantMappingUpdatesOnIndices = new HashSet<>();
|
||||
final Set<String> grantMappingUpdatesOnRestrictedIndices = new HashSet<>();
|
||||
final boolean isMappingUpdateAction = isMappingUpdateAction(action);
|
||||
for (final Group group : groups) {
|
||||
if (group.actionMatcher.test(action)) {
|
||||
final List<String> indexList = Arrays.asList(group.indices());
|
||||
final boolean dataAccess = group.checkSelector(IndexComponentSelector.DATA);
|
||||
final boolean failuresAccess = group.checkSelector(IndexComponentSelector.FAILURES);
|
||||
assert dataAccess || failuresAccess : "group must grant access at least one of [DATA, FAILURES] selectors";
|
||||
if (group.allowRestrictedIndices) {
|
||||
restrictedIndices.addAll(Arrays.asList(group.indices()));
|
||||
if (dataAccess) {
|
||||
dataAccessRestrictedIndices.addAll(indexList);
|
||||
}
|
||||
if (failuresAccess) {
|
||||
failuresAccessRestrictedIndices.addAll(indexList);
|
||||
}
|
||||
} else {
|
||||
ordinaryIndices.addAll(Arrays.asList(group.indices()));
|
||||
if (dataAccess) {
|
||||
dataAccessOrdinaryIndices.addAll(indexList);
|
||||
}
|
||||
if (failuresAccess) {
|
||||
failuresAccessOrdinaryIndices.addAll(indexList);
|
||||
}
|
||||
}
|
||||
} else if (isMappingUpdateAction && containsPrivilegeThatGrantsMappingUpdatesForBwc(group)) {
|
||||
// special BWC case for certain privileges: allow put mapping on indices and aliases (but not on data streams), even if
|
||||
|
@ -164,30 +180,44 @@ public final class IndicesPermission {
|
|||
}
|
||||
}
|
||||
}
|
||||
final StringMatcher nameMatcher = indexMatcher(ordinaryIndices, restrictedIndices);
|
||||
final StringMatcher dataAccessNameMatcher = indexMatcher(dataAccessOrdinaryIndices, dataAccessRestrictedIndices);
|
||||
final StringMatcher failuresAccessNameMatcher = indexMatcher(failuresAccessOrdinaryIndices, failuresAccessRestrictedIndices);
|
||||
final StringMatcher bwcSpecialCaseMatcher = indexMatcher(grantMappingUpdatesOnIndices, grantMappingUpdatesOnRestrictedIndices);
|
||||
return new IsResourceAuthorizedPredicate(nameMatcher, bwcSpecialCaseMatcher);
|
||||
return new IsResourceAuthorizedPredicate(dataAccessNameMatcher, failuresAccessNameMatcher, bwcSpecialCaseMatcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* This encapsulates the authorization test for resources.
|
||||
* There is an additional test for resources that are missing or that are not a datastream or a backing index.
|
||||
*/
|
||||
public static class IsResourceAuthorizedPredicate implements BiPredicate<String, IndexAbstraction> {
|
||||
public static class IsResourceAuthorizedPredicate {
|
||||
|
||||
private final BiPredicate<String, IndexAbstraction> biPredicate;
|
||||
private final BiPredicate<String, IndexAbstraction> isAuthorizedForDataAccess;
|
||||
private final BiPredicate<String, IndexAbstraction> isAuthorizedForFailuresAccess;
|
||||
|
||||
// public for tests
|
||||
public IsResourceAuthorizedPredicate(StringMatcher resourceNameMatcher, StringMatcher additionalNonDatastreamNameMatcher) {
|
||||
public IsResourceAuthorizedPredicate(
|
||||
StringMatcher dataResourceNameMatcher,
|
||||
StringMatcher failuresResourceNameMatcher,
|
||||
StringMatcher additionalNonDatastreamNameMatcher
|
||||
) {
|
||||
this((String name, @Nullable IndexAbstraction indexAbstraction) -> {
|
||||
assert indexAbstraction == null || name.equals(indexAbstraction.getName());
|
||||
return resourceNameMatcher.test(name)
|
||||
return dataResourceNameMatcher.test(name)
|
||||
|| (isPartOfDatastream(indexAbstraction) == false && additionalNonDatastreamNameMatcher.test(name));
|
||||
}, (String name, @Nullable IndexAbstraction indexAbstraction) -> {
|
||||
assert indexAbstraction == null || name.equals(indexAbstraction.getName());
|
||||
// we can't enforce that the abstraction is part of a data stream since we need to account for non-existent resources
|
||||
return failuresResourceNameMatcher.test(name);
|
||||
});
|
||||
}
|
||||
|
||||
private IsResourceAuthorizedPredicate(BiPredicate<String, IndexAbstraction> biPredicate) {
|
||||
this.biPredicate = biPredicate;
|
||||
private IsResourceAuthorizedPredicate(
|
||||
BiPredicate<String, IndexAbstraction> isAuthorizedForDataAccess,
|
||||
BiPredicate<String, IndexAbstraction> isAuthorizedForFailuresAccess
|
||||
) {
|
||||
this.isAuthorizedForDataAccess = isAuthorizedForDataAccess;
|
||||
this.isAuthorizedForFailuresAccess = isAuthorizedForFailuresAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -195,18 +225,25 @@ public final class IndicesPermission {
|
|||
* return a new {@link IsResourceAuthorizedPredicate} instance that is equivalent to the conjunction of
|
||||
* authorization tests of that other instance and this one.
|
||||
*/
|
||||
@Override
|
||||
public final IsResourceAuthorizedPredicate and(BiPredicate<? super String, ? super IndexAbstraction> other) {
|
||||
return new IsResourceAuthorizedPredicate(this.biPredicate.and(other));
|
||||
public final IsResourceAuthorizedPredicate and(IsResourceAuthorizedPredicate other) {
|
||||
return new IsResourceAuthorizedPredicate(
|
||||
this.isAuthorizedForDataAccess.and(other.isAuthorizedForDataAccess),
|
||||
this.isAuthorizedForFailuresAccess.and(other.isAuthorizedForFailuresAccess)
|
||||
);
|
||||
}
|
||||
|
||||
// TODO remove me (this has >700 usages in tests which would make for a horrible diff; will remove this once the main PR is merged)
|
||||
public boolean test(IndexAbstraction indexAbstraction) {
|
||||
return test(indexAbstraction.getName(), indexAbstraction, IndexComponentSelector.DATA);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if access is authorized to the given {@param indexAbstraction} resource.
|
||||
* The resource must exist. Otherwise, use the {@link #test(String, IndexAbstraction)} method.
|
||||
* The resource must exist. Otherwise, use the {@link #test(String, IndexAbstraction, IndexComponentSelector)} method.
|
||||
* Returns {@code true} if access to the given resource is authorized or {@code false} otherwise.
|
||||
*/
|
||||
public final boolean test(IndexAbstraction indexAbstraction) {
|
||||
return test(indexAbstraction.getName(), indexAbstraction);
|
||||
public boolean test(IndexAbstraction indexAbstraction, IndexComponentSelector selector) {
|
||||
return test(indexAbstraction.getName(), indexAbstraction, selector);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -215,9 +252,10 @@ public final class IndicesPermission {
|
|||
* if it doesn't.
|
||||
* Returns {@code true} if access to the given resource is authorized or {@code false} otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean test(String name, @Nullable IndexAbstraction indexAbstraction) {
|
||||
return biPredicate.test(name, indexAbstraction);
|
||||
public boolean test(String name, @Nullable IndexAbstraction indexAbstraction, IndexComponentSelector selector) {
|
||||
return IndexComponentSelector.FAILURES.equals(selector)
|
||||
? isAuthorizedForFailuresAccess.test(name, indexAbstraction)
|
||||
: isAuthorizedForDataAccess.test(name, indexAbstraction);
|
||||
}
|
||||
|
||||
private static boolean isPartOfDatastream(IndexAbstraction indexAbstraction) {
|
||||
|
@ -283,6 +321,7 @@ public final class IndicesPermission {
|
|||
combineIndexGroups && checkForIndexPatterns.stream().anyMatch(Automatons::isLuceneRegex)
|
||||
);
|
||||
for (String forIndexPattern : checkForIndexPatterns) {
|
||||
IndexNameExpressionResolver.assertExpressionHasNullOrDataSelector(forIndexPattern);
|
||||
Automaton checkIndexAutomaton = Automatons.patterns(forIndexPattern);
|
||||
if (false == allowRestrictedIndices && false == isConcreteRestrictedIndex(forIndexPattern)) {
|
||||
checkIndexAutomaton = Automatons.minusAndMinimize(checkIndexAutomaton, restrictedIndices.getAutomaton());
|
||||
|
@ -338,9 +377,12 @@ public final class IndicesPermission {
|
|||
}
|
||||
|
||||
public Automaton allowedActionsMatcher(String index) {
|
||||
Tuple<String, String> tuple = IndexNameExpressionResolver.splitSelectorExpression(index);
|
||||
String indexName = tuple.v1();
|
||||
IndexComponentSelector selector = IndexComponentSelector.getByKey(tuple.v2());
|
||||
List<Automaton> automatonList = new ArrayList<>();
|
||||
for (Group group : groups) {
|
||||
if (group.indexNameMatcher.test(index)) {
|
||||
if (group.checkSelector(selector) && group.indexNameMatcher.test(indexName)) {
|
||||
automatonList.add(group.privilege.getAutomaton());
|
||||
}
|
||||
}
|
||||
|
@ -373,15 +415,6 @@ public final class IndicesPermission {
|
|||
assert name != null : "Resource name cannot be null";
|
||||
assert abstraction == null || abstraction.getName().equals(name)
|
||||
: "Index abstraction has unexpected name [" + abstraction.getName() + "] vs [" + name + "]";
|
||||
assert abstraction == null
|
||||
|| selector == null
|
||||
|| IndexComponentSelector.FAILURES.equals(selector) == false
|
||||
|| abstraction.isDataStreamRelated()
|
||||
: "Invalid index component selector ["
|
||||
+ selector.getKey()
|
||||
+ "] applied to abstraction of type ["
|
||||
+ abstraction.getType()
|
||||
+ "]";
|
||||
this.name = name;
|
||||
this.indexAbstraction = abstraction;
|
||||
this.selector = selector;
|
||||
|
@ -411,11 +444,19 @@ public final class IndicesPermission {
|
|||
public boolean checkIndex(Group group) {
|
||||
final DataStream ds = indexAbstraction == null ? null : indexAbstraction.getParentDataStream();
|
||||
if (ds != null) {
|
||||
if (group.checkIndex(ds.getName())) {
|
||||
return true;
|
||||
}
|
||||
if (indexAbstraction.isFailureIndexOfDataStream()) {
|
||||
// failure indices are special: when accessed directly (not through ::failures on parent data stream) they are accessed
|
||||
// implicitly as data. However, authz to the parent data stream happens via the failures selector
|
||||
if (group.checkSelector(IndexComponentSelector.FAILURES) && group.checkIndex(ds.getName())) {
|
||||
return true;
|
||||
}
|
||||
} else if (IndexComponentSelector.DATA.equals(selector) || selector == null) {
|
||||
if (group.checkSelector(IndexComponentSelector.DATA) && group.checkIndex(ds.getName())) {
|
||||
return true;
|
||||
}
|
||||
} // we don't support granting access to a backing index with a failure selector via the parent data stream
|
||||
}
|
||||
return group.checkIndex(name);
|
||||
return group.checkSelector(selector) && group.checkIndex(name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -478,6 +519,13 @@ public final class IndicesPermission {
|
|||
public boolean canHaveBackingIndices() {
|
||||
return indexAbstraction != null && indexAbstraction.getType() != IndexAbstraction.Type.CONCRETE_INDEX;
|
||||
}
|
||||
|
||||
public String nameWithSelector() {
|
||||
String combined = IndexNameExpressionResolver.combineSelector(name, selector);
|
||||
assert false != IndexComponentSelector.FAILURES.equals(selector) || name.equals(combined)
|
||||
: "Only failures selectors should result in explicit selectors suffix";
|
||||
return combined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -500,18 +548,20 @@ public final class IndicesPermission {
|
|||
int totalResourceCount = 0;
|
||||
Map<String, IndexAbstraction> lookup = metadata.getIndicesLookup();
|
||||
for (String indexOrAlias : requestedIndicesOrAliases) {
|
||||
// Remove any selectors from abstraction name. Discard them for this check as we do not have access control for them (yet)
|
||||
// Remove any selectors from abstraction name. Access control is based on the `selector` field of the IndexResource
|
||||
Tuple<String, String> expressionAndSelector = IndexNameExpressionResolver.splitSelectorExpression(indexOrAlias);
|
||||
indexOrAlias = expressionAndSelector.v1();
|
||||
IndexComponentSelector selector = expressionAndSelector.v2() == null
|
||||
? null
|
||||
: IndexComponentSelector.getByKey(expressionAndSelector.v2());
|
||||
final IndexResource resource = new IndexResource(indexOrAlias, lookup.get(indexOrAlias), selector);
|
||||
resources.put(resource.name, resource);
|
||||
// We can't use resource.name here because we may be accessing a data stream _and_ its failure store,
|
||||
// where the selector-free name is the same for both and thus ambiguous.
|
||||
resources.put(resource.nameWithSelector(), resource);
|
||||
totalResourceCount += resource.size(lookup);
|
||||
}
|
||||
|
||||
final boolean overallGranted = isActionGranted(action, resources);
|
||||
final boolean overallGranted = isActionGranted(action, resources.values());
|
||||
final int finalTotalResourceCount = totalResourceCount;
|
||||
final Supplier<Map<String, IndicesAccessControl.IndexAccessControl>> indexPermissions = () -> buildIndicesAccessControl(
|
||||
action,
|
||||
|
@ -540,10 +590,11 @@ public final class IndicesPermission {
|
|||
|
||||
final boolean isMappingUpdateAction = isMappingUpdateAction(action);
|
||||
|
||||
for (IndexResource resource : requestedResources.values()) {
|
||||
for (Map.Entry<String, IndexResource> resourceEntry : requestedResources.entrySet()) {
|
||||
// true if ANY group covers the given index AND the given action
|
||||
boolean granted = false;
|
||||
|
||||
final String resourceName = resourceEntry.getKey();
|
||||
final IndexResource resource = resourceEntry.getValue();
|
||||
final Collection<String> concreteIndices = resource.resolveConcreteIndices(metadata);
|
||||
for (Group group : groups) {
|
||||
// the group covers the given index OR the given index is a backing index and the group covers the parent data stream
|
||||
|
@ -590,9 +641,9 @@ public final class IndicesPermission {
|
|||
roleQueriesByIndex.put(index, docPermissions);
|
||||
}
|
||||
|
||||
if (index.equals(resource.name) == false) {
|
||||
fieldPermissionsByIndex.put(resource.name, fieldPermissions);
|
||||
roleQueriesByIndex.put(resource.name, docPermissions);
|
||||
if (index.equals(resourceName) == false) {
|
||||
fieldPermissionsByIndex.put(resourceName, fieldPermissions);
|
||||
roleQueriesByIndex.put(resourceName, docPermissions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -600,10 +651,11 @@ public final class IndicesPermission {
|
|||
}
|
||||
|
||||
if (granted) {
|
||||
grantedResources.add(resource.name);
|
||||
grantedResources.add(resourceName);
|
||||
|
||||
if (resource.canHaveBackingIndices()) {
|
||||
for (String concreteIndex : concreteIndices) {
|
||||
// If the name appear directly as part of the requested indices, it takes precedence over implicit access
|
||||
// If the name appears directly as part of the requested indices, it takes precedence over implicit access
|
||||
if (false == requestedResources.containsKey(concreteIndex)) {
|
||||
grantedResources.add(concreteIndex);
|
||||
}
|
||||
|
@ -639,11 +691,11 @@ public final class IndicesPermission {
|
|||
* Returns {@code true} if action is granted for all {@code requestedResources}.
|
||||
* If action is not granted for at least one resource, this method will return {@code false}.
|
||||
*/
|
||||
private boolean isActionGranted(final String action, final Map<String, IndexResource> requestedResources) {
|
||||
private boolean isActionGranted(final String action, final Collection<IndexResource> requestedResources) {
|
||||
|
||||
final boolean isMappingUpdateAction = isMappingUpdateAction(action);
|
||||
|
||||
for (IndexResource resource : requestedResources.values()) {
|
||||
for (IndexResource resource : requestedResources) {
|
||||
// true if ANY group covers the given index AND the given action
|
||||
boolean granted = false;
|
||||
// true if ANY group, which contains certain ingest privileges, covers the given index AND the action is a mapping update for
|
||||
|
@ -758,6 +810,11 @@ public final class IndicesPermission {
|
|||
// Map of privilege automaton object references (cached by IndexPrivilege::CACHE)
|
||||
Map<Automaton, Automaton> allAutomatons = new HashMap<>();
|
||||
for (Group group : groups) {
|
||||
// TODO support failure store privileges
|
||||
// we also check that the group does not support data access to avoid erroneously filtering out `all` privilege groups
|
||||
if (group.checkSelector(IndexComponentSelector.FAILURES) && false == group.checkSelector(IndexComponentSelector.DATA)) {
|
||||
continue;
|
||||
}
|
||||
Automaton indexAutomaton = group.getIndexMatcherAutomaton();
|
||||
allAutomatons.compute(
|
||||
group.privilege().getAutomaton(),
|
||||
|
@ -864,8 +921,8 @@ public final class IndicesPermission {
|
|||
return query != null;
|
||||
}
|
||||
|
||||
public boolean checkSelector(IndexComponentSelector selector) {
|
||||
return selectorPredicate.test(selector);
|
||||
public boolean checkSelector(@Nullable IndexComponentSelector selector) {
|
||||
return selectorPredicate.test(selector == null ? IndexComponentSelector.DATA : selector);
|
||||
}
|
||||
|
||||
public boolean allowRestrictedIndices() {
|
||||
|
|
|
@ -48,11 +48,13 @@ import java.util.Arrays;
|
|||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -86,6 +88,7 @@ public final class IndexPrivilege extends Privilege {
|
|||
ResolveIndexAction.NAME,
|
||||
TransportResolveClusterAction.NAME
|
||||
);
|
||||
private static final Automaton READ_FAILURE_STORE_AUTOMATON = patterns("indices:data/read/*", ResolveIndexAction.NAME);
|
||||
private static final Automaton READ_CROSS_CLUSTER_AUTOMATON = patterns(
|
||||
"internal:transport/proxy/indices:data/read/*",
|
||||
TransportClusterSearchShardsAction.TYPE.name(),
|
||||
|
@ -183,12 +186,6 @@ public final class IndexPrivilege extends Privilege {
|
|||
|
||||
public static final IndexPrivilege NONE = new IndexPrivilege("none", Automatons.EMPTY);
|
||||
public static final IndexPrivilege ALL = new IndexPrivilege("all", ALL_AUTOMATON, IndexComponentSelectorPredicate.ALL);
|
||||
public static final IndexPrivilege READ_FAILURE_STORE = new IndexPrivilege(
|
||||
"read_failure_store",
|
||||
// TODO use READ_AUTOMATON here in authorization follow-up
|
||||
Automatons.EMPTY,
|
||||
IndexComponentSelectorPredicate.FAILURES
|
||||
);
|
||||
public static final IndexPrivilege READ = new IndexPrivilege("read", READ_AUTOMATON);
|
||||
public static final IndexPrivilege READ_CROSS_CLUSTER = new IndexPrivilege("read_cross_cluster", READ_CROSS_CLUSTER_AUTOMATON);
|
||||
public static final IndexPrivilege CREATE = new IndexPrivilege("create", CREATE_AUTOMATON);
|
||||
|
@ -219,38 +216,71 @@ public final class IndexPrivilege extends Privilege {
|
|||
CROSS_CLUSTER_REPLICATION_INTERNAL_AUTOMATON
|
||||
);
|
||||
|
||||
public static final IndexPrivilege READ_FAILURE_STORE = new IndexPrivilege(
|
||||
"read_failure_store",
|
||||
READ_FAILURE_STORE_AUTOMATON,
|
||||
IndexComponentSelectorPredicate.FAILURES
|
||||
);
|
||||
public static final IndexPrivilege MANAGE_FAILURE_STORE = new IndexPrivilege(
|
||||
"manage_failure_store",
|
||||
MANAGE_AUTOMATON,
|
||||
IndexComponentSelectorPredicate.FAILURES
|
||||
);
|
||||
|
||||
/**
|
||||
* If you are adding a new named index privilege, also add it to the
|
||||
* <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-privileges.html#privileges-list-indices">docs</a>.
|
||||
*/
|
||||
private static final Map<String, IndexPrivilege> VALUES = sortByAccessLevel(
|
||||
Stream.of(
|
||||
entry("none", NONE),
|
||||
entry("all", ALL),
|
||||
entry("manage", MANAGE),
|
||||
entry("create_index", CREATE_INDEX),
|
||||
entry("monitor", MONITOR),
|
||||
entry("read", READ),
|
||||
entry("index", INDEX),
|
||||
entry("delete", DELETE),
|
||||
entry("write", WRITE),
|
||||
entry("create", CREATE),
|
||||
entry("create_doc", CREATE_DOC),
|
||||
entry("delete_index", DELETE_INDEX),
|
||||
entry("view_index_metadata", VIEW_METADATA),
|
||||
entry("read_cross_cluster", READ_CROSS_CLUSTER),
|
||||
entry("manage_follow_index", MANAGE_FOLLOW_INDEX),
|
||||
entry("manage_leader_index", MANAGE_LEADER_INDEX),
|
||||
entry("manage_ilm", MANAGE_ILM),
|
||||
entry("manage_data_stream_lifecycle", MANAGE_DATA_STREAM_LIFECYCLE),
|
||||
entry("maintenance", MAINTENANCE),
|
||||
entry("auto_configure", AUTO_CONFIGURE),
|
||||
entry("cross_cluster_replication", CROSS_CLUSTER_REPLICATION),
|
||||
entry("cross_cluster_replication_internal", CROSS_CLUSTER_REPLICATION_INTERNAL),
|
||||
DataStream.isFailureStoreFeatureFlagEnabled() ? entry("read_failure_store", READ_FAILURE_STORE) : null
|
||||
).filter(Objects::nonNull).collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue))
|
||||
private static final Map<String, IndexPrivilege> VALUES = combineSortedInOrder(
|
||||
sortByAccessLevel(
|
||||
Stream.of(
|
||||
DataStream.isFailureStoreFeatureFlagEnabled() ? entry("read_failure_store", READ_FAILURE_STORE) : null,
|
||||
DataStream.isFailureStoreFeatureFlagEnabled() ? entry("manage_failure_store", MANAGE_FAILURE_STORE) : null
|
||||
).filter(Objects::nonNull).collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue))
|
||||
),
|
||||
sortByAccessLevel(
|
||||
Stream.of(
|
||||
entry("none", NONE),
|
||||
entry("all", ALL),
|
||||
entry("manage", MANAGE),
|
||||
entry("create_index", CREATE_INDEX),
|
||||
entry("monitor", MONITOR),
|
||||
entry("read", READ),
|
||||
entry("index", INDEX),
|
||||
entry("delete", DELETE),
|
||||
entry("write", WRITE),
|
||||
entry("create", CREATE),
|
||||
entry("create_doc", CREATE_DOC),
|
||||
entry("delete_index", DELETE_INDEX),
|
||||
entry("view_index_metadata", VIEW_METADATA),
|
||||
entry("read_cross_cluster", READ_CROSS_CLUSTER),
|
||||
entry("manage_follow_index", MANAGE_FOLLOW_INDEX),
|
||||
entry("manage_leader_index", MANAGE_LEADER_INDEX),
|
||||
entry("manage_ilm", MANAGE_ILM),
|
||||
entry("manage_data_stream_lifecycle", MANAGE_DATA_STREAM_LIFECYCLE),
|
||||
entry("maintenance", MAINTENANCE),
|
||||
entry("auto_configure", AUTO_CONFIGURE),
|
||||
entry("cross_cluster_replication", CROSS_CLUSTER_REPLICATION),
|
||||
entry("cross_cluster_replication_internal", CROSS_CLUSTER_REPLICATION_INTERNAL)
|
||||
).collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue))
|
||||
)
|
||||
);
|
||||
|
||||
private static Map<String, IndexPrivilege> combineSortedInOrder(
|
||||
SortedMap<String, IndexPrivilege> first,
|
||||
SortedMap<String, IndexPrivilege> second
|
||||
) {
|
||||
if (first.isEmpty()) {
|
||||
return second;
|
||||
}
|
||||
if (second.isEmpty()) {
|
||||
return first;
|
||||
}
|
||||
final Map<String, IndexPrivilege> combined = new LinkedHashMap<>(first);
|
||||
combined.putAll(second);
|
||||
return Collections.unmodifiableMap(combined);
|
||||
}
|
||||
|
||||
public static final Predicate<String> ACTION_MATCHER = ALL.predicate();
|
||||
public static final Predicate<String> CREATE_INDEX_MATCHER = CREATE_INDEX.predicate();
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.elasticsearch.action.downsample.DownsampleAction;
|
|||
import org.elasticsearch.action.index.TransportIndexAction;
|
||||
import org.elasticsearch.action.search.TransportSearchAction;
|
||||
import org.elasticsearch.action.search.TransportSearchScrollAction;
|
||||
import org.elasticsearch.cluster.metadata.DataStream;
|
||||
import org.elasticsearch.index.reindex.ReindexAction;
|
||||
import org.elasticsearch.xpack.core.XPackPlugin;
|
||||
import org.elasticsearch.xpack.core.ilm.action.ILMActions;
|
||||
|
@ -245,12 +246,25 @@ public class InternalUsers {
|
|||
new RoleDescriptor(
|
||||
UsernamesField.LAZY_ROLLOVER_ROLE,
|
||||
new String[] {},
|
||||
new RoleDescriptor.IndicesPrivileges[] {
|
||||
RoleDescriptor.IndicesPrivileges.builder()
|
||||
.indices("*")
|
||||
.privileges(LazyRolloverAction.NAME)
|
||||
.allowRestrictedIndices(true)
|
||||
.build() },
|
||||
DataStream.isFailureStoreFeatureFlagEnabled()
|
||||
? new RoleDescriptor.IndicesPrivileges[] {
|
||||
RoleDescriptor.IndicesPrivileges.builder()
|
||||
.indices("*")
|
||||
.privileges(LazyRolloverAction.NAME)
|
||||
.allowRestrictedIndices(true)
|
||||
.build(),
|
||||
RoleDescriptor.IndicesPrivileges.builder()
|
||||
.indices("*")
|
||||
// needed to rollover failure store
|
||||
.privileges("manage_failure_store")
|
||||
.allowRestrictedIndices(true)
|
||||
.build() }
|
||||
: new RoleDescriptor.IndicesPrivileges[] {
|
||||
RoleDescriptor.IndicesPrivileges.builder()
|
||||
.indices("*")
|
||||
.privileges(LazyRolloverAction.NAME)
|
||||
.allowRestrictedIndices(true)
|
||||
.build(), },
|
||||
null,
|
||||
null,
|
||||
new String[] {},
|
||||
|
|
|
@ -18,6 +18,8 @@ import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
|
|||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.hasItem;
|
||||
|
@ -134,13 +136,20 @@ public class ProfileHasPrivilegesRequestTests extends AbstractWireSerializingTes
|
|||
)];
|
||||
for (int i = 0; i < indicesPrivileges.length; i++) {
|
||||
indicesPrivileges[i] = RoleDescriptor.IndicesPrivileges.builder()
|
||||
.privileges(randomSubsetOf(randomIntBetween(1, 5), IndexPrivilege.names()))
|
||||
.privileges(randomSubsetOf(randomIntBetween(1, 5), validPrivilegeNames()))
|
||||
.indices(randomList(1, 3, () -> randomAlphaOfLengthBetween(2, 8) + (randomBoolean() ? "*" : "")))
|
||||
.build();
|
||||
}
|
||||
return indicesPrivileges;
|
||||
}
|
||||
|
||||
private static Set<String> validPrivilegeNames() {
|
||||
return IndexPrivilege.names()
|
||||
.stream()
|
||||
.filter(name -> false == name.equals("read_failure_store") && false == name.equals("manage_failure_store"))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private static RoleDescriptor.ApplicationResourcePrivileges[] randomApplicationResourcePrivileges(boolean allowEmpty) {
|
||||
RoleDescriptor.ApplicationResourcePrivileges[] appPrivileges = new RoleDescriptor.ApplicationResourcePrivileges[randomIntBetween(
|
||||
allowEmpty ? 0 : 1,
|
||||
|
|
|
@ -529,22 +529,22 @@ public class LimitedRoleTests extends ESTestCase {
|
|||
|
||||
public void testAllowedIndicesMatcher() {
|
||||
Role fromRole = Role.builder(EMPTY_RESTRICTED_INDICES, "a-role").add(IndexPrivilege.READ, "ind-1*").build();
|
||||
assertThat(fromRole.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-1")), is(true));
|
||||
assertThat(fromRole.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-11")), is(true));
|
||||
assertThat(fromRole.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-2")), is(false));
|
||||
assertThat(fromRole.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-1"), null), is(true));
|
||||
assertThat(fromRole.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-11"), null), is(true));
|
||||
assertThat(fromRole.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-2"), null), is(false));
|
||||
|
||||
{
|
||||
Role limitedByRole = Role.builder(EMPTY_RESTRICTED_INDICES, "limited-role").add(IndexPrivilege.READ, "ind-1", "ind-2").build();
|
||||
assertThat(
|
||||
limitedByRole.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-1")),
|
||||
limitedByRole.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-1"), null),
|
||||
is(true)
|
||||
);
|
||||
assertThat(
|
||||
limitedByRole.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-11")),
|
||||
limitedByRole.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-11"), null),
|
||||
is(false)
|
||||
);
|
||||
assertThat(
|
||||
limitedByRole.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-2")),
|
||||
limitedByRole.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-2"), null),
|
||||
is(true)
|
||||
);
|
||||
Role role;
|
||||
|
@ -553,18 +553,18 @@ public class LimitedRoleTests extends ESTestCase {
|
|||
} else {
|
||||
role = fromRole.limitedBy(limitedByRole);
|
||||
}
|
||||
assertThat(role.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-1")), is(true));
|
||||
assertThat(role.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-11")), is(false));
|
||||
assertThat(role.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-2")), is(false));
|
||||
assertThat(role.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-1"), null), is(true));
|
||||
assertThat(role.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-11"), null), is(false));
|
||||
assertThat(role.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-2"), null), is(false));
|
||||
}
|
||||
{
|
||||
Role limitedByRole = Role.builder(EMPTY_RESTRICTED_INDICES, "limited-role").add(IndexPrivilege.READ, "ind-*").build();
|
||||
assertThat(
|
||||
limitedByRole.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-1")),
|
||||
limitedByRole.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-1"), null),
|
||||
is(true)
|
||||
);
|
||||
assertThat(
|
||||
limitedByRole.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-2")),
|
||||
limitedByRole.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-2"), null),
|
||||
is(true)
|
||||
);
|
||||
Role role;
|
||||
|
@ -573,16 +573,16 @@ public class LimitedRoleTests extends ESTestCase {
|
|||
} else {
|
||||
role = fromRole.limitedBy(limitedByRole);
|
||||
}
|
||||
assertThat(role.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-1")), is(true));
|
||||
assertThat(role.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-2")), is(false));
|
||||
assertThat(role.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-1"), null), is(true));
|
||||
assertThat(role.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-2"), null), is(false));
|
||||
}
|
||||
}
|
||||
|
||||
public void testAllowedIndicesMatcherWithNestedRole() {
|
||||
Role role = Role.builder(EMPTY_RESTRICTED_INDICES, "a-role").add(IndexPrivilege.READ, "ind-1*").build();
|
||||
assertThat(role.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-1")), is(true));
|
||||
assertThat(role.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-11")), is(true));
|
||||
assertThat(role.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-2")), is(false));
|
||||
assertThat(role.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-1"), null), is(true));
|
||||
assertThat(role.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-11"), null), is(true));
|
||||
assertThat(role.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-2"), null), is(false));
|
||||
|
||||
final int depth = randomIntBetween(2, 4);
|
||||
boolean index11Excluded = false;
|
||||
|
@ -598,12 +598,12 @@ public class LimitedRoleTests extends ESTestCase {
|
|||
} else {
|
||||
role = role.limitedBy(limitedByRole);
|
||||
}
|
||||
assertThat(role.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-1")), is(true));
|
||||
assertThat(role.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-1"), null), is(true));
|
||||
assertThat(
|
||||
role.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-11")),
|
||||
role.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-11"), null),
|
||||
is(false == index11Excluded)
|
||||
);
|
||||
assertThat(role.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-2")), is(false));
|
||||
assertThat(role.allowedIndicesMatcher(TransportSearchAction.TYPE.name()).test(mockIndexAbstraction("ind-2"), null), is(false));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -646,6 +646,101 @@ public class LimitedRoleTests extends ESTestCase {
|
|||
assertThat(rolePredicate.test(TransportBulkAction.NAME), is(false));
|
||||
}
|
||||
|
||||
public void testAllowedActionsMatcherWithSelectors() {
|
||||
Role fromRole = Role.builder(EMPTY_RESTRICTED_INDICES, "fromRole")
|
||||
.add(IndexPrivilege.READ_FAILURE_STORE, "ind*")
|
||||
.add(IndexPrivilege.READ, "ind*")
|
||||
.add(IndexPrivilege.READ_FAILURE_STORE, "metric")
|
||||
.add(IndexPrivilege.READ, "logs")
|
||||
.build();
|
||||
Automaton fromRoleAutomaton = fromRole.allowedActionsMatcher("index1");
|
||||
Predicate<String> fromRolePredicate = Automatons.predicate(fromRoleAutomaton);
|
||||
assertThat(fromRolePredicate.test(TransportSearchAction.TYPE.name()), is(true));
|
||||
|
||||
fromRoleAutomaton = fromRole.allowedActionsMatcher("index1::failures");
|
||||
fromRolePredicate = Automatons.predicate(fromRoleAutomaton);
|
||||
assertThat(fromRolePredicate.test(TransportSearchAction.TYPE.name()), is(true));
|
||||
|
||||
fromRoleAutomaton = fromRole.allowedActionsMatcher("metric");
|
||||
fromRolePredicate = Automatons.predicate(fromRoleAutomaton);
|
||||
assertThat(fromRolePredicate.test(TransportSearchAction.TYPE.name()), is(false));
|
||||
|
||||
fromRoleAutomaton = fromRole.allowedActionsMatcher("metric::failures");
|
||||
fromRolePredicate = Automatons.predicate(fromRoleAutomaton);
|
||||
assertThat(fromRolePredicate.test(TransportSearchAction.TYPE.name()), is(true));
|
||||
|
||||
fromRoleAutomaton = fromRole.allowedActionsMatcher("logs");
|
||||
fromRolePredicate = Automatons.predicate(fromRoleAutomaton);
|
||||
assertThat(fromRolePredicate.test(TransportSearchAction.TYPE.name()), is(true));
|
||||
|
||||
fromRoleAutomaton = fromRole.allowedActionsMatcher("logs::failures");
|
||||
fromRolePredicate = Automatons.predicate(fromRoleAutomaton);
|
||||
assertThat(fromRolePredicate.test(TransportSearchAction.TYPE.name()), is(false));
|
||||
|
||||
Role limitedByRole = Role.builder(EMPTY_RESTRICTED_INDICES, "limitedRole")
|
||||
.add(IndexPrivilege.READ, "index1", "index2")
|
||||
.add(IndexPrivilege.READ_FAILURE_STORE, "index3")
|
||||
.build();
|
||||
Automaton limitedByRoleAutomaton = limitedByRole.allowedActionsMatcher("index1");
|
||||
Predicate<String> limitedByRolePredicated = Automatons.predicate(limitedByRoleAutomaton);
|
||||
assertThat(limitedByRolePredicated.test(TransportSearchAction.TYPE.name()), is(true));
|
||||
|
||||
limitedByRoleAutomaton = limitedByRole.allowedActionsMatcher("index1");
|
||||
limitedByRolePredicated = Automatons.predicate(limitedByRoleAutomaton);
|
||||
assertThat(limitedByRolePredicated.test(TransportSearchAction.TYPE.name()), is(true));
|
||||
|
||||
limitedByRoleAutomaton = limitedByRole.allowedActionsMatcher("index1::failures");
|
||||
limitedByRolePredicated = Automatons.predicate(limitedByRoleAutomaton);
|
||||
assertThat(limitedByRolePredicated.test(TransportSearchAction.TYPE.name()), is(false));
|
||||
|
||||
limitedByRoleAutomaton = limitedByRole.allowedActionsMatcher("index3");
|
||||
limitedByRolePredicated = Automatons.predicate(limitedByRoleAutomaton);
|
||||
assertThat(limitedByRolePredicated.test(TransportSearchAction.TYPE.name()), is(false));
|
||||
|
||||
limitedByRoleAutomaton = limitedByRole.allowedActionsMatcher("index3::failures");
|
||||
limitedByRolePredicated = Automatons.predicate(limitedByRoleAutomaton);
|
||||
assertThat(limitedByRolePredicated.test(TransportSearchAction.TYPE.name()), is(true));
|
||||
|
||||
Role role;
|
||||
if (randomBoolean()) {
|
||||
role = limitedByRole.limitedBy(fromRole);
|
||||
} else {
|
||||
role = fromRole.limitedBy(limitedByRole);
|
||||
}
|
||||
|
||||
Automaton roleAutomaton = role.allowedActionsMatcher("index1");
|
||||
Predicate<String> rolePredicate = Automatons.predicate(roleAutomaton);
|
||||
assertThat(rolePredicate.test(TransportSearchAction.TYPE.name()), is(true));
|
||||
|
||||
roleAutomaton = role.allowedActionsMatcher("index1::failures");
|
||||
rolePredicate = Automatons.predicate(roleAutomaton);
|
||||
assertThat(rolePredicate.test(TransportSearchAction.TYPE.name()), is(false));
|
||||
|
||||
roleAutomaton = role.allowedActionsMatcher("index3");
|
||||
rolePredicate = Automatons.predicate(roleAutomaton);
|
||||
assertThat(rolePredicate.test(TransportSearchAction.TYPE.name()), is(false));
|
||||
|
||||
roleAutomaton = role.allowedActionsMatcher("index3::failures");
|
||||
rolePredicate = Automatons.predicate(roleAutomaton);
|
||||
assertThat(rolePredicate.test(TransportSearchAction.TYPE.name()), is(true));
|
||||
|
||||
roleAutomaton = role.allowedActionsMatcher("metric");
|
||||
rolePredicate = Automatons.predicate(roleAutomaton);
|
||||
assertThat(rolePredicate.test(TransportSearchAction.TYPE.name()), is(false));
|
||||
|
||||
roleAutomaton = role.allowedActionsMatcher("metric::failures");
|
||||
rolePredicate = Automatons.predicate(roleAutomaton);
|
||||
assertThat(rolePredicate.test(TransportSearchAction.TYPE.name()), is(false));
|
||||
|
||||
roleAutomaton = role.allowedActionsMatcher("logs");
|
||||
rolePredicate = Automatons.predicate(roleAutomaton);
|
||||
assertThat(rolePredicate.test(TransportSearchAction.TYPE.name()), is(false));
|
||||
|
||||
roleAutomaton = role.allowedActionsMatcher("logs::failures");
|
||||
rolePredicate = Automatons.predicate(roleAutomaton);
|
||||
assertThat(rolePredicate.test(TransportSearchAction.TYPE.name()), is(false));
|
||||
}
|
||||
|
||||
public void testCheckClusterPrivilege() {
|
||||
Role fromRole = Role.builder(EMPTY_RESTRICTED_INDICES, "a-role")
|
||||
.cluster(Collections.singleton("manage_security"), Collections.emptyList())
|
||||
|
|
|
@ -376,7 +376,7 @@ public class InternalUsersTests extends ESTestCase {
|
|||
final IndexAbstraction.ConcreteIndex index = new IndexAbstraction.ConcreteIndex(metadata);
|
||||
assertThat(
|
||||
"Role " + role + ", action " + action + " access to " + indexName,
|
||||
role.allowedIndicesMatcher(action).test(index),
|
||||
role.allowedIndicesMatcher(action).test(index, null),
|
||||
is(expectedValue)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ import org.elasticsearch.cluster.block.ClusterBlockException;
|
|||
import org.elasticsearch.cluster.block.ClusterBlockLevel;
|
||||
import org.elasticsearch.cluster.metadata.IndexMetadata;
|
||||
import org.elasticsearch.cluster.metadata.IndexMetadata.DownsampleTaskStatus;
|
||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||
import org.elasticsearch.cluster.metadata.MetadataCreateIndexService;
|
||||
import org.elasticsearch.cluster.metadata.ProjectId;
|
||||
import org.elasticsearch.cluster.metadata.ProjectMetadata;
|
||||
|
@ -209,7 +210,8 @@ public class TransportDownsampleAction extends AcknowledgedTransportMasterNodeAc
|
|||
) {
|
||||
long startTime = client.threadPool().relativeTimeInMillis();
|
||||
String sourceIndexName = request.getSourceIndex();
|
||||
|
||||
IndexNameExpressionResolver.assertExpressionHasNullOrDataSelector(sourceIndexName);
|
||||
IndexNameExpressionResolver.assertExpressionHasNullOrDataSelector(request.getTargetIndex());
|
||||
final IndicesAccessControl indicesAccessControl = threadContext.getTransient(AuthorizationServiceField.INDICES_PERMISSIONS_KEY);
|
||||
if (indicesAccessControl != null) {
|
||||
final IndicesAccessControl.IndexAccessControl indexPermissions = indicesAccessControl.getIndexPermissions(sourceIndexName);
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2211,6 +2211,7 @@ public class Security extends Plugin
|
|||
return FieldPredicate.ACCEPT_ALL;
|
||||
}
|
||||
assert indicesAccessControl.isGranted();
|
||||
IndexNameExpressionResolver.assertExpressionHasNullOrDataSelector(index);
|
||||
IndicesAccessControl.IndexAccessControl indexPermissions = indicesAccessControl.getIndexPermissions(index);
|
||||
if (indexPermissions == null) {
|
||||
return FieldPredicate.ACCEPT_ALL;
|
||||
|
|
|
@ -48,8 +48,7 @@ import java.util.Optional;
|
|||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
import static org.elasticsearch.xpack.core.security.authz.IndicesAndAliasesResolverField.NO_INDEX_PLACEHOLDER;
|
||||
|
||||
|
@ -323,7 +322,8 @@ class IndicesAndAliasesResolver {
|
|||
);
|
||||
}
|
||||
if (indicesOptions.expandWildcardExpressions()) {
|
||||
for (String authorizedIndex : authorizedIndices.all().get()) {
|
||||
IndexComponentSelector selector = IndexComponentSelector.getByKeyOrThrow(allIndicesPatternSelector);
|
||||
for (String authorizedIndex : authorizedIndices.all(selector)) {
|
||||
if (IndexAbstractionResolver.isIndexVisible(
|
||||
"*",
|
||||
allIndicesPatternSelector,
|
||||
|
@ -352,7 +352,7 @@ class IndicesAndAliasesResolver {
|
|||
split.getLocal(),
|
||||
indicesOptions,
|
||||
projectMetadata,
|
||||
authorizedIndices.all(),
|
||||
authorizedIndices::all,
|
||||
authorizedIndices::check,
|
||||
indicesRequest.includeDataStreams()
|
||||
);
|
||||
|
@ -389,7 +389,7 @@ class IndicesAndAliasesResolver {
|
|||
if (aliasesRequest.expandAliasesWildcards()) {
|
||||
List<String> aliases = replaceWildcardsWithAuthorizedAliases(
|
||||
aliasesRequest.aliases(),
|
||||
loadAuthorizedAliases(authorizedIndices.all(), projectMetadata)
|
||||
loadAuthorizedAliases(authorizedIndices, projectMetadata)
|
||||
);
|
||||
aliasesRequest.replaceAliases(aliases.toArray(new String[aliases.size()]));
|
||||
}
|
||||
|
@ -431,8 +431,13 @@ class IndicesAndAliasesResolver {
|
|||
* request's concrete index is not in the list of authorized indices, then we need to look to
|
||||
* see if this can be authorized against an alias
|
||||
*/
|
||||
static String getPutMappingIndexOrAlias(PutMappingRequest request, Predicate<String> isAuthorized, ProjectMetadata projectMetadata) {
|
||||
static String getPutMappingIndexOrAlias(
|
||||
PutMappingRequest request,
|
||||
BiPredicate<String, IndexComponentSelector> isAuthorized,
|
||||
ProjectMetadata projectMetadata
|
||||
) {
|
||||
final String concreteIndexName = request.getConcreteIndex().getName();
|
||||
assert IndexNameExpressionResolver.hasSelectorSuffix(concreteIndexName) == false : "selectors are not allowed in this context";
|
||||
|
||||
// validate that the concrete index exists, otherwise there is no remapping that we could do
|
||||
final IndexAbstraction indexAbstraction = projectMetadata.getIndicesLookup().get(concreteIndexName);
|
||||
|
@ -447,7 +452,8 @@ class IndicesAndAliasesResolver {
|
|||
+ indexAbstraction.getType().getDisplayName()
|
||||
+ "], but a concrete index is expected"
|
||||
);
|
||||
} else if (isAuthorized.test(concreteIndexName)) {
|
||||
// we know this is implicit data access (as opposed to another selector) so the default selector check is correct
|
||||
} else if (isAuthorized.test(concreteIndexName, IndexComponentSelector.DATA)) {
|
||||
// user is authorized to put mappings for this index
|
||||
resolvedAliasOrIndex = concreteIndexName;
|
||||
} else {
|
||||
|
@ -456,7 +462,12 @@ class IndicesAndAliasesResolver {
|
|||
Map<String, List<AliasMetadata>> foundAliases = projectMetadata.findAllAliases(new String[] { concreteIndexName });
|
||||
List<AliasMetadata> aliasMetadata = foundAliases.get(concreteIndexName);
|
||||
if (aliasMetadata != null) {
|
||||
Optional<String> foundAlias = aliasMetadata.stream().map(AliasMetadata::alias).filter(isAuthorized).filter(aliasName -> {
|
||||
Optional<String> foundAlias = aliasMetadata.stream().map(AliasMetadata::alias).filter(aliasName -> {
|
||||
// we know this is implicit data access (as opposed to another selector) so the default selector check is correct
|
||||
assert IndexNameExpressionResolver.hasSelectorSuffix(aliasName) == false : "selectors are not allowed in this context";
|
||||
if (false == isAuthorized.test(aliasName, IndexComponentSelector.DATA)) {
|
||||
return false;
|
||||
}
|
||||
IndexAbstraction alias = projectMetadata.getIndicesLookup().get(aliasName);
|
||||
List<Index> indices = alias.getIndices();
|
||||
if (indices.size() == 1) {
|
||||
|
@ -476,10 +487,13 @@ class IndicesAndAliasesResolver {
|
|||
return resolvedAliasOrIndex;
|
||||
}
|
||||
|
||||
private static List<String> loadAuthorizedAliases(Supplier<Set<String>> authorizedIndices, ProjectMetadata projectMetadata) {
|
||||
private static List<String> loadAuthorizedAliases(
|
||||
AuthorizationEngine.AuthorizedIndices authorizedIndices,
|
||||
ProjectMetadata projectMetadata
|
||||
) {
|
||||
List<String> authorizedAliases = new ArrayList<>();
|
||||
SortedMap<String, IndexAbstraction> existingAliases = projectMetadata.getIndicesLookup();
|
||||
for (String authorizedIndex : authorizedIndices.get()) {
|
||||
for (String authorizedIndex : authorizedIndices.all(IndexComponentSelector.DATA)) {
|
||||
IndexAbstraction indexAbstraction = existingAliases.get(authorizedIndex);
|
||||
if (indexAbstraction != null && indexAbstraction.getType() == IndexAbstraction.Type.ALIAS) {
|
||||
authorizedAliases.add(authorizedIndex);
|
||||
|
@ -498,6 +512,7 @@ class IndicesAndAliasesResolver {
|
|||
}
|
||||
|
||||
for (String aliasExpression : aliases) {
|
||||
IndexNameExpressionResolver.assertExpressionHasNullOrDataSelector(aliasExpression);
|
||||
boolean include = true;
|
||||
if (aliasExpression.charAt(0) == '-') {
|
||||
include = false;
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.elasticsearch.action.search.TransportClearScrollAction;
|
|||
import org.elasticsearch.action.search.TransportClosePointInTimeAction;
|
||||
import org.elasticsearch.action.search.TransportMultiSearchAction;
|
||||
import org.elasticsearch.action.search.TransportSearchScrollAction;
|
||||
import org.elasticsearch.action.support.IndexComponentSelector;
|
||||
import org.elasticsearch.action.support.SubscribableListener;
|
||||
import org.elasticsearch.action.termvectors.MultiTermVectorsAction;
|
||||
import org.elasticsearch.cluster.metadata.DataStream;
|
||||
|
@ -107,6 +108,7 @@ import java.util.Map.Entry;
|
|||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
@ -882,42 +884,76 @@ public class RBACEngine implements AuthorizationEngine {
|
|||
// TODO: can this be done smarter? I think there are usually more indices/aliases in the cluster then indices defined a roles?
|
||||
if (includeDataStreams) {
|
||||
for (IndexAbstraction indexAbstraction : lookup.values()) {
|
||||
if (predicate.test(indexAbstraction)) {
|
||||
// failure indices are special: when accessed directly (not through ::failures on parent data stream) they are accessed
|
||||
// implicitly as data. However, authz to the parent data stream happens via the failures selector
|
||||
if (indexAbstraction.isFailureIndexOfDataStream()
|
||||
&& predicate.test(indexAbstraction.getParentDataStream(), IndexComponentSelector.FAILURES)) {
|
||||
indicesAndAliases.add(indexAbstraction.getName());
|
||||
// we know this is a failure index, and it's authorized so no need to check further
|
||||
continue;
|
||||
}
|
||||
if (predicate.test(indexAbstraction, IndexComponentSelector.DATA)) {
|
||||
indicesAndAliases.add(indexAbstraction.getName());
|
||||
if (indexAbstraction.getType() == IndexAbstraction.Type.DATA_STREAM) {
|
||||
// add data stream and its backing indices for any authorized data streams
|
||||
for (Index index : indexAbstraction.getIndices()) {
|
||||
indicesAndAliases.add(index.getName());
|
||||
}
|
||||
// TODO: We need to limit if a data stream's failure indices should return here.
|
||||
for (Index index : ((DataStream) indexAbstraction).getFailureIndices()) {
|
||||
indicesAndAliases.add(index.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (IndexAbstraction indexAbstraction : lookup.values()) {
|
||||
if (indexAbstraction.getType() != IndexAbstraction.Type.DATA_STREAM && predicate.test(indexAbstraction)) {
|
||||
if (indexAbstraction.getType() != IndexAbstraction.Type.DATA_STREAM
|
||||
// data check is correct, even for failure indices -- in this context, we treat them as regular indices, and
|
||||
// only include them if direct data access to them is granted (e.g., if a role has `read` over "*")
|
||||
&& predicate.test(indexAbstraction, IndexComponentSelector.DATA)) {
|
||||
indicesAndAliases.add(indexAbstraction.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
timeChecker.accept(indicesAndAliases);
|
||||
return indicesAndAliases;
|
||||
}, name -> {
|
||||
}, () -> {
|
||||
// TODO handle time checking in a follow-up
|
||||
Set<String> indicesAndAliases = new HashSet<>();
|
||||
if (includeDataStreams) {
|
||||
for (IndexAbstraction indexAbstraction : lookup.values()) {
|
||||
if (indexAbstraction.getType() == IndexAbstraction.Type.DATA_STREAM
|
||||
&& predicate.test(indexAbstraction, IndexComponentSelector.FAILURES)) {
|
||||
indicesAndAliases.add(indexAbstraction.getName());
|
||||
// add data stream and its backing failure indices for any authorized data streams
|
||||
for (Index index : ((DataStream) indexAbstraction).getFailureIndices()) {
|
||||
indicesAndAliases.add(index.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return indicesAndAliases;
|
||||
}, (name, selector) -> {
|
||||
final IndexAbstraction indexAbstraction = lookup.get(name);
|
||||
if (indexAbstraction == null) {
|
||||
// test access (by name) to a resource that does not currently exist
|
||||
// the action handler must handle the case of accessing resources that do not exist
|
||||
return predicate.test(name, null);
|
||||
} else {
|
||||
// We check the parent data stream first if there is one. For testing requested indices, this is most likely
|
||||
// more efficient than checking the index name first because we recommend grant privileges over data stream
|
||||
// instead of backing indices.
|
||||
return (indexAbstraction.getParentDataStream() != null && predicate.test(indexAbstraction.getParentDataStream()))
|
||||
|| predicate.test(indexAbstraction);
|
||||
return predicate.test(name, null, selector);
|
||||
}
|
||||
// We check the parent data stream first if there is one. For testing requested indices, this is most likely
|
||||
// more efficient than checking the index name first because we recommend grant privileges over data stream
|
||||
// instead of backing indices.
|
||||
if (indexAbstraction.getParentDataStream() != null) {
|
||||
if (indexAbstraction.isFailureIndexOfDataStream()) {
|
||||
// access to failure indices is authorized via failures-based selectors on the parent data stream _not_ via data
|
||||
// ones
|
||||
if (predicate.test(indexAbstraction.getParentDataStream(), IndexComponentSelector.FAILURES)) {
|
||||
return true;
|
||||
}
|
||||
} else if (IndexComponentSelector.DATA.equals(selector) || selector == null) {
|
||||
if (predicate.test(indexAbstraction.getParentDataStream(), IndexComponentSelector.DATA)) {
|
||||
return true;
|
||||
}
|
||||
} // we don't support granting access to a backing index with a failure selector via the parent data stream
|
||||
}
|
||||
return predicate.test(indexAbstraction, selector);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1042,23 +1078,32 @@ public class RBACEngine implements AuthorizationEngine {
|
|||
}
|
||||
|
||||
static final class AuthorizedIndices implements AuthorizationEngine.AuthorizedIndices {
|
||||
private final CachedSupplier<Set<String>> authorizedAndAvailableDataResources;
|
||||
private final CachedSupplier<Set<String>> authorizedAndAvailableFailuresResources;
|
||||
private final BiPredicate<String, IndexComponentSelector> isAuthorizedPredicate;
|
||||
|
||||
private final CachedSupplier<Set<String>> allAuthorizedAndAvailableSupplier;
|
||||
private final Predicate<String> isAuthorizedPredicate;
|
||||
|
||||
AuthorizedIndices(Supplier<Set<String>> allAuthorizedAndAvailableSupplier, Predicate<String> isAuthorizedPredicate) {
|
||||
this.allAuthorizedAndAvailableSupplier = CachedSupplier.wrap(allAuthorizedAndAvailableSupplier);
|
||||
AuthorizedIndices(
|
||||
Supplier<Set<String>> authorizedAndAvailableDataResources,
|
||||
Supplier<Set<String>> authorizedAndAvailableFailuresResources,
|
||||
BiPredicate<String, IndexComponentSelector> isAuthorizedPredicate
|
||||
) {
|
||||
this.authorizedAndAvailableDataResources = CachedSupplier.wrap(authorizedAndAvailableDataResources);
|
||||
this.authorizedAndAvailableFailuresResources = CachedSupplier.wrap(authorizedAndAvailableFailuresResources);
|
||||
this.isAuthorizedPredicate = Objects.requireNonNull(isAuthorizedPredicate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<Set<String>> all() {
|
||||
return allAuthorizedAndAvailableSupplier;
|
||||
public Set<String> all(IndexComponentSelector selector) {
|
||||
Objects.requireNonNull(selector, "must specify a selector to get authorized indices");
|
||||
return IndexComponentSelector.FAILURES.equals(selector)
|
||||
? authorizedAndAvailableFailuresResources.get()
|
||||
: authorizedAndAvailableDataResources.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check(String name) {
|
||||
return this.isAuthorizedPredicate.test(name);
|
||||
public boolean check(String name, IndexComponentSelector selector) {
|
||||
Objects.requireNonNull(selector, "must specify a selector for authorization check");
|
||||
return isAuthorizedPredicate.test(name, selector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ public class RestGetBuiltinPrivilegesAction extends SecurityBaseRestHandler {
|
|||
|
||||
private static final Logger logger = LogManager.getLogger(RestGetBuiltinPrivilegesAction.class);
|
||||
// TODO remove this once we can update docs tests again
|
||||
private static final Set<String> FAILURE_STORE_PRIVILEGES_TO_EXCLUDE = Set.of("read_failure_store");
|
||||
private static final Set<String> FAILURE_STORE_PRIVILEGES_TO_EXCLUDE = Set.of("read_failure_store", "manage_failure_store");
|
||||
private final GetBuiltinPrivilegesResponseTranslator responseTranslator;
|
||||
|
||||
public RestGetBuiltinPrivilegesAction(
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.elasticsearch.xpack.security.authz;
|
|||
|
||||
import org.elasticsearch.action.admin.indices.resolve.ResolveIndexAction;
|
||||
import org.elasticsearch.action.search.TransportSearchAction;
|
||||
import org.elasticsearch.action.support.IndexComponentSelector;
|
||||
import org.elasticsearch.action.support.PlainActionFuture;
|
||||
import org.elasticsearch.cluster.metadata.AliasMetadata;
|
||||
import org.elasticsearch.cluster.metadata.DataStream;
|
||||
|
@ -55,7 +56,7 @@ public class AuthorizedIndicesTests extends ESTestCase {
|
|||
Metadata.EMPTY_METADATA.getProject().getIndicesLookup(),
|
||||
() -> ignore -> {}
|
||||
);
|
||||
assertTrue(authorizedIndices.all().get().isEmpty());
|
||||
assertTrue(authorizedIndices.all(IndexComponentSelector.DATA).isEmpty());
|
||||
}
|
||||
|
||||
public void testAuthorizedIndicesUserWithSomeRoles() {
|
||||
|
@ -115,15 +116,15 @@ public class AuthorizedIndicesTests extends ESTestCase {
|
|||
metadata.getProject().getIndicesLookup(),
|
||||
() -> ignore -> {}
|
||||
);
|
||||
assertThat(authorizedIndices.all().get(), containsInAnyOrder("a1", "a2", "aaaaaa", "b", "ab"));
|
||||
assertThat(authorizedIndices.all().get(), not(contains("bbbbb")));
|
||||
assertThat(authorizedIndices.check("bbbbb"), is(false));
|
||||
assertThat(authorizedIndices.all().get(), not(contains("ba")));
|
||||
assertThat(authorizedIndices.check("ba"), is(false));
|
||||
assertThat(authorizedIndices.all().get(), not(contains(internalSecurityIndex)));
|
||||
assertThat(authorizedIndices.check(internalSecurityIndex), is(false));
|
||||
assertThat(authorizedIndices.all().get(), not(contains(SecuritySystemIndices.SECURITY_MAIN_ALIAS)));
|
||||
assertThat(authorizedIndices.check(SecuritySystemIndices.SECURITY_MAIN_ALIAS), is(false));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), containsInAnyOrder("a1", "a2", "aaaaaa", "b", "ab"));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), not(contains("bbbbb")));
|
||||
assertThat(authorizedIndices.check("bbbbb", IndexComponentSelector.DATA), is(false));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), not(contains("ba")));
|
||||
assertThat(authorizedIndices.check("ba", IndexComponentSelector.DATA), is(false));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), not(contains(internalSecurityIndex)));
|
||||
assertThat(authorizedIndices.check(internalSecurityIndex, IndexComponentSelector.DATA), is(false));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), not(contains(SecuritySystemIndices.SECURITY_MAIN_ALIAS)));
|
||||
assertThat(authorizedIndices.check(SecuritySystemIndices.SECURITY_MAIN_ALIAS, IndexComponentSelector.DATA), is(false));
|
||||
}
|
||||
|
||||
public void testAuthorizedIndicesUserWithSomeRolesEmptyMetadata() {
|
||||
|
@ -134,7 +135,7 @@ public class AuthorizedIndicesTests extends ESTestCase {
|
|||
Metadata.EMPTY_METADATA.getProject().getIndicesLookup(),
|
||||
() -> ignore -> {}
|
||||
);
|
||||
assertTrue(authorizedIndices.all().get().isEmpty());
|
||||
assertTrue(authorizedIndices.all(IndexComponentSelector.DATA).isEmpty());
|
||||
}
|
||||
|
||||
public void testSecurityIndicesAreRemovedFromRegularUser() {
|
||||
|
@ -145,7 +146,7 @@ public class AuthorizedIndicesTests extends ESTestCase {
|
|||
Metadata.EMPTY_METADATA.getProject().getIndicesLookup(),
|
||||
() -> ignore -> {}
|
||||
);
|
||||
assertTrue(authorizedIndices.all().get().isEmpty());
|
||||
assertTrue(authorizedIndices.all(IndexComponentSelector.DATA).isEmpty());
|
||||
}
|
||||
|
||||
public void testSecurityIndicesAreRestrictedForDefaultRole() {
|
||||
|
@ -177,13 +178,13 @@ public class AuthorizedIndicesTests extends ESTestCase {
|
|||
metadata.getProject().getIndicesLookup(),
|
||||
() -> ignore -> {}
|
||||
);
|
||||
assertThat(authorizedIndices.all().get(), containsInAnyOrder("an-index", "another-index"));
|
||||
assertThat(authorizedIndices.check("an-index"), is(true));
|
||||
assertThat(authorizedIndices.check("another-index"), is(true));
|
||||
assertThat(authorizedIndices.all().get(), not(contains(internalSecurityIndex)));
|
||||
assertThat(authorizedIndices.check(internalSecurityIndex), is(false));
|
||||
assertThat(authorizedIndices.all().get(), not(contains(SecuritySystemIndices.SECURITY_MAIN_ALIAS)));
|
||||
assertThat(authorizedIndices.check(SecuritySystemIndices.SECURITY_MAIN_ALIAS), is(false));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), containsInAnyOrder("an-index", "another-index"));
|
||||
assertThat(authorizedIndices.check("an-index", IndexComponentSelector.DATA), is(true));
|
||||
assertThat(authorizedIndices.check("another-index", IndexComponentSelector.DATA), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), not(contains(internalSecurityIndex)));
|
||||
assertThat(authorizedIndices.check(internalSecurityIndex, IndexComponentSelector.DATA), is(false));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), not(contains(SecuritySystemIndices.SECURITY_MAIN_ALIAS)));
|
||||
assertThat(authorizedIndices.check(SecuritySystemIndices.SECURITY_MAIN_ALIAS, IndexComponentSelector.DATA), is(false));
|
||||
}
|
||||
|
||||
public void testSecurityIndicesAreNotRemovedFromUnrestrictedRole() {
|
||||
|
@ -216,7 +217,7 @@ public class AuthorizedIndicesTests extends ESTestCase {
|
|||
() -> ignore -> {}
|
||||
);
|
||||
assertThat(
|
||||
authorizedIndices.all().get(),
|
||||
authorizedIndices.all(IndexComponentSelector.DATA),
|
||||
containsInAnyOrder("an-index", "another-index", SecuritySystemIndices.SECURITY_MAIN_ALIAS, internalSecurityIndex)
|
||||
);
|
||||
|
||||
|
@ -227,7 +228,7 @@ public class AuthorizedIndicesTests extends ESTestCase {
|
|||
() -> ignore -> {}
|
||||
);
|
||||
assertThat(
|
||||
authorizedIndicesSuperUser.all().get(),
|
||||
authorizedIndicesSuperUser.all(IndexComponentSelector.DATA),
|
||||
containsInAnyOrder("an-index", "another-index", SecuritySystemIndices.SECURITY_MAIN_ALIAS, internalSecurityIndex)
|
||||
);
|
||||
}
|
||||
|
@ -274,10 +275,92 @@ public class AuthorizedIndicesTests extends ESTestCase {
|
|||
true
|
||||
)
|
||||
.put(new IndexMetadata.Builder(backingIndex).settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(DataStreamTestHelper.newInstance("adatastream1", List.of(new Index(backingIndex, "_na_"))))
|
||||
.build();
|
||||
final PlainActionFuture<Role> future = new PlainActionFuture<>();
|
||||
final Set<RoleDescriptor> descriptors = Sets.newHashSet(aStarRole, bRole);
|
||||
CompositeRolesStore.buildRoleFromDescriptors(
|
||||
descriptors,
|
||||
new FieldPermissionsCache(Settings.EMPTY),
|
||||
null,
|
||||
RESTRICTED_INDICES,
|
||||
future
|
||||
);
|
||||
Role roles = future.actionGet();
|
||||
AuthorizedIndices authorizedIndices = RBACEngine.resolveAuthorizedIndicesFromRole(
|
||||
roles,
|
||||
getRequestInfo(TransportSearchAction.TYPE.name()),
|
||||
metadata.getProject().getIndicesLookup(),
|
||||
() -> ignore -> {}
|
||||
);
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), containsInAnyOrder("a1", "a2", "aaaaaa", "b", "ab"));
|
||||
for (String resource : List.of("a1", "a2", "aaaaaa", "b", "ab")) {
|
||||
assertThat(authorizedIndices.check(resource, IndexComponentSelector.DATA), is(true));
|
||||
}
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), not(contains("bbbbb")));
|
||||
assertThat(authorizedIndices.check("bbbbb", IndexComponentSelector.DATA), is(false));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), not(contains("ba")));
|
||||
assertThat(authorizedIndices.check("ba", IndexComponentSelector.DATA), is(false));
|
||||
// due to context, datastreams are excluded from wildcard expansion
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), not(contains("adatastream1")));
|
||||
// but they are authorized when explicitly tested (they are not "unavailable" for the Security filter)
|
||||
assertThat(authorizedIndices.check("adatastream1", IndexComponentSelector.DATA), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), not(contains(internalSecurityIndex)));
|
||||
assertThat(authorizedIndices.check(internalSecurityIndex, IndexComponentSelector.DATA), is(false));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), not(contains(SecuritySystemIndices.SECURITY_MAIN_ALIAS)));
|
||||
assertThat(authorizedIndices.check(SecuritySystemIndices.SECURITY_MAIN_ALIAS, IndexComponentSelector.DATA), is(false));
|
||||
}
|
||||
|
||||
public void testDataStreamsAreNotIncludedInAuthorizedIndicesWithFailuresSelectorAndAllPrivilege() {
|
||||
assumeTrue("requires failure store", DataStream.isFailureStoreFeatureFlagEnabled());
|
||||
RoleDescriptor aStarRole = new RoleDescriptor(
|
||||
"a_star",
|
||||
null,
|
||||
new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a*").privileges("all").build() },
|
||||
null
|
||||
);
|
||||
RoleDescriptor bRole = new RoleDescriptor(
|
||||
"b",
|
||||
null,
|
||||
new IndicesPrivileges[] { IndicesPrivileges.builder().indices("b").privileges("READ").build() },
|
||||
null
|
||||
);
|
||||
Settings indexSettings = Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, IndexVersion.current()).build();
|
||||
final String internalSecurityIndex = randomFrom(
|
||||
TestRestrictedIndices.INTERNAL_SECURITY_MAIN_INDEX_6,
|
||||
TestRestrictedIndices.INTERNAL_SECURITY_MAIN_INDEX_7
|
||||
);
|
||||
String backingIndex = DataStream.getDefaultBackingIndexName("adatastream1", 1);
|
||||
String failureIndex = DataStream.getDefaultFailureStoreName("adatastream1", 1, 1);
|
||||
Metadata metadata = Metadata.builder()
|
||||
.put(new IndexMetadata.Builder("a1").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(new IndexMetadata.Builder("a2").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(new IndexMetadata.Builder("aaaaaa").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(new IndexMetadata.Builder("bbbbb").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(
|
||||
new IndexMetadata.Builder("b").settings(indexSettings)
|
||||
.numberOfShards(1)
|
||||
.numberOfReplicas(0)
|
||||
.putAlias(new AliasMetadata.Builder("ab").build())
|
||||
.putAlias(new AliasMetadata.Builder("ba").build())
|
||||
.build(),
|
||||
true
|
||||
)
|
||||
.put(
|
||||
new IndexMetadata.Builder(internalSecurityIndex).settings(indexSettings)
|
||||
.numberOfShards(1)
|
||||
.numberOfReplicas(0)
|
||||
.putAlias(new AliasMetadata.Builder(SecuritySystemIndices.SECURITY_MAIN_ALIAS).build())
|
||||
.build(),
|
||||
true
|
||||
)
|
||||
.put(new IndexMetadata.Builder(backingIndex).settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(new IndexMetadata.Builder(failureIndex).settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(
|
||||
DataStreamTestHelper.newInstance(
|
||||
"adatastream1",
|
||||
List.of(new Index(DataStream.getDefaultBackingIndexName("adatastream1", 1), "_na_"))
|
||||
List.of(new Index(backingIndex, "_na_")),
|
||||
List.of(new Index(failureIndex, "_na_"))
|
||||
)
|
||||
)
|
||||
.build();
|
||||
|
@ -297,22 +380,279 @@ public class AuthorizedIndicesTests extends ESTestCase {
|
|||
metadata.getProject().getIndicesLookup(),
|
||||
() -> ignore -> {}
|
||||
);
|
||||
assertThat(authorizedIndices.all().get(), containsInAnyOrder("a1", "a2", "aaaaaa", "b", "ab"));
|
||||
for (String resource : List.of("a1", "a2", "aaaaaa", "b", "ab")) {
|
||||
assertThat(authorizedIndices.check(resource), is(true));
|
||||
}
|
||||
assertThat(authorizedIndices.all().get(), not(contains("bbbbb")));
|
||||
assertThat(authorizedIndices.check("bbbbb"), is(false));
|
||||
assertThat(authorizedIndices.all().get(), not(contains("ba")));
|
||||
assertThat(authorizedIndices.check("ba"), is(false));
|
||||
// due to context, datastreams are excluded from wildcard expansion
|
||||
assertThat(authorizedIndices.all().get(), not(contains("adatastream1")));
|
||||
// but they are authorized when explicitly tested (they are not "unavailable" for the Security filter)
|
||||
assertThat(authorizedIndices.check("adatastream1"), is(true));
|
||||
assertThat(authorizedIndices.all().get(), not(contains(internalSecurityIndex)));
|
||||
assertThat(authorizedIndices.check(internalSecurityIndex), is(false));
|
||||
assertThat(authorizedIndices.all().get(), not(contains(SecuritySystemIndices.SECURITY_MAIN_ALIAS)));
|
||||
assertThat(authorizedIndices.check(SecuritySystemIndices.SECURITY_MAIN_ALIAS), is(false));
|
||||
assertAuthorizedFor(authorizedIndices, IndexComponentSelector.DATA, "a1", "a2", "aaaaaa", "b", "ab");
|
||||
assertAuthorizedFor(authorizedIndices, IndexComponentSelector.FAILURES);
|
||||
|
||||
assertThat(authorizedIndices.check("bbbbb", IndexComponentSelector.DATA), is(false));
|
||||
assertThat(authorizedIndices.check("bbbbb", IndexComponentSelector.FAILURES), is(false));
|
||||
|
||||
assertThat(authorizedIndices.check("ba", IndexComponentSelector.DATA), is(false));
|
||||
assertThat(authorizedIndices.check("ba", IndexComponentSelector.FAILURES), is(false));
|
||||
|
||||
// data are authorized when explicitly tested (they are not "unavailable" for the Security filter)
|
||||
assertThat(authorizedIndices.check("adatastream1", IndexComponentSelector.DATA), is(true));
|
||||
assertThat(authorizedIndices.check("adatastream1", IndexComponentSelector.FAILURES), is(true));
|
||||
|
||||
assertThat(authorizedIndices.check(internalSecurityIndex, IndexComponentSelector.DATA), is(false));
|
||||
assertThat(authorizedIndices.check(internalSecurityIndex, IndexComponentSelector.FAILURES), is(false));
|
||||
assertThat(authorizedIndices.check(SecuritySystemIndices.SECURITY_MAIN_ALIAS, IndexComponentSelector.DATA), is(false));
|
||||
assertThat(authorizedIndices.check(SecuritySystemIndices.SECURITY_MAIN_ALIAS, IndexComponentSelector.FAILURES), is(false));
|
||||
}
|
||||
|
||||
public void testDataStreamsAreIncludedInAuthorizedIndicesWithFailuresSelectorAndAllPrivilege() {
|
||||
assumeTrue("requires failure store", DataStream.isFailureStoreFeatureFlagEnabled());
|
||||
RoleDescriptor aStarRole = new RoleDescriptor(
|
||||
"a_star",
|
||||
null,
|
||||
new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a*").privileges("all").build() },
|
||||
null
|
||||
);
|
||||
RoleDescriptor bRole = new RoleDescriptor(
|
||||
"b",
|
||||
null,
|
||||
new IndicesPrivileges[] { IndicesPrivileges.builder().indices("b").privileges("READ").build() },
|
||||
null
|
||||
);
|
||||
Settings indexSettings = Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, IndexVersion.current()).build();
|
||||
final String internalSecurityIndex = randomFrom(
|
||||
TestRestrictedIndices.INTERNAL_SECURITY_MAIN_INDEX_6,
|
||||
TestRestrictedIndices.INTERNAL_SECURITY_MAIN_INDEX_7
|
||||
);
|
||||
String backingIndex = DataStream.getDefaultBackingIndexName("adatastream1", 1);
|
||||
String failureIndex = DataStream.getDefaultFailureStoreName("adatastream1", 1, 1);
|
||||
Metadata metadata = Metadata.builder()
|
||||
.put(new IndexMetadata.Builder("a1").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(new IndexMetadata.Builder("a2").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(new IndexMetadata.Builder("aaaaaa").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(new IndexMetadata.Builder("bbbbb").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(
|
||||
new IndexMetadata.Builder("b").settings(indexSettings)
|
||||
.numberOfShards(1)
|
||||
.numberOfReplicas(0)
|
||||
.putAlias(new AliasMetadata.Builder("ab").build())
|
||||
.putAlias(new AliasMetadata.Builder("ba").build())
|
||||
.build(),
|
||||
true
|
||||
)
|
||||
.put(
|
||||
new IndexMetadata.Builder(internalSecurityIndex).settings(indexSettings)
|
||||
.numberOfShards(1)
|
||||
.numberOfReplicas(0)
|
||||
.putAlias(new AliasMetadata.Builder(SecuritySystemIndices.SECURITY_MAIN_ALIAS).build())
|
||||
.build(),
|
||||
true
|
||||
)
|
||||
.put(new IndexMetadata.Builder(backingIndex).settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(new IndexMetadata.Builder(failureIndex).settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(
|
||||
DataStreamTestHelper.newInstance(
|
||||
"adatastream1",
|
||||
List.of(new Index(backingIndex, "_na_")),
|
||||
List.of(new Index(failureIndex, "_na_"))
|
||||
)
|
||||
)
|
||||
.build();
|
||||
final PlainActionFuture<Role> future = new PlainActionFuture<>();
|
||||
final Set<RoleDescriptor> descriptors = Sets.newHashSet(aStarRole, bRole);
|
||||
CompositeRolesStore.buildRoleFromDescriptors(
|
||||
descriptors,
|
||||
new FieldPermissionsCache(Settings.EMPTY),
|
||||
null,
|
||||
RESTRICTED_INDICES,
|
||||
future
|
||||
);
|
||||
Role roles = future.actionGet();
|
||||
TransportRequest request = new ResolveIndexAction.Request(new String[] { "a*" });
|
||||
AuthorizationEngine.RequestInfo requestInfo = getRequestInfo(request, TransportSearchAction.TYPE.name());
|
||||
AuthorizedIndices authorizedIndices = RBACEngine.resolveAuthorizedIndicesFromRole(
|
||||
roles,
|
||||
requestInfo,
|
||||
metadata.getProject().getIndicesLookup(),
|
||||
() -> ignore -> {}
|
||||
);
|
||||
assertAuthorizedFor(
|
||||
authorizedIndices,
|
||||
IndexComponentSelector.DATA,
|
||||
"a1",
|
||||
"a2",
|
||||
"aaaaaa",
|
||||
"b",
|
||||
"ab",
|
||||
"adatastream1",
|
||||
backingIndex,
|
||||
failureIndex
|
||||
);
|
||||
assertAuthorizedFor(authorizedIndices, IndexComponentSelector.FAILURES, "adatastream1", failureIndex);
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), not(contains("bbbbb")));
|
||||
assertThat(authorizedIndices.check("bbbbb", IndexComponentSelector.DATA), is(false));
|
||||
assertThat(authorizedIndices.check("bbbbb", IndexComponentSelector.FAILURES), is(false));
|
||||
assertThat(authorizedIndices.check("ba", IndexComponentSelector.DATA), is(false));
|
||||
assertThat(authorizedIndices.check("ba", IndexComponentSelector.FAILURES), is(false));
|
||||
assertThat(authorizedIndices.check(internalSecurityIndex, IndexComponentSelector.DATA), is(false));
|
||||
assertThat(authorizedIndices.check(internalSecurityIndex, IndexComponentSelector.FAILURES), is(false));
|
||||
assertThat(authorizedIndices.check(SecuritySystemIndices.SECURITY_MAIN_ALIAS, IndexComponentSelector.FAILURES), is(false));
|
||||
}
|
||||
|
||||
public void testDataStreamsAreIncludedInAuthorizedIndicesWithFailuresSelector() {
|
||||
assumeTrue("requires failure store", DataStream.isFailureStoreFeatureFlagEnabled());
|
||||
RoleDescriptor aReadFailuresStarRole = new RoleDescriptor(
|
||||
"a_read_failure_store",
|
||||
null,
|
||||
new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a*").privileges("read_failure_store").build() },
|
||||
null
|
||||
);
|
||||
RoleDescriptor aReadRole = new RoleDescriptor(
|
||||
"a_read",
|
||||
null,
|
||||
new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a*").privileges("read").build() },
|
||||
null
|
||||
);
|
||||
Settings indexSettings = Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, IndexVersion.current()).build();
|
||||
final String internalSecurityIndex = randomFrom(
|
||||
TestRestrictedIndices.INTERNAL_SECURITY_MAIN_INDEX_6,
|
||||
TestRestrictedIndices.INTERNAL_SECURITY_MAIN_INDEX_7
|
||||
);
|
||||
String backingIndex = DataStream.getDefaultBackingIndexName("adatastream1", 1);
|
||||
String failureIndex = DataStream.getDefaultFailureStoreName("adatastream1", 1, 1);
|
||||
Metadata metadata = Metadata.builder()
|
||||
.put(new IndexMetadata.Builder("a1").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(new IndexMetadata.Builder("a2").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(new IndexMetadata.Builder("aaaaaa").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(new IndexMetadata.Builder("bbbbb").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(
|
||||
new IndexMetadata.Builder(internalSecurityIndex).settings(indexSettings)
|
||||
.numberOfShards(1)
|
||||
.numberOfReplicas(0)
|
||||
.putAlias(new AliasMetadata.Builder(SecuritySystemIndices.SECURITY_MAIN_ALIAS).build())
|
||||
.build(),
|
||||
true
|
||||
)
|
||||
.put(new IndexMetadata.Builder(backingIndex).settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(new IndexMetadata.Builder(failureIndex).settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(
|
||||
DataStreamTestHelper.newInstance(
|
||||
"adatastream1",
|
||||
List.of(new Index(backingIndex, "_na_")),
|
||||
List.of(new Index(failureIndex, "_na_"))
|
||||
)
|
||||
)
|
||||
.build();
|
||||
final PlainActionFuture<Role> future = new PlainActionFuture<>();
|
||||
final Set<RoleDescriptor> descriptors = Sets.newHashSet(aReadFailuresStarRole, aReadRole);
|
||||
CompositeRolesStore.buildRoleFromDescriptors(
|
||||
descriptors,
|
||||
new FieldPermissionsCache(Settings.EMPTY),
|
||||
null,
|
||||
RESTRICTED_INDICES,
|
||||
future
|
||||
);
|
||||
Role roles = future.actionGet();
|
||||
TransportRequest request = new ResolveIndexAction.Request(new String[] { "a*" });
|
||||
AuthorizationEngine.RequestInfo requestInfo = getRequestInfo(request, TransportSearchAction.TYPE.name());
|
||||
AuthorizedIndices authorizedIndices = RBACEngine.resolveAuthorizedIndicesFromRole(
|
||||
roles,
|
||||
requestInfo,
|
||||
metadata.getProject().getIndicesLookup(),
|
||||
() -> ignore -> {}
|
||||
);
|
||||
assertAuthorizedFor(
|
||||
authorizedIndices,
|
||||
IndexComponentSelector.DATA,
|
||||
"a1",
|
||||
"a2",
|
||||
"aaaaaa",
|
||||
"adatastream1",
|
||||
backingIndex,
|
||||
failureIndex
|
||||
);
|
||||
assertAuthorizedFor(authorizedIndices, IndexComponentSelector.FAILURES, "adatastream1", failureIndex);
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), not(contains("bbbbb")));
|
||||
assertThat(authorizedIndices.check("bbbbb", IndexComponentSelector.DATA), is(false));
|
||||
assertThat(authorizedIndices.check("bbbbb", IndexComponentSelector.FAILURES), is(false));
|
||||
assertThat(authorizedIndices.check("ba", IndexComponentSelector.DATA), is(false));
|
||||
assertThat(authorizedIndices.check("ba", IndexComponentSelector.FAILURES), is(false));
|
||||
assertThat(authorizedIndices.check(internalSecurityIndex, IndexComponentSelector.DATA), is(false));
|
||||
assertThat(authorizedIndices.check(internalSecurityIndex, IndexComponentSelector.FAILURES), is(false));
|
||||
assertThat(authorizedIndices.check(SecuritySystemIndices.SECURITY_MAIN_ALIAS, IndexComponentSelector.FAILURES), is(false));
|
||||
}
|
||||
|
||||
public void testDataStreamsAreNotIncludedInAuthorizedIndicesWithFailuresSelector() {
|
||||
assumeTrue("requires failure store", DataStream.isFailureStoreFeatureFlagEnabled());
|
||||
RoleDescriptor aReadFailuresStarRole = new RoleDescriptor(
|
||||
"a_read_failure_store",
|
||||
null,
|
||||
new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a*").privileges("read_failure_store").build() },
|
||||
null
|
||||
);
|
||||
RoleDescriptor aReadRole = new RoleDescriptor(
|
||||
"a_read",
|
||||
null,
|
||||
new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a*").privileges("read").build() },
|
||||
null
|
||||
);
|
||||
Settings indexSettings = Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, IndexVersion.current()).build();
|
||||
final String internalSecurityIndex = randomFrom(
|
||||
TestRestrictedIndices.INTERNAL_SECURITY_MAIN_INDEX_6,
|
||||
TestRestrictedIndices.INTERNAL_SECURITY_MAIN_INDEX_7
|
||||
);
|
||||
String backingIndex = DataStream.getDefaultBackingIndexName("adatastream1", 1);
|
||||
String failureIndex = DataStream.getDefaultFailureStoreName("adatastream1", 1, 1);
|
||||
Metadata metadata = Metadata.builder()
|
||||
.put(new IndexMetadata.Builder("a1").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(new IndexMetadata.Builder("a2").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(new IndexMetadata.Builder("aaaaaa").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(new IndexMetadata.Builder("bbbbb").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(
|
||||
new IndexMetadata.Builder(internalSecurityIndex).settings(indexSettings)
|
||||
.numberOfShards(1)
|
||||
.numberOfReplicas(0)
|
||||
.putAlias(new AliasMetadata.Builder(SecuritySystemIndices.SECURITY_MAIN_ALIAS).build())
|
||||
.build(),
|
||||
true
|
||||
)
|
||||
.put(new IndexMetadata.Builder(backingIndex).settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(new IndexMetadata.Builder(failureIndex).settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(
|
||||
DataStreamTestHelper.newInstance(
|
||||
"adatastream1",
|
||||
List.of(new Index(backingIndex, "_na_")),
|
||||
List.of(new Index(failureIndex, "_na_"))
|
||||
)
|
||||
)
|
||||
.build();
|
||||
final PlainActionFuture<Role> future = new PlainActionFuture<>();
|
||||
final Set<RoleDescriptor> descriptors = Sets.newHashSet(aReadFailuresStarRole, aReadRole);
|
||||
CompositeRolesStore.buildRoleFromDescriptors(
|
||||
descriptors,
|
||||
new FieldPermissionsCache(Settings.EMPTY),
|
||||
null,
|
||||
RESTRICTED_INDICES,
|
||||
future
|
||||
);
|
||||
Role roles = future.actionGet();
|
||||
AuthorizedIndices authorizedIndices = RBACEngine.resolveAuthorizedIndicesFromRole(
|
||||
roles,
|
||||
getRequestInfo(TransportSearchAction.TYPE.name()),
|
||||
metadata.getProject().getIndicesLookup(),
|
||||
() -> ignore -> {}
|
||||
);
|
||||
assertAuthorizedFor(authorizedIndices, IndexComponentSelector.DATA, "a1", "a2", "aaaaaa");
|
||||
assertAuthorizedFor(authorizedIndices, IndexComponentSelector.FAILURES);
|
||||
|
||||
assertThat(authorizedIndices.check("bbbbb", IndexComponentSelector.DATA), is(false));
|
||||
assertThat(authorizedIndices.check("bbbbb", IndexComponentSelector.FAILURES), is(false));
|
||||
|
||||
assertThat(authorizedIndices.check("ba", IndexComponentSelector.DATA), is(false));
|
||||
assertThat(authorizedIndices.check("ba", IndexComponentSelector.FAILURES), is(false));
|
||||
|
||||
// data are authorized when explicitly tested (they are not "unavailable" for the Security filter)
|
||||
assertThat(authorizedIndices.check("adatastream1", IndexComponentSelector.DATA), is(true));
|
||||
assertThat(authorizedIndices.check("adatastream1", IndexComponentSelector.FAILURES), is(true));
|
||||
|
||||
assertThat(authorizedIndices.check(internalSecurityIndex, IndexComponentSelector.DATA), is(false));
|
||||
assertThat(authorizedIndices.check(internalSecurityIndex, IndexComponentSelector.FAILURES), is(false));
|
||||
assertThat(authorizedIndices.check(SecuritySystemIndices.SECURITY_MAIN_ALIAS, IndexComponentSelector.DATA), is(false));
|
||||
assertThat(authorizedIndices.check(SecuritySystemIndices.SECURITY_MAIN_ALIAS, IndexComponentSelector.FAILURES), is(false));
|
||||
}
|
||||
|
||||
public void testDataStreamsAreIncludedInAuthorizedIndices() {
|
||||
|
@ -357,12 +697,7 @@ public class AuthorizedIndicesTests extends ESTestCase {
|
|||
true
|
||||
)
|
||||
.put(new IndexMetadata.Builder(backingIndex).settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true)
|
||||
.put(
|
||||
DataStreamTestHelper.newInstance(
|
||||
"adatastream1",
|
||||
List.of(new Index(DataStream.getDefaultBackingIndexName("adatastream1", 1), "_na_"))
|
||||
)
|
||||
)
|
||||
.put(DataStreamTestHelper.newInstance("adatastream1", List.of(new Index(backingIndex, "_na_"))))
|
||||
.build();
|
||||
final PlainActionFuture<Role> future = new PlainActionFuture<>();
|
||||
final Set<RoleDescriptor> descriptors = Sets.newHashSet(aStarRole, bRole);
|
||||
|
@ -382,15 +717,18 @@ public class AuthorizedIndicesTests extends ESTestCase {
|
|||
metadata.getProject().getIndicesLookup(),
|
||||
() -> ignore -> {}
|
||||
);
|
||||
assertThat(authorizedIndices.all().get(), containsInAnyOrder("a1", "a2", "aaaaaa", "b", "ab", "adatastream1", backingIndex));
|
||||
assertThat(authorizedIndices.all().get(), not(contains("bbbbb")));
|
||||
assertThat(authorizedIndices.check("bbbbb"), is(false));
|
||||
assertThat(authorizedIndices.all().get(), not(contains("ba")));
|
||||
assertThat(authorizedIndices.check("ba"), is(false));
|
||||
assertThat(authorizedIndices.all().get(), not(contains(internalSecurityIndex)));
|
||||
assertThat(authorizedIndices.check(internalSecurityIndex), is(false));
|
||||
assertThat(authorizedIndices.all().get(), not(contains(SecuritySystemIndices.SECURITY_MAIN_ALIAS)));
|
||||
assertThat(authorizedIndices.check(SecuritySystemIndices.SECURITY_MAIN_ALIAS), is(false));
|
||||
assertThat(
|
||||
authorizedIndices.all(IndexComponentSelector.DATA),
|
||||
containsInAnyOrder("a1", "a2", "aaaaaa", "b", "ab", "adatastream1", backingIndex)
|
||||
);
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), not(contains("bbbbb")));
|
||||
assertThat(authorizedIndices.check("bbbbb", IndexComponentSelector.DATA), is(false));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), not(contains("ba")));
|
||||
assertThat(authorizedIndices.check("ba", IndexComponentSelector.DATA), is(false));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), not(contains(internalSecurityIndex)));
|
||||
assertThat(authorizedIndices.check(internalSecurityIndex, IndexComponentSelector.DATA), is(false));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), not(contains(SecuritySystemIndices.SECURITY_MAIN_ALIAS)));
|
||||
assertThat(authorizedIndices.check(SecuritySystemIndices.SECURITY_MAIN_ALIAS, IndexComponentSelector.DATA), is(false));
|
||||
}
|
||||
|
||||
public static AuthorizationEngine.RequestInfo getRequestInfo(String action) {
|
||||
|
@ -410,4 +748,15 @@ public class AuthorizedIndicesTests extends ESTestCase {
|
|||
null
|
||||
);
|
||||
}
|
||||
|
||||
private static void assertAuthorizedFor(
|
||||
AuthorizedIndices authorizedIndices,
|
||||
IndexComponentSelector selector,
|
||||
String... expectedIndices
|
||||
) {
|
||||
assertThat(authorizedIndices.all(selector), containsInAnyOrder(expectedIndices));
|
||||
for (String resource : expectedIndices) {
|
||||
assertThat(authorizedIndices.check(resource, selector), is(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.elasticsearch.action.search.SearchShardsRequest;
|
|||
import org.elasticsearch.action.search.TransportMultiSearchAction;
|
||||
import org.elasticsearch.action.search.TransportSearchAction;
|
||||
import org.elasticsearch.action.search.TransportSearchShardsAction;
|
||||
import org.elasticsearch.action.support.IndexComponentSelector;
|
||||
import org.elasticsearch.action.support.IndicesOptions;
|
||||
import org.elasticsearch.action.support.PlainActionFuture;
|
||||
import org.elasticsearch.action.termvectors.MultiTermVectorsRequest;
|
||||
|
@ -1941,7 +1942,11 @@ public class IndicesAndAliasesResolverTests extends ESTestCase {
|
|||
String index = "logs-00003"; // write index
|
||||
PutMappingRequest request = new PutMappingRequest(Strings.EMPTY_ARRAY).setConcreteIndex(new Index(index, UUIDs.base64UUID()));
|
||||
assert projectMetadata.getIndicesLookup().get("logs-alias").getIndices().size() == 3;
|
||||
String putMappingIndexOrAlias = IndicesAndAliasesResolver.getPutMappingIndexOrAlias(request, "logs-alias"::equals, projectMetadata);
|
||||
String putMappingIndexOrAlias = IndicesAndAliasesResolver.getPutMappingIndexOrAlias(
|
||||
request,
|
||||
(i, s) -> i.equals("logs-alias"),
|
||||
projectMetadata
|
||||
);
|
||||
String message = "user is authorized to access `logs-alias` and the put mapping request is for a write index"
|
||||
+ "so this should have returned the alias name";
|
||||
assertEquals(message, "logs-alias", putMappingIndexOrAlias);
|
||||
|
@ -1951,7 +1956,11 @@ public class IndicesAndAliasesResolverTests extends ESTestCase {
|
|||
String index = "logs-00002"; // read index
|
||||
PutMappingRequest request = new PutMappingRequest(Strings.EMPTY_ARRAY).setConcreteIndex(new Index(index, UUIDs.base64UUID()));
|
||||
assert projectMetadata.getIndicesLookup().get("logs-alias").getIndices().size() == 3;
|
||||
String putMappingIndexOrAlias = IndicesAndAliasesResolver.getPutMappingIndexOrAlias(request, "logs-alias"::equals, projectMetadata);
|
||||
String putMappingIndexOrAlias = IndicesAndAliasesResolver.getPutMappingIndexOrAlias(
|
||||
request,
|
||||
(i, s) -> i.equals("logs-alias"),
|
||||
projectMetadata
|
||||
);
|
||||
String message = "user is authorized to access `logs-alias` and the put mapping request is for a read index"
|
||||
+ "so this should have returned the concrete index as fallback";
|
||||
assertEquals(message, index, putMappingIndexOrAlias);
|
||||
|
@ -2225,14 +2234,14 @@ public class IndicesAndAliasesResolverTests extends ESTestCase {
|
|||
List<String> dataStreams = List.of("logs-foo", "logs-foobar");
|
||||
final AuthorizedIndices authorizedIndices = buildAuthorizedIndices(user, GetAliasesAction.NAME, request);
|
||||
for (String dsName : dataStreams) {
|
||||
assertThat(authorizedIndices.all().get(), hasItem(dsName));
|
||||
assertThat(authorizedIndices.check(dsName), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), hasItem(dsName));
|
||||
assertThat(authorizedIndices.check(dsName, IndexComponentSelector.DATA), is(true));
|
||||
DataStream dataStream = projectMetadata.dataStreams().get(dsName);
|
||||
assertThat(authorizedIndices.all().get(), hasItem(dsName));
|
||||
assertThat(authorizedIndices.check(dsName), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), hasItem(dsName));
|
||||
assertThat(authorizedIndices.check(dsName, IndexComponentSelector.DATA), is(true));
|
||||
for (Index i : dataStream.getIndices()) {
|
||||
assertThat(authorizedIndices.all().get(), hasItem(i.getName()));
|
||||
assertThat(authorizedIndices.check(i.getName()), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), hasItem(i.getName()));
|
||||
assertThat(authorizedIndices.check(i.getName(), IndexComponentSelector.DATA), is(true));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2264,14 +2273,14 @@ public class IndicesAndAliasesResolverTests extends ESTestCase {
|
|||
// data streams and their backing indices should _not_ be in the authorized list since the backing indices
|
||||
// do not match the requested name
|
||||
final AuthorizedIndices authorizedIndices = buildAuthorizedIndices(user, GetAliasesAction.NAME, request);
|
||||
assertThat(authorizedIndices.all().get(), hasItem(dataStreamName));
|
||||
assertThat(authorizedIndices.check(dataStreamName), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), hasItem(dataStreamName));
|
||||
assertThat(authorizedIndices.check(dataStreamName, IndexComponentSelector.DATA), is(true));
|
||||
DataStream dataStream = projectMetadata.dataStreams().get(dataStreamName);
|
||||
assertThat(authorizedIndices.all().get(), hasItem(dataStreamName));
|
||||
assertThat(authorizedIndices.check(dataStreamName), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), hasItem(dataStreamName));
|
||||
assertThat(authorizedIndices.check(dataStreamName, IndexComponentSelector.DATA), is(true));
|
||||
for (Index i : dataStream.getIndices()) {
|
||||
assertThat(authorizedIndices.all().get(), hasItem(i.getName()));
|
||||
assertThat(authorizedIndices.check(i.getName()), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), hasItem(i.getName()));
|
||||
assertThat(authorizedIndices.check(i.getName(), IndexComponentSelector.DATA), is(true));
|
||||
}
|
||||
|
||||
// neither data streams nor their backing indices will be in the resolved list since the backing indices do not match the
|
||||
|
@ -2299,11 +2308,11 @@ public class IndicesAndAliasesResolverTests extends ESTestCase {
|
|||
final AuthorizedIndices authorizedIndices = buildAuthorizedIndices(user, TransportSearchAction.TYPE.name(), request);
|
||||
for (String dsName : expectedDataStreams) {
|
||||
DataStream dataStream = projectMetadata.dataStreams().get(dsName);
|
||||
assertThat(authorizedIndices.all().get(), hasItem(dsName));
|
||||
assertThat(authorizedIndices.check(dsName), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), hasItem(dsName));
|
||||
assertThat(authorizedIndices.check(dsName, IndexComponentSelector.DATA), is(true));
|
||||
for (Index i : dataStream.getIndices()) {
|
||||
assertThat(authorizedIndices.all().get(), hasItem(i.getName()));
|
||||
assertThat(authorizedIndices.check(i.getName()), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), hasItem(i.getName()));
|
||||
assertThat(authorizedIndices.check(i.getName(), IndexComponentSelector.DATA), is(true));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2340,11 +2349,11 @@ public class IndicesAndAliasesResolverTests extends ESTestCase {
|
|||
|
||||
final AuthorizedIndices authorizedIndices = buildAuthorizedIndices(user, TransportSearchAction.TYPE.name(), request);
|
||||
// data streams and their backing indices should be in the authorized list
|
||||
assertThat(authorizedIndices.all().get(), hasItem(dataStreamName));
|
||||
assertThat(authorizedIndices.check(dataStreamName), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), hasItem(dataStreamName));
|
||||
assertThat(authorizedIndices.check(dataStreamName, IndexComponentSelector.DATA), is(true));
|
||||
for (Index i : dataStream.getIndices()) {
|
||||
assertThat(authorizedIndices.all().get(), hasItem(i.getName()));
|
||||
assertThat(authorizedIndices.check(i.getName()), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), hasItem(i.getName()));
|
||||
assertThat(authorizedIndices.check(i.getName(), IndexComponentSelector.DATA), is(true));
|
||||
}
|
||||
|
||||
ResolvedIndices resolvedIndices = defaultIndicesResolver.resolveIndicesAndAliases(
|
||||
|
@ -2373,15 +2382,15 @@ public class IndicesAndAliasesResolverTests extends ESTestCase {
|
|||
final AuthorizedIndices authorizedIndices = buildAuthorizedIndices(user, TransportSearchAction.TYPE.name(), request);
|
||||
for (String dsName : expectedDataStreams) {
|
||||
DataStream dataStream = projectMetadata.dataStreams().get(dsName);
|
||||
assertThat(authorizedIndices.all().get(), hasItem(dsName));
|
||||
assertThat(authorizedIndices.check(dsName), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), hasItem(dsName));
|
||||
assertThat(authorizedIndices.check(dsName, IndexComponentSelector.DATA), is(true));
|
||||
for (Index i : dataStream.getIndices()) {
|
||||
assertThat(authorizedIndices.all().get(), hasItem(i.getName()));
|
||||
assertThat(authorizedIndices.check(i.getName()), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), hasItem(i.getName()));
|
||||
assertThat(authorizedIndices.check(i.getName(), IndexComponentSelector.DATA), is(true));
|
||||
}
|
||||
for (Index i : dataStream.getFailureIndices()) {
|
||||
assertThat(authorizedIndices.all().get(), hasItem(i.getName()));
|
||||
assertThat(authorizedIndices.check(i.getName()), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), hasItem(i.getName()));
|
||||
assertThat(authorizedIndices.check(i.getName(), IndexComponentSelector.DATA), is(true));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2412,16 +2421,16 @@ public class IndicesAndAliasesResolverTests extends ESTestCase {
|
|||
// data streams and their backing indices should _not_ be in the authorized list since the backing indices
|
||||
// did not match the requested pattern and the request does not support data streams
|
||||
final AuthorizedIndices authorizedIndices = buildAuthorizedIndices(user, GetAliasesAction.NAME, request);
|
||||
assertThat(authorizedIndices.all().get(), hasItem(dataStreamName));
|
||||
assertThat(authorizedIndices.check(dataStreamName), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), hasItem(dataStreamName));
|
||||
assertThat(authorizedIndices.check(dataStreamName, IndexComponentSelector.DATA), is(true));
|
||||
DataStream dataStream = projectMetadata.dataStreams().get(dataStreamName);
|
||||
for (Index i : dataStream.getIndices()) {
|
||||
assertThat(authorizedIndices.all().get(), hasItem(i.getName()));
|
||||
assertThat(authorizedIndices.check(i.getName()), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), hasItem(i.getName()));
|
||||
assertThat(authorizedIndices.check(i.getName(), IndexComponentSelector.DATA), is(true));
|
||||
}
|
||||
for (Index i : dataStream.getFailureIndices()) {
|
||||
assertThat(authorizedIndices.all().get(), hasItem(i.getName()));
|
||||
assertThat(authorizedIndices.check(i.getName()), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), hasItem(i.getName()));
|
||||
assertThat(authorizedIndices.check(i.getName(), IndexComponentSelector.DATA), is(true));
|
||||
}
|
||||
|
||||
// neither data streams nor their backing indices will be in the resolved list since the request does not support data streams
|
||||
|
@ -2452,19 +2461,19 @@ public class IndicesAndAliasesResolverTests extends ESTestCase {
|
|||
// data streams should _not_ be in the authorized list but their backing indices that matched both the requested pattern
|
||||
// and the authorized pattern should be in the list
|
||||
final AuthorizedIndices authorizedIndices = buildAuthorizedIndices(user, GetAliasesAction.NAME, request);
|
||||
assertThat(authorizedIndices.all().get(), not(hasItem("logs-foobar")));
|
||||
assertThat(authorizedIndices.check("logs-foobar"), is(false));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), not(hasItem("logs-foobar")));
|
||||
assertThat(authorizedIndices.check("logs-foobar", IndexComponentSelector.DATA), is(false));
|
||||
DataStream dataStream = projectMetadata.dataStreams().get("logs-foobar");
|
||||
assertThat(authorizedIndices.all().get(), not(hasItem(indexName)));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), not(hasItem(indexName)));
|
||||
// request pattern is subset of the authorized pattern, but be aware that patterns are never passed to #check in main code
|
||||
assertThat(authorizedIndices.check(indexName), is(true));
|
||||
assertThat(authorizedIndices.check(indexName, IndexComponentSelector.DATA), is(true));
|
||||
for (Index i : dataStream.getIndices()) {
|
||||
assertThat(authorizedIndices.all().get(), hasItem(i.getName()));
|
||||
assertThat(authorizedIndices.check(i.getName()), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), hasItem(i.getName()));
|
||||
assertThat(authorizedIndices.check(i.getName(), IndexComponentSelector.DATA), is(true));
|
||||
}
|
||||
for (Index i : dataStream.getFailureIndices()) {
|
||||
assertThat(authorizedIndices.all().get(), hasItem(i.getName()));
|
||||
assertThat(authorizedIndices.check(i.getName()), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), hasItem(i.getName()));
|
||||
assertThat(authorizedIndices.check(i.getName(), IndexComponentSelector.DATA), is(true));
|
||||
}
|
||||
|
||||
// only the backing indices will be in the resolved list since the request does not support data streams
|
||||
|
@ -2492,14 +2501,14 @@ public class IndicesAndAliasesResolverTests extends ESTestCase {
|
|||
// data streams should _not_ be in the authorized list but a single backing index that matched the requested pattern
|
||||
// and the authorized name should be in the list
|
||||
final AuthorizedIndices authorizedIndices = buildAuthorizedIndices(user, GetAliasesAction.NAME, request);
|
||||
assertThat(authorizedIndices.all().get(), not(hasItem("logs-foobar")));
|
||||
assertThat(authorizedIndices.check("logs-foobar"), is(false));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), not(hasItem("logs-foobar")));
|
||||
assertThat(authorizedIndices.check("logs-foobar", IndexComponentSelector.DATA), is(false));
|
||||
|
||||
String expectedIndex = failureStore
|
||||
? DataStream.getDefaultFailureStoreName("logs-foobar", 1, System.currentTimeMillis())
|
||||
: DataStream.getDefaultBackingIndexName("logs-foobar", 1);
|
||||
assertThat(authorizedIndices.all().get(), hasItem(expectedIndex));
|
||||
assertThat(authorizedIndices.check(expectedIndex), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), hasItem(expectedIndex));
|
||||
assertThat(authorizedIndices.check(expectedIndex, IndexComponentSelector.DATA), is(true));
|
||||
|
||||
// only the single backing index will be in the resolved list since the request does not support data streams
|
||||
// but one of the backing indices matched the requested pattern
|
||||
|
@ -2524,19 +2533,19 @@ public class IndicesAndAliasesResolverTests extends ESTestCase {
|
|||
// data streams should _not_ be in the authorized list but their backing indices that matched both the requested pattern
|
||||
// and the authorized pattern should be in the list
|
||||
final AuthorizedIndices authorizedIndices = buildAuthorizedIndices(user, GetAliasesAction.NAME, request);
|
||||
assertThat(authorizedIndices.all().get(), not(hasItem("logs-foobar")));
|
||||
assertThat(authorizedIndices.check("logs-foobar"), is(false));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), not(hasItem("logs-foobar")));
|
||||
assertThat(authorizedIndices.check("logs-foobar", IndexComponentSelector.DATA), is(false));
|
||||
DataStream dataStream = projectMetadata.dataStreams().get("logs-foobar");
|
||||
assertThat(authorizedIndices.all().get(), not(hasItem(indexName)));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), not(hasItem(indexName)));
|
||||
// request pattern is subset of the authorized pattern, but be aware that patterns are never passed to #check in main code
|
||||
assertThat(authorizedIndices.check(indexName), is(true));
|
||||
assertThat(authorizedIndices.check(indexName, IndexComponentSelector.DATA), is(true));
|
||||
for (Index i : dataStream.getIndices()) {
|
||||
assertThat(authorizedIndices.all().get(), hasItem(i.getName()));
|
||||
assertThat(authorizedIndices.check(i.getName()), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), hasItem(i.getName()));
|
||||
assertThat(authorizedIndices.check(i.getName(), IndexComponentSelector.DATA), is(true));
|
||||
}
|
||||
for (Index i : dataStream.getFailureIndices()) {
|
||||
assertThat(authorizedIndices.all().get(), hasItem(i.getName()));
|
||||
assertThat(authorizedIndices.check(i.getName()), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), hasItem(i.getName()));
|
||||
assertThat(authorizedIndices.check(i.getName(), IndexComponentSelector.DATA), is(true));
|
||||
}
|
||||
|
||||
// only the backing indices will be in the resolved list since the request does not support data streams
|
||||
|
@ -2567,10 +2576,10 @@ public class IndicesAndAliasesResolverTests extends ESTestCase {
|
|||
? DataStream.getDefaultFailureStoreName("logs-foobar", 1, System.currentTimeMillis())
|
||||
: DataStream.getDefaultBackingIndexName("logs-foobar", 1);
|
||||
final AuthorizedIndices authorizedIndices = buildAuthorizedIndices(user, GetAliasesAction.NAME, request);
|
||||
assertThat(authorizedIndices.all().get(), not(hasItem("logs-foobar")));
|
||||
assertThat(authorizedIndices.check("logs-foobar"), is(false));
|
||||
assertThat(authorizedIndices.all().get(), hasItem(expectedIndex));
|
||||
assertThat(authorizedIndices.check(expectedIndex), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), not(hasItem("logs-foobar")));
|
||||
assertThat(authorizedIndices.check("logs-foobar", IndexComponentSelector.DATA), is(false));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), hasItem(expectedIndex));
|
||||
assertThat(authorizedIndices.check(expectedIndex, IndexComponentSelector.DATA), is(true));
|
||||
|
||||
// only the single backing index will be in the resolved list since the request does not support data streams
|
||||
// but one of the backing indices matched the requested pattern
|
||||
|
|
|
@ -19,6 +19,7 @@ import org.elasticsearch.action.delete.TransportDeleteAction;
|
|||
import org.elasticsearch.action.index.TransportIndexAction;
|
||||
import org.elasticsearch.action.search.SearchRequest;
|
||||
import org.elasticsearch.action.search.TransportSearchAction;
|
||||
import org.elasticsearch.action.support.IndexComponentSelector;
|
||||
import org.elasticsearch.action.support.PlainActionFuture;
|
||||
import org.elasticsearch.action.support.SubscribableListener;
|
||||
import org.elasticsearch.client.internal.Client;
|
||||
|
@ -1444,14 +1445,14 @@ public class RBACEngineTests extends ESTestCase {
|
|||
lookup,
|
||||
() -> ignore -> {}
|
||||
);
|
||||
assertThat(authorizedIndices.all().get(), hasItem(dataStreamName));
|
||||
assertThat(authorizedIndices.check(dataStreamName), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA), hasItem(dataStreamName));
|
||||
assertThat(authorizedIndices.check(dataStreamName, IndexComponentSelector.DATA), is(true));
|
||||
assertThat(
|
||||
authorizedIndices.all().get(),
|
||||
hasItems(backingIndices.stream().map(im -> im.getIndex().getName()).collect(Collectors.toList()).toArray(Strings.EMPTY_ARRAY))
|
||||
authorizedIndices.all(IndexComponentSelector.DATA),
|
||||
hasItems(backingIndices.stream().map(im -> im.getIndex().getName()).toList().toArray(Strings.EMPTY_ARRAY))
|
||||
);
|
||||
for (String index : backingIndices.stream().map(im -> im.getIndex().getName()).toList()) {
|
||||
assertThat(authorizedIndices.check(index), is(true));
|
||||
assertThat(authorizedIndices.check(index, IndexComponentSelector.DATA), is(true));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1487,7 +1488,8 @@ public class RBACEngineTests extends ESTestCase {
|
|||
lookup,
|
||||
() -> ignore -> {}
|
||||
);
|
||||
assertThat(authorizedIndices.all().get().isEmpty(), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.DATA).isEmpty(), is(true));
|
||||
assertThat(authorizedIndices.all(IndexComponentSelector.FAILURES).isEmpty(), is(true));
|
||||
}
|
||||
|
||||
public void testNoInfiniteRecursionForRBACAuthorizationInfoHashCode() {
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.elasticsearch.TransportVersion;
|
|||
import org.elasticsearch.action.admin.indices.mapping.put.TransportAutoPutMappingAction;
|
||||
import org.elasticsearch.action.admin.indices.mapping.put.TransportPutMappingAction;
|
||||
import org.elasticsearch.action.search.TransportSearchAction;
|
||||
import org.elasticsearch.action.support.IndexComponentSelector;
|
||||
import org.elasticsearch.cluster.metadata.AliasMetadata;
|
||||
import org.elasticsearch.cluster.metadata.DataStream;
|
||||
import org.elasticsearch.cluster.metadata.DataStreamTestHelper;
|
||||
|
@ -185,6 +186,269 @@ public class IndicesPermissionTests extends ESTestCase {
|
|||
|
||||
}
|
||||
|
||||
public void testAuthorizeDataStreamAccessWithFailuresSelector() {
|
||||
Metadata.Builder builder = Metadata.builder();
|
||||
String dataStreamName = randomAlphaOfLength(6);
|
||||
int numBackingIndices = randomIntBetween(1, 3);
|
||||
List<IndexMetadata> backingIndices = new ArrayList<>();
|
||||
for (int backingIndexNumber = 1; backingIndexNumber <= numBackingIndices; backingIndexNumber++) {
|
||||
backingIndices.add(createBackingIndexMetadata(DataStream.getDefaultBackingIndexName(dataStreamName, backingIndexNumber)));
|
||||
}
|
||||
DataStream ds = DataStreamTestHelper.newInstance(
|
||||
dataStreamName,
|
||||
backingIndices.stream().map(IndexMetadata::getIndex).collect(Collectors.toList())
|
||||
);
|
||||
builder.put(ds);
|
||||
for (IndexMetadata index : backingIndices) {
|
||||
builder.put(index, false);
|
||||
}
|
||||
var metadata = builder.build().getProject();
|
||||
FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY);
|
||||
|
||||
for (var privilege : List.of(IndexPrivilege.ALL, IndexPrivilege.READ)) {
|
||||
Role role = Role.builder(RESTRICTED_INDICES, "_role")
|
||||
.add(
|
||||
new FieldPermissions(fieldPermissionDef(null, null)),
|
||||
null,
|
||||
privilege,
|
||||
randomBoolean(),
|
||||
randomFrom(dataStreamName, dataStreamName + "*")
|
||||
)
|
||||
.build();
|
||||
IndicesAccessControl permissions = role.authorize(
|
||||
TransportSearchAction.TYPE.name(),
|
||||
Sets.newHashSet(randomFrom(dataStreamName, dataStreamName + "::data")),
|
||||
metadata,
|
||||
fieldPermissionsCache
|
||||
);
|
||||
assertThat("for privilege " + privilege, permissions.isGranted(), is(true));
|
||||
assertThat("for privilege " + privilege, permissions.hasIndexPermissions(dataStreamName + "::failures"), is(false));
|
||||
assertThat("for privilege " + privilege, permissions.hasIndexPermissions(dataStreamName), is(true));
|
||||
}
|
||||
|
||||
for (var privilege : List.of(IndexPrivilege.ALL, IndexPrivilege.READ_FAILURE_STORE)) {
|
||||
Role role = Role.builder(RESTRICTED_INDICES, "_role")
|
||||
.add(
|
||||
new FieldPermissions(fieldPermissionDef(null, null)),
|
||||
null,
|
||||
privilege,
|
||||
randomBoolean(),
|
||||
randomFrom(dataStreamName, dataStreamName + "*")
|
||||
)
|
||||
.build();
|
||||
IndicesAccessControl permissions = role.authorize(
|
||||
TransportSearchAction.TYPE.name(),
|
||||
Sets.newHashSet(dataStreamName + "::failures"),
|
||||
metadata,
|
||||
fieldPermissionsCache
|
||||
);
|
||||
assertThat("for privilege " + privilege, permissions.isGranted(), is(true));
|
||||
assertThat("for privilege " + privilege, permissions.hasIndexPermissions(dataStreamName + "::failures"), is(true));
|
||||
assertThat("for privilege " + privilege, permissions.hasIndexPermissions(dataStreamName), is(false));
|
||||
}
|
||||
|
||||
for (var privilege : List.of(IndexPrivilege.ALL, IndexPrivilege.READ_FAILURE_STORE)) {
|
||||
Role role = Role.builder(RESTRICTED_INDICES, "_role")
|
||||
.add(
|
||||
new FieldPermissions(fieldPermissionDef(null, null)),
|
||||
null,
|
||||
privilege,
|
||||
randomBoolean(),
|
||||
randomFrom(dataStreamName, dataStreamName + "*")
|
||||
)
|
||||
.build();
|
||||
IndicesAccessControl permissions = role.authorize(
|
||||
TransportSearchAction.TYPE.name(),
|
||||
Sets.newHashSet(dataStreamName + "::failures"),
|
||||
metadata,
|
||||
fieldPermissionsCache
|
||||
);
|
||||
|
||||
assertThat("for privilege " + privilege, permissions.isGranted(), is(true));
|
||||
assertThat("for privilege " + privilege, permissions.hasIndexPermissions(dataStreamName + "::failures"), is(true));
|
||||
assertThat("for privilege " + privilege, permissions.hasIndexPermissions(dataStreamName), is(false));
|
||||
}
|
||||
|
||||
{
|
||||
Role role = Role.builder(RESTRICTED_INDICES, "_role")
|
||||
.add(
|
||||
new FieldPermissions(fieldPermissionDef(null, null)),
|
||||
null,
|
||||
IndexPrivilege.READ,
|
||||
randomBoolean(),
|
||||
randomFrom(dataStreamName, dataStreamName + "*")
|
||||
)
|
||||
.add(
|
||||
new FieldPermissions(fieldPermissionDef(null, null)),
|
||||
null,
|
||||
IndexPrivilege.READ_FAILURE_STORE,
|
||||
randomBoolean(),
|
||||
randomFrom(dataStreamName, dataStreamName + "*")
|
||||
)
|
||||
.build();
|
||||
IndicesAccessControl permissions = role.authorize(
|
||||
TransportSearchAction.TYPE.name(),
|
||||
Sets.newHashSet(randomFrom(dataStreamName, dataStreamName + "::data"), dataStreamName + "::failures"),
|
||||
metadata,
|
||||
fieldPermissionsCache
|
||||
);
|
||||
assertThat(permissions.isGranted(), is(true));
|
||||
assertThat(permissions.hasIndexPermissions(dataStreamName + "::failures"), is(true));
|
||||
assertThat(permissions.hasIndexPermissions(dataStreamName), is(true));
|
||||
}
|
||||
|
||||
{
|
||||
Role role = Role.builder(RESTRICTED_INDICES, "_role")
|
||||
.add(
|
||||
new FieldPermissions(fieldPermissionDef(null, null)),
|
||||
null,
|
||||
IndexPrivilege.ALL,
|
||||
randomBoolean(),
|
||||
randomFrom(dataStreamName, dataStreamName + "*")
|
||||
)
|
||||
.build();
|
||||
IndicesAccessControl permissions = role.authorize(
|
||||
TransportSearchAction.TYPE.name(),
|
||||
Sets.newHashSet(randomFrom(dataStreamName, dataStreamName + "::data"), dataStreamName + "::failures"),
|
||||
metadata,
|
||||
fieldPermissionsCache
|
||||
);
|
||||
assertThat("for privilege " + IndexPrivilege.ALL, permissions.isGranted(), is(true));
|
||||
assertThat("for privilege " + IndexPrivilege.ALL, permissions.hasIndexPermissions(dataStreamName + "::failures"), is(true));
|
||||
assertThat("for privilege " + IndexPrivilege.ALL, permissions.hasIndexPermissions(dataStreamName), is(true));
|
||||
}
|
||||
{
|
||||
Role role = Role.builder(RESTRICTED_INDICES, "_role")
|
||||
.add(
|
||||
new FieldPermissions(fieldPermissionDef(null, null)),
|
||||
null,
|
||||
IndexPrivilege.READ_FAILURE_STORE,
|
||||
randomBoolean(),
|
||||
randomFrom(dataStreamName, dataStreamName + "*")
|
||||
)
|
||||
.build();
|
||||
IndicesAccessControl permissions = role.authorize(
|
||||
TransportSearchAction.TYPE.name(),
|
||||
Sets.newHashSet(randomFrom(dataStreamName, dataStreamName + "::data"), dataStreamName + "::failures"),
|
||||
metadata,
|
||||
fieldPermissionsCache
|
||||
);
|
||||
assertThat("for privilege " + IndexPrivilege.READ_FAILURE_STORE, permissions.isGranted(), is(false));
|
||||
assertThat(
|
||||
"for privilege " + IndexPrivilege.READ_FAILURE_STORE,
|
||||
permissions.hasIndexPermissions(dataStreamName + "::failures"),
|
||||
is(true)
|
||||
);
|
||||
assertThat("for privilege " + IndexPrivilege.READ_FAILURE_STORE, permissions.hasIndexPermissions(dataStreamName), is(false));
|
||||
}
|
||||
|
||||
{
|
||||
Role role = Role.builder(RESTRICTED_INDICES, "_role")
|
||||
.add(
|
||||
new FieldPermissions(fieldPermissionDef(null, null)),
|
||||
null,
|
||||
IndexPrivilege.READ,
|
||||
randomBoolean(),
|
||||
randomFrom(dataStreamName, dataStreamName + "*")
|
||||
)
|
||||
.build();
|
||||
IndicesAccessControl permissions = role.authorize(
|
||||
TransportSearchAction.TYPE.name(),
|
||||
Sets.newHashSet(randomFrom(dataStreamName, dataStreamName + "::data"), dataStreamName + "::failures"),
|
||||
metadata,
|
||||
fieldPermissionsCache
|
||||
);
|
||||
assertThat("for privilege " + IndexPrivilege.READ, permissions.isGranted(), is(false));
|
||||
assertThat("for privilege " + IndexPrivilege.READ, permissions.hasIndexPermissions(dataStreamName + "::failures"), is(false));
|
||||
assertThat("for privilege " + IndexPrivilege.READ, permissions.hasIndexPermissions(dataStreamName), is(true));
|
||||
}
|
||||
}
|
||||
|
||||
public void testAuthorizeDataStreamFailureIndices() {
|
||||
Metadata.Builder builder = Metadata.builder();
|
||||
String dataStreamName = randomAlphaOfLength(6);
|
||||
int numBackingIndices = randomIntBetween(1, 3);
|
||||
List<IndexMetadata> backingIndices = new ArrayList<>();
|
||||
for (int backingIndexNumber = 1; backingIndexNumber <= numBackingIndices; backingIndexNumber++) {
|
||||
backingIndices.add(createBackingIndexMetadata(DataStream.getDefaultBackingIndexName(dataStreamName, backingIndexNumber)));
|
||||
}
|
||||
List<IndexMetadata> failureIndices = new ArrayList<>();
|
||||
int numFailureIndices = randomIntBetween(1, 3);
|
||||
for (int failureIndexNumber = 1; failureIndexNumber <= numFailureIndices; failureIndexNumber++) {
|
||||
failureIndices.add(createBackingIndexMetadata(DataStream.getDefaultFailureStoreName(dataStreamName, failureIndexNumber, 1L)));
|
||||
}
|
||||
DataStream ds = DataStreamTestHelper.newInstance(
|
||||
dataStreamName,
|
||||
backingIndices.stream().map(IndexMetadata::getIndex).collect(Collectors.toList()),
|
||||
failureIndices.stream().map(IndexMetadata::getIndex).collect(Collectors.toList())
|
||||
);
|
||||
builder.put(ds);
|
||||
for (IndexMetadata index : backingIndices) {
|
||||
builder.put(index, false);
|
||||
}
|
||||
for (IndexMetadata index : failureIndices) {
|
||||
builder.put(index, false);
|
||||
}
|
||||
var metadata = builder.build().getProject();
|
||||
FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY);
|
||||
|
||||
for (var privilege : List.of(IndexPrivilege.READ)) {
|
||||
Role role = Role.builder(RESTRICTED_INDICES, "_role")
|
||||
.add(
|
||||
new FieldPermissions(fieldPermissionDef(null, null)),
|
||||
null,
|
||||
privilege,
|
||||
randomBoolean(),
|
||||
randomFrom(dataStreamName, dataStreamName + "*")
|
||||
)
|
||||
.build();
|
||||
String failureIndex = randomFrom(failureIndices).getIndex().getName();
|
||||
IndicesAccessControl permissions = role.authorize(
|
||||
TransportSearchAction.TYPE.name(),
|
||||
Sets.newHashSet(failureIndex),
|
||||
metadata,
|
||||
fieldPermissionsCache
|
||||
);
|
||||
assertThat("for privilege " + privilege, permissions.isGranted(), is(false));
|
||||
assertThat("for privilege " + privilege, permissions.hasIndexPermissions(failureIndex), is(false));
|
||||
|
||||
String dataIndex = randomFrom(backingIndices).getIndex().getName();
|
||||
permissions = role.authorize(TransportSearchAction.TYPE.name(), Sets.newHashSet(dataIndex), metadata, fieldPermissionsCache);
|
||||
|
||||
assertThat("for privilege " + privilege, permissions.isGranted(), is(true));
|
||||
assertThat("for privilege " + privilege, permissions.hasIndexPermissions(dataIndex), is(true));
|
||||
}
|
||||
|
||||
for (var privilege : List.of(IndexPrivilege.READ_FAILURE_STORE)) {
|
||||
Role role = Role.builder(RESTRICTED_INDICES, "_role")
|
||||
.add(
|
||||
new FieldPermissions(fieldPermissionDef(null, null)),
|
||||
null,
|
||||
privilege,
|
||||
randomBoolean(),
|
||||
randomFrom(dataStreamName, dataStreamName + "*")
|
||||
)
|
||||
.build();
|
||||
|
||||
String failureIndex = randomFrom(failureIndices).getIndex().getName();
|
||||
IndicesAccessControl permissions = role.authorize(
|
||||
TransportSearchAction.TYPE.name(),
|
||||
Sets.newHashSet(failureIndex),
|
||||
metadata,
|
||||
fieldPermissionsCache
|
||||
);
|
||||
|
||||
assertThat("for privilege " + privilege, permissions.isGranted(), is(true));
|
||||
assertThat("for privilege " + privilege, permissions.hasIndexPermissions(failureIndex), is(true));
|
||||
|
||||
String dataIndex = randomFrom(backingIndices).getIndex().getName();
|
||||
permissions = role.authorize(TransportSearchAction.TYPE.name(), Sets.newHashSet(dataIndex), metadata, fieldPermissionsCache);
|
||||
|
||||
assertThat("for privilege " + privilege, permissions.isGranted(), is(false));
|
||||
assertThat("for privilege " + privilege, permissions.hasIndexPermissions(dataIndex), is(false));
|
||||
}
|
||||
}
|
||||
|
||||
public void testAuthorizeMultipleGroupsMixedDls() {
|
||||
IndexMetadata.Builder imbBuilder = IndexMetadata.builder("_index")
|
||||
.settings(indexSettings(IndexVersion.current(), 1, 1))
|
||||
|
@ -705,14 +969,15 @@ public class IndicesPermissionTests extends ESTestCase {
|
|||
);
|
||||
IndicesPermission.IsResourceAuthorizedPredicate predicate = new IndicesPermission.IsResourceAuthorizedPredicate(
|
||||
StringMatcher.of("other"),
|
||||
StringMatcher.of(),
|
||||
StringMatcher.of(dataStreamName, backingIndex.getName(), concreteIndex.getName(), alias.getName())
|
||||
);
|
||||
assertThat(predicate.test(dataStream), is(false));
|
||||
// test authorization for a missing resource with the datastream's name
|
||||
assertThat(predicate.test(dataStream.getName(), null), is(true));
|
||||
assertThat(predicate.test(dataStream.getName(), null, IndexComponentSelector.DATA), is(true));
|
||||
assertThat(predicate.test(backingIndex), is(false));
|
||||
// test authorization for a missing resource with the backing index's name
|
||||
assertThat(predicate.test(backingIndex.getName(), null), is(true));
|
||||
assertThat(predicate.test(backingIndex.getName(), null, IndexComponentSelector.DATA), is(true));
|
||||
assertThat(predicate.test(concreteIndex), is(true));
|
||||
assertThat(predicate.test(alias), is(true));
|
||||
}
|
||||
|
@ -720,10 +985,12 @@ public class IndicesPermissionTests extends ESTestCase {
|
|||
public void testResourceAuthorizedPredicateAnd() {
|
||||
IndicesPermission.IsResourceAuthorizedPredicate predicate1 = new IndicesPermission.IsResourceAuthorizedPredicate(
|
||||
StringMatcher.of("c", "a"),
|
||||
StringMatcher.of(),
|
||||
StringMatcher.of("b", "d")
|
||||
);
|
||||
IndicesPermission.IsResourceAuthorizedPredicate predicate2 = new IndicesPermission.IsResourceAuthorizedPredicate(
|
||||
StringMatcher.of("c", "b"),
|
||||
StringMatcher.of(),
|
||||
StringMatcher.of("a", "d")
|
||||
);
|
||||
Metadata.Builder mb = Metadata.builder(
|
||||
|
@ -754,6 +1021,75 @@ public class IndicesPermissionTests extends ESTestCase {
|
|||
assertThat(predicate.test(concreteIndexD), is(true));
|
||||
}
|
||||
|
||||
public void testResourceAuthorizedPredicateAndWithFailures() {
|
||||
IndicesPermission.IsResourceAuthorizedPredicate predicate1 = new IndicesPermission.IsResourceAuthorizedPredicate(
|
||||
StringMatcher.of("c", "a"),
|
||||
StringMatcher.of("e", "f"),
|
||||
StringMatcher.of("b", "d")
|
||||
);
|
||||
IndicesPermission.IsResourceAuthorizedPredicate predicate2 = new IndicesPermission.IsResourceAuthorizedPredicate(
|
||||
StringMatcher.of("c", "b"),
|
||||
StringMatcher.of("a", "f", "g"),
|
||||
StringMatcher.of("a", "d")
|
||||
);
|
||||
Metadata.Builder mb = Metadata.builder(
|
||||
DataStreamTestHelper.getClusterStateWithDataStreams(
|
||||
List.of(
|
||||
Tuple.tuple("a", 1),
|
||||
Tuple.tuple("b", 1),
|
||||
Tuple.tuple("c", 1),
|
||||
Tuple.tuple("d", 1),
|
||||
Tuple.tuple("e", 1),
|
||||
Tuple.tuple("f", 1)
|
||||
),
|
||||
List.of(),
|
||||
Instant.now().toEpochMilli(),
|
||||
builder().build(),
|
||||
1
|
||||
).getMetadata()
|
||||
);
|
||||
DataStream dataStreamA = mb.dataStream("a");
|
||||
DataStream dataStreamB = mb.dataStream("b");
|
||||
DataStream dataStreamC = mb.dataStream("c");
|
||||
DataStream dataStreamD = mb.dataStream("d");
|
||||
DataStream dataStreamE = mb.dataStream("e");
|
||||
DataStream dataStreamF = mb.dataStream("f");
|
||||
IndexAbstraction concreteIndexA = concreteIndexAbstraction("a");
|
||||
IndexAbstraction concreteIndexB = concreteIndexAbstraction("b");
|
||||
IndexAbstraction concreteIndexC = concreteIndexAbstraction("c");
|
||||
IndexAbstraction concreteIndexD = concreteIndexAbstraction("d");
|
||||
IndexAbstraction concreteIndexE = concreteIndexAbstraction("e");
|
||||
IndexAbstraction concreteIndexF = concreteIndexAbstraction("f");
|
||||
IndicesPermission.IsResourceAuthorizedPredicate predicate = predicate1.and(predicate2);
|
||||
assertThat(predicate.test(dataStreamA), is(false));
|
||||
assertThat(predicate.test(dataStreamB), is(false));
|
||||
assertThat(predicate.test(dataStreamC), is(true));
|
||||
assertThat(predicate.test(dataStreamD), is(false));
|
||||
assertThat(predicate.test(dataStreamE), is(false));
|
||||
assertThat(predicate.test(dataStreamF), is(false));
|
||||
|
||||
assertThat(predicate.test(dataStreamA, IndexComponentSelector.FAILURES), is(false));
|
||||
assertThat(predicate.test(dataStreamB, IndexComponentSelector.FAILURES), is(false));
|
||||
assertThat(predicate.test(dataStreamC, IndexComponentSelector.FAILURES), is(false));
|
||||
assertThat(predicate.test(dataStreamD, IndexComponentSelector.FAILURES), is(false));
|
||||
assertThat(predicate.test(dataStreamE, IndexComponentSelector.FAILURES), is(false));
|
||||
assertThat(predicate.test(dataStreamF, IndexComponentSelector.FAILURES), is(true));
|
||||
|
||||
assertThat(predicate.test(concreteIndexA), is(true));
|
||||
assertThat(predicate.test(concreteIndexB), is(true));
|
||||
assertThat(predicate.test(concreteIndexC), is(true));
|
||||
assertThat(predicate.test(concreteIndexD), is(true));
|
||||
assertThat(predicate.test(concreteIndexE), is(false));
|
||||
assertThat(predicate.test(concreteIndexF), is(false));
|
||||
|
||||
assertThat(predicate.test(concreteIndexA, IndexComponentSelector.FAILURES), is(false));
|
||||
assertThat(predicate.test(concreteIndexB, IndexComponentSelector.FAILURES), is(false));
|
||||
assertThat(predicate.test(concreteIndexC, IndexComponentSelector.FAILURES), is(false));
|
||||
assertThat(predicate.test(concreteIndexD, IndexComponentSelector.FAILURES), is(false));
|
||||
assertThat(predicate.test(concreteIndexE, IndexComponentSelector.FAILURES), is(false));
|
||||
assertThat(predicate.test(concreteIndexF, IndexComponentSelector.FAILURES), is(true));
|
||||
}
|
||||
|
||||
private static IndexAbstraction concreteIndexAbstraction(String name) {
|
||||
return new IndexAbstraction.ConcreteIndex(
|
||||
IndexMetadata.builder(name).settings(indexSettings(IndexVersion.current(), 1, 0)).build()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue