|
@@ -240,35 +240,125 @@ public final class IndicesPermission {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Authorizes the provided action against the provided indices, given the current cluster metadata
|
|
|
+ * Represents the set of data required about an IndexAbstraction (index/alias/datastream) in order to perform authorization on that
|
|
|
+ * object (including setting up the necessary data structures for Field and Document Level Security).
|
|
|
*/
|
|
|
- public Map<String, IndicesAccessControl.IndexAccessControl> authorize(String action, Set<String> requestedIndicesOrAliases,
|
|
|
- Map<String, IndexAbstraction> lookup,
|
|
|
- FieldPermissionsCache fieldPermissionsCache) {
|
|
|
- // now... every index that is associated with the request, must be granted
|
|
|
- // by at least one indices permission group
|
|
|
- Map<String, Set<FieldPermissions>> fieldPermissionsByIndex = new HashMap<>();
|
|
|
- Map<String, DocumentLevelPermissions> roleQueriesByIndex = new HashMap<>();
|
|
|
- Map<String, Boolean> grantedBuilder = new HashMap<>();
|
|
|
+ private static class IndexResource {
|
|
|
+ /**
|
|
|
+ * The name of the IndexAbstraction on which authorization is being performed
|
|
|
+ */
|
|
|
+ private final String name;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The IndexAbstraction on which authorization is being performed, or {@code null} if nothing in the cluster matches the name
|
|
|
+ */
|
|
|
+ @Nullable
|
|
|
+ private final IndexAbstraction indexAbstraction;
|
|
|
+
|
|
|
+ public Collection<String> concreteIndices;
|
|
|
+
|
|
|
+ private IndexResource(String name, @Nullable IndexAbstraction abstraction) {
|
|
|
+ assert name != null : "Resource name cannot be null";
|
|
|
+ assert abstraction == null || abstraction.getName().equals(name) : "Index abstraction has unexpected name ["
|
|
|
+ + abstraction.getName()
|
|
|
+ + "] vs ["
|
|
|
+ + name
|
|
|
+ + "]";
|
|
|
+ this.name = name;
|
|
|
+ this.indexAbstraction = abstraction;
|
|
|
+ }
|
|
|
|
|
|
- final boolean isMappingUpdateAction = isMappingUpdateAction(action);
|
|
|
+ /**
|
|
|
+ * @return {@code true} if-and-only-if this object is related to a data-stream, either by having a
|
|
|
+ * {@link IndexAbstraction#getType()} of {@link IndexAbstraction.Type#DATA_STREAM} or by being the backing index for a
|
|
|
+ * {@link IndexAbstraction#getParentDataStream()} data-stream}.
|
|
|
+ */
|
|
|
+ public boolean isPartOfDataStream() {
|
|
|
+ if (indexAbstraction == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ switch (indexAbstraction.getType()) {
|
|
|
+ case DATA_STREAM:
|
|
|
+ return true;
|
|
|
+ case CONCRETE_INDEX:
|
|
|
+ return indexAbstraction.getParentDataStream() != null;
|
|
|
+ default:
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- for (String indexOrAlias : requestedIndicesOrAliases) {
|
|
|
- final boolean isBackingIndex;
|
|
|
- final boolean isDataStream;
|
|
|
- final Set<String> concreteIndices = new HashSet<>();
|
|
|
- final IndexAbstraction indexAbstraction = lookup.get(indexOrAlias);
|
|
|
- if (indexAbstraction != null) {
|
|
|
- for (IndexMetadata indexMetadata : indexAbstraction.getIndices()) {
|
|
|
- concreteIndices.add(indexMetadata.getIndex().getName());
|
|
|
+ /**
|
|
|
+ * Check whether this object is covered by the provided permission {@link Group}.
|
|
|
+ * For indices that are part of a data-stream, this checks both the index name and the parent data-stream name.
|
|
|
+ * In all other cases, it checks the name of this object only.
|
|
|
+ */
|
|
|
+ public boolean checkIndex(Group group) {
|
|
|
+ final IndexAbstraction.DataStream ds = indexAbstraction == null ? null : indexAbstraction.getParentDataStream();
|
|
|
+ if (ds != null) {
|
|
|
+ if (group.checkIndex(ds.getName())) {
|
|
|
+ return true;
|
|
|
}
|
|
|
- isBackingIndex = indexAbstraction.getType() == IndexAbstraction.Type.CONCRETE_INDEX &&
|
|
|
- indexAbstraction.getParentDataStream() != null;
|
|
|
- isDataStream = indexAbstraction.getType() == IndexAbstraction.Type.DATA_STREAM;
|
|
|
+ }
|
|
|
+ return group.checkIndex(name);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @return the number of distinct objects to which this expansion refers.
|
|
|
+ */
|
|
|
+ public int size() {
|
|
|
+ if (indexAbstraction == null) {
|
|
|
+ return 1;
|
|
|
+ } else if (indexAbstraction.getType() == IndexAbstraction.Type.CONCRETE_INDEX) {
|
|
|
+ return 1;
|
|
|
} else {
|
|
|
- isBackingIndex = isDataStream = false;
|
|
|
+ return 1 + indexAbstraction.getIndices().size();
|
|
|
}
|
|
|
+ }
|
|
|
+
|
|
|
+ public Collection<String> resolveConcreteIndices() {
|
|
|
+ if (indexAbstraction == null) {
|
|
|
+ return List.of();
|
|
|
+ } else if (indexAbstraction.getType() == IndexAbstraction.Type.CONCRETE_INDEX) {
|
|
|
+ return List.of(indexAbstraction.getName());
|
|
|
+ } else {
|
|
|
+ final List<IndexMetadata> indices = indexAbstraction.getIndices();
|
|
|
+ final List<String> concreteIndices = new ArrayList<>(indices.size());
|
|
|
+ for (var idx : indices) {
|
|
|
+ concreteIndices.add(idx.getIndex().getName());
|
|
|
+ }
|
|
|
+ return concreteIndices;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Authorizes the provided action against the provided indices, given the current cluster metadata
|
|
|
+ */
|
|
|
+ public Map<String, IndicesAccessControl.IndexAccessControl> authorize(
|
|
|
+ String action,
|
|
|
+ Set<String> requestedIndicesOrAliases,
|
|
|
+ Map<String, IndexAbstraction> lookup,
|
|
|
+ FieldPermissionsCache fieldPermissionsCache
|
|
|
+ ) {
|
|
|
|
|
|
+ final List<IndexResource> resources = new ArrayList<>(requestedIndicesOrAliases.size());
|
|
|
+ int totalResourceCount = 0;
|
|
|
+
|
|
|
+ for (String indexOrAlias : requestedIndicesOrAliases) {
|
|
|
+ final IndexResource resource = new IndexResource(indexOrAlias, lookup.get(indexOrAlias));
|
|
|
+ resources.add(resource);
|
|
|
+ totalResourceCount += resource.size();
|
|
|
+ }
|
|
|
+
|
|
|
+ // now... every index that is associated with the request, must be granted
|
|
|
+ // by at least one indices permission group
|
|
|
+ final Map<String, Set<FieldPermissions>> fieldPermissionsByIndex = new HashMap<>(totalResourceCount);
|
|
|
+ final Map<String, DocumentLevelPermissions> roleQueriesByIndex = new HashMap<>(totalResourceCount);
|
|
|
+ final Map<String, Boolean> grantedBuilder = new HashMap<>(totalResourceCount);
|
|
|
+
|
|
|
+ final boolean isMappingUpdateAction = isMappingUpdateAction(action);
|
|
|
+
|
|
|
+ for (IndexResource resource : resources) {
|
|
|
// 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
|
|
@@ -276,27 +366,30 @@ public final class IndicesPermission {
|
|
|
boolean bwcGrantMappingUpdate = false;
|
|
|
final List<Runnable> bwcDeprecationLogActions = new ArrayList<>();
|
|
|
|
|
|
+ final Collection<String> concreteIndices = resource.resolveConcreteIndices();
|
|
|
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
|
|
|
- final boolean indexCheck = group.checkIndex(indexOrAlias) ||
|
|
|
- (isBackingIndex && group.checkIndex(indexAbstraction.getParentDataStream().getName()));
|
|
|
- if (indexCheck) {
|
|
|
+ if (resource.checkIndex(group)) {
|
|
|
boolean actionCheck = group.checkAction(action);
|
|
|
granted = granted || actionCheck;
|
|
|
+
|
|
|
// mapping updates are allowed for certain privileges on indices and aliases (but not on data streams),
|
|
|
// outside of the privilege definition
|
|
|
- boolean bwcMappingActionCheck = isMappingUpdateAction && false == isDataStream && false == isBackingIndex &&
|
|
|
- containsPrivilegeThatGrantsMappingUpdatesForBwc(group);
|
|
|
+ boolean bwcMappingActionCheck = isMappingUpdateAction
|
|
|
+ && false == resource.isPartOfDataStream()
|
|
|
+ && containsPrivilegeThatGrantsMappingUpdatesForBwc(group);
|
|
|
bwcGrantMappingUpdate = bwcGrantMappingUpdate || bwcMappingActionCheck;
|
|
|
+
|
|
|
if (actionCheck || bwcMappingActionCheck) {
|
|
|
// propagate DLS and FLS permissions over the concrete indices
|
|
|
for (String index : concreteIndices) {
|
|
|
Set<FieldPermissions> fieldPermissions = fieldPermissionsByIndex.computeIfAbsent(index, (k) -> new HashSet<>());
|
|
|
- fieldPermissionsByIndex.put(indexOrAlias, fieldPermissions);
|
|
|
+ fieldPermissionsByIndex.put(resource.name, fieldPermissions);
|
|
|
fieldPermissions.add(group.getFieldPermissions());
|
|
|
+
|
|
|
DocumentLevelPermissions permissions =
|
|
|
roleQueriesByIndex.computeIfAbsent(index, (k) -> new DocumentLevelPermissions());
|
|
|
- roleQueriesByIndex.putIfAbsent(indexOrAlias, permissions);
|
|
|
+ roleQueriesByIndex.putIfAbsent(resource.name, permissions);
|
|
|
if (group.hasQuery()) {
|
|
|
permissions.addAll(group.getQuery());
|
|
|
} else {
|
|
@@ -311,9 +404,9 @@ public final class IndicesPermission {
|
|
|
if (PRIVILEGE_NAME_SET_BWC_ALLOW_MAPPING_UPDATE.contains(privilegeName)) {
|
|
|
bwcDeprecationLogActions.add(() ->
|
|
|
deprecationLogger.critical(DeprecationCategory.SECURITY,
|
|
|
- "[" + indexOrAlias + "] mapping update for ingest privilege [" +
|
|
|
+ "[" + resource.name + "] mapping update for ingest privilege [" +
|
|
|
privilegeName + "]", "the index privilege [" + privilegeName + "] allowed the update " +
|
|
|
- "mapping action [" + action + "] on index [" + indexOrAlias + "], this privilege " +
|
|
|
+ "mapping action [" + action + "] on index [" + resource.name + "], this privilege " +
|
|
|
"will not permit mapping updates in the next major release - users who require access " +
|
|
|
"to update mappings must be granted explicit privileges")
|
|
|
);
|
|
@@ -330,17 +423,13 @@ public final class IndicesPermission {
|
|
|
bwcDeprecationLogActions.forEach(Runnable::run);
|
|
|
}
|
|
|
|
|
|
- if (concreteIndices.isEmpty()) {
|
|
|
- grantedBuilder.put(indexOrAlias, granted);
|
|
|
- } else {
|
|
|
- grantedBuilder.put(indexOrAlias, granted);
|
|
|
- for (String concreteIndex : concreteIndices) {
|
|
|
- grantedBuilder.put(concreteIndex, granted);
|
|
|
- }
|
|
|
+ grantedBuilder.put(resource.name, granted);
|
|
|
+ for (String concreteIndex : concreteIndices) {
|
|
|
+ grantedBuilder.put(concreteIndex, granted);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- Map<String, IndicesAccessControl.IndexAccessControl> indexPermissions = new HashMap<>();
|
|
|
+ Map<String, IndicesAccessControl.IndexAccessControl> indexPermissions = new HashMap<>(grantedBuilder.size());
|
|
|
for (Map.Entry<String, Boolean> entry : grantedBuilder.entrySet()) {
|
|
|
String index = entry.getKey();
|
|
|
DocumentLevelPermissions permissions = roleQueriesByIndex.get(index);
|
|
@@ -464,7 +553,7 @@ public final class IndicesPermission {
|
|
|
private void addAll(Set<BytesReference> query) {
|
|
|
if (allowAll == false) {
|
|
|
if (queries == null) {
|
|
|
- queries = new HashSet<>();
|
|
|
+ queries = new HashSet<>(query.size());
|
|
|
}
|
|
|
queries.addAll(query);
|
|
|
}
|