Skip to content

Commit

Permalink
Add manage roles privilege
Browse files Browse the repository at this point in the history
  • Loading branch information
jfreden committed Aug 15, 2024
1 parent 5934190 commit e7f995d
Show file tree
Hide file tree
Showing 18 changed files with 1,301 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ static TransportVersion def(int id) {
public static final TransportVersion ML_INFERENCE_EIS_INTEGRATION_ADDED = def(8_720_00_0);
public static final TransportVersion INGEST_PIPELINE_EXCEPTION_ADDED = def(8_721_00_0);
public static final TransportVersion ZDT_NANOS_SUPPORT = def(8_722_00_0);
public static final TransportVersion ADD_MANAGE_ROLES_PRIVILEGE = def(8_723_00_0);
/*
* STOP! READ THIS FIRST! No, really,
* ____ _____ ___ ____ _ ____ _____ _ ____ _____ _ _ ___ ____ _____ ___ ____ ____ _____ _
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public List<NamedWriteableRegistry.Entry> getNamedWriteables() {
new NamedWriteableRegistry.Entry(ClusterState.Custom.class, TokenMetadata.TYPE, TokenMetadata::new),
new NamedWriteableRegistry.Entry(NamedDiff.class, TokenMetadata.TYPE, TokenMetadata::readDiffFrom),
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.SECURITY, SecurityFeatureSetUsage::new),
// security : conditional privileges
// security : configurable cluster privileges
new NamedWriteableRegistry.Entry(
ConfigurableClusterPrivilege.class,
ConfigurableClusterPrivileges.ManageApplicationPrivileges.WRITEABLE_NAME,
Expand All @@ -160,6 +160,11 @@ public List<NamedWriteableRegistry.Entry> getNamedWriteables() {
ConfigurableClusterPrivileges.WriteProfileDataPrivileges.WRITEABLE_NAME,
ConfigurableClusterPrivileges.WriteProfileDataPrivileges::createFrom
),
new NamedWriteableRegistry.Entry(
ConfigurableClusterPrivilege.class,
ConfigurableClusterPrivileges.ManageRolesPrivilege.WRITEABLE_NAME,
ConfigurableClusterPrivileges.ManageRolesPrivilege::createFrom
),
// security : role-mappings
new NamedWriteableRegistry.Entry(Metadata.Custom.class, RoleMappingMetadata.TYPE, RoleMappingMetadata::new),
new NamedWriteableRegistry.Entry(NamedDiff.class, RoleMappingMetadata.TYPE, RoleMappingMetadata::readDiffFrom),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
import org.apache.lucene.util.automaton.Operations;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authz.RestrictedIndices;
import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.xpack.core.security.support.Automatons;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;

/**
Expand Down Expand Up @@ -84,6 +86,16 @@ public static class Builder {
private final List<Automaton> actionAutomatons = new ArrayList<>();
private final List<PermissionCheck> permissionChecks = new ArrayList<>();

private final RestrictedIndices restrictedIndices;

public Builder(RestrictedIndices restrictedIndices) {
this.restrictedIndices = restrictedIndices;
}

public Builder() {
this.restrictedIndices = null;
}

public Builder add(
final ClusterPrivilege clusterPrivilege,
final Set<String> allowedActionPatterns,
Expand All @@ -110,6 +122,16 @@ public Builder add(final ClusterPrivilege clusterPrivilege, final PermissionChec
return this;
}

public Builder addWithPredicateSupplier(
final ClusterPrivilege clusterPrivilege,
final Set<String> allowedActionPatterns,
final Function<RestrictedIndices, Predicate<TransportRequest>> requestPredicateSupplier
) {
final Automaton actionAutomaton = createAutomaton(allowedActionPatterns, Set.of());
Predicate<TransportRequest> requestPredicate = requestPredicateSupplier.apply(restrictedIndices);
return add(clusterPrivilege, new ActionRequestBasedPermissionCheck(clusterPrivilege, actionAutomaton, requestPredicate));
}

public ClusterPermission build() {
if (clusterPrivileges.isEmpty()) {
return NONE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
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.index.Index;
import org.elasticsearch.xpack.core.security.authz.RestrictedIndices;
import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
Expand All @@ -31,7 +32,6 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -86,6 +86,7 @@ public Builder addGroup(
public IndicesPermission build() {
return new IndicesPermission(restrictedIndices, groups.toArray(Group.EMPTY_ARRAY));
}

}

private IndicesPermission(RestrictedIndices restrictedIndices, Group[] groups) {
Expand Down Expand Up @@ -238,6 +239,21 @@ public boolean check(String action) {
return false;
}

public boolean checkResourcePrivileges(
Set<String> checkForIndexPatterns,
boolean allowRestrictedIndices,
Set<String> checkForPrivileges,
@Nullable ResourcePrivilegesMap.Builder resourcePrivilegesMapBuilder
) {
return checkResourcePrivileges(
checkForIndexPatterns,
allowRestrictedIndices,
checkForPrivileges,
false,
resourcePrivilegesMapBuilder
);
}

/**
* For given index patterns and index privileges determines allowed privileges and creates an instance of {@link ResourcePrivilegesMap}
* holding a map of resource to {@link ResourcePrivileges} where resource is index pattern and the map of index privilege to whether it
Expand All @@ -246,6 +262,7 @@ public boolean check(String action) {
* @param checkForIndexPatterns check permission grants for the set of index patterns
* @param allowRestrictedIndices if {@code true} then checks permission grants even for restricted indices by index matching
* @param checkForPrivileges check permission grants for the set of index privileges
* @param combineIndexGroups combine index groups to enable checking against regular expressions
* @param resourcePrivilegesMapBuilder out-parameter for returning the details on which privilege over which resource is granted or not.
* Can be {@code null} when no such details are needed so the method can return early, after
* encountering the first privilege that is not granted over some resource.
Expand All @@ -255,9 +272,9 @@ public boolean checkResourcePrivileges(
Set<String> checkForIndexPatterns,
boolean allowRestrictedIndices,
Set<String> checkForPrivileges,
boolean combineIndexGroups,
@Nullable ResourcePrivilegesMap.Builder resourcePrivilegesMapBuilder
) {
final Map<IndicesPermission.Group, Automaton> predicateCache = new HashMap<>();
boolean allMatch = true;
for (String forIndexPattern : checkForIndexPatterns) {
Automaton checkIndexAutomaton = Automatons.patterns(forIndexPattern);
Expand All @@ -266,15 +283,14 @@ public boolean checkResourcePrivileges(
}
if (false == Operations.isEmpty(checkIndexAutomaton)) {
Automaton allowedIndexPrivilegesAutomaton = null;
for (Group group : groups) {
final Automaton groupIndexAutomaton = predicateCache.computeIfAbsent(group, Group::getIndexMatcherAutomaton);
if (Operations.subsetOf(checkIndexAutomaton, groupIndexAutomaton)) {
for (var indexAndPrivilegeAutomaton : indexGroupAutomatons(combineIndexGroups)) {
if (Operations.subsetOf(checkIndexAutomaton, indexAndPrivilegeAutomaton.v1())) {
if (allowedIndexPrivilegesAutomaton != null) {
allowedIndexPrivilegesAutomaton = Automatons.unionAndMinimize(
Arrays.asList(allowedIndexPrivilegesAutomaton, group.privilege().getAutomaton())
Arrays.asList(allowedIndexPrivilegesAutomaton, indexAndPrivilegeAutomaton.v2())
);
} else {
allowedIndexPrivilegesAutomaton = group.privilege().getAutomaton();
allowedIndexPrivilegesAutomaton = indexAndPrivilegeAutomaton.v2();
}
}
}
Expand Down Expand Up @@ -656,6 +672,58 @@ private static boolean containsPrivilegeThatGrantsMappingUpdatesForBwc(Group gro
return group.privilege().name().stream().anyMatch(PRIVILEGE_NAME_SET_BWC_ALLOW_MAPPING_UPDATE::contains);
}

/**
* Combine index groups to enable checking if a set of index patterns specified using a regular expression grants a set of index
* privileges.
*
* <p>An index group is defined as a set of index patterns and a set of privileges (excluding field permissions and DLS queries).
* {@link IndicesPermission} consist of a set of index groups. For non-regular expression checks, an index pattern is checked against
* each index group, to see if it's a sub-pattern of the index pattern for the group and then if that group grants some or all of the
* privileges requested. For regular expressions it's not sufficient to check per group since the index patterns covered by a group can
* be distinct sets and a regular expressions can cover several distinct sets.
*
* <p>For example the two index groups: {"names": ["a"], "privileges": ["read", "create"]} and {"names": ["b"],
* "privileges": ["read","delete"]} will not match on ["\[ab]\"], while a single index group:
* {"names": ["a", "b"], "privileges": ["read"]} will. This happens because the index groups are evaluated against a request index
* pattern without first being combined. In the example above, the two index patterns should be combined to:
* {"names": ["a", "b"], "privileges": ["read"]} before being checked.
*
*
* @param combine combine index groups to allow for checking against regular expressions
*
* @return a list of tuples of all index and privilege pattern automaton
*/
public List<Tuple<Automaton, Automaton>> indexGroupAutomatons(boolean combine) {
if (groups.length == 0) {
return List.of();
}

List<Tuple<Automaton, Automaton>> allAutomatons = new ArrayList<>();
allAutomatons.add(new Tuple<>(groups[0].getIndexMatcherAutomaton(), groups[0].privilege().getAutomaton()));

for (Group group : groups) {
Automaton indexAutomaton = group.getIndexMatcherAutomaton();
if (combine) {
List<Tuple<Automaton, Automaton>> combinedAutomatons = new ArrayList<>();
for (var indexAndPrivilegeAutomaton : allAutomatons) {
Automaton intersectingPrivileges = Operations.intersection(
indexAndPrivilegeAutomaton.v2(),
group.privilege().getAutomaton()
);
if (Operations.isEmpty(intersectingPrivileges) == false) {
Automaton indexPatternAutomaton = Automatons.unionAndMinimize(
List.of(indexAndPrivilegeAutomaton.v1(), indexAutomaton)
);
combinedAutomatons.add(new Tuple<>(indexPatternAutomaton, intersectingPrivileges));
}
}
allAutomatons.addAll(combinedAutomatons);
}
allAutomatons.add(new Tuple<>(indexAutomaton, group.privilege().getAutomaton()));
}
return allAutomatons;
}

public static class Group {
public static final Group[] EMPTY_ARRAY = new Group[0];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ private Builder(RestrictedIndices restrictedIndices, String[] names) {
}

public Builder cluster(Set<String> privilegeNames, Iterable<ConfigurableClusterPrivilege> configurableClusterPrivileges) {
ClusterPermission.Builder builder = ClusterPermission.builder();
ClusterPermission.Builder builder = new ClusterPermission.Builder(restrictedIndices);
if (privilegeNames.isEmpty() == false) {
for (String name : privilegeNames) {
builder = ClusterPrivilegeResolver.resolve(name).buildPermission(builder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ public interface ConfigurableClusterPrivilege extends NamedWriteable, ToXContent
*/
enum Category {
APPLICATION(new ParseField("application")),
PROFILE(new ParseField("profile"));
PROFILE(new ParseField("profile")),
ROLE(new ParseField("role"));

public final ParseField field;

Expand Down
Loading

0 comments on commit e7f995d

Please sign in to comment.