Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce "ConditionalClusterPrivilege" #32073

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,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 : role-mappings
new NamedWriteableRegistry.Entry(RoleMapperExpression.class, AllExpression.NAME, AllExpression::new),
new NamedWriteableRegistry.Entry(RoleMapperExpression.class, AnyExpression.NAME, AnyExpression::new),
new NamedWriteableRegistry.Entry(RoleMapperExpression.class, FieldExpression.NAME, FieldExpression::new),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges;
import org.elasticsearch.xpack.core.security.support.MetadataUtils;

import java.io.IOException;
Expand Down Expand Up @@ -182,6 +183,7 @@ public RoleDescriptor roleDescriptor() {
clusterPrivileges,
indicesPrivileges.toArray(new RoleDescriptor.IndicesPrivileges[indicesPrivileges.size()]),
applicationPrivileges.toArray(new RoleDescriptor.ApplicationResourcePrivileges[applicationPrivileges.size()]),
ConditionalClusterPrivileges.EMPTY_ARRAY,
runAs,
metadata,
Collections.emptyMap());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges;
import org.elasticsearch.xpack.core.security.support.MetadataUtils;
import org.elasticsearch.xpack.core.security.support.Validation;
import org.elasticsearch.xpack.core.security.xcontent.XContentUtils;
Expand All @@ -48,6 +50,7 @@ public class RoleDescriptor implements ToXContentObject {

private final String name;
private final String[] clusterPrivileges;
private final ConditionalClusterPrivilege[] conditionalClusterPrivileges;
private final IndicesPrivileges[] indicesPrivileges;
private final ApplicationResourcePrivileges[] applicationPrivileges;
private final String[] runAs;
Expand All @@ -62,7 +65,8 @@ public RoleDescriptor(String name,
}

/**
* @deprecated Use {@link #RoleDescriptor(String, String[], IndicesPrivileges[], ApplicationResourcePrivileges[], String[], Map, Map)}
* @deprecated Use {@link #RoleDescriptor(String, String[], IndicesPrivileges[], ApplicationResourcePrivileges[],
* ConditionalClusterPrivilege[], String[], Map, Map)}
*/
@Deprecated
public RoleDescriptor(String name,
Expand All @@ -74,7 +78,8 @@ public RoleDescriptor(String name,
}

/**
* @deprecated Use {@link #RoleDescriptor(String, String[], IndicesPrivileges[], ApplicationResourcePrivileges[], String[], Map, Map)}
* @deprecated Use {@link #RoleDescriptor(String, String[], IndicesPrivileges[], ApplicationResourcePrivileges[],
* ConditionalClusterPrivilege[], String[], Map, Map)}
*/
@Deprecated
public RoleDescriptor(String name,
Expand All @@ -83,18 +88,21 @@ public RoleDescriptor(String name,
@Nullable String[] runAs,
@Nullable Map<String, Object> metadata,
@Nullable Map<String, Object> transientMetadata) {
this(name, clusterPrivileges, indicesPrivileges, null, runAs, metadata, transientMetadata);
this(name, clusterPrivileges, indicesPrivileges, null, null, runAs, metadata, transientMetadata);
}

public RoleDescriptor(String name,
@Nullable String[] clusterPrivileges,
@Nullable IndicesPrivileges[] indicesPrivileges,
@Nullable ApplicationResourcePrivileges[] applicationPrivileges,
@Nullable ConditionalClusterPrivilege[] conditionalClusterPrivileges,
@Nullable String[] runAs,
@Nullable Map<String, Object> metadata,
@Nullable Map<String, Object> transientMetadata) {
this.name = name;
this.clusterPrivileges = clusterPrivileges != null ? clusterPrivileges : Strings.EMPTY_ARRAY;
this.conditionalClusterPrivileges = conditionalClusterPrivileges != null
? conditionalClusterPrivileges : ConditionalClusterPrivileges.EMPTY_ARRAY;
this.indicesPrivileges = indicesPrivileges != null ? indicesPrivileges : IndicesPrivileges.NONE;
this.applicationPrivileges = applicationPrivileges != null ? applicationPrivileges : ApplicationResourcePrivileges.NONE;
this.runAs = runAs != null ? runAs : Strings.EMPTY_ARRAY;
Expand All @@ -111,6 +119,10 @@ public String[] getClusterPrivileges() {
return this.clusterPrivileges;
}

public ConditionalClusterPrivilege[] getConditionalClusterPrivileges() {
return this.conditionalClusterPrivileges;
}

public IndicesPrivileges[] getIndicesPrivileges() {
return this.indicesPrivileges;
}
Expand Down Expand Up @@ -140,6 +152,7 @@ public String toString() {
StringBuilder sb = new StringBuilder("Role[");
sb.append("name=").append(name);
sb.append(", cluster=[").append(Strings.arrayToCommaDelimitedString(clusterPrivileges));
sb.append("], policy=[").append(Strings.arrayToCommaDelimitedString(conditionalClusterPrivileges));
sb.append("], indicesPrivileges=[");
for (IndicesPrivileges group : indicesPrivileges) {
sb.append(group.toString()).append(",");
Expand All @@ -164,6 +177,7 @@ public boolean equals(Object o) {

if (!name.equals(that.name)) return false;
if (!Arrays.equals(clusterPrivileges, that.clusterPrivileges)) return false;
if (!Arrays.equals(conditionalClusterPrivileges, that.conditionalClusterPrivileges)) return false;
if (!Arrays.equals(indicesPrivileges, that.indicesPrivileges)) return false;
if (!Arrays.equals(applicationPrivileges, that.applicationPrivileges)) return false;
if (!metadata.equals(that.getMetadata())) return false;
Expand All @@ -174,6 +188,7 @@ public boolean equals(Object o) {
public int hashCode() {
int result = name.hashCode();
result = 31 * result + Arrays.hashCode(clusterPrivileges);
result = 31 * result + Arrays.hashCode(conditionalClusterPrivileges);
result = 31 * result + Arrays.hashCode(indicesPrivileges);
result = 31 * result + Arrays.hashCode(applicationPrivileges);
result = 31 * result + Arrays.hashCode(runAs);
Expand Down Expand Up @@ -233,13 +248,17 @@ public static RoleDescriptor readFrom(StreamInput in) throws IOException {
}

final ApplicationResourcePrivileges[] applicationPrivileges;
final ConditionalClusterPrivilege[] conditionalClusterPrivileges;
if (in.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
applicationPrivileges = in.readArray(ApplicationResourcePrivileges::createFrom, ApplicationResourcePrivileges[]::new);
conditionalClusterPrivileges = ConditionalClusterPrivileges.readArray(in);
} else {
applicationPrivileges = ApplicationResourcePrivileges.NONE;
conditionalClusterPrivileges = ConditionalClusterPrivileges.EMPTY_ARRAY;
}

return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, applicationPrivileges, runAs, metadata, transientMetadata);
return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, applicationPrivileges, conditionalClusterPrivileges,
runAs, metadata, transientMetadata);
}

public static void writeTo(RoleDescriptor descriptor, StreamOutput out) throws IOException {
Expand All @@ -256,6 +275,7 @@ public static void writeTo(RoleDescriptor descriptor, StreamOutput out) throws I
}
if (out.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) {
out.writeArray(ApplicationResourcePrivileges::write, descriptor.applicationPrivileges);
ConditionalClusterPrivileges.writeArray(out, descriptor.getConditionalClusterPrivileges());
}
}

Expand Down Expand Up @@ -287,6 +307,7 @@ public static RoleDescriptor parse(String name, XContentParser parser, boolean a
String currentFieldName = null;
IndicesPrivileges[] indicesPrivileges = null;
String[] clusterPrivileges = null;
List<ConditionalClusterPrivilege> conditionalClusterPrivileges = Collections.emptyList();
ApplicationResourcePrivileges[] applicationPrivileges = null;
String[] runAsUsers = null;
Map<String, Object> metadata = null;
Expand Down Expand Up @@ -322,7 +343,9 @@ public static RoleDescriptor parse(String name, XContentParser parser, boolean a
throw new ElasticsearchParseException("failed to parse role [{}]. unexpected field [{}]", name, currentFieldName);
}
}
return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, applicationPrivileges, runAsUsers, metadata, null);
return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, applicationPrivileges,
conditionalClusterPrivileges.toArray(new ConditionalClusterPrivilege[conditionalClusterPrivileges.size()]), runAsUsers,
metadata, null);
}

private static String[] readStringArray(String roleName, XContentParser parser, boolean allowNull) throws IOException {
Expand Down Expand Up @@ -377,7 +400,7 @@ public static RoleDescriptor parsePrivilegesCheck(String description, BytesRefer
throw new ElasticsearchParseException("Field [{}] is not supported in a has_privileges request", Fields.QUERY);
}
}
return new RoleDescriptor(description, clusterPrivileges, indexPrivileges, applicationPrivileges, null, null, null);
return new RoleDescriptor(description, clusterPrivileges, indexPrivileges, applicationPrivileges, null, null, null, null);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege;

import java.util.Collection;
import java.util.Set;
Expand Down Expand Up @@ -56,6 +57,10 @@ public static class ConditionalClusterPermission extends ClusterPermission {
private final Predicate<String> actionPredicate;
private final Predicate<TransportRequest> requestPredicate;

public ConditionalClusterPermission(ConditionalClusterPrivilege conditionalPrivilege) {
this(conditionalPrivilege.getPrivilege(), conditionalPrivilege.getRequestPredicate());
}

public ConditionalClusterPermission(ClusterPrivilege privilege, Predicate<TransportRequest> requestPredicate) {
super(privilege);
this.actionPredicate = privilege.predicate();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.Privilege;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -106,11 +108,7 @@ private Builder(String[] names) {

private Builder(RoleDescriptor rd, @Nullable FieldPermissionsCache fieldPermissionsCache) {
this.names = new String[] { rd.getName() };
if (rd.getClusterPrivileges().length == 0) {
cluster = ClusterPermission.SimpleClusterPermission.NONE;
} else {
this.cluster(ClusterPrivilege.get(Sets.newHashSet(rd.getClusterPrivileges())));
}
cluster(Sets.newHashSet(rd.getClusterPrivileges()), Arrays.asList(rd.getConditionalClusterPrivileges()));
groups.addAll(convertFromIndicesPrivileges(rd.getIndicesPrivileges(), fieldPermissionsCache));

final RoleDescriptor.ApplicationResourcePrivileges[] applicationPrivileges = rd.getApplicationPrivileges();
Expand All @@ -124,6 +122,28 @@ private Builder(RoleDescriptor rd, @Nullable FieldPermissionsCache fieldPermissi
}
}

public Builder cluster(Set<String> privilegeNames, Iterable<ConditionalClusterPrivilege> conditionalClusterPrivileges) {
List<ClusterPermission> clusterPermissions = new ArrayList<>();
if (privilegeNames.isEmpty() == false) {
clusterPermissions.add(new ClusterPermission.SimpleClusterPermission(ClusterPrivilege.get(privilegeNames)));
}
for (ConditionalClusterPrivilege ccp : conditionalClusterPrivileges) {
clusterPermissions.add(new ClusterPermission.ConditionalClusterPermission(ccp));
}
if (clusterPermissions.isEmpty()) {
this.cluster = ClusterPermission.SimpleClusterPermission.NONE;
} else if (clusterPermissions.size() == 1) {
this.cluster = clusterPermissions.get(0);
} else {
this.cluster = new ClusterPermission.CompositeClusterPermission(clusterPermissions);
}
return this;
}

/**
* @deprecated Use {@link #cluster(Set, Iterable)}
*/
@Deprecated
public Builder cluster(ClusterPrivilege privilege) {
cluster = new ClusterPermission.SimpleClusterPermission(privilege);
return this;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.core.security.authz.privilege;

import org.elasticsearch.common.io.stream.NamedWriteable;
import org.elasticsearch.transport.TransportRequest;

import java.util.function.Predicate;

/**
* A ConditionalClusterPrivilege is a composition of a {@link ClusterPrivilege} (that determines which actions may be executed)
* with a {@link Predicate} for a {@link TransportRequest} (that determines which requests may be executed).
* The a given execution of an action is considered to be permitted if both the action and the request are permitted.
*/
public interface ConditionalClusterPrivilege extends NamedWriteable {

/**
* The action-level privilege that is required by this conditional privilege.
*/
ClusterPrivilege getPrivilege();

/**
* The request-level privilege (as a {@link Predicate}) that is required by this conditional privilege.
*/
Predicate<TransportRequest> getRequestPredicate();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We currently have special handling for same user privileges. I wonder if it is worth a refactor to change this to a BiPredicate, which would cover that case as well. I'm tossing this out there as an idea and we can always defer this to later on

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good idea - I'll see about adding it in a followup.


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.core.security.authz.privilege;

import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;

import java.io.IOException;

/**
* Static utility class for working with {@link ConditionalClusterPrivilege} instances
*/
public final class ConditionalClusterPrivileges {

public static final ConditionalClusterPrivilege[] EMPTY_ARRAY = new ConditionalClusterPrivilege[0];

private ConditionalClusterPrivileges() {
}

/**
* Utility method to read an array of {@link ConditionalClusterPrivilege} objects from a {@link StreamInput}
*/
public static ConditionalClusterPrivilege[] readArray(StreamInput in) throws IOException {
return in.readArray(in1 ->
in1.readNamedWriteable(ConditionalClusterPrivilege.class), ConditionalClusterPrivilege[]::new);
}

/**
* Utility method to write an array of {@link ConditionalClusterPrivilege} objects to a {@link StreamOutput}
*/
public static void writeArray(StreamOutput out, ConditionalClusterPrivilege[] privileges) throws IOException {
out.writeArray((out1, value) -> out1.writeNamedWriteable(value), privileges);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is small and seems a bit redundant right now, but it will handle the XContent parsing/building when we add that.

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class ReservedRolesStore {
new RoleDescriptor.ApplicationResourcePrivileges[] {
RoleDescriptor.ApplicationResourcePrivileges.builder().application("*").privileges("*").resources("*").build()
},
new String[] { "*" },
null, new String[] { "*" },
MetadataUtils.DEFAULT_RESERVED_METADATA, Collections.emptyMap());
public static final Role SUPERUSER_ROLE = Role.builder(SUPERUSER_ROLE_DESCRIPTOR, null).build();
private static final Map<String, RoleDescriptor> RESERVED_ROLES = initializeReservedRoles();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsDefinition.FieldGrantExcludeGroup;
import org.elasticsearch.xpack.core.security.authz.permission.Role;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.Privilege;
import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
Expand Down Expand Up @@ -253,6 +253,7 @@ public static void buildRoleFromDescriptors(Collection<RoleDescriptor> roleDescr
}

Set<String> clusterPrivileges = new HashSet<>();
final List<ConditionalClusterPrivilege> conditionalClusterPrivileges = new ArrayList<>();
Set<String> runAs = new HashSet<>();
Map<Set<String>, MergeableIndicesPrivilege> indicesPrivilegesMap = new HashMap<>();

Expand All @@ -265,6 +266,9 @@ public static void buildRoleFromDescriptors(Collection<RoleDescriptor> roleDescr
if (descriptor.getClusterPrivileges() != null) {
clusterPrivileges.addAll(Arrays.asList(descriptor.getClusterPrivileges()));
}
if (descriptor.getConditionalClusterPrivileges() != null) {
conditionalClusterPrivileges.addAll(Arrays.asList(descriptor.getConditionalClusterPrivileges()));
}
if (descriptor.getRunAs() != null) {
runAs.addAll(Arrays.asList(descriptor.getRunAs()));
}
Expand Down Expand Up @@ -301,10 +305,9 @@ public static void buildRoleFromDescriptors(Collection<RoleDescriptor> roleDescr
}
}

final Set<String> clusterPrivs = clusterPrivileges.isEmpty() ? null : clusterPrivileges;
final Privilege runAsPrivilege = runAs.isEmpty() ? Privilege.NONE : new Privilege(runAs, runAs.toArray(Strings.EMPTY_ARRAY));
final Role.Builder builder = Role.builder(roleNames.toArray(new String[roleNames.size()]))
.cluster(ClusterPrivilege.get(clusterPrivs))
.cluster(clusterPrivileges, conditionalClusterPrivileges)
.runAs(runAsPrivilege);
indicesPrivilegesMap.entrySet().forEach((entry) -> {
MergeableIndicesPrivilege privilege = entry.getValue();
Expand Down
Loading