diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java index a3263e7f6e920..00c732bd032ef 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java @@ -313,6 +313,7 @@ public static class ClusterPrivilegeName { public static final String MANAGE_SAML = "manage_saml"; public static final String MANAGE_OIDC = "manage_oidc"; public static final String MANAGE_TOKEN = "manage_token"; + public static final String MANAGE_API_KEY = "manage_api_key"; public static final String MANAGE_PIPELINE = "manage_pipeline"; public static final String MANAGE_CCR = "manage_ccr"; public static final String READ_CCR = "read_ccr"; @@ -320,7 +321,8 @@ public static class ClusterPrivilegeName { public static final String READ_ILM = "read_ilm"; public static final String[] ALL_ARRAY = new String[] { NONE, ALL, MONITOR, MONITOR_ML, MONITOR_WATCHER, MONITOR_ROLLUP, MANAGE, MANAGE_ML, MANAGE_WATCHER, MANAGE_ROLLUP, MANAGE_INDEX_TEMPLATES, MANAGE_INGEST_PIPELINES, TRANSPORT_CLIENT, - MANAGE_SECURITY, MANAGE_SAML, MANAGE_OIDC, MANAGE_TOKEN, MANAGE_PIPELINE, MANAGE_CCR, READ_CCR, MANAGE_ILM, READ_ILM}; + MANAGE_SECURITY, MANAGE_SAML, MANAGE_OIDC, MANAGE_TOKEN, MANAGE_API_KEY, MANAGE_PIPELINE, MANAGE_CCR, READ_CCR, MANAGE_ILM, + READ_ILM }; } /** diff --git a/plugins/examples/security-authorization-engine/src/main/java/org/elasticsearch/example/CustomAuthorizationEngine.java b/plugins/examples/security-authorization-engine/src/main/java/org/elasticsearch/example/CustomAuthorizationEngine.java index 9916eb5dfed37..d87b565b2150d 100644 --- a/plugins/examples/security-authorization-engine/src/main/java/org/elasticsearch/example/CustomAuthorizationEngine.java +++ b/plugins/examples/security-authorization-engine/src/main/java/org/elasticsearch/example/CustomAuthorizationEngine.java @@ -36,7 +36,7 @@ import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissions; import org.elasticsearch.xpack.core.security.authz.permission.ResourcePrivileges; import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivilege; import org.elasticsearch.xpack.core.security.user.User; import java.util.ArrayList; @@ -199,7 +199,7 @@ private HasPrivilegesResponse getHasPrivilegesResponse(Authentication authentica private GetUserPrivilegesResponse getUserPrivilegesResponse(boolean isSuperuser) { final Set cluster = isSuperuser ? Collections.singleton("ALL") : Collections.emptySet(); - final Set conditionalCluster = Collections.emptySet(); + final Set conditionalCluster = Collections.emptySet(); final Set indices = isSuperuser ? Collections.singleton(new Indices(Collections.singleton("*"), Collections.singleton("*"), Collections.emptySet(), Collections.emptySet(), true)) : Collections.emptySet(); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java index 1930c05bc38eb..54347442bec3a 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java @@ -168,8 +168,8 @@ import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.ExceptExpression; import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.FieldExpression; import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.RoleMapperExpression; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges; +import org.elasticsearch.xpack.core.security.authz.privilege.ManageApplicationPrivileges; +import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivilege; import org.elasticsearch.xpack.core.sql.SqlFeatureSetUsage; import org.elasticsearch.xpack.core.ssl.action.GetCertificateInfoAction; import org.elasticsearch.xpack.core.upgrade.actions.IndexUpgradeAction; @@ -386,9 +386,9 @@ public List getNamedWriteables() { new NamedWriteableRegistry.Entry(NamedDiff.class, TokenMetaData.TYPE, TokenMetaData::readDiffFrom), new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.SECURITY, SecurityFeatureSetUsage::new), // security : conditional privileges - new NamedWriteableRegistry.Entry(ConditionalClusterPrivilege.class, - ConditionalClusterPrivileges.ManageApplicationPrivileges.WRITEABLE_NAME, - ConditionalClusterPrivileges.ManageApplicationPrivileges::createFrom), + new NamedWriteableRegistry.Entry(GlobalClusterPrivilege.class, + ManageApplicationPrivileges.WRITEABLE_NAME, + ManageApplicationPrivileges::createFrom), // security : role-mappings new NamedWriteableRegistry.Entry(RoleMapperExpression.class, AllExpression.NAME, AllExpression::new), new NamedWriteableRegistry.Entry(RoleMapperExpression.class, AnyExpression.NAME, AnyExpression::new), diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/GetApiKeyRequest.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/GetApiKeyRequest.java index 287ebcee4b6f2..ce4ed4de5c6e4 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/GetApiKeyRequest.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/GetApiKeyRequest.java @@ -117,16 +117,6 @@ public ActionRequestValidationException validate() { validationException = addValidationError("One of [api key id, api key name, username, realm name] must be specified", validationException); } - if (Strings.hasText(apiKeyId) || Strings.hasText(apiKeyName)) { - if (Strings.hasText(realmName) || Strings.hasText(userName)) { - validationException = addValidationError( - "username or realm name must not be specified when the api key id or api key name is specified", - validationException); - } - } - if (Strings.hasText(apiKeyId) && Strings.hasText(apiKeyName)) { - validationException = addValidationError("only one of [api key id, api key name] can be specified", validationException); - } return validationException; } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/InvalidateApiKeyRequest.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/InvalidateApiKeyRequest.java index f8815785d53d8..ba7e0971d6d4c 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/InvalidateApiKeyRequest.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/InvalidateApiKeyRequest.java @@ -117,16 +117,6 @@ public ActionRequestValidationException validate() { validationException = addValidationError("One of [api key id, api key name, username, realm name] must be specified", validationException); } - if (Strings.hasText(id) || Strings.hasText(name)) { - if (Strings.hasText(realmName) || Strings.hasText(userName)) { - validationException = addValidationError( - "username or realm name must not be specified when the api key id or api key name is specified", - validationException); - } - } - if (Strings.hasText(id) && Strings.hasText(name)) { - validationException = addValidationError("only one of [api key id, api key name] can be specified", validationException); - } return validationException; } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/role/PutRoleRequest.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/role/PutRoleRequest.java index e19d9cebb64c1..82a8c0e97ad4b 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/role/PutRoleRequest.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/role/PutRoleRequest.java @@ -15,8 +15,8 @@ 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.ConditionalClusterPrivilege; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges; +import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivileges; import org.elasticsearch.xpack.core.security.support.MetadataUtils; import java.io.IOException; @@ -35,7 +35,7 @@ public class PutRoleRequest extends ActionRequest implements WriteRequest indicesPrivileges = new ArrayList<>(); private List applicationPrivileges = new ArrayList<>(); private String[] runAs = Strings.EMPTY_ARRAY; @@ -82,7 +82,7 @@ public void cluster(String... clusterPrivileges) { this.clusterPrivileges = clusterPrivileges; } - void conditionalCluster(ConditionalClusterPrivilege... conditionalClusterPrivileges) { + void conditionalCluster(GlobalClusterPrivilege... conditionalClusterPrivileges) { this.conditionalClusterPrivileges = conditionalClusterPrivileges; } @@ -145,7 +145,7 @@ public List applicationPrivileges( return Collections.unmodifiableList(applicationPrivileges); } - public ConditionalClusterPrivilege[] conditionalClusterPrivileges() { + public GlobalClusterPrivilege[] conditionalClusterPrivileges() { return conditionalClusterPrivileges; } @@ -168,7 +168,7 @@ public void readFrom(StreamInput in) throws IOException { indicesPrivileges.add(new RoleDescriptor.IndicesPrivileges(in)); } applicationPrivileges = in.readList(RoleDescriptor.ApplicationResourcePrivileges::new); - conditionalClusterPrivileges = ConditionalClusterPrivileges.readArray(in); + conditionalClusterPrivileges = GlobalClusterPrivileges.readArray(in); runAs = in.readStringArray(); refreshPolicy = RefreshPolicy.readFrom(in); metadata = in.readMap(); @@ -184,7 +184,7 @@ public void writeTo(StreamOutput out) throws IOException { index.writeTo(out); } out.writeList(applicationPrivileges); - ConditionalClusterPrivileges.writeArray(out, this.conditionalClusterPrivileges); + GlobalClusterPrivileges.writeArray(out, this.conditionalClusterPrivileges); out.writeStringArray(runAs); refreshPolicy.writeTo(out); out.writeMap(metadata); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/user/GetUserPrivilegesResponse.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/user/GetUserPrivilegesResponse.java index 7c47b700cc0b5..0d1903564aed7 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/user/GetUserPrivilegesResponse.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/user/GetUserPrivilegesResponse.java @@ -15,8 +15,8 @@ import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsDefinition; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges; +import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivileges; import java.io.IOException; import java.util.Collection; @@ -32,7 +32,7 @@ public final class GetUserPrivilegesResponse extends ActionResponse { private Set cluster; - private Set conditionalCluster; + private Set conditionalCluster; private Set index; private Set application; private Set runAs; @@ -41,7 +41,7 @@ public GetUserPrivilegesResponse() { this(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet()); } - public GetUserPrivilegesResponse(Set cluster, Set conditionalCluster, + public GetUserPrivilegesResponse(Set cluster, Set conditionalCluster, Set index, Set application, Set runAs) { @@ -56,7 +56,7 @@ public Set getClusterPrivileges() { return cluster; } - public Set getConditionalClusterPrivileges() { + public Set getConditionalClusterPrivileges() { return conditionalCluster; } @@ -75,7 +75,7 @@ public Set getRunAs() { public void readFrom(StreamInput in) throws IOException { super.readFrom(in); cluster = Collections.unmodifiableSet(in.readSet(StreamInput::readString)); - conditionalCluster = Collections.unmodifiableSet(in.readSet(ConditionalClusterPrivileges.READER)); + conditionalCluster = Collections.unmodifiableSet(in.readSet(GlobalClusterPrivileges.READER)); index = Collections.unmodifiableSet(in.readSet(Indices::new)); application = Collections.unmodifiableSet(in.readSet(RoleDescriptor.ApplicationResourcePrivileges::new)); runAs = Collections.unmodifiableSet(in.readSet(StreamInput::readString)); @@ -85,7 +85,7 @@ public void readFrom(StreamInput in) throws IOException { public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); out.writeCollection(cluster, StreamOutput::writeString); - out.writeCollection(conditionalCluster, ConditionalClusterPrivileges.WRITER); + out.writeCollection(conditionalCluster, GlobalClusterPrivileges.WRITER); out.writeCollection(index); out.writeCollection(application); out.writeCollection(runAs, StreamOutput::writeString); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/RoleDescriptor.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/RoleDescriptor.java index 15304ff85dbd9..bdca1027107a7 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/RoleDescriptor.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/RoleDescriptor.java @@ -23,8 +23,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.authz.privilege.GlobalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivileges; import org.elasticsearch.xpack.core.security.support.Validation; import org.elasticsearch.xpack.core.security.xcontent.XContentUtils; @@ -48,7 +48,7 @@ public class RoleDescriptor implements ToXContentObject, Writeable { private final String name; private final String[] clusterPrivileges; - private final ConditionalClusterPrivilege[] conditionalClusterPrivileges; + private final GlobalClusterPrivilege[] conditionalClusterPrivileges; private final IndicesPrivileges[] indicesPrivileges; private final ApplicationResourcePrivileges[] applicationPrivileges; private final String[] runAs; @@ -64,7 +64,7 @@ public RoleDescriptor(String name, /** * @deprecated Use {@link #RoleDescriptor(String, String[], IndicesPrivileges[], ApplicationResourcePrivileges[], - * ConditionalClusterPrivilege[], String[], Map, Map)} + * GlobalClusterPrivilege[], String[], Map, Map)} */ @Deprecated public RoleDescriptor(String name, @@ -77,7 +77,7 @@ public RoleDescriptor(String name, /** * @deprecated Use {@link #RoleDescriptor(String, String[], IndicesPrivileges[], ApplicationResourcePrivileges[], - * ConditionalClusterPrivilege[], String[], Map, Map)} + * GlobalClusterPrivilege[], String[], Map, Map)} */ @Deprecated public RoleDescriptor(String name, @@ -93,14 +93,14 @@ public RoleDescriptor(String name, @Nullable String[] clusterPrivileges, @Nullable IndicesPrivileges[] indicesPrivileges, @Nullable ApplicationResourcePrivileges[] applicationPrivileges, - @Nullable ConditionalClusterPrivilege[] conditionalClusterPrivileges, + @Nullable GlobalClusterPrivilege[] conditionalClusterPrivileges, @Nullable String[] runAs, @Nullable Map metadata, @Nullable Map transientMetadata) { this.name = name; this.clusterPrivileges = clusterPrivileges != null ? clusterPrivileges : Strings.EMPTY_ARRAY; this.conditionalClusterPrivileges = conditionalClusterPrivileges != null - ? conditionalClusterPrivileges : ConditionalClusterPrivileges.EMPTY_ARRAY; + ? conditionalClusterPrivileges : GlobalClusterPrivileges.EMPTY_ARRAY; this.indicesPrivileges = indicesPrivileges != null ? indicesPrivileges : IndicesPrivileges.NONE; this.applicationPrivileges = applicationPrivileges != null ? applicationPrivileges : ApplicationResourcePrivileges.NONE; this.runAs = runAs != null ? runAs : Strings.EMPTY_ARRAY; @@ -122,7 +122,7 @@ public RoleDescriptor(StreamInput in) throws IOException { this.transientMetadata = in.readMap(); this.applicationPrivileges = in.readArray(ApplicationResourcePrivileges::new, ApplicationResourcePrivileges[]::new); - this.conditionalClusterPrivileges = ConditionalClusterPrivileges.readArray(in); + this.conditionalClusterPrivileges = GlobalClusterPrivileges.readArray(in); } public String getName() { @@ -133,7 +133,7 @@ public String[] getClusterPrivileges() { return this.clusterPrivileges; } - public ConditionalClusterPrivilege[] getConditionalClusterPrivileges() { + public GlobalClusterPrivilege[] getConditionalClusterPrivileges() { return this.conditionalClusterPrivileges; } @@ -231,7 +231,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params, boolea builder.array(Fields.CLUSTER.getPreferredName(), clusterPrivileges); if (conditionalClusterPrivileges.length != 0) { builder.field(Fields.GLOBAL.getPreferredName()); - ConditionalClusterPrivileges.toXContent(builder, params, Arrays.asList(conditionalClusterPrivileges)); + GlobalClusterPrivileges.toXContent(builder, params, Arrays.asList(conditionalClusterPrivileges)); } builder.array(Fields.INDICES.getPreferredName(), (Object[]) indicesPrivileges); builder.array(Fields.APPLICATIONS.getPreferredName(), (Object[]) applicationPrivileges); @@ -259,7 +259,7 @@ public void writeTo(StreamOutput out) throws IOException { out.writeMap(metadata); out.writeMap(transientMetadata); out.writeArray(ApplicationResourcePrivileges::write, applicationPrivileges); - ConditionalClusterPrivileges.writeArray(out, getConditionalClusterPrivileges()); + GlobalClusterPrivileges.writeArray(out, getConditionalClusterPrivileges()); } public static RoleDescriptor parse(String name, BytesReference source, boolean allow2xFormat, XContentType xContentType) @@ -290,7 +290,7 @@ public static RoleDescriptor parse(String name, XContentParser parser, boolean a String currentFieldName = null; IndicesPrivileges[] indicesPrivileges = null; String[] clusterPrivileges = null; - List conditionalClusterPrivileges = Collections.emptyList(); + List conditionalClusterPrivileges = Collections.emptyList(); ApplicationResourcePrivileges[] applicationPrivileges = null; String[] runAsUsers = null; Map metadata = null; @@ -308,7 +308,7 @@ public static RoleDescriptor parse(String name, XContentParser parser, boolean a || Fields.APPLICATION.match(currentFieldName, parser.getDeprecationHandler())) { applicationPrivileges = parseApplicationPrivileges(name, parser); } else if (Fields.GLOBAL.match(currentFieldName, parser.getDeprecationHandler())) { - conditionalClusterPrivileges = ConditionalClusterPrivileges.parse(parser); + conditionalClusterPrivileges = GlobalClusterPrivileges.parse(parser); } else if (Fields.METADATA.match(currentFieldName, parser.getDeprecationHandler())) { if (token != XContentParser.Token.START_OBJECT) { throw new ElasticsearchParseException( @@ -329,7 +329,7 @@ public static RoleDescriptor parse(String name, XContentParser parser, boolean a } } return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, applicationPrivileges, - conditionalClusterPrivileges.toArray(new ConditionalClusterPrivilege[conditionalClusterPrivileges.size()]), runAsUsers, + conditionalClusterPrivileges.toArray(new GlobalClusterPrivilege[conditionalClusterPrivileges.size()]), runAsUsers, metadata, null); } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermission.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermission.java index 687798971399f..095b4ab7151a5 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermission.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermission.java @@ -6,16 +6,16 @@ package org.elasticsearch.xpack.core.security.authz.permission; import org.apache.lucene.util.automaton.Operations; -import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.core.security.authc.Authentication; 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.DefaultClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.MergeableClusterPrivilege; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Set; -import java.util.function.Predicate; import java.util.stream.Collectors; /** @@ -32,36 +32,37 @@ public ClusterPrivilege privilege() { return privilege; } - public abstract boolean check(String action, TransportRequest request); + public abstract boolean check(String action, TransportRequest request, Authentication authentication); public boolean grants(ClusterPrivilege clusterPrivilege) { return Operations.subsetOf(clusterPrivilege.getAutomaton(), this.privilege().getAutomaton()); } - public abstract List> privileges(); + public abstract List privileges(); /** * A permission that is based solely on cluster privileges and does not consider request state */ public static class SimpleClusterPermission extends ClusterPermission { - public static final SimpleClusterPermission NONE = new SimpleClusterPermission(ClusterPrivilege.NONE); - - private final Predicate predicate; + public static final SimpleClusterPermission NONE = new SimpleClusterPermission(DefaultClusterPrivilege.NONE.clusterPrivilege()); SimpleClusterPermission(ClusterPrivilege privilege) { super(privilege); - this.predicate = privilege.predicate(); } @Override - public boolean check(String action, TransportRequest request) { - return predicate.test(action); + public boolean check(String action, TransportRequest request, Authentication authentication) { + boolean allowed = this.privilege().predicate().test(action); + if (this.privilege() instanceof ConditionalClusterPrivilege) { + allowed = allowed && ((ConditionalClusterPrivilege) this.privilege()).getRequestPredicate().test(request, authentication); + } + return allowed; } @Override - public List> privileges() { - return Collections.singletonList(new Tuple<>(super.privilege, null)); + public List privileges() { + return Collections.singletonList(super.privilege); } } @@ -69,21 +70,20 @@ public List> privileges() { * A permission that makes use of both cluster privileges and request inspection */ public static class ConditionalClusterPermission extends ClusterPermission { - private final ConditionalClusterPrivilege conditionalPrivilege; public ConditionalClusterPermission(ConditionalClusterPrivilege conditionalPrivilege) { - super(conditionalPrivilege.getPrivilege()); - this.conditionalPrivilege = conditionalPrivilege; + super(conditionalPrivilege); } @Override - public boolean check(String action, TransportRequest request) { - return super.privilege.predicate().test(action) && conditionalPrivilege.getRequestPredicate().test(request); + public boolean check(String action, TransportRequest request, Authentication authentication) { + return privilege().predicate().test(action) + && ((ConditionalClusterPrivilege) privilege()).getRequestPredicate().test(request, authentication); } @Override - public List> privileges() { - return Collections.singletonList(new Tuple<>(super.privilege, conditionalPrivilege)); + public List privileges() { + return Collections.singletonList(privilege()); } } @@ -99,27 +99,22 @@ public CompositeClusterPermission(Collection children) { } private static ClusterPrivilege buildPrivilege(Collection children) { - final Set names = children.stream() - .map(ClusterPermission::privilege) - .map(ClusterPrivilege::name) - .flatMap(Set::stream) - .collect(Collectors.toSet()); - return ClusterPrivilege.get(names); + return MergeableClusterPrivilege.merge(privileges(children)); } - @Override - public List> privileges() { + private static List privileges(Collection children) { return children.stream().map(ClusterPermission::privileges).flatMap(List::stream).collect(Collectors.toList()); } @Override - public boolean check(String action, TransportRequest request) { - return children.stream().anyMatch(p -> p.check(action, request)); + public List privileges() { + return privileges(children); } @Override - public boolean grants(ClusterPrivilege clusterPrivilege) { - return children.stream().anyMatch(p -> p.grants(clusterPrivilege)); + public boolean check(String action, TransportRequest request, Authentication authentication) { + return children.stream().anyMatch(p -> p.check(action, request, authentication)); } + } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRole.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRole.java index 8c7491d0a9a3d..323578fcf9dcc 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRole.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRole.java @@ -9,6 +9,7 @@ import org.apache.lucene.util.automaton.Automaton; import org.elasticsearch.cluster.metadata.AliasOrIndex; import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl; import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor; import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege; @@ -129,8 +130,8 @@ public ResourcePrivilegesMap checkIndicesPrivileges(Set checkForIndexPat * @return {@code true} if action is allowed else returns {@code false} */ @Override - public boolean checkClusterAction(String action, TransportRequest request) { - return super.checkClusterAction(action, request) && limitedBy.checkClusterAction(action, request); + public boolean checkClusterAction(String action, TransportRequest request, Authentication authentication) { + return super.checkClusterAction(action, request, authentication) && limitedBy.checkClusterAction(action, request, authentication); } /** diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/Role.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/Role.java index 207a5ab056709..782cc84541297 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/Role.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/Role.java @@ -12,12 +12,14 @@ import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; 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.ApplicationPrivilegeDescriptor; 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.ClusterPrivilegeResolver; +import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.Privilege; @@ -124,10 +126,11 @@ public ResourcePrivilegesMap checkIndicesPrivileges(Set checkForIndexPat * * @param action cluster action * @param request {@link TransportRequest} + * @param authentication {@link Authentication} current authenticated user * @return {@code true} if action is allowed else returns {@code false} */ - public boolean checkClusterAction(String action, TransportRequest request) { - return cluster.check(action, request); + public boolean checkClusterAction(String action, TransportRequest request, Authentication authentication) { + return cluster.check(action, request, authentication); } /** @@ -209,12 +212,13 @@ private Builder(RoleDescriptor rd, @Nullable FieldPermissionsCache fieldPermissi } } - public Builder cluster(Set privilegeNames, Iterable conditionalClusterPrivileges) { + public Builder cluster(Set privilegeNames, Iterable conditionalClusterPrivileges) { List clusterPermissions = new ArrayList<>(); if (privilegeNames.isEmpty() == false) { - clusterPermissions.add(new ClusterPermission.SimpleClusterPermission(ClusterPrivilege.get(privilegeNames))); + ClusterPrivilege privilege = ClusterPrivilegeResolver.resolve(privilegeNames); + clusterPermissions.add(new ClusterPermission.SimpleClusterPermission(privilege)); } - for (ConditionalClusterPrivilege ccp : conditionalClusterPrivileges) { + for (GlobalClusterPrivilege ccp : conditionalClusterPrivileges) { clusterPermissions.add(new ClusterPermission.ConditionalClusterPermission(ccp)); } if (clusterPermissions.isEmpty()) { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilege.java index f7d03c2356e5b..8d330fa50d505 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilege.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilege.java @@ -6,171 +6,22 @@ package org.elasticsearch.xpack.core.security.authz.privilege; import org.apache.lucene.util.automaton.Automaton; -import org.elasticsearch.action.admin.cluster.repositories.get.GetRepositoriesAction; -import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotAction; -import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsAction; -import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotsStatusAction; -import org.elasticsearch.action.admin.cluster.state.ClusterStateAction; -import org.elasticsearch.common.Strings; -import org.elasticsearch.xpack.core.indexlifecycle.action.GetLifecycleAction; -import org.elasticsearch.xpack.core.indexlifecycle.action.GetStatusAction; -import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenAction; -import org.elasticsearch.xpack.core.security.action.token.RefreshTokenAction; -import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesAction; -import org.elasticsearch.xpack.core.security.support.Automatons; import java.util.Collections; -import java.util.HashSet; -import java.util.Locale; -import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Predicate; -import static java.util.Map.entry; -import static org.elasticsearch.xpack.core.security.support.Automatons.minusAndMinimize; -import static org.elasticsearch.xpack.core.security.support.Automatons.patterns; +public class ClusterPrivilege extends Privilege { -public final class ClusterPrivilege extends Privilege { - - // shared automatons - private static final Automaton MANAGE_SECURITY_AUTOMATON = patterns("cluster:admin/xpack/security/*"); - private static final Automaton MANAGE_SAML_AUTOMATON = patterns("cluster:admin/xpack/security/saml/*", - InvalidateTokenAction.NAME, RefreshTokenAction.NAME); - private static final Automaton MANAGE_OIDC_AUTOMATON = patterns("cluster:admin/xpack/security/oidc/*"); - private static final Automaton MANAGE_TOKEN_AUTOMATON = patterns("cluster:admin/xpack/security/token/*"); - private static final Automaton MONITOR_AUTOMATON = patterns("cluster:monitor/*"); - private static final Automaton MONITOR_ML_AUTOMATON = patterns("cluster:monitor/xpack/ml/*"); - private static final Automaton MONITOR_DATA_FRAME_AUTOMATON = patterns("cluster:monitor/data_frame/*"); - private static final Automaton MONITOR_WATCHER_AUTOMATON = patterns("cluster:monitor/xpack/watcher/*"); - private static final Automaton MONITOR_ROLLUP_AUTOMATON = patterns("cluster:monitor/xpack/rollup/*"); - private static final Automaton ALL_CLUSTER_AUTOMATON = patterns("cluster:*", "indices:admin/template/*"); - private static final Automaton MANAGE_AUTOMATON = minusAndMinimize(ALL_CLUSTER_AUTOMATON, MANAGE_SECURITY_AUTOMATON); - private static final Automaton MANAGE_ML_AUTOMATON = patterns("cluster:admin/xpack/ml/*", "cluster:monitor/xpack/ml/*"); - private static final Automaton MANAGE_DATA_FRAME_AUTOMATON = patterns("cluster:admin/data_frame/*", "cluster:monitor/data_frame/*"); - private static final Automaton MANAGE_WATCHER_AUTOMATON = patterns("cluster:admin/xpack/watcher/*", "cluster:monitor/xpack/watcher/*"); - private static final Automaton TRANSPORT_CLIENT_AUTOMATON = patterns("cluster:monitor/nodes/liveness", "cluster:monitor/state"); - private static final Automaton MANAGE_IDX_TEMPLATE_AUTOMATON = patterns("indices:admin/template/*"); - private static final Automaton MANAGE_INGEST_PIPELINE_AUTOMATON = patterns("cluster:admin/ingest/pipeline/*"); - private static final Automaton MANAGE_ROLLUP_AUTOMATON = patterns("cluster:admin/xpack/rollup/*", "cluster:monitor/xpack/rollup/*"); - private static final Automaton MANAGE_CCR_AUTOMATON = - patterns("cluster:admin/xpack/ccr/*", ClusterStateAction.NAME, HasPrivilegesAction.NAME); - private static final Automaton CREATE_SNAPSHOT_AUTOMATON = patterns(CreateSnapshotAction.NAME, SnapshotsStatusAction.NAME + "*", - GetSnapshotsAction.NAME, SnapshotsStatusAction.NAME, GetRepositoriesAction.NAME); - private static final Automaton READ_CCR_AUTOMATON = patterns(ClusterStateAction.NAME, HasPrivilegesAction.NAME); - private static final Automaton MANAGE_ILM_AUTOMATON = patterns("cluster:admin/ilm/*"); - private static final Automaton READ_ILM_AUTOMATON = patterns(GetLifecycleAction.NAME, GetStatusAction.NAME); - - public static final ClusterPrivilege NONE = new ClusterPrivilege("none", Automatons.EMPTY); - public static final ClusterPrivilege ALL = new ClusterPrivilege("all", ALL_CLUSTER_AUTOMATON); - public static final ClusterPrivilege MONITOR = new ClusterPrivilege("monitor", MONITOR_AUTOMATON); - public static final ClusterPrivilege MONITOR_ML = new ClusterPrivilege("monitor_ml", MONITOR_ML_AUTOMATON); - public static final ClusterPrivilege MONITOR_DATA_FRAME = - new ClusterPrivilege("monitor_data_frame_transforms", MONITOR_DATA_FRAME_AUTOMATON); - public static final ClusterPrivilege MONITOR_WATCHER = new ClusterPrivilege("monitor_watcher", MONITOR_WATCHER_AUTOMATON); - public static final ClusterPrivilege MONITOR_ROLLUP = new ClusterPrivilege("monitor_rollup", MONITOR_ROLLUP_AUTOMATON); - public static final ClusterPrivilege MANAGE = new ClusterPrivilege("manage", MANAGE_AUTOMATON); - public static final ClusterPrivilege MANAGE_ML = new ClusterPrivilege("manage_ml", MANAGE_ML_AUTOMATON); - public static final ClusterPrivilege MANAGE_DATA_FRAME = - new ClusterPrivilege("manage_data_frame_transforms", MANAGE_DATA_FRAME_AUTOMATON); - public static final ClusterPrivilege MANAGE_TOKEN = new ClusterPrivilege("manage_token", MANAGE_TOKEN_AUTOMATON); - public static final ClusterPrivilege MANAGE_WATCHER = new ClusterPrivilege("manage_watcher", MANAGE_WATCHER_AUTOMATON); - public static final ClusterPrivilege MANAGE_ROLLUP = new ClusterPrivilege("manage_rollup", MANAGE_ROLLUP_AUTOMATON); - public static final ClusterPrivilege MANAGE_IDX_TEMPLATES = - new ClusterPrivilege("manage_index_templates", MANAGE_IDX_TEMPLATE_AUTOMATON); - public static final ClusterPrivilege MANAGE_INGEST_PIPELINES = - new ClusterPrivilege("manage_ingest_pipelines", MANAGE_INGEST_PIPELINE_AUTOMATON); - public static final ClusterPrivilege TRANSPORT_CLIENT = new ClusterPrivilege("transport_client", TRANSPORT_CLIENT_AUTOMATON); - public static final ClusterPrivilege MANAGE_SECURITY = new ClusterPrivilege("manage_security", MANAGE_SECURITY_AUTOMATON); - public static final ClusterPrivilege MANAGE_SAML = new ClusterPrivilege("manage_saml", MANAGE_SAML_AUTOMATON); - public static final ClusterPrivilege MANAGE_OIDC = new ClusterPrivilege("manage_oidc", MANAGE_OIDC_AUTOMATON); - public static final ClusterPrivilege MANAGE_PIPELINE = new ClusterPrivilege("manage_pipeline", "cluster:admin/ingest/pipeline/*"); - public static final ClusterPrivilege MANAGE_CCR = new ClusterPrivilege("manage_ccr", MANAGE_CCR_AUTOMATON); - public static final ClusterPrivilege READ_CCR = new ClusterPrivilege("read_ccr", READ_CCR_AUTOMATON); - public static final ClusterPrivilege CREATE_SNAPSHOT = new ClusterPrivilege("create_snapshot", CREATE_SNAPSHOT_AUTOMATON); - public static final ClusterPrivilege MANAGE_ILM = new ClusterPrivilege("manage_ilm", MANAGE_ILM_AUTOMATON); - public static final ClusterPrivilege READ_ILM = new ClusterPrivilege("read_ilm", READ_ILM_AUTOMATON); - - public static final Predicate ACTION_MATCHER = ClusterPrivilege.ALL.predicate(); - - private static final Map VALUES = Map.ofEntries( - entry("none", NONE), - entry("all", ALL), - entry("monitor", MONITOR), - entry("monitor_ml", MONITOR_ML), - entry("monitor_data_frame_transforms", MONITOR_DATA_FRAME), - entry("monitor_watcher", MONITOR_WATCHER), - entry("monitor_rollup", MONITOR_ROLLUP), - entry("manage", MANAGE), - entry("manage_ml", MANAGE_ML), - entry("manage_data_frame_transforms", MANAGE_DATA_FRAME), - entry("manage_token", MANAGE_TOKEN), - entry("manage_watcher", MANAGE_WATCHER), - entry("manage_index_templates", MANAGE_IDX_TEMPLATES), - entry("manage_ingest_pipelines", MANAGE_INGEST_PIPELINES), - entry("transport_client", TRANSPORT_CLIENT), - entry("manage_security", MANAGE_SECURITY), - entry("manage_saml", MANAGE_SAML), - entry("manage_oidc", MANAGE_OIDC), - entry("manage_pipeline", MANAGE_PIPELINE), - entry("manage_rollup", MANAGE_ROLLUP), - entry("manage_ccr", MANAGE_CCR), - entry("read_ccr", READ_CCR), - entry("create_snapshot", CREATE_SNAPSHOT), - entry("manage_ilm", MANAGE_ILM), - entry("read_ilm", READ_ILM)); - - private static final ConcurrentHashMap, ClusterPrivilege> CACHE = new ConcurrentHashMap<>(); - - private ClusterPrivilege(String name, String... patterns) { + ClusterPrivilege(String name, String... patterns) { super(name, patterns); } - private ClusterPrivilege(String name, Automaton automaton) { + ClusterPrivilege(String name, Automaton automaton) { super(Collections.singleton(name), automaton); } - private ClusterPrivilege(Set name, Automaton automaton) { + ClusterPrivilege(Set name, Automaton automaton) { super(name, automaton); } - public static ClusterPrivilege get(Set name) { - if (name == null || name.isEmpty()) { - return NONE; - } - return CACHE.computeIfAbsent(name, ClusterPrivilege::resolve); - } - - private static ClusterPrivilege resolve(Set name) { - final int size = name.size(); - if (size == 0) { - throw new IllegalArgumentException("empty set should not be used"); - } - - Set actions = new HashSet<>(); - Set automata = new HashSet<>(); - for (String part : name) { - part = part.toLowerCase(Locale.ROOT); - if (ACTION_MATCHER.test(part)) { - actions.add(actionToPattern(part)); - } else { - ClusterPrivilege privilege = VALUES.get(part); - if (privilege != null && size == 1) { - return privilege; - } else if (privilege != null) { - automata.add(privilege.automaton); - } else { - throw new IllegalArgumentException("unknown cluster privilege [" + name + "]. a privilege must be either " + - "one of the predefined fixed cluster privileges [" + - Strings.collectionToCommaDelimitedString(VALUES.entrySet()) + "] or a pattern over one of the available " + - "cluster actions"); - } - } - } - - if (actions.isEmpty() == false) { - automata.add(patterns(actions)); - } - return new ClusterPrivilege(name, Automatons.unionAndMinimize(automata)); - } -} +} \ No newline at end of file diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolver.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolver.java new file mode 100644 index 0000000000000..71e991005daaf --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolver.java @@ -0,0 +1,76 @@ +/* + * 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.Strings; + +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; + +import static org.elasticsearch.xpack.core.security.support.Automatons.patterns; + +/** + * This class helps resolving set of cluster privilege names to a {@link ClusterPrivilege} + */ +public final class ClusterPrivilegeResolver { + + public static final Predicate ACTION_MATCHER = DefaultClusterPrivilege.ALL.clusterPrivilege().predicate(); + + private static final ConcurrentHashMap, ClusterPrivilege> CACHE = + new ConcurrentHashMap<>(); + + /** + * For given set of privilege names returns a {@link ClusterPrivilege} + * + * @param name set of predefined names in {@link DefaultClusterPrivilege} or a valid + * cluster action + * @return a {@link ClusterPrivilege} + */ + public static ClusterPrivilege resolve(final Set name) { + if (name == null || name.isEmpty()) { + return DefaultClusterPrivilege.NONE.clusterPrivilege(); + } + return CACHE.computeIfAbsent(name, ClusterPrivilegeResolver::resolvePrivileges); + } + + private static ClusterPrivilege resolvePrivileges(Set name) { + Set actions = new HashSet<>(); + Set clusterPrivilegeNames = new HashSet<>(); + Set clusterPrivileges = new HashSet<>(); + for (String part : name) { + part = part.toLowerCase(Locale.ROOT); + if (ACTION_MATCHER.test(part)) { + clusterPrivilegeNames.add(part); + actions.add(actionToPattern(part)); + } else { + DefaultClusterPrivilege privilege = DefaultClusterPrivilege.fromString(part); + if (privilege != null && name.size() == 1) { + return privilege.clusterPrivilege(); + } else if (privilege != null) { + clusterPrivileges.add(privilege.clusterPrivilege()); + } else { + throw new IllegalArgumentException("unknown cluster privilege [" + name + "]. a privilege must be either " + + "one of the predefined fixed cluster privileges [" + + Strings.collectionToCommaDelimitedString(DefaultClusterPrivilege.names()) + "] " + + "or a pattern over one of the available cluster actions"); + } + } + } + + if (actions.isEmpty() == false) { + clusterPrivileges.add(new ClusterPrivilege(clusterPrivilegeNames, patterns(actions))); + } + return MergeableClusterPrivilege.merge(clusterPrivileges); + } + + private static String actionToPattern(String text) { + return text + "*"; + } +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ConditionalClusterPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ConditionalClusterPrivilege.java index dd89c2bda705d..07c2b02c0e65d 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ConditionalClusterPrivilege.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ConditionalClusterPrivilege.java @@ -6,58 +6,27 @@ package org.elasticsearch.xpack.core.security.authz.privilege; -import org.elasticsearch.common.ParseField; -import org.elasticsearch.common.io.stream.NamedWriteable; -import org.elasticsearch.common.xcontent.ToXContentFragment; -import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.transport.TransportRequest; +import org.apache.lucene.util.automaton.Automaton; -import java.io.IOException; -import java.util.Collection; -import java.util.function.Predicate; +import java.util.Collections; +import java.util.Set; /** - * 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. + * A ConditionalClusterPrivilege is a {@link ClusterPrivilege} that determines which cluster actions + * may be executed based on the {@link ConditionalPrivilege#getRequestPredicate()}. */ -public interface ConditionalClusterPrivilege extends NamedWriteable, ToXContentFragment { - - /** - * The category under which this privilege should be rendered when output as XContent. - */ - Category getCategory(); - - /** - * 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 getRequestPredicate(); - - /** - * A {@link ConditionalClusterPrivilege} should generate a fragment of {@code XContent}, which consists of - * a single field name, followed by its value (which may be an object, an array, or a simple value). - */ - @Override - XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException; - - /** - * Categories exist for to segment privileges for the purposes of rendering to XContent. - * {@link ConditionalClusterPrivileges#toXContent(XContentBuilder, Params, Collection)} builds one XContent - * object for a collection of {@link ConditionalClusterPrivilege} instances, with the top level fields built - * from the categories. - */ - enum Category { - APPLICATION(new ParseField("application")); - - public final ParseField field; - - Category(ParseField field) { - this.field = field; - } +public abstract class ConditionalClusterPrivilege extends ClusterPrivilege implements ConditionalPrivilege { + + ConditionalClusterPrivilege(String name, String... patterns) { + super(name, patterns); + } + + ConditionalClusterPrivilege(String name, Automaton automaton) { + super(Collections.singleton(name), automaton); } + + ConditionalClusterPrivilege(Set name, Automaton automaton) { + super(name, automaton); + } + } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ConditionalClusterPrivileges.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ConditionalClusterPrivileges.java deleted file mode 100644 index e5cfd2448aaad..0000000000000 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ConditionalClusterPrivileges.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * 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.ParseField; -import org.elasticsearch.common.Strings; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.common.io.stream.Writeable; -import org.elasticsearch.common.xcontent.ToXContent; -import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.XContentParseException; -import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.transport.TransportRequest; -import org.elasticsearch.xpack.core.security.action.privilege.ApplicationPrivilegesRequest; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege.Category; -import org.elasticsearch.xpack.core.security.support.Automatons; -import org.elasticsearch.xpack.core.security.xcontent.XContentUtils; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.function.Predicate; - -/** - * Static utility class for working with {@link ConditionalClusterPrivilege} instances - */ -public final class ConditionalClusterPrivileges { - - public static final ConditionalClusterPrivilege[] EMPTY_ARRAY = new ConditionalClusterPrivilege[0]; - - public static final Writeable.Reader READER = - in1 -> in1.readNamedWriteable(ConditionalClusterPrivilege.class); - public static final Writeable.Writer WRITER = - (out1, value) -> out1.writeNamedWriteable(value); - - 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(READER, 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(WRITER, privileges); - } - - /** - * Writes a single object value to the {@code builder} that contains each of the provided privileges. - * The privileges are grouped according to their {@link ConditionalClusterPrivilege#getCategory() categories} - */ - public static XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params, - Collection privileges) throws IOException { - builder.startObject(); - for (Category category : Category.values()) { - builder.startObject(category.field.getPreferredName()); - for (ConditionalClusterPrivilege privilege : privileges) { - if (category == privilege.getCategory()) { - privilege.toXContent(builder, params); - } - } - builder.endObject(); - } - return builder.endObject(); - } - - /** - * Read a list of privileges from the parser. The parser should be positioned at the - * {@link XContentParser.Token#START_OBJECT} token for the privileges value - */ - public static List parse(XContentParser parser) throws IOException { - List privileges = new ArrayList<>(); - - expectedToken(parser.currentToken(), parser, XContentParser.Token.START_OBJECT); - while (parser.nextToken() != XContentParser.Token.END_OBJECT) { - expectedToken(parser.currentToken(), parser, XContentParser.Token.FIELD_NAME); - - expectFieldName(parser, Category.APPLICATION.field); - expectedToken(parser.nextToken(), parser, XContentParser.Token.START_OBJECT); - expectedToken(parser.nextToken(), parser, XContentParser.Token.FIELD_NAME); - - expectFieldName(parser, ManageApplicationPrivileges.Fields.MANAGE); - privileges.add(ManageApplicationPrivileges.parse(parser)); - expectedToken(parser.nextToken(), parser, XContentParser.Token.END_OBJECT); - } - - return privileges; - } - - private static void expectedToken(XContentParser.Token read, XContentParser parser, XContentParser.Token expected) { - if (read != expected) { - throw new XContentParseException(parser.getTokenLocation(), - "failed to parse privilege. expected [" + expected + "] but found [" + read + "] instead"); - } - } - - private static void expectFieldName(XContentParser parser, ParseField... fields) throws IOException { - final String fieldName = parser.currentName(); - if (Arrays.stream(fields).anyMatch(pf -> pf.match(fieldName, parser.getDeprecationHandler())) == false) { - throw new XContentParseException(parser.getTokenLocation(), - "failed to parse privilege. expected " + (fields.length == 1 ? "field name" : "one of") + " [" - + Strings.arrayToCommaDelimitedString(fields) + "] but found [" + fieldName + "] instead"); - } - } - - /** - * The {@code ManageApplicationPrivileges} privilege is a {@link ConditionalClusterPrivilege} that grants the - * ability to execute actions related to the management of application privileges (Get, Put, Delete) for a subset - * of applications (identified by a wildcard-aware application-name). - */ - public static class ManageApplicationPrivileges implements ConditionalClusterPrivilege { - - private static final ClusterPrivilege PRIVILEGE = ClusterPrivilege.get( - Collections.singleton("cluster:admin/xpack/security/privilege/*") - ); - public static final String WRITEABLE_NAME = "manage-application-privileges"; - - private final Set applicationNames; - private final Predicate applicationPredicate; - private final Predicate requestPredicate; - - public ManageApplicationPrivileges(Set applicationNames) { - this.applicationNames = Collections.unmodifiableSet(applicationNames); - this.applicationPredicate = Automatons.predicate(applicationNames); - this.requestPredicate = request -> { - if (request instanceof ApplicationPrivilegesRequest) { - final ApplicationPrivilegesRequest privRequest = (ApplicationPrivilegesRequest) request; - final Collection requestApplicationNames = privRequest.getApplicationNames(); - return requestApplicationNames.isEmpty() ? this.applicationNames.contains("*") - : requestApplicationNames.stream().allMatch(application -> applicationPredicate.test(application)); - } - return false; - }; - } - - @Override - public Category getCategory() { - return Category.APPLICATION; - } - - @Override - public ClusterPrivilege getPrivilege() { - return PRIVILEGE; - } - - @Override - public Predicate getRequestPredicate() { - return this.requestPredicate; - } - - public Collection getApplicationNames() { - return Collections.unmodifiableCollection(this.applicationNames); - } - - @Override - public String getWriteableName() { - return WRITEABLE_NAME; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeCollection(this.applicationNames, StreamOutput::writeString); - } - - public static ManageApplicationPrivileges createFrom(StreamInput in) throws IOException { - final Set applications = in.readSet(StreamInput::readString); - return new ManageApplicationPrivileges(applications); - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - return builder.field(Fields.MANAGE.getPreferredName(), - Collections.singletonMap(Fields.APPLICATIONS.getPreferredName(), applicationNames) - ); - } - - public static ManageApplicationPrivileges parse(XContentParser parser) throws IOException { - expectedToken(parser.currentToken(), parser, XContentParser.Token.FIELD_NAME); - expectFieldName(parser, Fields.MANAGE); - expectedToken(parser.nextToken(), parser, XContentParser.Token.START_OBJECT); - expectedToken(parser.nextToken(), parser, XContentParser.Token.FIELD_NAME); - expectFieldName(parser, Fields.APPLICATIONS); - expectedToken(parser.nextToken(), parser, XContentParser.Token.START_ARRAY); - final String[] applications = XContentUtils.readStringArray(parser, false); - expectedToken(parser.nextToken(), parser, XContentParser.Token.END_OBJECT); - return new ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList(applications))); - } - - @Override - public String toString() { - return "{" + getCategory() + ":" + Fields.MANAGE.getPreferredName() + ":" + Fields.APPLICATIONS.getPreferredName() + "=" - + Strings.collectionToDelimitedString(applicationNames, ",") + "}"; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - final ManageApplicationPrivileges that = (ManageApplicationPrivileges) o; - return this.applicationNames.equals(that.applicationNames); - } - - @Override - public int hashCode() { - return applicationNames.hashCode(); - } - - private interface Fields { - ParseField MANAGE = new ParseField("manage"); - ParseField APPLICATIONS = new ParseField("applications"); - } - } -} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ConditionalPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ConditionalPrivilege.java new file mode 100644 index 0000000000000..ccea55e4ba197 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ConditionalPrivilege.java @@ -0,0 +1,26 @@ +/* + * 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.transport.TransportRequest; +import org.elasticsearch.xpack.core.security.authc.Authentication; + +import java.util.function.BiPredicate; + +/** + * A ConditionalPrivilege is an interface that helps adding a condition to a {@link Privilege}, that defines a {@link BiPredicate} for a + * {@link TransportRequest} (that determines which requests may be executed) and a {@link Authentication} (for current authenticated user). + * The predicate can be used to determine if both the action and the request are permitted in the context of given authentication. + */ +public interface ConditionalPrivilege { + + /** + * The request-level privilege (as a {@link BiPredicate}) that is required by this conditional privilege. + * Conditions can also be evaluated based on the {@link Authentication} details. + */ + BiPredicate getRequestPredicate(); +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/DefaultClusterPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/DefaultClusterPrivilege.java new file mode 100644 index 0000000000000..4fc0d03381894 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/DefaultClusterPrivilege.java @@ -0,0 +1,135 @@ +/* + * 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.apache.lucene.util.automaton.Automaton; +import org.elasticsearch.action.admin.cluster.repositories.get.GetRepositoriesAction; +import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotAction; +import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsAction; +import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotsStatusAction; +import org.elasticsearch.action.admin.cluster.state.ClusterStateAction; +import org.elasticsearch.xpack.core.indexlifecycle.action.GetLifecycleAction; +import org.elasticsearch.xpack.core.indexlifecycle.action.GetStatusAction; +import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenAction; +import org.elasticsearch.xpack.core.security.action.token.RefreshTokenAction; +import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesAction; +import org.elasticsearch.xpack.core.security.support.Automatons; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static org.elasticsearch.xpack.core.security.support.Automatons.minusAndMinimize; +import static org.elasticsearch.xpack.core.security.support.Automatons.patterns; + +/** + * Enum that defines default cluster privileges + */ +public enum DefaultClusterPrivilege { + + NONE("none", Automatons.EMPTY), + ALL("all", ClusterAutomatons.ALL_CLUSTER_AUTOMATON), + MONITOR("monitor", ClusterAutomatons.MONITOR_AUTOMATON), + MONITOR_ML("monitor_ml", ClusterAutomatons.MONITOR_ML_AUTOMATON), + MONITOR_DATA_FRAME("monitor_data_frame_transforms", ClusterAutomatons.MONITOR_DATA_FRAME_AUTOMATON), + MONITOR_WATCHER("monitor_watcher", ClusterAutomatons.MONITOR_WATCHER_AUTOMATON), + MONITOR_ROLLUP("monitor_rollup", ClusterAutomatons.MONITOR_ROLLUP_AUTOMATON), + MANAGE("manage", ClusterAutomatons.MANAGE_AUTOMATON), + MANAGE_ML("manage_ml", ClusterAutomatons.MANAGE_ML_AUTOMATON), + MANAGE_DATA_FRAME("manage_data_frame_transforms", ClusterAutomatons.MANAGE_DATA_FRAME_AUTOMATON), + MANAGE_TOKEN("manage_token", ClusterAutomatons.MANAGE_TOKEN_AUTOMATON), + MANAGE_API_KEY("manage_api_key", ClusterAutomatons.MANAGE_API_KEY_AUTOMATON), + MANAGE_WATCHER("manage_watcher", ClusterAutomatons.MANAGE_WATCHER_AUTOMATON), + MANAGE_ROLLUP("manage_rollup", ClusterAutomatons.MANAGE_ROLLUP_AUTOMATON), + MANAGE_IDX_TEMPLATES("manage_index_templates", ClusterAutomatons.MANAGE_IDX_TEMPLATE_AUTOMATON), + MANAGE_INGEST_PIPELINES("manage_ingest_pipelines", ClusterAutomatons.MANAGE_INGEST_PIPELINE_AUTOMATON), + TRANSPORT_CLIENT("transport_client", ClusterAutomatons.TRANSPORT_CLIENT_AUTOMATON), + MANAGE_SECURITY("manage_security", ClusterAutomatons.MANAGE_SECURITY_AUTOMATON), + MANAGE_SAML("manage_saml", ClusterAutomatons.MANAGE_SAML_AUTOMATON), + MANAGE_OIDC("manage_oidc", ClusterAutomatons.MANAGE_OIDC_AUTOMATON), + MANAGE_PIPELINE("manage_pipeline", "cluster:admin/ingest/pipeline/*"), + MANAGE_CCR("manage_ccr", ClusterAutomatons.MANAGE_CCR_AUTOMATON), + READ_CCR("read_ccr", ClusterAutomatons.READ_CCR_AUTOMATON), + CREATE_SNAPSHOT("create_snapshot", ClusterAutomatons.CREATE_SNAPSHOT_AUTOMATON), + MANAGE_ILM("manage_ilm", ClusterAutomatons.MANAGE_ILM_AUTOMATON), + READ_ILM("read_ilm", ClusterAutomatons.READ_ILM_AUTOMATON), + + // Conditional + MANAGE_OWN_API_KEY("manage_own_api_key", ManageApiKeyConditionalClusterPrivilege.createOwnerManageApiKeyConditionalClusterPrivilege()); + + private final ClusterPrivilege clusterPrivilege; + private final String privilegeName; + private static Map privilegeNameToEnumMap = new HashMap<>(); + static { + for (DefaultClusterPrivilege privilege : values()) { + privilegeNameToEnumMap.put(privilege.privilegeName, privilege); + } + } + + DefaultClusterPrivilege(String privilegeName, String clusterAction) { + this(privilegeName, new ClusterPrivilege(privilegeName, clusterAction)); + } + + DefaultClusterPrivilege(String privilegeName, Automaton automaton) { + this(privilegeName, new ClusterPrivilege(privilegeName, automaton)); + } + + DefaultClusterPrivilege(String privilegeName, ClusterPrivilege clusterPrivilege) { + this.clusterPrivilege = clusterPrivilege; + this.privilegeName = privilegeName; + } + + public String privilegeName() { + return privilegeName; + } + + public ClusterPrivilege clusterPrivilege() { + return clusterPrivilege; + } + + public static DefaultClusterPrivilege fromString(String privilegeName) { + return privilegeNameToEnumMap.get(privilegeName); + } + + public static Set names() { + return privilegeNameToEnumMap.keySet(); + } + + static class ClusterAutomatons { + // shared automatons + private static final Automaton MANAGE_SECURITY_AUTOMATON = patterns("cluster:admin/xpack/security/*"); + private static final Automaton MANAGE_SAML_AUTOMATON = patterns("cluster:admin/xpack/security/saml/*", + InvalidateTokenAction.NAME, RefreshTokenAction.NAME); + private static final Automaton MANAGE_OIDC_AUTOMATON = patterns("cluster:admin/xpack/security/oidc/*"); + private static final Automaton MANAGE_TOKEN_AUTOMATON = patterns("cluster:admin/xpack/security/token/*"); + + private static final Automaton MANAGE_API_KEY_AUTOMATON = patterns("cluster:admin/xpack/security/api_key/*"); + + private static final Automaton MONITOR_AUTOMATON = patterns("cluster:monitor/*"); + private static final Automaton MONITOR_ML_AUTOMATON = patterns("cluster:monitor/xpack/ml/*"); + private static final Automaton MONITOR_DATA_FRAME_AUTOMATON = patterns("cluster:monitor/data_frame/*"); + private static final Automaton MONITOR_WATCHER_AUTOMATON = patterns("cluster:monitor/xpack/watcher/*"); + private static final Automaton MONITOR_ROLLUP_AUTOMATON = patterns("cluster:monitor/xpack/rollup/*"); + private static final Automaton ALL_CLUSTER_AUTOMATON = patterns("cluster:*", "indices:admin/template/*"); + private static final Automaton MANAGE_AUTOMATON = minusAndMinimize(ALL_CLUSTER_AUTOMATON, MANAGE_SECURITY_AUTOMATON); + private static final Automaton MANAGE_ML_AUTOMATON = patterns("cluster:admin/xpack/ml/*", "cluster:monitor/xpack/ml/*"); + private static final Automaton MANAGE_DATA_FRAME_AUTOMATON = patterns("cluster:admin/data_frame/*", "cluster:monitor/data_frame/*"); + private static final Automaton MANAGE_WATCHER_AUTOMATON = patterns("cluster:admin/xpack/watcher/*", + "cluster:monitor/xpack/watcher/*"); + private static final Automaton TRANSPORT_CLIENT_AUTOMATON = patterns("cluster:monitor/nodes/liveness", "cluster:monitor/state"); + private static final Automaton MANAGE_IDX_TEMPLATE_AUTOMATON = patterns("indices:admin/template/*"); + private static final Automaton MANAGE_INGEST_PIPELINE_AUTOMATON = patterns("cluster:admin/ingest/pipeline/*"); + private static final Automaton MANAGE_ROLLUP_AUTOMATON = patterns("cluster:admin/xpack/rollup/*", "cluster:monitor/xpack/rollup/*"); + private static final Automaton MANAGE_CCR_AUTOMATON = + patterns("cluster:admin/xpack/ccr/*", ClusterStateAction.NAME, HasPrivilegesAction.NAME); + private static final Automaton CREATE_SNAPSHOT_AUTOMATON = patterns(CreateSnapshotAction.NAME, SnapshotsStatusAction.NAME + "*", + GetSnapshotsAction.NAME, SnapshotsStatusAction.NAME, GetRepositoriesAction.NAME); + private static final Automaton READ_CCR_AUTOMATON = patterns(ClusterStateAction.NAME, HasPrivilegesAction.NAME); + private static final Automaton MANAGE_ILM_AUTOMATON = patterns("cluster:admin/ilm/*"); + private static final Automaton READ_ILM_AUTOMATON = patterns(GetLifecycleAction.NAME, GetStatusAction.NAME); + } +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/GlobalClusterPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/GlobalClusterPrivilege.java new file mode 100644 index 0000000000000..4e8596967dada --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/GlobalClusterPrivilege.java @@ -0,0 +1,64 @@ +/* + * 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.apache.lucene.util.automaton.Automaton; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.io.stream.NamedWriteable; +import org.elasticsearch.common.xcontent.ToXContentFragment; +import org.elasticsearch.common.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +/** + * A GlobalClusterPrivilege is a {@link ConditionalClusterPrivilege} that can be serialized / rendered as `XContent`. + */ +public abstract class GlobalClusterPrivilege extends ConditionalClusterPrivilege implements NamedWriteable, ToXContentFragment { + + public GlobalClusterPrivilege(String name, String... patterns) { + super(name, patterns); + } + + public GlobalClusterPrivilege(String name, Automaton automaton) { + this(Collections.singleton(name), automaton); + } + + public GlobalClusterPrivilege(Set name, Automaton automaton) { + super(name, automaton); + } + + /** + * The category under which this privilege should be rendered when output as XContent. + */ + public abstract Category getCategory(); + + /** + * A {@link GlobalClusterPrivilege} should generate a fragment of {@code XContent}, which consists of + * a single field name, followed by its value (which may be an object, an array, or a simple value). + */ + @Override + public abstract XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException; + + /** + * Categories exist for to segment privileges for the purposes of rendering to XContent. + * {@link GlobalClusterPrivileges#toXContent(XContentBuilder, Params, Collection)} builds one XContent + * object for a collection of {@link GlobalClusterPrivilege} instances, with the top level fields built + * from the categories. + */ + public enum Category { + APPLICATION(new ParseField("application")); + + public final ParseField field; + + Category(ParseField field) { + this.field = field; + } + } +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/GlobalClusterPrivileges.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/GlobalClusterPrivileges.java new file mode 100644 index 0000000000000..9e741a6ae8e11 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/GlobalClusterPrivileges.java @@ -0,0 +1,113 @@ +/* + * 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.ParseField; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParseException; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivilege.Category; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +/** + * Static utility class for working with {@link GlobalClusterPrivilege} instances + */ +public final class GlobalClusterPrivileges { + + public static final GlobalClusterPrivilege[] EMPTY_ARRAY = new GlobalClusterPrivilege[0]; + + public static final Writeable.Reader READER = + in1 -> in1.readNamedWriteable(GlobalClusterPrivilege.class); + public static final Writeable.Writer WRITER = + (out1, value) -> out1.writeNamedWriteable(value); + + private GlobalClusterPrivileges() { + } + + /** + * Utility method to read an array of {@link GlobalClusterPrivilege} objects from a {@link StreamInput} + */ + public static GlobalClusterPrivilege[] readArray(StreamInput in) throws IOException { + return in.readArray(READER, GlobalClusterPrivilege[]::new); + } + + /** + * Utility method to write an array of {@link GlobalClusterPrivilege} objects to a {@link StreamOutput} + */ + public static void writeArray(StreamOutput out, GlobalClusterPrivilege[] privileges) throws IOException { + out.writeArray(WRITER, privileges); + } + + /** + * Writes a single object value to the {@code builder} that contains each of the provided privileges. + * The privileges are grouped according to their {@link GlobalClusterPrivilege#getCategory() categories} + */ + public static XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params, + Collection privileges) throws IOException { + builder.startObject(); + for (Category category : Category.values()) { + builder.startObject(category.field.getPreferredName()); + for (GlobalClusterPrivilege privilege : privileges) { + if (category == privilege.getCategory()) { + privilege.toXContent(builder, params); + } + } + builder.endObject(); + } + return builder.endObject(); + } + + /** + * Read a list of privileges from the parser. The parser should be positioned at the + * {@link XContentParser.Token#START_OBJECT} token for the privileges value + */ + public static List parse(XContentParser parser) throws IOException { + List privileges = new ArrayList<>(); + + expectedToken(parser.currentToken(), parser, XContentParser.Token.START_OBJECT); + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + expectedToken(parser.currentToken(), parser, XContentParser.Token.FIELD_NAME); + + expectFieldName(parser, Category.APPLICATION.field); + expectedToken(parser.nextToken(), parser, XContentParser.Token.START_OBJECT); + expectedToken(parser.nextToken(), parser, XContentParser.Token.FIELD_NAME); + + expectFieldName(parser, ManageApplicationPrivileges.Fields.MANAGE); + privileges.add(ManageApplicationPrivileges.parse(parser)); + expectedToken(parser.nextToken(), parser, XContentParser.Token.END_OBJECT); + } + + return privileges; + } + + private static void expectedToken(XContentParser.Token read, XContentParser parser, XContentParser.Token expected) { + if (read != expected) { + throw new XContentParseException(parser.getTokenLocation(), + "failed to parse privilege. expected [" + expected + "] but found [" + read + "] instead"); + } + } + + private static void expectFieldName(XContentParser parser, ParseField... fields) throws IOException { + final String fieldName = parser.currentName(); + if (Arrays.stream(fields).anyMatch(pf -> pf.match(fieldName, parser.getDeprecationHandler())) == false) { + throw new XContentParseException(parser.getTokenLocation(), + "failed to parse privilege. expected " + (fields.length == 1 ? "field name" : "one of") + " [" + + Strings.arrayToCommaDelimitedString(fields) + "] but found [" + fieldName + "] instead"); + } + } + +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalClusterPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalClusterPrivilege.java new file mode 100644 index 0000000000000..4912779673a41 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalClusterPrivilege.java @@ -0,0 +1,125 @@ +/* + * 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.Strings; +import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.core.security.action.CreateApiKeyRequest; +import org.elasticsearch.xpack.core.security.action.GetApiKeyRequest; +import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyRequest; +import org.elasticsearch.xpack.core.security.authc.Authentication; +import org.elasticsearch.xpack.core.security.support.Automatons; + +import java.util.List; +import java.util.Set; +import java.util.function.BiPredicate; + +/** + * Conditional cluster privilege for managing API keys + */ +public final class ManageApiKeyConditionalClusterPrivilege extends ConditionalClusterPrivilege { + + private static final String MANAGE_API_KEY_PATTERN = "cluster:admin/xpack/security/api_key/*"; + private static final String CREATE_API_KEY_PATTERN = "cluster:admin/xpack/security/api_key/create"; + private static final String GET_API_KEY_PATTERN = "cluster:admin/xpack/security/api_key/get"; + private static final String INVALIDATE_API_KEY_PATTERN = "cluster:admin/xpack/security/api_key/invalidate"; + private static final List API_KEY_ACTION_PATTERNS = List.of(MANAGE_API_KEY_PATTERN, CREATE_API_KEY_PATTERN, GET_API_KEY_PATTERN, + INVALIDATE_API_KEY_PATTERN); + + private final boolean restrictActionsToAuthenticatedUser; + private final BiPredicate requestPredicate; + + /** + * Constructor for {@link ManageApiKeyConditionalClusterPrivilege} + * + * @param actions set of API key cluster actions + * @param restrictActionsToAuthenticatedUser if {@code true} privileges will be restricted to current authenticated user. + */ + private ManageApiKeyConditionalClusterPrivilege(Set actions, boolean restrictActionsToAuthenticatedUser) { + super(actions, Automatons.patterns(actions)); + // validate allowed actions + for (String action : actions) { + if (DefaultClusterPrivilege.MANAGE_API_KEY.clusterPrivilege().predicate().test(action) == false) { + throw new IllegalArgumentException("invalid action [ " + action + " ] specified, expected API key privilege actions from [ " + + API_KEY_ACTION_PATTERNS + " ]"); + } + } + this.restrictActionsToAuthenticatedUser = restrictActionsToAuthenticatedUser; + + this.requestPredicate = (request, authentication) -> { + if (request instanceof CreateApiKeyRequest) { + return true; + } else if (request instanceof GetApiKeyRequest) { + final GetApiKeyRequest getApiKeyRequest = (GetApiKeyRequest) request; + if (this.restrictActionsToAuthenticatedUser) { + return checkIfUserIsOwnerOfApiKeys(authentication, getApiKeyRequest.getApiKeyId(), getApiKeyRequest.getUserName(), + getApiKeyRequest.getRealmName()); + } else { + return true; + } + } else if (request instanceof InvalidateApiKeyRequest) { + final InvalidateApiKeyRequest invalidateApiKeyRequest = (InvalidateApiKeyRequest) request; + if (this.restrictActionsToAuthenticatedUser) { + return checkIfUserIsOwnerOfApiKeys(authentication, invalidateApiKeyRequest.getId(), + invalidateApiKeyRequest.getUserName(), invalidateApiKeyRequest.getRealmName()); + } else { + return true; + } + } + return false; + }; + } + + public static ManageApiKeyConditionalClusterPrivilege createOwnerManageApiKeyConditionalClusterPrivilege() { + return new ManageApiKeyConditionalClusterPrivilege(Set.of("cluster:admin/xpack/security/api_key/*"), true); + } + + private boolean checkIfUserIsOwnerOfApiKeys(Authentication authentication, String apiKeyId, String username, String realmName) { + if (authentication.getAuthenticatedBy().getType().equals("_es_api_key")) { + // API key id from authentication must match the id from request + String authenticatedApiKeyId = (String) authentication.getMetadata().get("_security_api_key_id"); + if (Strings.hasText(apiKeyId)) { + return apiKeyId.equals(authenticatedApiKeyId); + } + } else { + String authenticatedUserPrincipal = authentication.getUser().principal(); + String authenticatedUserRealm = authentication.getAuthenticatedBy().getName(); + if (Strings.hasText(username) && Strings.hasText(realmName)) { + return username.equals(authenticatedUserPrincipal) && realmName.equals(authenticatedUserRealm); + } + } + return false; + } + + @Override + public BiPredicate getRequestPredicate() { + return requestPredicate; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + (restrictActionsToAuthenticatedUser ? 1231 : 1237); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + ManageApiKeyConditionalClusterPrivilege other = (ManageApiKeyConditionalClusterPrivilege) obj; + if (restrictActionsToAuthenticatedUser != other.restrictActionsToAuthenticatedUser) + return false; + return true; + } + +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApplicationPrivileges.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApplicationPrivileges.java new file mode 100644 index 0000000000000..3d8b8eeba32f8 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApplicationPrivileges.java @@ -0,0 +1,151 @@ +/* + * 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.ParseField; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParseException; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.core.security.action.privilege.ApplicationPrivilegesRequest; +import org.elasticsearch.xpack.core.security.authc.Authentication; +import org.elasticsearch.xpack.core.security.support.Automatons; +import org.elasticsearch.xpack.core.security.xcontent.XContentUtils; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.function.BiPredicate; +import java.util.function.Predicate; + +/** + * The {@code ManageApplicationPrivileges} privilege is a {@link GlobalClusterPrivilege} that grants the + * ability to execute actions related to the management of application privileges (Get, Put, Delete) for a subset + * of applications (identified by a wildcard-aware application-name). + */ +public final class ManageApplicationPrivileges extends GlobalClusterPrivilege { + + public static final String WRITEABLE_NAME = "manage-application-privileges"; + + private final Set applicationNames; + private final Predicate applicationPredicate; + private final BiPredicate requestPredicate; + + public ManageApplicationPrivileges(Set applicationNames) { + super("cluster:admin/xpack/security/privilege/*", + Automatons.patterns(Collections.singleton("cluster:admin/xpack/security/privilege/*"))); + this.applicationNames = Collections.unmodifiableSet(applicationNames); + this.applicationPredicate = Automatons.predicate(applicationNames); + this.requestPredicate = (request, authentication) -> { + if (request instanceof ApplicationPrivilegesRequest) { + final ApplicationPrivilegesRequest privRequest = (ApplicationPrivilegesRequest) request; + final Collection requestApplicationNames = privRequest.getApplicationNames(); + return requestApplicationNames.isEmpty() ? this.applicationNames.contains("*") + : requestApplicationNames.stream().allMatch(application -> applicationPredicate.test(application)); + } + return false; + }; + } + + @Override + public Category getCategory() { + return Category.APPLICATION; + } + + @Override + public BiPredicate getRequestPredicate() { + return this.requestPredicate; + } + + public Collection getApplicationNames() { + return Collections.unmodifiableCollection(this.applicationNames); + } + + @Override + public String getWriteableName() { + return WRITEABLE_NAME; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeCollection(this.applicationNames, StreamOutput::writeString); + } + + public static ManageApplicationPrivileges createFrom(StreamInput in) throws IOException { + final Set applications = in.readSet(StreamInput::readString); + return new ManageApplicationPrivileges(applications); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + return builder.field(Fields.MANAGE.getPreferredName(), + Collections.singletonMap(Fields.APPLICATIONS.getPreferredName(), applicationNames) + ); + } + + public static ManageApplicationPrivileges parse(XContentParser parser) throws IOException { + expectedToken(parser.currentToken(), parser, XContentParser.Token.FIELD_NAME); + expectFieldName(parser, Fields.MANAGE); + expectedToken(parser.nextToken(), parser, XContentParser.Token.START_OBJECT); + expectedToken(parser.nextToken(), parser, XContentParser.Token.FIELD_NAME); + expectFieldName(parser, Fields.APPLICATIONS); + expectedToken(parser.nextToken(), parser, XContentParser.Token.START_ARRAY); + final String[] applications = XContentUtils.readStringArray(parser, false); + expectedToken(parser.nextToken(), parser, XContentParser.Token.END_OBJECT); + return new ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList(applications))); + } + + @Override + public String toString() { + return "{" + getCategory() + ":" + Fields.MANAGE.getPreferredName() + ":" + Fields.APPLICATIONS.getPreferredName() + "=" + + Strings.collectionToDelimitedString(applicationNames, ",") + "}"; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final ManageApplicationPrivileges that = (ManageApplicationPrivileges) o; + return this.applicationNames.equals(that.applicationNames); + } + + @Override + public int hashCode() { + return applicationNames.hashCode(); + } + + interface Fields { + ParseField MANAGE = new ParseField("manage"); + ParseField APPLICATIONS = new ParseField("applications"); + } + + private static void expectedToken(XContentParser.Token read, XContentParser parser, XContentParser.Token expected) { + if (read != expected) { + throw new XContentParseException(parser.getTokenLocation(), + "failed to parse privilege. expected [" + expected + "] but found [" + read + "] instead"); + } + } + + private static void expectFieldName(XContentParser parser, ParseField... fields) throws IOException { + final String fieldName = parser.currentName(); + if (Arrays.stream(fields).anyMatch(pf -> pf.match(fieldName, parser.getDeprecationHandler())) == false) { + throw new XContentParseException(parser.getTokenLocation(), + "failed to parse privilege. expected " + (fields.length == 1 ? "field name" : "one of") + " [" + + Strings.arrayToCommaDelimitedString(fields) + "] but found [" + fieldName + "] instead"); + } + } +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/MergeableClusterPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/MergeableClusterPrivilege.java new file mode 100644 index 0000000000000..f1da6dbcd53e0 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/MergeableClusterPrivilege.java @@ -0,0 +1,62 @@ +/* + * 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.apache.lucene.util.automaton.Automaton; +import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.core.security.authc.Authentication; +import org.elasticsearch.xpack.core.security.support.Automatons; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.function.BiPredicate; + +/** + * Merges list of {@link ClusterPrivilege} into a single cluster privilege. + * If the list contains {@link ConditionalClusterPrivilege} then the resultant is also a {@link ConditionalClusterPrivilege} + * else it will be plain {@link ClusterPrivilege} + */ +public class MergeableClusterPrivilege extends ConditionalClusterPrivilege { + + private BiPredicate conditionalPredicate; + + private MergeableClusterPrivilege(Set name, Automaton automaton, + BiPredicate conditionalPredicate) { + super(name, automaton); + this.conditionalPredicate = conditionalPredicate; + } + + /** + * The request-level privilege (as a {@link BiPredicate}) that is required by this conditional privilege. Conditions can also be + * evaluated based on the {@link Authentication} details. + */ + @Override + public BiPredicate getRequestPredicate() { + return conditionalPredicate; + } + + public static ClusterPrivilege merge(Collection clusterPrivileges) { + Set names = new HashSet<>(); + Set automatons = new HashSet<>(); + BiPredicate conditionalPredicate = null; + for (ClusterPrivilege clusterPrivilege : clusterPrivileges) { + names.addAll(clusterPrivilege.name()); + automatons.add(clusterPrivilege.getAutomaton()); + if (clusterPrivilege instanceof ConditionalClusterPrivilege) { + if (conditionalPredicate == null) { + conditionalPredicate = ((ConditionalClusterPrivilege) clusterPrivilege).getRequestPredicate(); + } else { + conditionalPredicate = conditionalPredicate.or(((ConditionalClusterPrivilege) clusterPrivilege).getRequestPredicate()); + } + } + } + return (conditionalPredicate != null) + ? new MergeableClusterPrivilege(names, Automatons.unionAndMinimize(automatons), conditionalPredicate) + : new ClusterPrivilege(names, Automatons.unionAndMinimize(automatons)); + } +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java index ab06fc32e288f..d94a035f4b413 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java @@ -11,8 +11,8 @@ import org.elasticsearch.xpack.core.monitoring.action.MonitoringBulkAction; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; import org.elasticsearch.xpack.core.security.authz.permission.Role; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges.ManageApplicationPrivileges; +import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.ManageApplicationPrivileges; import org.elasticsearch.xpack.core.security.support.MetadataUtils; import org.elasticsearch.xpack.core.security.user.KibanaUser; import org.elasticsearch.xpack.core.security.user.UsernamesField; @@ -123,7 +123,7 @@ private static Map initializeReservedRoles() { .indices(".code-*", ".code_internal-*").privileges("all").build(), }, null, - new ConditionalClusterPrivilege[] { new ManageApplicationPrivileges(Collections.singleton("kibana-*")) }, + new GlobalClusterPrivilege[] { new ManageApplicationPrivileges(Collections.singleton("kibana-*")) }, null, MetadataUtils.DEFAULT_RESERVED_METADATA, null)) .put("logstash_system", new RoleDescriptor("logstash_system", new String[] { "monitor", MonitoringBulkAction.NAME}, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA)) diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/test/TestMatchers.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/test/TestMatchers.java index 2a9041575df2b..80ea3f443df83 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/test/TestMatchers.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/test/TestMatchers.java @@ -12,6 +12,7 @@ import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; +import java.util.function.BiPredicate; import java.util.function.Predicate; import java.util.regex.Pattern; @@ -26,6 +27,19 @@ public boolean matches(Object item) { }; } + public static Matcher> predicateMatches(T arg1, U arg2) { + return new CustomMatcher>("Matches " + arg1) { + @Override + public boolean matches(Object item) { + if (BiPredicate.class.isInstance(item)) { + return ((BiPredicate) item).test(arg1, arg2); + } else { + return false; + } + } + }; + } + public static Matcher> predicateMatches(T value) { return new CustomMatcher>("Matches " + value) { @Override diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/GetApiKeyRequestTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/GetApiKeyRequestTests.java index 27be0d88eb82c..58ce4dcb770b7 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/GetApiKeyRequestTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/GetApiKeyRequestTests.java @@ -70,18 +70,9 @@ public void writeTo(StreamOutput out) throws IOException { String[][] inputs = new String[][] { { randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), - randomFrom(new String[] { null, "" }) }, - { randomFrom(new String[] { null, "" }), "user", "api-kid", "api-kname" }, - { "realm", randomFrom(new String[] { null, "" }), "api-kid", "api-kname" }, - { "realm", "user", "api-kid", randomFrom(new String[] { null, "" }) }, - { randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), "api-kid", "api-kname" } }; - String[][] expectedErrorMessages = new String[][] { { "One of [api key id, api key name, username, realm name] must be specified" }, - { "username or realm name must not be specified when the api key id or api key name is specified", - "only one of [api key id, api key name] can be specified" }, - { "username or realm name must not be specified when the api key id or api key name is specified", - "only one of [api key id, api key name] can be specified" }, - { "username or realm name must not be specified when the api key id or api key name is specified" }, - { "only one of [api key id, api key name] can be specified" } }; + randomFrom(new String[] { null, "" }) } }; + String[][] expectedErrorMessages = new String[][] { { "One of [api key id, api key name, username, realm name] must be specified" + } }; for (int caseNo = 0; caseNo < inputs.length; caseNo++) { try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/InvalidateApiKeyRequestTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/InvalidateApiKeyRequestTests.java index 3d7fd90234286..6ba940c51b6dd 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/InvalidateApiKeyRequestTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/InvalidateApiKeyRequestTests.java @@ -70,18 +70,9 @@ public void writeTo(StreamOutput out) throws IOException { String[][] inputs = new String[][] { { randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), - randomFrom(new String[] { null, "" }) }, - { randomFrom(new String[] { null, "" }), "user", "api-kid", "api-kname" }, - { "realm", randomFrom(new String[] { null, "" }), "api-kid", "api-kname" }, - { "realm", "user", "api-kid", randomFrom(new String[] { null, "" }) }, - { randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), "api-kid", "api-kname" } }; - String[][] expectedErrorMessages = new String[][] { { "One of [api key id, api key name, username, realm name] must be specified" }, - { "username or realm name must not be specified when the api key id or api key name is specified", - "only one of [api key id, api key name] can be specified" }, - { "username or realm name must not be specified when the api key id or api key name is specified", - "only one of [api key id, api key name] can be specified" }, - { "username or realm name must not be specified when the api key id or api key name is specified" }, - { "only one of [api key id, api key name] can be specified" } }; + randomFrom(new String[] { null, "" }) } }; + String[][] expectedErrorMessages = new String[][] { { "One of [api key id, api key name, username, realm name] must be specified" + } }; for (int caseNo = 0; caseNo < inputs.length; caseNo++) { diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/role/PutRoleRequestTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/role/PutRoleRequestTests.java index 7ca9f4da74ab3..fb4a1de41972a 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/role/PutRoleRequestTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/role/PutRoleRequestTests.java @@ -21,7 +21,7 @@ import org.elasticsearch.test.VersionUtils; import org.elasticsearch.xpack.core.XPackClientPlugin; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor.ApplicationResourcePrivileges; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges; +import org.elasticsearch.xpack.core.security.authz.privilege.ManageApplicationPrivileges; import java.io.IOException; import java.util.Arrays; @@ -128,7 +128,7 @@ private PutRoleRequest buildRandomRequest() { if (randomBoolean()) { final String[] appNames = randomArray(1, 4, String[]::new, stringWithInitialLowercase); - request.conditionalCluster(new ConditionalClusterPrivileges.ManageApplicationPrivileges(Sets.newHashSet(appNames))); + request.conditionalCluster(new ManageApplicationPrivileges(Sets.newHashSet(appNames))); } request.runAs(generateRandomStringArray(4, 3, false, true)); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/user/GetUserPrivilegesResponseTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/user/GetUserPrivilegesResponseTests.java index a9e60fff3a167..69afde1977472 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/user/GetUserPrivilegesResponseTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/user/GetUserPrivilegesResponseTests.java @@ -21,8 +21,8 @@ import org.elasticsearch.xpack.core.XPackClientPlugin; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor.ApplicationResourcePrivileges; import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsDefinition.FieldGrantExcludeGroup; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges.ManageApplicationPrivileges; +import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.ManageApplicationPrivileges; import java.io.IOException; import java.util.ArrayList; @@ -75,7 +75,7 @@ public void testEqualsAndHashCode() throws IOException { public GetUserPrivilegesResponse mutate(GetUserPrivilegesResponse original) { final int random = randomIntBetween(1, 0b11111); final Set cluster = maybeMutate(random, 0, original.getClusterPrivileges(), () -> randomAlphaOfLength(5)); - final Set conditionalCluster = maybeMutate(random, 1, + final Set conditionalCluster = maybeMutate(random, 1, original.getConditionalClusterPrivileges(), () -> new ManageApplicationPrivileges(randomStringSet(3))); final Set index = maybeMutate(random, 2, original.getIndexPrivileges(), () -> new GetUserPrivilegesResponse.Indices(randomStringSet(1), randomStringSet(1), emptySet(), emptySet(), @@ -103,10 +103,10 @@ private Set maybeMutate(int random, int index, Set original, Supplier< private GetUserPrivilegesResponse randomResponse() { final Set cluster = randomStringSet(5); - final Set conditionalCluster = Sets.newHashSet(randomArray(3, ConditionalClusterPrivilege[]::new, - () -> new ManageApplicationPrivileges( - randomStringSet(3) - ))); + final Set conditionalCluster = Sets.newHashSet(randomArray(3, GlobalClusterPrivilege[]::new, + () -> new ManageApplicationPrivileges( + randomStringSet(3) + ))); final Set index = Sets.newHashSet(randomArray(5, GetUserPrivilegesResponse.Indices[]::new, () -> new GetUserPrivilegesResponse.Indices(randomStringSet(6), randomStringSet(8), Sets.newHashSet(randomArray(3, FieldGrantExcludeGroup[]::new, () -> new FieldGrantExcludeGroup( diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/user/HasPrivilegesRequestTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/user/HasPrivilegesRequestTests.java index 6dd1d8a25f088..b92945d6db5aa 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/user/HasPrivilegesRequestTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/user/HasPrivilegesRequestTests.java @@ -15,12 +15,11 @@ import org.elasticsearch.test.VersionUtils; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor.ApplicationResourcePrivileges; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor.IndicesPrivileges; -import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.DefaultClusterPrivilege; import java.io.IOException; import java.util.Arrays; import java.util.List; -import java.util.stream.Collectors; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; @@ -87,9 +86,10 @@ private HasPrivilegesRequest randomRequest() { final HasPrivilegesRequest request = new HasPrivilegesRequest(); request.username(randomAlphaOfLength(8)); - final List clusterPrivileges = randomSubsetOf(Arrays.asList(ClusterPrivilege.MONITOR, ClusterPrivilege.MANAGE, - ClusterPrivilege.MANAGE_ML, ClusterPrivilege.MANAGE_SECURITY, ClusterPrivilege.MANAGE_PIPELINE, ClusterPrivilege.ALL)) - .stream().flatMap(p -> p.name().stream()).collect(Collectors.toList()); + final List clusterPrivileges = randomSubsetOf( + Arrays.asList(DefaultClusterPrivilege.MONITOR.privilegeName(), DefaultClusterPrivilege.MANAGE.privilegeName(), + DefaultClusterPrivilege.MANAGE_ML.privilegeName(), DefaultClusterPrivilege.MANAGE_SECURITY.privilegeName(), + DefaultClusterPrivilege.MANAGE_PIPELINE.privilegeName(), DefaultClusterPrivilege.ALL.privilegeName())); request.clusterPrivileges(clusterPrivileges.toArray(Strings.EMPTY_ARRAY)); IndicesPrivileges[] indicesPrivileges = new IndicesPrivileges[randomInt(5)]; diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRoleTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRoleTests.java index 121f40c44d9a1..814b9f37f7167 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRoleTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRoleTests.java @@ -18,10 +18,11 @@ import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.core.security.authc.Authentication; 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.ApplicationPrivilegeDescriptor; -import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.DefaultClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege; import org.junit.Before; @@ -127,25 +128,24 @@ public void testAuthorize() { } public void testCheckClusterAction() { - Role fromRole = Role.builder("a-role").cluster(Collections.singleton("manage_security"), Collections.emptyList()) - .build(); - assertThat(fromRole.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class)), is(true)); + final Authentication authentication = mock(Authentication.class); + Role fromRole = Role.builder("a-role").cluster(Collections.singleton("manage_security"), Collections.emptyList()).build(); + assertThat(fromRole.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class), authentication), is(true)); { - Role limitedByRole = Role.builder("limited-role") - .cluster(Collections.singleton("all"), Collections.emptyList()).build(); - assertThat(limitedByRole.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class)), is(true)); - assertThat(limitedByRole.checkClusterAction("cluster:other-action", mock(TransportRequest.class)), is(true)); + Role limitedByRole = Role.builder("limited-role").cluster(Collections.singleton("all"), Collections.emptyList()).build(); + assertThat(limitedByRole.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class), authentication), + is(true)); + assertThat(limitedByRole.checkClusterAction("cluster:other-action", mock(TransportRequest.class), authentication), is(true)); Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole); - assertThat(role.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class)), is(true)); - assertThat(role.checkClusterAction("cluster:other-action", mock(TransportRequest.class)), is(false)); + assertThat(role.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class), authentication), is(true)); + assertThat(role.checkClusterAction("cluster:other-action", mock(TransportRequest.class), authentication), is(false)); } { - Role limitedByRole = Role.builder("limited-role") - .cluster(Collections.singleton("monitor"), Collections.emptyList()).build(); - assertThat(limitedByRole.checkClusterAction("cluster:monitor/me", mock(TransportRequest.class)), is(true)); + Role limitedByRole = Role.builder("limited-role").cluster(Collections.singleton("monitor"), Collections.emptyList()).build(); + assertThat(limitedByRole.checkClusterAction("cluster:monitor/me", mock(TransportRequest.class), authentication), is(true)); Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole); - assertThat(role.checkClusterAction("cluster:monitor/me", mock(TransportRequest.class)), is(false)); - assertThat(role.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class)), is(false)); + assertThat(role.checkClusterAction("cluster:monitor/me", mock(TransportRequest.class), authentication), is(false)); + assertThat(role.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class), authentication), is(false)); } } @@ -200,27 +200,27 @@ public void testAllowedIndicesMatcher() { public void testCheckClusterPrivilege() { Role fromRole = Role.builder("a-role").cluster(Collections.singleton("manage_security"), Collections.emptyList()) .build(); - assertThat(fromRole.grants(ClusterPrivilege.ALL), is(false)); - assertThat(fromRole.grants(ClusterPrivilege.MANAGE_SECURITY), is(true)); + assertThat(fromRole.grants(DefaultClusterPrivilege.ALL.clusterPrivilege()), is(false)); + assertThat(fromRole.grants(DefaultClusterPrivilege.MANAGE_SECURITY.clusterPrivilege()), is(true)); { Role limitedByRole = Role.builder("scoped-role") .cluster(Collections.singleton("all"), Collections.emptyList()).build(); - assertThat(limitedByRole.grants(ClusterPrivilege.ALL), is(true)); - assertThat(limitedByRole.grants(ClusterPrivilege.MANAGE_SECURITY), is(true)); + assertThat(limitedByRole.grants(DefaultClusterPrivilege.ALL.clusterPrivilege()), is(true)); + assertThat(limitedByRole.grants(DefaultClusterPrivilege.MANAGE_SECURITY.clusterPrivilege()), is(true)); Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole); - assertThat(role.grants(ClusterPrivilege.ALL), is(false)); - assertThat(role.grants(ClusterPrivilege.MANAGE_SECURITY), is(true)); + assertThat(role.grants(DefaultClusterPrivilege.ALL.clusterPrivilege()), is(false)); + assertThat(role.grants(DefaultClusterPrivilege.MANAGE_SECURITY.clusterPrivilege()), is(true)); } { Role limitedByRole = Role.builder("scoped-role") .cluster(Collections.singleton("monitor"), Collections.emptyList()).build(); - assertThat(limitedByRole.grants(ClusterPrivilege.ALL), is(false)); - assertThat(limitedByRole.grants(ClusterPrivilege.MONITOR), is(true)); + assertThat(limitedByRole.grants(DefaultClusterPrivilege.ALL.clusterPrivilege()), is(false)); + assertThat(limitedByRole.grants(DefaultClusterPrivilege.MONITOR.clusterPrivilege()), is(true)); Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole); - assertThat(role.grants(ClusterPrivilege.ALL), is(false)); - assertThat(role.grants(ClusterPrivilege.MANAGE_SECURITY), is(false)); - assertThat(role.grants(ClusterPrivilege.MONITOR), is(false)); + assertThat(role.grants(DefaultClusterPrivilege.ALL.clusterPrivilege()), is(false)); + assertThat(role.grants(DefaultClusterPrivilege.MANAGE_SECURITY.clusterPrivilege()), is(false)); + assertThat(role.grants(DefaultClusterPrivilege.MONITOR.clusterPrivilege()), is(false)); } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolverTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolverTests.java new file mode 100644 index 0000000000000..17dc7f1660deb --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolverTests.java @@ -0,0 +1,89 @@ +/* + * 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.test.ESTestCase; +import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyRequest; +import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenRequest; +import org.elasticsearch.xpack.core.security.authc.Authentication; +import org.elasticsearch.xpack.core.security.user.User; +import org.junit.Before; + +import java.util.Map; +import java.util.Set; + +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ClusterPrivilegeResolverTests extends ESTestCase { + + private User user; + private Authentication authentication = mock(Authentication.class); + private Authentication.RealmRef authenticatedBy = mock(Authentication.RealmRef.class); + + @Before + public void setup() { + user = new User("user1"); + when(authentication.getUser()).thenReturn(user); + when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy); + when(authenticatedBy.getName()).thenReturn("realm1"); + when(authenticatedBy.getType()).thenReturn("kerberos"); + } + + public void testResolveNamesContainingMixOfConditionalsAndNonConditionals() { + Set names = Set.of("manage_token", "manage_own_api_key"); + ClusterPrivilege cp = ClusterPrivilegeResolver.resolve(names); + assertThat(cp.predicate().test("cluster:admin/xpack/security/token/invalidate"), is(true)); + assertThat(cp.predicate().test("cluster:admin/xpack/security/api_key/invalidate"), is(true)); + + assertThat(cp, instanceOf(ConditionalClusterPrivilege.class)); + ConditionalClusterPrivilege conditionalClusterPrivilege = (ConditionalClusterPrivilege) cp; + TransportRequest tr = new InvalidateTokenRequest(); + assertThat(conditionalClusterPrivilege.getRequestPredicate().test(tr, authentication), is(false)); + tr = InvalidateApiKeyRequest.usingApiKeyId("user1-api-key-id"); + // API key id is always required to evaluate condition if authenticated by API key id + when(authenticatedBy.getName()).thenReturn("_es_api_key"); + when(authenticatedBy.getType()).thenReturn("_es_api_key"); + when(authentication.getMetadata()).thenReturn(Map.of("_security_api_key_id", "user1-api-key-id")); + assertThat(conditionalClusterPrivilege.getRequestPredicate().test(tr, authentication), is(true)); + + tr = new InvalidateApiKeyRequest(); + assertThat(conditionalClusterPrivilege.getRequestPredicate().test(tr, authentication), is(false)); + } + + public void testResolveNamesContainingNonConditionalsOnly() { + Set names = Set.of("manage_token", "manage_api_key"); + ClusterPrivilege cp = ClusterPrivilegeResolver.resolve(names); + assertThat(cp.predicate().test("cluster:admin/xpack/security/token/invalidate"), is(true)); + assertThat(cp.predicate().test("cluster:admin/xpack/security/api_key/invalidate"), is(true)); + assertThat(cp.predicate().test("cluster:admin/xpack/security/token/get"), is(true)); + assertThat(cp.predicate().test("cluster:admin/xpack/security/api_key/get"), is(true)); + assertThat(cp, not(instanceOf(ConditionalClusterPrivilege.class))); + } + + public void testResolveNamesContainingConditionalsOnly() { + Set names = Set.of("manage_own_api_key"); + ClusterPrivilege cp = ClusterPrivilegeResolver.resolve(names); + assertThat(cp.predicate().test("cluster:admin/xpack/security/api_key/invalidate"), is(true)); + assertThat(cp.predicate().test("cluster:admin/xpack/security/api_key/get"), is(true)); + + assertThat(cp, instanceOf(ConditionalClusterPrivilege.class)); + ConditionalClusterPrivilege conditionalClusterPrivilege = (ConditionalClusterPrivilege) cp; + TransportRequest tr = new InvalidateTokenRequest(); + assertThat(conditionalClusterPrivilege.getRequestPredicate().test(tr, authentication), is(false)); + tr = InvalidateApiKeyRequest.usingApiKeyId("user1-api-key-id"); + // API key id is always required to evaluate condition if authenticated by API key id + when(authenticatedBy.getName()).thenReturn("_es_api_key"); + when(authenticatedBy.getType()).thenReturn("_es_api_key"); + when(authentication.getMetadata()).thenReturn(Map.of("_security_api_key_id", "user1-api-key-id")); + assertThat(conditionalClusterPrivilege.getRequestPredicate().test(tr, authentication), is(true)); + } +} diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ConditionalClusterPrivilegesTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/GlobalClusterPrivilegesTests.java similarity index 76% rename from x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ConditionalClusterPrivilegesTests.java rename to x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/GlobalClusterPrivilegesTests.java index ebcd70869cb02..45eac5d0e8336 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ConditionalClusterPrivilegesTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/GlobalClusterPrivilegesTests.java @@ -27,15 +27,15 @@ import static org.elasticsearch.common.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION; import static org.hamcrest.Matchers.equalTo; -public class ConditionalClusterPrivilegesTests extends ESTestCase { +public class GlobalClusterPrivilegesTests extends ESTestCase { public void testSerialization() throws Exception { - final ConditionalClusterPrivilege[] original = buildSecurityPrivileges(); + final GlobalClusterPrivilege[] original = buildSecurityPrivileges(); try (BytesStreamOutput out = new BytesStreamOutput()) { - ConditionalClusterPrivileges.writeArray(out, original); + GlobalClusterPrivileges.writeArray(out, original); final NamedWriteableRegistry registry = new NamedWriteableRegistry(new XPackClientPlugin(Settings.EMPTY).getNamedWriteables()); try (StreamInput in = new NamedWriteableAwareStreamInput(out.bytes().streamInput(), registry)) { - final ConditionalClusterPrivilege[] copy = ConditionalClusterPrivileges.readArray(in); + final GlobalClusterPrivilege[] copy = GlobalClusterPrivileges.readArray(in); assertThat(copy, equalTo(original)); assertThat(original, equalTo(copy)); } @@ -47,26 +47,26 @@ public void testGenerateAndParseXContent() throws Exception { try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { final XContentBuilder builder = new XContentBuilder(xContent, out); - final List original = Arrays.asList(buildSecurityPrivileges()); - ConditionalClusterPrivileges.toXContent(builder, ToXContent.EMPTY_PARAMS, original); + final List original = Arrays.asList(buildSecurityPrivileges()); + GlobalClusterPrivileges.toXContent(builder, ToXContent.EMPTY_PARAMS, original); builder.flush(); final byte[] bytes = out.toByteArray(); try (XContentParser parser = xContent.createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, bytes)) { assertThat(parser.nextToken(), equalTo(XContentParser.Token.START_OBJECT)); - final List clone = ConditionalClusterPrivileges.parse(parser); + final List clone = GlobalClusterPrivileges.parse(parser); assertThat(clone, equalTo(original)); assertThat(original, equalTo(clone)); } } } - private ConditionalClusterPrivilege[] buildSecurityPrivileges() { + private GlobalClusterPrivilege[] buildSecurityPrivileges() { return buildSecurityPrivileges(randomIntBetween(4, 7)); } - private ConditionalClusterPrivilege[] buildSecurityPrivileges(int applicationNameLength) { - return new ConditionalClusterPrivilege[] { + private GlobalClusterPrivilege[] buildSecurityPrivileges(int applicationNameLength) { + return new GlobalClusterPrivilege[] { ManageApplicationPrivilegesTests.buildPrivileges(applicationNameLength) }; } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalClusterPrivilegeTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalClusterPrivilegeTests.java new file mode 100644 index 0000000000000..d206f265c0c04 --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalClusterPrivilegeTests.java @@ -0,0 +1,98 @@ +/* + * 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.test.ESTestCase; +import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.core.security.action.CreateApiKeyRequest; +import org.elasticsearch.xpack.core.security.action.GetApiKeyRequest; +import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyRequest; +import org.elasticsearch.xpack.core.security.authc.Authentication; +import org.elasticsearch.xpack.core.security.user.User; +import org.junit.Before; + +import java.util.Map; + +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ManageApiKeyConditionalClusterPrivilegeTests extends ESTestCase { + private static final String CREATE_ACTION = "cluster:admin/xpack/security/api_key/create"; + private static final String GET_ACTION = "cluster:admin/xpack/security/api_key/get"; + private static final String INVALIDATE_ACTION = "cluster:admin/xpack/security/api_key/invalidate"; + + private User user; + private Authentication authentication = mock(Authentication.class); + private Authentication.RealmRef authenticatedBy = mock(Authentication.RealmRef.class); + + @Before + public void setup() { + user = new User("user1"); + when(authentication.getUser()).thenReturn(user); + when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy); + when(authenticatedBy.getName()).thenReturn("realm1"); + when(authenticatedBy.getType()).thenReturn("kerberos"); + } + + public void testManagePrivilegeOwnerOnly() { + final ManageApiKeyConditionalClusterPrivilege condPrivilege = ManageApiKeyConditionalClusterPrivilege + .createOwnerManageApiKeyConditionalClusterPrivilege(); + + boolean accessAllowed = checkAccess(condPrivilege, CREATE_ACTION, new CreateApiKeyRequest(), authentication); + assertThat(accessAllowed, is(true)); + + // Username and realm name is always required to evaluate condition if authenticated by user + accessAllowed = checkAccess(condPrivilege, GET_ACTION, GetApiKeyRequest.usingRealmAndUserName("realm1", "user1"), authentication); + assertThat(accessAllowed, is(true)); + accessAllowed = checkAccess(condPrivilege, GET_ACTION, GetApiKeyRequest.usingRealmAndUserName("realm1", randomAlphaOfLength(4)), + authentication); + assertThat(accessAllowed, is(false)); + accessAllowed = checkAccess(condPrivilege, GET_ACTION, GetApiKeyRequest.usingRealmAndUserName(randomAlphaOfLength(4), "user1"), + authentication); + assertThat(accessAllowed, is(false)); + + accessAllowed = checkAccess(condPrivilege, INVALIDATE_ACTION, InvalidateApiKeyRequest.usingRealmAndUserName("realm1", "user1"), + authentication); + assertThat(accessAllowed, is(true)); + accessAllowed = checkAccess(condPrivilege, INVALIDATE_ACTION, + InvalidateApiKeyRequest.usingRealmAndUserName("realm2", randomAlphaOfLength(4)), authentication); + assertThat(accessAllowed, is(false)); + accessAllowed = checkAccess(condPrivilege, GET_ACTION, GetApiKeyRequest.usingApiKeyName("api-key-name"), authentication); + assertThat(accessAllowed, is(false)); + accessAllowed = checkAccess(condPrivilege, INVALIDATE_ACTION, InvalidateApiKeyRequest.usingApiKeyName("api-key-name"), + authentication); + assertThat(accessAllowed, is(false)); + + // API key id is always required to evaluate condition if authenticated by API key id + when(authenticatedBy.getName()).thenReturn("_es_api_key"); + when(authenticatedBy.getType()).thenReturn("_es_api_key"); + when(authentication.getMetadata()).thenReturn(Map.of("_security_api_key_id", "user1-api-key-id")); + + accessAllowed = checkAccess(condPrivilege, GET_ACTION, GetApiKeyRequest.usingApiKeyId("user1-api-key-id"), authentication); + assertThat(accessAllowed, is(true)); + accessAllowed = checkAccess(condPrivilege, GET_ACTION, GetApiKeyRequest.usingApiKeyId(randomAlphaOfLength(5)), authentication); + assertThat(accessAllowed, is(false)); + accessAllowed = checkAccess(condPrivilege, INVALIDATE_ACTION, InvalidateApiKeyRequest.usingApiKeyId("user1-api-key-id"), + authentication); + assertThat(accessAllowed, is(true)); + accessAllowed = checkAccess(condPrivilege, INVALIDATE_ACTION, InvalidateApiKeyRequest.usingApiKeyId(randomAlphaOfLength(5)), + authentication); + assertThat(accessAllowed, is(false)); + accessAllowed = checkAccess(condPrivilege, GET_ACTION, GetApiKeyRequest.usingApiKeyName("api-key-name"), authentication); + assertThat(accessAllowed, is(false)); + accessAllowed = checkAccess(condPrivilege, INVALIDATE_ACTION, InvalidateApiKeyRequest.usingApiKeyName("api-key-name"), + authentication); + assertThat(accessAllowed, is(false)); + } + + private boolean checkAccess(ManageApiKeyConditionalClusterPrivilege privilege, String action, TransportRequest request, + Authentication authentication) { + return privilege.predicate().test(action) && privilege.getRequestPredicate().test(request, authentication); + } + +} diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApplicationPrivilegesTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApplicationPrivilegesTests.java index 9c113d4ff0f94..2a83e09b6e21e 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApplicationPrivilegesTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApplicationPrivilegesTests.java @@ -32,7 +32,7 @@ import org.elasticsearch.xpack.core.security.action.rolemapping.DeleteRoleMappingAction; import org.elasticsearch.xpack.core.security.action.user.GetUsersAction; import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesAction; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges.ManageApplicationPrivileges; +import org.elasticsearch.xpack.core.security.authc.Authentication; import java.io.ByteArrayOutputStream; import java.util.ArrayList; @@ -42,7 +42,7 @@ import java.util.List; import java.util.Locale; import java.util.Set; -import java.util.function.Predicate; +import java.util.function.BiPredicate; import static org.elasticsearch.common.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION; import static org.elasticsearch.test.TestMatchers.predicateMatches; @@ -51,6 +51,7 @@ import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; +import static org.mockito.Mockito.mock; public class ManageApplicationPrivilegesTests extends ESTestCase { @@ -102,32 +103,33 @@ public void testEqualsAndHashCode() { public void testPrivilege() { final ManageApplicationPrivileges privileges = buildPrivileges(); - assertThat(privileges.getPrivilege(), instanceOf(ClusterPrivilege.class)); + assertThat(privileges, instanceOf(ClusterPrivilege.class)); for (String actionName : Arrays.asList(GetPrivilegesAction.NAME, PutPrivilegesAction.NAME, DeletePrivilegesAction.NAME)) { - assertThat(privileges.getPrivilege().predicate(), predicateMatches(actionName)); + assertThat(privileges.predicate(), predicateMatches(actionName)); } for (String actionName : Arrays.asList(GetUsersAction.NAME, PutRoleAction.NAME, DeleteRoleMappingAction.NAME, HasPrivilegesAction.NAME)) { - assertThat(privileges.getPrivilege().predicate(), not(predicateMatches(actionName))); + assertThat(privileges.predicate(), not(predicateMatches(actionName))); } } public void testRequestPredicate() { + final Authentication authentication = mock(Authentication.class); final ManageApplicationPrivileges kibanaAndLogstash = new ManageApplicationPrivileges(Sets.newHashSet("kibana-*", "logstash")); final ManageApplicationPrivileges cloudAndSwiftype = new ManageApplicationPrivileges(Sets.newHashSet("cloud-*", "swiftype")); - final Predicate kibanaAndLogstashPredicate = kibanaAndLogstash.getRequestPredicate(); - final Predicate cloudAndSwiftypePredicate = cloudAndSwiftype.getRequestPredicate(); + final BiPredicate kibanaAndLogstashPredicate = kibanaAndLogstash.getRequestPredicate(); + final BiPredicate cloudAndSwiftypePredicate = cloudAndSwiftype.getRequestPredicate(); assertThat(kibanaAndLogstashPredicate, notNullValue()); assertThat(cloudAndSwiftypePredicate, notNullValue()); final GetPrivilegesRequest getKibana1 = new GetPrivilegesRequest(); getKibana1.application("kibana-1"); - assertThat(kibanaAndLogstashPredicate, predicateMatches(getKibana1)); - assertThat(cloudAndSwiftypePredicate, not(predicateMatches(getKibana1))); + assertThat(kibanaAndLogstashPredicate, predicateMatches(getKibana1, authentication)); + assertThat(cloudAndSwiftypePredicate, not(predicateMatches(getKibana1, authentication))); final DeletePrivilegesRequest deleteLogstash = new DeletePrivilegesRequest("logstash", new String[]{"all"}); - assertThat(kibanaAndLogstashPredicate, predicateMatches(deleteLogstash)); - assertThat(cloudAndSwiftypePredicate, not(predicateMatches(deleteLogstash))); + assertThat(kibanaAndLogstashPredicate, predicateMatches(deleteLogstash, authentication)); + assertThat(cloudAndSwiftypePredicate, not(predicateMatches(deleteLogstash, authentication))); final PutPrivilegesRequest putKibana = new PutPrivilegesRequest(); @@ -137,11 +139,12 @@ public void testRequestPredicate() { randomAlphaOfLengthBetween(3, 6).toLowerCase(Locale.ROOT), Collections.emptySet(), Collections.emptyMap())); } putKibana.setPrivileges(kibanaPrivileges); - assertThat(kibanaAndLogstashPredicate, predicateMatches(putKibana)); - assertThat(cloudAndSwiftypePredicate, not(predicateMatches(putKibana))); + assertThat(kibanaAndLogstashPredicate, predicateMatches(putKibana, authentication)); + assertThat(cloudAndSwiftypePredicate, not(predicateMatches(putKibana, authentication))); } public void testSecurityForGetAllApplicationPrivileges() { + final Authentication authentication = mock(Authentication.class); final GetPrivilegesRequest getAll = new GetPrivilegesRequest(); getAll.application(null); getAll.privileges(new String[0]); @@ -151,8 +154,8 @@ public void testSecurityForGetAllApplicationPrivileges() { final ManageApplicationPrivileges kibanaOnly = new ManageApplicationPrivileges(Sets.newHashSet("kibana-*")); final ManageApplicationPrivileges allApps = new ManageApplicationPrivileges(Sets.newHashSet("*")); - assertThat(kibanaOnly.getRequestPredicate(), not(predicateMatches(getAll))); - assertThat(allApps.getRequestPredicate(), predicateMatches(getAll)); + assertThat(kibanaOnly.getRequestPredicate(), not(predicateMatches(getAll, authentication))); + assertThat(allApps.getRequestPredicate(), predicateMatches(getAll, authentication)); } private ManageApplicationPrivileges clone(ManageApplicationPrivileges original) { diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/MergeableClusterPrivilegeTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/MergeableClusterPrivilegeTests.java new file mode 100644 index 0000000000000..013681cf6cfd9 --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/MergeableClusterPrivilegeTests.java @@ -0,0 +1,99 @@ +/* + * 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.test.ESTestCase; +import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyRequest; +import org.elasticsearch.xpack.core.security.action.privilege.PutPrivilegesRequest; +import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenRequest; +import org.elasticsearch.xpack.core.security.authc.Authentication; +import org.elasticsearch.xpack.core.security.user.User; +import org.junit.Before; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class MergeableClusterPrivilegeTests extends ESTestCase { + + private User user; + private Authentication authentication = mock(Authentication.class); + private Authentication.RealmRef authenticatedBy = mock(Authentication.RealmRef.class); + + @Before + public void setup() { + user = new User("user1"); + when(authentication.getUser()).thenReturn(user); + when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy); + when(authenticatedBy.getName()).thenReturn("realm1"); + when(authenticatedBy.getType()).thenReturn("kerberos"); + } + + public void testMergeNonConditionalClusterPrivilegesOnly() { + final List clusterPrivileges = List.of(ClusterPrivilegeResolver.resolve(Set.of("manage_token")), + ClusterPrivilegeResolver.resolve(Set.of("manage_api_key"))); + final ClusterPrivilege cp = MergeableClusterPrivilege.merge(clusterPrivileges); + assertThat(cp, instanceOf(ClusterPrivilege.class)); + assertThat(cp, not(instanceOf(MergeableClusterPrivilege.class))); + assertThat(cp.predicate().test("cluster:admin/xpack/security/token/invalidate"), is(true)); + assertThat(cp.predicate().test("cluster:admin/xpack/security/api_key/invalidate"), is(true)); + } + + public void testMergeConditionalClusterPrivilegesOnly() { + final List clusterPrivileges = List.of(ClusterPrivilegeResolver.resolve(Set.of("manage_own_api_key")), + new ManageApplicationPrivileges(Set.of("kibana-app-*"))); + final ClusterPrivilege cp = MergeableClusterPrivilege.merge(clusterPrivileges); + assertThat(cp, instanceOf(ClusterPrivilege.class)); + assertThat(cp, instanceOf(MergeableClusterPrivilege.class)); + ConditionalClusterPrivilege ccp = (ConditionalClusterPrivilege) cp; + assertThat(ccp.predicate().test("cluster:admin/xpack/security/token/invalidate"), is(false)); + assertThat(ccp.predicate().test("cluster:admin/xpack/security/api_key/invalidate"), is(true)); + + TransportRequest tr = new InvalidateTokenRequest(); + assertThat(ccp.getRequestPredicate().test(tr, authentication), is(false)); + tr = InvalidateApiKeyRequest.usingApiKeyId("user1-api-key-id"); + // API key id is always required to evaluate condition if authenticated by API key id + when(authenticatedBy.getName()).thenReturn("_es_api_key"); + when(authenticatedBy.getType()).thenReturn("_es_api_key"); + when(authentication.getMetadata()).thenReturn(Map.of("_security_api_key_id", "user1-api-key-id")); + assertThat(ccp.getRequestPredicate().test(tr, authentication), is(true)); + + PutPrivilegesRequest putPrivilegesReq = new PutPrivilegesRequest(); + putPrivilegesReq.setPrivileges( + List.of(new ApplicationPrivilegeDescriptor("kibana-app-1", "foo", Set.of("read", "write"), Collections.emptyMap()))); + assertThat(ccp.predicate().test("cluster:admin/xpack/security/privilege/put"), is(true)); + assertThat(ccp.getRequestPredicate().test(putPrivilegesReq, authentication), is(true)); + } + + public void testMergeMixOfConditionalAndNonConditionalClusterPrivileges() { + final List clusterPrivileges = List.of(ClusterPrivilegeResolver.resolve(Set.of("manage_token")), + ClusterPrivilegeResolver.resolve(Set.of("manage_own_api_key"))); + final ClusterPrivilege cp = MergeableClusterPrivilege.merge(clusterPrivileges); + assertThat(cp, instanceOf(ClusterPrivilege.class)); + assertThat(cp, instanceOf(MergeableClusterPrivilege.class)); + ConditionalClusterPrivilege ccp = (ConditionalClusterPrivilege) cp; + assertThat(ccp.predicate().test("cluster:admin/xpack/security/token/invalidate"), is(true)); + assertThat(ccp.predicate().test("cluster:admin/xpack/security/api_key/invalidate"), is(true)); + + TransportRequest tr = new InvalidateTokenRequest(); + assertThat(ccp.getRequestPredicate().test(tr, authentication), is(false)); + tr = InvalidateApiKeyRequest.usingApiKeyId("user1-api-key-id"); + // API key id is always required to evaluate condition if authenticated by API key id + when(authenticatedBy.getName()).thenReturn("_es_api_key"); + when(authenticatedBy.getType()).thenReturn("_es_api_key"); + when(authentication.getMetadata()).thenReturn(Map.of("_security_api_key_id", "user1-api-key-id")); + assertThat(ccp.getRequestPredicate().test(tr, authentication), is(true)); + } +} diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/PrivilegeTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/PrivilegeTests.java index 4af7dd2e57d62..b8ea69e0c92a4 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/PrivilegeTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/PrivilegeTests.java @@ -32,36 +32,36 @@ public void testSubActionPattern() throws Exception { public void testCluster() throws Exception { Set name = Sets.newHashSet("monitor"); - ClusterPrivilege cluster = ClusterPrivilege.get(name); - assertThat(cluster, is(ClusterPrivilege.MONITOR)); + ClusterPrivilege cluster = ClusterPrivilegeResolver.resolve(name); + assertThat(cluster, is(DefaultClusterPrivilege.MONITOR.clusterPrivilege())); // since "all" implies "monitor", this should be the same language as All name = Sets.newHashSet("monitor", "all"); - cluster = ClusterPrivilege.get(name); - assertTrue(Operations.sameLanguage(ClusterPrivilege.ALL.automaton, cluster.automaton)); + cluster = ClusterPrivilegeResolver.resolve(name); + assertTrue(Operations.sameLanguage(DefaultClusterPrivilege.ALL.clusterPrivilege().getAutomaton(), cluster.automaton)); name = Sets.newHashSet("monitor", "none"); - cluster = ClusterPrivilege.get(name); - assertTrue(Operations.sameLanguage(ClusterPrivilege.MONITOR.automaton, cluster.automaton)); + cluster = ClusterPrivilegeResolver.resolve(name); + assertTrue(Operations.sameLanguage(DefaultClusterPrivilege.MONITOR.clusterPrivilege().getAutomaton(), cluster.automaton)); Set name2 = Sets.newHashSet("none", "monitor"); - ClusterPrivilege cluster2 = ClusterPrivilege.get(name2); + ClusterPrivilege cluster2 = ClusterPrivilegeResolver.resolve(name2); assertThat(cluster, is(cluster2)); } public void testClusterTemplateActions() throws Exception { Set name = Sets.newHashSet("indices:admin/template/delete"); - ClusterPrivilege cluster = ClusterPrivilege.get(name); + ClusterPrivilege cluster = ClusterPrivilegeResolver.resolve(name); assertThat(cluster, notNullValue()); assertThat(cluster.predicate().test("indices:admin/template/delete"), is(true)); name = Sets.newHashSet("indices:admin/template/get"); - cluster = ClusterPrivilege.get(name); + cluster = ClusterPrivilegeResolver.resolve(name); assertThat(cluster, notNullValue()); assertThat(cluster.predicate().test("indices:admin/template/get"), is(true)); name = Sets.newHashSet("indices:admin/template/put"); - cluster = ClusterPrivilege.get(name); + cluster = ClusterPrivilegeResolver.resolve(name); assertThat(cluster, notNullValue()); assertThat(cluster.predicate().test("indices:admin/template/put"), is(true)); } @@ -69,12 +69,12 @@ public void testClusterTemplateActions() throws Exception { public void testClusterInvalidName() throws Exception { thrown.expect(IllegalArgumentException.class); Set actionName = Sets.newHashSet("foobar"); - ClusterPrivilege.get(actionName); + ClusterPrivilegeResolver.resolve(actionName); } public void testClusterAction() throws Exception { Set actionName = Sets.newHashSet("cluster:admin/snapshot/delete"); - ClusterPrivilege cluster = ClusterPrivilege.get(actionName); + ClusterPrivilege cluster = ClusterPrivilegeResolver.resolve(actionName); assertThat(cluster, notNullValue()); assertThat(cluster.predicate().test("cluster:admin/snapshot/delete"), is(true)); assertThat(cluster.predicate().test("cluster:admin/snapshot/dele"), is(false)); @@ -144,7 +144,7 @@ public void testSystem() throws Exception { } public void testManageCcrPrivilege() { - Predicate predicate = ClusterPrivilege.MANAGE_CCR.predicate(); + Predicate predicate = DefaultClusterPrivilege.MANAGE_CCR.clusterPrivilege().predicate(); assertThat(predicate.test("cluster:admin/xpack/ccr/follow_index"), is(true)); assertThat(predicate.test("cluster:admin/xpack/ccr/unfollow_index"), is(true)); assertThat(predicate.test("cluster:admin/xpack/ccr/brand_new_api"), is(true)); @@ -153,7 +153,7 @@ public void testManageCcrPrivilege() { public void testIlmPrivileges() { { - Predicate predicate = ClusterPrivilege.MANAGE_ILM.predicate(); + Predicate predicate = DefaultClusterPrivilege.MANAGE_ILM.clusterPrivilege().predicate(); // check cluster actions assertThat(predicate.test("cluster:admin/ilm/delete"), is(true)); assertThat(predicate.test("cluster:admin/ilm/_move/post"), is(true)); @@ -168,7 +168,7 @@ public void testIlmPrivileges() { } { - Predicate predicate = ClusterPrivilege.READ_ILM.predicate(); + Predicate predicate = DefaultClusterPrivilege.READ_ILM.clusterPrivilege().predicate(); // check cluster actions assertThat(predicate.test("cluster:admin/ilm/delete"), is(false)); assertThat(predicate.test("cluster:admin/ilm/_move/post"), is(false)); @@ -204,4 +204,29 @@ public void testIlmPrivileges() { assertThat(predicate.test("indices:admin/whatever"), is(false)); } } + + public void testClusterPrivilegeAndPlainConditionalClusterPrivilege() { + Set actionName = Sets.newHashSet("cluster:admin/snapshot/delete", "manage_own_api_key"); + ClusterPrivilege cluster = ClusterPrivilegeResolver.resolve(actionName); + assertThat(cluster, notNullValue()); + assertThat(cluster.predicate().test("cluster:admin/snapshot/delete"), is(true)); + assertThat(cluster.predicate().test("cluster:admin/snapshot/dele"), is(false)); + + assertThat(cluster.predicate().test("cluster:admin/xpack/security/api_key/create"), is(true)); + assertThat(cluster.predicate().test("cluster:admin/xpack/security/api_key/get"), is(true)); + assertThat(cluster.predicate().test("cluster:admin/xpack/security/api_key/invalidate"), is(true)); + } + + public void testConditionalClusterPrivilegesOnly() { + Set actionName = Sets.newHashSet("manage_own_api_key"); + ClusterPrivilege cluster = ClusterPrivilegeResolver.resolve(actionName); + + assertThat(cluster, notNullValue()); + assertThat(cluster.predicate().test("cluster:admin/snapshot/delete"), is(false)); + assertThat(cluster.predicate().test("cluster:admin/snapshot/dele"), is(false)); + + assertThat(cluster.predicate().test("cluster:admin/xpack/security/api_key/create"), is(true)); + assertThat(cluster.predicate().test("cluster:admin/xpack/security/api_key/get"), is(true)); + assertThat(cluster.predicate().test("cluster:admin/xpack/security/api_key/invalidate"), is(true)); + } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java index bf2c08a913821..1e176a28b7a52 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java @@ -123,6 +123,7 @@ import org.elasticsearch.xpack.core.security.action.token.CreateTokenAction; import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenAction; import org.elasticsearch.xpack.core.security.action.user.PutUserAction; +import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl.IndexAccessControl; import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsCache; @@ -198,6 +199,7 @@ public void testIsReserved() { } public void testSnapshotUserRole() { + final Authentication authentication = mock(Authentication.class); final TransportRequest request = mock(TransportRequest.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("snapshot_user"); @@ -205,27 +207,27 @@ public void testSnapshotUserRole() { assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role snapshotUserRole = Role.builder(roleDescriptor, null).build(); - assertThat(snapshotUserRole.cluster().check(GetRepositoriesAction.NAME, request), is(true)); - assertThat(snapshotUserRole.cluster().check(CreateSnapshotAction.NAME, request), is(true)); - assertThat(snapshotUserRole.cluster().check(SnapshotsStatusAction.NAME, request), is(true)); - assertThat(snapshotUserRole.cluster().check(GetSnapshotsAction.NAME, request), is(true)); - - assertThat(snapshotUserRole.cluster().check(PutRepositoryAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(GetIndexTemplatesAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(DeleteIndexTemplateAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(PutPipelineAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(GetPipelineAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(DeletePipelineAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(MonitoringBulkAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(GetWatchAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(PutWatchAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(DeleteWatchAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(ExecuteWatchAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(AckWatchAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(ActivateWatchAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(WatcherServiceAction.NAME, request), is(false)); + assertThat(snapshotUserRole.cluster().check(GetRepositoriesAction.NAME, request, authentication), is(true)); + assertThat(snapshotUserRole.cluster().check(CreateSnapshotAction.NAME, request, authentication), is(true)); + assertThat(snapshotUserRole.cluster().check(SnapshotsStatusAction.NAME, request, authentication), is(true)); + assertThat(snapshotUserRole.cluster().check(GetSnapshotsAction.NAME, request, authentication), is(true)); + + assertThat(snapshotUserRole.cluster().check(PutRepositoryAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(GetIndexTemplatesAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(DeleteIndexTemplateAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(PutPipelineAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(GetPipelineAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(DeletePipelineAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(GetWatchAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(PutWatchAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(DeleteWatchAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(ExecuteWatchAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(AckWatchAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(ActivateWatchAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(WatcherServiceAction.NAME, request, authentication), is(false)); assertThat(snapshotUserRole.indices().allowedIndicesMatcher(IndexAction.NAME).test(randomAlphaOfLengthBetween(8, 24)), is(false)); assertThat(snapshotUserRole.indices().allowedIndicesMatcher("indices:foo").test(randomAlphaOfLengthBetween(8, 24)), is(false)); @@ -245,6 +247,7 @@ public void testSnapshotUserRole() { } public void testIngestAdminRole() { + final Authentication authentication = mock(Authentication.class); final TransportRequest request = mock(TransportRequest.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("ingest_admin"); @@ -252,16 +255,16 @@ public void testIngestAdminRole() { assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role ingestAdminRole = Role.builder(roleDescriptor, null).build(); - assertThat(ingestAdminRole.cluster().check(PutIndexTemplateAction.NAME, request), is(true)); - assertThat(ingestAdminRole.cluster().check(GetIndexTemplatesAction.NAME, request), is(true)); - assertThat(ingestAdminRole.cluster().check(DeleteIndexTemplateAction.NAME, request), is(true)); - assertThat(ingestAdminRole.cluster().check(PutPipelineAction.NAME, request), is(true)); - assertThat(ingestAdminRole.cluster().check(GetPipelineAction.NAME, request), is(true)); - assertThat(ingestAdminRole.cluster().check(DeletePipelineAction.NAME, request), is(true)); + assertThat(ingestAdminRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(true)); + assertThat(ingestAdminRole.cluster().check(GetIndexTemplatesAction.NAME, request, authentication), is(true)); + assertThat(ingestAdminRole.cluster().check(DeleteIndexTemplateAction.NAME, request, authentication), is(true)); + assertThat(ingestAdminRole.cluster().check(PutPipelineAction.NAME, request, authentication), is(true)); + assertThat(ingestAdminRole.cluster().check(GetPipelineAction.NAME, request, authentication), is(true)); + assertThat(ingestAdminRole.cluster().check(DeletePipelineAction.NAME, request, authentication), is(true)); - assertThat(ingestAdminRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(ingestAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(ingestAdminRole.cluster().check(MonitoringBulkAction.NAME, request), is(false)); + assertThat(ingestAdminRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(ingestAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(ingestAdminRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false)); assertThat(ingestAdminRole.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false)); assertThat(ingestAdminRole.indices().allowedIndicesMatcher("indices:foo").test(randomAlphaOfLengthBetween(8, 24)), @@ -273,6 +276,7 @@ public void testIngestAdminRole() { } public void testKibanaSystemRole() { + final Authentication authentication = mock(Authentication.class); final TransportRequest request = mock(TransportRequest.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("kibana_system"); @@ -280,33 +284,33 @@ public void testKibanaSystemRole() { assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role kibanaRole = Role.builder(roleDescriptor, null).build(); - assertThat(kibanaRole.cluster().check(ClusterHealthAction.NAME, request), is(true)); - assertThat(kibanaRole.cluster().check(ClusterStateAction.NAME, request), is(true)); - assertThat(kibanaRole.cluster().check(ClusterStatsAction.NAME, request), is(true)); - assertThat(kibanaRole.cluster().check(PutIndexTemplateAction.NAME, request), is(true)); - assertThat(kibanaRole.cluster().check(GetIndexTemplatesAction.NAME, request), is(true)); - assertThat(kibanaRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(kibanaRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(kibanaRole.cluster().check(MonitoringBulkAction.NAME, request), is(true)); + assertThat(kibanaRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true)); + assertThat(kibanaRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true)); + assertThat(kibanaRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true)); + assertThat(kibanaRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(true)); + assertThat(kibanaRole.cluster().check(GetIndexTemplatesAction.NAME, request, authentication), is(true)); + assertThat(kibanaRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(kibanaRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(kibanaRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(true)); // SAML and token - assertThat(kibanaRole.cluster().check(SamlPrepareAuthenticationAction.NAME, request), is(true)); - assertThat(kibanaRole.cluster().check(SamlAuthenticateAction.NAME, request), is(true)); - assertThat(kibanaRole.cluster().check(InvalidateTokenAction.NAME, request), is(true)); - assertThat(kibanaRole.cluster().check(CreateTokenAction.NAME, request), is(true)); + assertThat(kibanaRole.cluster().check(SamlPrepareAuthenticationAction.NAME, request, authentication), is(true)); + assertThat(kibanaRole.cluster().check(SamlAuthenticateAction.NAME, request, authentication), is(true)); + assertThat(kibanaRole.cluster().check(InvalidateTokenAction.NAME, request, authentication), is(true)); + assertThat(kibanaRole.cluster().check(CreateTokenAction.NAME, request, authentication), is(true)); // Application Privileges DeletePrivilegesRequest deleteKibanaPrivileges = new DeletePrivilegesRequest("kibana-.kibana", new String[]{ "all", "read" }); DeletePrivilegesRequest deleteLogstashPrivileges = new DeletePrivilegesRequest("logstash", new String[]{ "all", "read" }); - assertThat(kibanaRole.cluster().check(DeletePrivilegesAction.NAME, deleteKibanaPrivileges), is(true)); - assertThat(kibanaRole.cluster().check(DeletePrivilegesAction.NAME, deleteLogstashPrivileges), is(false)); + assertThat(kibanaRole.cluster().check(DeletePrivilegesAction.NAME, deleteKibanaPrivileges, authentication), is(true)); + assertThat(kibanaRole.cluster().check(DeletePrivilegesAction.NAME, deleteLogstashPrivileges, authentication), is(false)); GetPrivilegesRequest getKibanaPrivileges = new GetPrivilegesRequest(); getKibanaPrivileges.application("kibana-.kibana-sales"); GetPrivilegesRequest getApmPrivileges = new GetPrivilegesRequest(); getApmPrivileges.application("apm"); - assertThat(kibanaRole.cluster().check(GetPrivilegesAction.NAME, getKibanaPrivileges), is(true)); - assertThat(kibanaRole.cluster().check(GetPrivilegesAction.NAME, getApmPrivileges), is(false)); + assertThat(kibanaRole.cluster().check(GetPrivilegesAction.NAME, getKibanaPrivileges, authentication), is(true)); + assertThat(kibanaRole.cluster().check(GetPrivilegesAction.NAME, getApmPrivileges, authentication), is(false)); PutPrivilegesRequest putKibanaPrivileges = new PutPrivilegesRequest(); putKibanaPrivileges.setPrivileges(Collections.singletonList(new ApplicationPrivilegeDescriptor( @@ -314,8 +318,8 @@ public void testKibanaSystemRole() { PutPrivilegesRequest putSwiftypePrivileges = new PutPrivilegesRequest(); putSwiftypePrivileges.setPrivileges(Collections.singletonList(new ApplicationPrivilegeDescriptor( "swiftype-kibana" , "all", Collections.emptySet(), Collections.emptyMap()))); - assertThat(kibanaRole.cluster().check(PutPrivilegesAction.NAME, putKibanaPrivileges), is(true)); - assertThat(kibanaRole.cluster().check(PutPrivilegesAction.NAME, putSwiftypePrivileges), is(false)); + assertThat(kibanaRole.cluster().check(PutPrivilegesAction.NAME, putKibanaPrivileges, authentication), is(true)); + assertThat(kibanaRole.cluster().check(PutPrivilegesAction.NAME, putSwiftypePrivileges, authentication), is(false)); // Everything else assertThat(kibanaRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false)); @@ -374,6 +378,7 @@ public void testKibanaSystemRole() { } public void testKibanaUserRole() { + final Authentication authentication = mock(Authentication.class); final TransportRequest request = mock(TransportRequest.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("kibana_user"); @@ -381,13 +386,13 @@ public void testKibanaUserRole() { assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role kibanaUserRole = Role.builder(roleDescriptor, null).build(); - assertThat(kibanaUserRole.cluster().check(ClusterHealthAction.NAME, request), is(false)); - assertThat(kibanaUserRole.cluster().check(ClusterStateAction.NAME, request), is(false)); - assertThat(kibanaUserRole.cluster().check(ClusterStatsAction.NAME, request), is(false)); - assertThat(kibanaUserRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false)); - assertThat(kibanaUserRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(kibanaUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(kibanaUserRole.cluster().check(MonitoringBulkAction.NAME, request), is(false)); + assertThat(kibanaUserRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false)); + assertThat(kibanaUserRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(false)); + assertThat(kibanaUserRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(false)); + assertThat(kibanaUserRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); + assertThat(kibanaUserRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(kibanaUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(kibanaUserRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false)); assertThat(kibanaUserRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false)); @@ -411,6 +416,7 @@ public void testKibanaUserRole() { } public void testMonitoringUserRole() { + final Authentication authentication = mock(Authentication.class); final TransportRequest request = mock(TransportRequest.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("monitoring_user"); @@ -418,15 +424,15 @@ public void testMonitoringUserRole() { assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role monitoringUserRole = Role.builder(roleDescriptor, null).build(); - assertThat(monitoringUserRole.cluster().check(MainAction.NAME, request), is(true)); - assertThat(monitoringUserRole.cluster().check(XPackInfoAction.NAME, request), is(true)); - assertThat(monitoringUserRole.cluster().check(ClusterHealthAction.NAME, request), is(false)); - assertThat(monitoringUserRole.cluster().check(ClusterStateAction.NAME, request), is(false)); - assertThat(monitoringUserRole.cluster().check(ClusterStatsAction.NAME, request), is(false)); - assertThat(monitoringUserRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false)); - assertThat(monitoringUserRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(monitoringUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(monitoringUserRole.cluster().check(MonitoringBulkAction.NAME, request), is(false)); + assertThat(monitoringUserRole.cluster().check(MainAction.NAME, request, authentication), is(true)); + assertThat(monitoringUserRole.cluster().check(XPackInfoAction.NAME, request, authentication), is(true)); + assertThat(monitoringUserRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false)); + assertThat(monitoringUserRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(false)); + assertThat(monitoringUserRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(false)); + assertThat(monitoringUserRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); + assertThat(monitoringUserRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(monitoringUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(monitoringUserRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false)); assertThat(monitoringUserRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false)); @@ -467,6 +473,7 @@ public void testMonitoringUserRole() { } public void testRemoteMonitoringAgentRole() { + final Authentication authentication = mock(Authentication.class); final TransportRequest request = mock(TransportRequest.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("remote_monitoring_agent"); @@ -474,22 +481,22 @@ public void testRemoteMonitoringAgentRole() { assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role remoteMonitoringAgentRole = Role.builder(roleDescriptor, null).build(); - assertThat(remoteMonitoringAgentRole.cluster().check(ClusterHealthAction.NAME, request), is(true)); - assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStateAction.NAME, request), is(true)); - assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStatsAction.NAME, request), is(true)); - assertThat(remoteMonitoringAgentRole.cluster().check(PutIndexTemplateAction.NAME, request), is(true)); - assertThat(remoteMonitoringAgentRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(remoteMonitoringAgentRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(remoteMonitoringAgentRole.cluster().check(MonitoringBulkAction.NAME, request), is(false)); - assertThat(remoteMonitoringAgentRole.cluster().check(GetWatchAction.NAME, request), is(true)); - assertThat(remoteMonitoringAgentRole.cluster().check(PutWatchAction.NAME, request), is(true)); - assertThat(remoteMonitoringAgentRole.cluster().check(DeleteWatchAction.NAME, request), is(true)); - assertThat(remoteMonitoringAgentRole.cluster().check(ExecuteWatchAction.NAME, request), is(false)); - assertThat(remoteMonitoringAgentRole.cluster().check(AckWatchAction.NAME, request), is(false)); - assertThat(remoteMonitoringAgentRole.cluster().check(ActivateWatchAction.NAME, request), is(false)); - assertThat(remoteMonitoringAgentRole.cluster().check(WatcherServiceAction.NAME, request), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(GetWatchAction.NAME, request, authentication), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(PutWatchAction.NAME, request, authentication), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(DeleteWatchAction.NAME, request, authentication), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(ExecuteWatchAction.NAME, request, authentication), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(AckWatchAction.NAME, request, authentication), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(ActivateWatchAction.NAME, request, authentication), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(WatcherServiceAction.NAME, request, authentication), is(false)); // we get this from the cluster:monitor privilege - assertThat(remoteMonitoringAgentRole.cluster().check(WatcherStatsAction.NAME, request), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(WatcherStatsAction.NAME, request, authentication), is(true)); assertThat(remoteMonitoringAgentRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false)); @@ -526,6 +533,7 @@ public void testRemoteMonitoringAgentRole() { } public void testRemoteMonitoringCollectorRole() { + final Authentication authentication = mock(Authentication.class); final TransportRequest request = mock(TransportRequest.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("remote_monitoring_collector"); @@ -533,15 +541,15 @@ public void testRemoteMonitoringCollectorRole() { assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role remoteMonitoringAgentRole = Role.builder(roleDescriptor, null).build(); - assertThat(remoteMonitoringAgentRole.cluster().check(ClusterHealthAction.NAME, request), is(true)); - assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStateAction.NAME, request), is(true)); - assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStatsAction.NAME, request), is(true)); - assertThat(remoteMonitoringAgentRole.cluster().check(GetIndexTemplatesAction.NAME, request), is(false)); - assertThat(remoteMonitoringAgentRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false)); - assertThat(remoteMonitoringAgentRole.cluster().check(DeleteIndexTemplateAction.NAME, request), is(false)); - assertThat(remoteMonitoringAgentRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(remoteMonitoringAgentRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(remoteMonitoringAgentRole.cluster().check(MonitoringBulkAction.NAME, request), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(GetIndexTemplatesAction.NAME, request, authentication), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(DeleteIndexTemplateAction.NAME, request, authentication), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false)); assertThat(remoteMonitoringAgentRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false)); @@ -624,6 +632,7 @@ private void assertMonitoringOnRestrictedIndices(Role role) { } public void testReportingUserRole() { + final Authentication authentication = mock(Authentication.class); final TransportRequest request = mock(TransportRequest.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("reporting_user"); @@ -631,13 +640,13 @@ public void testReportingUserRole() { assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role reportingUserRole = Role.builder(roleDescriptor, null).build(); - assertThat(reportingUserRole.cluster().check(ClusterHealthAction.NAME, request), is(false)); - assertThat(reportingUserRole.cluster().check(ClusterStateAction.NAME, request), is(false)); - assertThat(reportingUserRole.cluster().check(ClusterStatsAction.NAME, request), is(false)); - assertThat(reportingUserRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false)); - assertThat(reportingUserRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(reportingUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(reportingUserRole.cluster().check(MonitoringBulkAction.NAME, request), is(false)); + assertThat(reportingUserRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false)); + assertThat(reportingUserRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(false)); + assertThat(reportingUserRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(false)); + assertThat(reportingUserRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); + assertThat(reportingUserRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(reportingUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(reportingUserRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false)); assertThat(reportingUserRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false)); @@ -664,6 +673,7 @@ public void testReportingUserRole() { } public void testKibanaDashboardOnlyUserRole() { + final Authentication authentication = mock(Authentication.class); final TransportRequest request = mock(TransportRequest.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("kibana_dashboard_only_user"); @@ -671,13 +681,13 @@ public void testKibanaDashboardOnlyUserRole() { assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role dashboardsOnlyUserRole = Role.builder(roleDescriptor, null).build(); - assertThat(dashboardsOnlyUserRole.cluster().check(ClusterHealthAction.NAME, request), is(false)); - assertThat(dashboardsOnlyUserRole.cluster().check(ClusterStateAction.NAME, request), is(false)); - assertThat(dashboardsOnlyUserRole.cluster().check(ClusterStatsAction.NAME, request), is(false)); - assertThat(dashboardsOnlyUserRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false)); - assertThat(dashboardsOnlyUserRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(dashboardsOnlyUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(dashboardsOnlyUserRole.cluster().check(MonitoringBulkAction.NAME, request), is(false)); + assertThat(dashboardsOnlyUserRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false)); + assertThat(dashboardsOnlyUserRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(false)); + assertThat(dashboardsOnlyUserRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(false)); + assertThat(dashboardsOnlyUserRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); + assertThat(dashboardsOnlyUserRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(dashboardsOnlyUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(dashboardsOnlyUserRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false)); assertThat(dashboardsOnlyUserRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false)); @@ -698,6 +708,7 @@ public void testKibanaDashboardOnlyUserRole() { } public void testSuperuserRole() { + final Authentication authentication = mock(Authentication.class); final TransportRequest request = mock(TransportRequest.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("superuser"); @@ -705,12 +716,12 @@ public void testSuperuserRole() { assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role superuserRole = Role.builder(roleDescriptor, null).build(); - assertThat(superuserRole.cluster().check(ClusterHealthAction.NAME, request), is(true)); - assertThat(superuserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(true)); - assertThat(superuserRole.cluster().check(PutUserAction.NAME, request), is(true)); - assertThat(superuserRole.cluster().check(PutRoleAction.NAME, request), is(true)); - assertThat(superuserRole.cluster().check(PutIndexTemplateAction.NAME, request), is(true)); - assertThat(superuserRole.cluster().check("internal:admin/foo", request), is(false)); + assertThat(superuserRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true)); + assertThat(superuserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(true)); + assertThat(superuserRole.cluster().check(PutUserAction.NAME, request, authentication), is(true)); + assertThat(superuserRole.cluster().check(PutRoleAction.NAME, request, authentication), is(true)); + assertThat(superuserRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(true)); + assertThat(superuserRole.cluster().check("internal:admin/foo", request, authentication), is(false)); final Settings indexSettings = Settings.builder().put("index.version.created", Version.CURRENT).build(); final String internalSecurityIndex = randomFrom(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_6, @@ -768,6 +779,7 @@ public void testSuperuserRole() { } public void testLogstashSystemRole() { + final Authentication authentication = mock(Authentication.class); final TransportRequest request = mock(TransportRequest.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("logstash_system"); @@ -775,13 +787,13 @@ public void testLogstashSystemRole() { assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role logstashSystemRole = Role.builder(roleDescriptor, null).build(); - assertThat(logstashSystemRole.cluster().check(ClusterHealthAction.NAME, request), is(true)); - assertThat(logstashSystemRole.cluster().check(ClusterStateAction.NAME, request), is(true)); - assertThat(logstashSystemRole.cluster().check(ClusterStatsAction.NAME, request), is(true)); - assertThat(logstashSystemRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false)); - assertThat(logstashSystemRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(logstashSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(logstashSystemRole.cluster().check(MonitoringBulkAction.NAME, request), is(true)); + assertThat(logstashSystemRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true)); + assertThat(logstashSystemRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true)); + assertThat(logstashSystemRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true)); + assertThat(logstashSystemRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); + assertThat(logstashSystemRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(logstashSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(logstashSystemRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(true)); assertThat(logstashSystemRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); @@ -794,6 +806,7 @@ public void testLogstashSystemRole() { } public void testBeatsAdminRole() { + final Authentication authentication = mock(Authentication.class); final TransportRequest request = mock(TransportRequest.class); final RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("beats_admin"); @@ -802,13 +815,13 @@ public void testBeatsAdminRole() { final Role beatsAdminRole = Role.builder(roleDescriptor, null).build(); - assertThat(beatsAdminRole.cluster().check(ClusterHealthAction.NAME, request), is(false)); - assertThat(beatsAdminRole.cluster().check(ClusterStateAction.NAME, request), is(false)); - assertThat(beatsAdminRole.cluster().check(ClusterStatsAction.NAME, request), is(false)); - assertThat(beatsAdminRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false)); - assertThat(beatsAdminRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(beatsAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(beatsAdminRole.cluster().check(MonitoringBulkAction.NAME, request), is(false)); + assertThat(beatsAdminRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false)); + assertThat(beatsAdminRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(false)); + assertThat(beatsAdminRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(false)); + assertThat(beatsAdminRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); + assertThat(beatsAdminRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(beatsAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(beatsAdminRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false)); assertThat(beatsAdminRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); @@ -832,6 +845,7 @@ public void testBeatsAdminRole() { } public void testBeatsSystemRole() { + final Authentication authentication = mock(Authentication.class); final TransportRequest request = mock(TransportRequest.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor(BeatsSystemUser.ROLE_NAME); @@ -839,13 +853,13 @@ public void testBeatsSystemRole() { assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role beatsSystemRole = Role.builder(roleDescriptor, null).build(); - assertThat(beatsSystemRole.cluster().check(ClusterHealthAction.NAME, request), is(true)); - assertThat(beatsSystemRole.cluster().check(ClusterStateAction.NAME, request), is(true)); - assertThat(beatsSystemRole.cluster().check(ClusterStatsAction.NAME, request), is(true)); - assertThat(beatsSystemRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false)); - assertThat(beatsSystemRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(beatsSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(beatsSystemRole.cluster().check(MonitoringBulkAction.NAME, request), is(true)); + assertThat(beatsSystemRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true)); + assertThat(beatsSystemRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true)); + assertThat(beatsSystemRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true)); + assertThat(beatsSystemRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); + assertThat(beatsSystemRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(beatsSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(beatsSystemRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(true)); assertThat(beatsSystemRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); @@ -865,6 +879,7 @@ public void testBeatsSystemRole() { } public void testAPMSystemRole() { + final Authentication authentication = mock(Authentication.class); final TransportRequest request = mock(TransportRequest.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor(APMSystemUser.ROLE_NAME); @@ -872,13 +887,13 @@ public void testAPMSystemRole() { assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role APMSystemRole = Role.builder(roleDescriptor, null).build(); - assertThat(APMSystemRole.cluster().check(ClusterHealthAction.NAME, request), is(true)); - assertThat(APMSystemRole.cluster().check(ClusterStateAction.NAME, request), is(true)); - assertThat(APMSystemRole.cluster().check(ClusterStatsAction.NAME, request), is(true)); - assertThat(APMSystemRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false)); - assertThat(APMSystemRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(APMSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(APMSystemRole.cluster().check(MonitoringBulkAction.NAME, request), is(true)); + assertThat(APMSystemRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true)); + assertThat(APMSystemRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true)); + assertThat(APMSystemRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true)); + assertThat(APMSystemRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); + assertThat(APMSystemRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(APMSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(APMSystemRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(true)); assertThat(APMSystemRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); @@ -908,6 +923,7 @@ public void testAPMUserRole() { } public void testMachineLearningAdminRole() { + final Authentication authentication = mock(Authentication.class); final TransportRequest request = mock(TransportRequest.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("machine_learning_admin"); @@ -915,56 +931,56 @@ public void testMachineLearningAdminRole() { assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role role = Role.builder(roleDescriptor, null).build(); - assertThat(role.cluster().check(CloseJobAction.NAME, request), is(true)); - assertThat(role.cluster().check(DeleteCalendarAction.NAME, request), is(true)); - assertThat(role.cluster().check(DeleteCalendarEventAction.NAME, request), is(true)); - assertThat(role.cluster().check(DeleteDatafeedAction.NAME, request), is(true)); - assertThat(role.cluster().check(DeleteExpiredDataAction.NAME, request), is(true)); - assertThat(role.cluster().check(DeleteFilterAction.NAME, request), is(true)); - assertThat(role.cluster().check(DeleteForecastAction.NAME, request), is(true)); - assertThat(role.cluster().check(DeleteJobAction.NAME, request), is(true)); - assertThat(role.cluster().check(DeleteModelSnapshotAction.NAME, request), is(true)); - assertThat(role.cluster().check(FinalizeJobExecutionAction.NAME, request), is(false)); // internal use only - assertThat(role.cluster().check(FindFileStructureAction.NAME, request), is(true)); - assertThat(role.cluster().check(FlushJobAction.NAME, request), is(true)); - assertThat(role.cluster().check(ForecastJobAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetBucketsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetCalendarEventsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetCalendarsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetCategoriesAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetDatafeedsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetDatafeedsStatsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetFiltersAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetInfluencersAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetJobsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetJobsStatsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetModelSnapshotsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetOverallBucketsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetRecordsAction.NAME, request), is(true)); - assertThat(role.cluster().check(IsolateDatafeedAction.NAME, request), is(false)); // internal use only - assertThat(role.cluster().check(KillProcessAction.NAME, request), is(false)); // internal use only - assertThat(role.cluster().check(MlInfoAction.NAME, request), is(true)); - assertThat(role.cluster().check(OpenJobAction.NAME, request), is(true)); - assertThat(role.cluster().check(PersistJobAction.NAME, request), is(true)); - assertThat(role.cluster().check(PostCalendarEventsAction.NAME, request), is(true)); - assertThat(role.cluster().check(PostDataAction.NAME, request), is(true)); - assertThat(role.cluster().check(PreviewDatafeedAction.NAME, request), is(true)); - assertThat(role.cluster().check(PutCalendarAction.NAME, request), is(true)); - assertThat(role.cluster().check(PutDatafeedAction.NAME, request), is(true)); - assertThat(role.cluster().check(PutFilterAction.NAME, request), is(true)); - assertThat(role.cluster().check(PutJobAction.NAME, request), is(true)); - assertThat(role.cluster().check(RevertModelSnapshotAction.NAME, request), is(true)); - assertThat(role.cluster().check(SetUpgradeModeAction.NAME, request), is(true)); - assertThat(role.cluster().check(StartDatafeedAction.NAME, request), is(true)); - assertThat(role.cluster().check(StopDatafeedAction.NAME, request), is(true)); - assertThat(role.cluster().check(UpdateCalendarJobAction.NAME, request), is(true)); - assertThat(role.cluster().check(UpdateDatafeedAction.NAME, request), is(true)); - assertThat(role.cluster().check(UpdateFilterAction.NAME, request), is(true)); - assertThat(role.cluster().check(UpdateJobAction.NAME, request), is(true)); - assertThat(role.cluster().check(UpdateModelSnapshotAction.NAME, request), is(true)); - assertThat(role.cluster().check(UpdateProcessAction.NAME, request), is(false)); // internal use only - assertThat(role.cluster().check(ValidateDetectorAction.NAME, request), is(true)); - assertThat(role.cluster().check(ValidateJobConfigAction.NAME, request), is(true)); + assertThat(role.cluster().check(CloseJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteCalendarAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteCalendarEventAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteDatafeedAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteExpiredDataAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteFilterAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteForecastAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteModelSnapshotAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(FinalizeJobExecutionAction.NAME, request, authentication), is(false)); // internal use only + assertThat(role.cluster().check(FindFileStructureAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(FlushJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(ForecastJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetBucketsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetCalendarEventsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetCalendarsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetCategoriesAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetDatafeedsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetDatafeedsStatsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetFiltersAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetInfluencersAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetJobsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetJobsStatsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetModelSnapshotsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetOverallBucketsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetRecordsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(IsolateDatafeedAction.NAME, request, authentication), is(false)); // internal use only + assertThat(role.cluster().check(KillProcessAction.NAME, request, authentication), is(false)); // internal use only + assertThat(role.cluster().check(MlInfoAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(OpenJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PersistJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PostCalendarEventsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PostDataAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PreviewDatafeedAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PutCalendarAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PutDatafeedAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PutFilterAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PutJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(RevertModelSnapshotAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(SetUpgradeModeAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(StartDatafeedAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(StopDatafeedAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(UpdateCalendarJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(UpdateDatafeedAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(UpdateFilterAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(UpdateJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(UpdateModelSnapshotAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(UpdateProcessAction.NAME, request, authentication), is(false)); // internal use only + assertThat(role.cluster().check(ValidateDetectorAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(ValidateJobConfigAction.NAME, request, authentication), is(true)); assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); assertNoAccessAllowed(role, "foo"); @@ -991,6 +1007,7 @@ public void testMachineLearningAdminRole() { } public void testMachineLearningUserRole() { + final Authentication authentication = mock(Authentication.class); final TransportRequest request = mock(TransportRequest.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("machine_learning_user"); @@ -998,56 +1015,56 @@ public void testMachineLearningUserRole() { assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role role = Role.builder(roleDescriptor, null).build(); - assertThat(role.cluster().check(CloseJobAction.NAME, request), is(false)); - assertThat(role.cluster().check(DeleteCalendarAction.NAME, request), is(false)); - assertThat(role.cluster().check(DeleteCalendarEventAction.NAME, request), is(false)); - assertThat(role.cluster().check(DeleteDatafeedAction.NAME, request), is(false)); - assertThat(role.cluster().check(DeleteExpiredDataAction.NAME, request), is(false)); - assertThat(role.cluster().check(DeleteFilterAction.NAME, request), is(false)); - assertThat(role.cluster().check(DeleteForecastAction.NAME, request), is(false)); - assertThat(role.cluster().check(DeleteJobAction.NAME, request), is(false)); - assertThat(role.cluster().check(DeleteModelSnapshotAction.NAME, request), is(false)); - assertThat(role.cluster().check(FinalizeJobExecutionAction.NAME, request), is(false)); - assertThat(role.cluster().check(FindFileStructureAction.NAME, request), is(true)); - assertThat(role.cluster().check(FlushJobAction.NAME, request), is(false)); - assertThat(role.cluster().check(ForecastJobAction.NAME, request), is(false)); - assertThat(role.cluster().check(GetBucketsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetCalendarEventsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetCalendarsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetCategoriesAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetDatafeedsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetDatafeedsStatsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetFiltersAction.NAME, request), is(false)); - assertThat(role.cluster().check(GetInfluencersAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetJobsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetJobsStatsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetModelSnapshotsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetOverallBucketsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetRecordsAction.NAME, request), is(true)); - assertThat(role.cluster().check(IsolateDatafeedAction.NAME, request), is(false)); - assertThat(role.cluster().check(KillProcessAction.NAME, request), is(false)); - assertThat(role.cluster().check(MlInfoAction.NAME, request), is(true)); - assertThat(role.cluster().check(OpenJobAction.NAME, request), is(false)); - assertThat(role.cluster().check(PersistJobAction.NAME, request), is(false)); - assertThat(role.cluster().check(PostCalendarEventsAction.NAME, request), is(false)); - assertThat(role.cluster().check(PostDataAction.NAME, request), is(false)); - assertThat(role.cluster().check(PreviewDatafeedAction.NAME, request), is(false)); - assertThat(role.cluster().check(PutCalendarAction.NAME, request), is(false)); - assertThat(role.cluster().check(PutDatafeedAction.NAME, request), is(false)); - assertThat(role.cluster().check(PutFilterAction.NAME, request), is(false)); - assertThat(role.cluster().check(PutJobAction.NAME, request), is(false)); - assertThat(role.cluster().check(RevertModelSnapshotAction.NAME, request), is(false)); - assertThat(role.cluster().check(SetUpgradeModeAction.NAME, request), is(false)); - assertThat(role.cluster().check(StartDatafeedAction.NAME, request), is(false)); - assertThat(role.cluster().check(StopDatafeedAction.NAME, request), is(false)); - assertThat(role.cluster().check(UpdateCalendarJobAction.NAME, request), is(false)); - assertThat(role.cluster().check(UpdateDatafeedAction.NAME, request), is(false)); - assertThat(role.cluster().check(UpdateFilterAction.NAME, request), is(false)); - assertThat(role.cluster().check(UpdateJobAction.NAME, request), is(false)); - assertThat(role.cluster().check(UpdateModelSnapshotAction.NAME, request), is(false)); - assertThat(role.cluster().check(UpdateProcessAction.NAME, request), is(false)); - assertThat(role.cluster().check(ValidateDetectorAction.NAME, request), is(false)); - assertThat(role.cluster().check(ValidateJobConfigAction.NAME, request), is(false)); + assertThat(role.cluster().check(CloseJobAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(DeleteCalendarAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(DeleteCalendarEventAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(DeleteDatafeedAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(DeleteExpiredDataAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(DeleteFilterAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(DeleteForecastAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(DeleteJobAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(DeleteModelSnapshotAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(FinalizeJobExecutionAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(FindFileStructureAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(FlushJobAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(ForecastJobAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(GetBucketsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetCalendarEventsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetCalendarsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetCategoriesAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetDatafeedsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetDatafeedsStatsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetFiltersAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(GetInfluencersAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetJobsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetJobsStatsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetModelSnapshotsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetOverallBucketsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetRecordsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(IsolateDatafeedAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(KillProcessAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(MlInfoAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(OpenJobAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(PersistJobAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(PostCalendarEventsAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(PostDataAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(PreviewDatafeedAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(PutCalendarAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(PutDatafeedAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(PutFilterAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(PutJobAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(RevertModelSnapshotAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(SetUpgradeModeAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(StartDatafeedAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(StopDatafeedAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(UpdateCalendarJobAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(UpdateDatafeedAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(UpdateFilterAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(UpdateJobAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(UpdateModelSnapshotAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(UpdateProcessAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(ValidateDetectorAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(ValidateJobConfigAction.NAME, request, authentication), is(false)); assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); assertNoAccessAllowed(role, "foo"); @@ -1075,6 +1092,7 @@ public void testMachineLearningUserRole() { } public void testDataFrameTransformsAdminRole() { + final Authentication authentication = mock(Authentication.class); final TransportRequest request = mock(TransportRequest.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("data_frame_transforms_admin"); @@ -1082,13 +1100,13 @@ public void testDataFrameTransformsAdminRole() { assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role role = Role.builder(roleDescriptor, null).build(); - assertThat(role.cluster().check(DeleteDataFrameTransformAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetDataFrameTransformsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetDataFrameTransformsStatsAction.NAME, request), is(true)); - assertThat(role.cluster().check(PreviewDataFrameTransformAction.NAME, request), is(true)); - assertThat(role.cluster().check(PutDataFrameTransformAction.NAME, request), is(true)); - assertThat(role.cluster().check(StartDataFrameTransformAction.NAME, request), is(true)); - assertThat(role.cluster().check(StopDataFrameTransformAction.NAME, request), is(true)); + assertThat(role.cluster().check(DeleteDataFrameTransformAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetDataFrameTransformsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetDataFrameTransformsStatsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PreviewDataFrameTransformAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PutDataFrameTransformAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(StartDataFrameTransformAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(StopDataFrameTransformAction.NAME, request, authentication), is(true)); assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); assertOnlyReadAllowed(role, ".data-frame-notifications-1"); @@ -1111,6 +1129,7 @@ public void testDataFrameTransformsAdminRole() { } public void testDataFrameTransformsUserRole() { + final Authentication authentication = mock(Authentication.class); final TransportRequest request = mock(TransportRequest.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("data_frame_transforms_user"); @@ -1118,13 +1137,13 @@ public void testDataFrameTransformsUserRole() { assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role role = Role.builder(roleDescriptor, null).build(); - assertThat(role.cluster().check(DeleteDataFrameTransformAction.NAME, request), is(false)); - assertThat(role.cluster().check(GetDataFrameTransformsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetDataFrameTransformsStatsAction.NAME, request), is(true)); - assertThat(role.cluster().check(PreviewDataFrameTransformAction.NAME, request), is(false)); - assertThat(role.cluster().check(PutDataFrameTransformAction.NAME, request), is(false)); - assertThat(role.cluster().check(StartDataFrameTransformAction.NAME, request), is(false)); - assertThat(role.cluster().check(StopDataFrameTransformAction.NAME, request), is(false)); + assertThat(role.cluster().check(DeleteDataFrameTransformAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(GetDataFrameTransformsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetDataFrameTransformsStatsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PreviewDataFrameTransformAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(PutDataFrameTransformAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(StartDataFrameTransformAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(StopDataFrameTransformAction.NAME, request, authentication), is(false)); assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); assertOnlyReadAllowed(role, ".data-frame-notifications-1"); @@ -1147,6 +1166,7 @@ public void testDataFrameTransformsUserRole() { } public void testWatcherAdminRole() { + final Authentication authentication = mock(Authentication.class); final TransportRequest request = mock(TransportRequest.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("watcher_admin"); @@ -1154,14 +1174,14 @@ public void testWatcherAdminRole() { assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role role = Role.builder(roleDescriptor, null).build(); - assertThat(role.cluster().check(PutWatchAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetWatchAction.NAME, request), is(true)); - assertThat(role.cluster().check(DeleteWatchAction.NAME, request), is(true)); - assertThat(role.cluster().check(ExecuteWatchAction.NAME, request), is(true)); - assertThat(role.cluster().check(AckWatchAction.NAME, request), is(true)); - assertThat(role.cluster().check(ActivateWatchAction.NAME, request), is(true)); - assertThat(role.cluster().check(WatcherServiceAction.NAME, request), is(true)); - assertThat(role.cluster().check(WatcherStatsAction.NAME, request), is(true)); + assertThat(role.cluster().check(PutWatchAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetWatchAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteWatchAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(ExecuteWatchAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(AckWatchAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(ActivateWatchAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(WatcherServiceAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(WatcherStatsAction.NAME, request, authentication), is(true)); assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false)); @@ -1176,6 +1196,7 @@ public void testWatcherAdminRole() { } public void testWatcherUserRole() { + final Authentication authentication = mock(Authentication.class); final TransportRequest request = mock(TransportRequest.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("watcher_user"); @@ -1183,14 +1204,14 @@ public void testWatcherUserRole() { assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role role = Role.builder(roleDescriptor, null).build(); - assertThat(role.cluster().check(PutWatchAction.NAME, request), is(false)); - assertThat(role.cluster().check(GetWatchAction.NAME, request), is(true)); - assertThat(role.cluster().check(DeleteWatchAction.NAME, request), is(false)); - assertThat(role.cluster().check(ExecuteWatchAction.NAME, request), is(false)); - assertThat(role.cluster().check(AckWatchAction.NAME, request), is(false)); - assertThat(role.cluster().check(ActivateWatchAction.NAME, request), is(false)); - assertThat(role.cluster().check(WatcherServiceAction.NAME, request), is(false)); - assertThat(role.cluster().check(WatcherStatsAction.NAME, request), is(true)); + assertThat(role.cluster().check(PutWatchAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(GetWatchAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteWatchAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(ExecuteWatchAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(AckWatchAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(ActivateWatchAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(WatcherServiceAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(WatcherStatsAction.NAME, request, authentication), is(true)); assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false)); @@ -1248,6 +1269,7 @@ private void assertNoAccessAllowed(Role role, String index) { } public void testLogstashAdminRole() { + final Authentication authentication = mock(Authentication.class); final TransportRequest request = mock(TransportRequest.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("logstash_admin"); @@ -1255,10 +1277,10 @@ public void testLogstashAdminRole() { assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role logstashAdminRole = Role.builder(roleDescriptor, null).build(); - assertThat(logstashAdminRole.cluster().check(ClusterHealthAction.NAME, request), is(false)); - assertThat(logstashAdminRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false)); - assertThat(logstashAdminRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(logstashAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); + assertThat(logstashAdminRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false)); + assertThat(logstashAdminRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); + assertThat(logstashAdminRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(logstashAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); assertThat(logstashAdminRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportGetApiKeyAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportGetApiKeyAction.java index 403ce482805a2..bade78f96448d 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportGetApiKeyAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportGetApiKeyAction.java @@ -9,7 +9,6 @@ import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.HandledTransportAction; -import org.elasticsearch.common.Strings; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.tasks.Task; @@ -32,15 +31,10 @@ public TransportGetApiKeyAction(TransportService transportService, ActionFilters @Override protected void doExecute(Task task, GetApiKeyRequest request, ActionListener listener) { - if (Strings.hasText(request.getRealmName()) || Strings.hasText(request.getUserName())) { - apiKeyService.getApiKeysForRealmAndUser(request.getRealmName(), request.getUserName(), listener); - } else if (Strings.hasText(request.getApiKeyId())) { - apiKeyService.getApiKeyForApiKeyId(request.getApiKeyId(), listener); - } else if (Strings.hasText(request.getApiKeyName())) { - apiKeyService.getApiKeyForApiKeyName(request.getApiKeyName(), listener); - } else { - listener.onFailure(new IllegalArgumentException("One of [api key id, api key name, username, realm name] must be specified")); - } + apiKeyService.getApiKeys(request.getRealmName(), request.getUserName(), request.getApiKeyName(), request.getApiKeyId(), + ActionListener.wrap(getApiKeysResult -> { + listener.onResponse(new GetApiKeyResponse(getApiKeysResult.getApiKeyInfos())); + }, listener::onFailure)); } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportInvalidateApiKeyAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportInvalidateApiKeyAction.java index 886d15b1f257d..05d61f3e029c0 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportInvalidateApiKeyAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportInvalidateApiKeyAction.java @@ -9,7 +9,6 @@ import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.HandledTransportAction; -import org.elasticsearch.common.Strings; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.tasks.Task; @@ -32,13 +31,11 @@ public TransportInvalidateApiKeyAction(TransportService transportService, Action @Override protected void doExecute(Task task, InvalidateApiKeyRequest request, ActionListener listener) { - if (Strings.hasText(request.getRealmName()) || Strings.hasText(request.getUserName())) { - apiKeyService.invalidateApiKeysForRealmAndUser(request.getRealmName(), request.getUserName(), listener); - } else if (Strings.hasText(request.getId())) { - apiKeyService.invalidateApiKeyForApiKeyId(request.getId(), listener); - } else { - apiKeyService.invalidateApiKeyForApiKeyName(request.getName(), listener); - } + apiKeyService.invalidateApiKeys(request.getRealmName(), request.getUserName(), request.getName(), request.getId(), + ActionListener.wrap(invalidateApiKeysResult -> { + listener.onResponse(new InvalidateApiKeyResponse(invalidateApiKeysResult.getInvalidatedApiKeys(), + invalidateApiKeysResult.getPreviouslyInvalidatedApiKeys(), invalidateApiKeysResult.getErrors())); + }, listener::onFailure)); } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java index 74d4cce8d02de..18aefc7ce7f83 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java @@ -62,7 +62,6 @@ import org.elasticsearch.xpack.core.security.action.ApiKey; import org.elasticsearch.xpack.core.security.action.CreateApiKeyRequest; import org.elasticsearch.xpack.core.security.action.CreateApiKeyResponse; -import org.elasticsearch.xpack.core.security.action.GetApiKeyResponse; import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyResponse; import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; @@ -71,7 +70,6 @@ import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.support.SecurityIndexManager; -import javax.crypto.SecretKeyFactory; import java.io.Closeable; import java.io.IOException; import java.io.UncheckedIOException; @@ -95,6 +93,8 @@ import java.util.function.Function; import java.util.stream.Collectors; +import javax.crypto.SecretKeyFactory; + import static org.elasticsearch.index.mapper.MapperService.SINGLE_MAPPING_NAME; import static org.elasticsearch.search.SearchService.DEFAULT_KEEPALIVE_SETTING; import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN; @@ -105,7 +105,7 @@ public class ApiKeyService { private static final Logger logger = LogManager.getLogger(ApiKeyService.class); private static final DeprecationLogger deprecationLogger = new DeprecationLogger(logger); - static final String API_KEY_ID_KEY = "_security_api_key_id"; + public static final String API_KEY_ID_KEY = "_security_api_key_id"; static final String API_KEY_ROLE_DESCRIPTORS_KEY = "_security_api_key_role_descriptors"; static final String API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY = "_security_api_key_limited_by_role_descriptors"; @@ -640,96 +640,38 @@ public void usedDeprecatedField(String usedName, String replacedWith) { } /** - * Invalidate API keys for given realm and user name. + * Invalidate API keys for given realm, user name, API key name and id. * @param realmName realm name - * @param userName user name - * @param invalidateListener listener for {@link InvalidateApiKeyResponse} - */ - public void invalidateApiKeysForRealmAndUser(String realmName, String userName, - ActionListener invalidateListener) { - ensureEnabled(); - if (Strings.hasText(realmName) == false && Strings.hasText(userName) == false) { - logger.trace("No realm name or username provided"); - invalidateListener.onFailure(new IllegalArgumentException("realm name or username must be provided")); - } else { - findApiKeysForUserAndRealm(userName, realmName, true, false, ActionListener.wrap(apiKeyIds -> { - if (apiKeyIds.isEmpty()) { - logger.warn("No active api keys to invalidate for realm [{}] and username [{}]", realmName, userName); - invalidateListener.onResponse(InvalidateApiKeyResponse.emptyResponse()); - } else { - invalidateAllApiKeys(apiKeyIds.stream().map(apiKey -> apiKey.getId()).collect(Collectors.toSet()), invalidateListener); - } - }, invalidateListener::onFailure)); - } - } - - private void invalidateAllApiKeys(Collection apiKeyIds, ActionListener invalidateListener) { - indexInvalidation(apiKeyIds, invalidateListener, null); - } - - /** - * Invalidate API key for given API key id - * @param apiKeyId API key id - * @param invalidateListener listener for {@link InvalidateApiKeyResponse} - */ - public void invalidateApiKeyForApiKeyId(String apiKeyId, ActionListener invalidateListener) { - ensureEnabled(); - if (Strings.hasText(apiKeyId) == false) { - logger.trace("No api key id provided"); - invalidateListener.onFailure(new IllegalArgumentException("api key id must be provided")); - } else { - findApiKeysForApiKeyId(apiKeyId, true, false, ActionListener.wrap(apiKeyIds -> { - if (apiKeyIds.isEmpty()) { - logger.warn("No api key to invalidate for api key id [{}]", apiKeyId); - invalidateListener.onResponse(InvalidateApiKeyResponse.emptyResponse()); - } else { - invalidateAllApiKeys(apiKeyIds.stream().map(apiKey -> apiKey.getId()).collect(Collectors.toSet()), invalidateListener); - } - }, invalidateListener::onFailure)); - } - } - - /** - * Invalidate API key for given API key name + * @param username user name * @param apiKeyName API key name - * @param invalidateListener listener for {@link InvalidateApiKeyResponse} + * @param apiKeyId API key id + * @param invalidateListener listener for {@link InvalidateApiKeysResult} */ - public void invalidateApiKeyForApiKeyName(String apiKeyName, ActionListener invalidateListener) { + public void invalidateApiKeys(String realmName, String username, String apiKeyName, String apiKeyId, + ActionListener invalidateListener) { ensureEnabled(); - if (Strings.hasText(apiKeyName) == false) { - logger.trace("No api key name provided"); - invalidateListener.onFailure(new IllegalArgumentException("api key name must be provided")); + if (Strings.hasText(realmName) == false && Strings.hasText(username) == false && Strings.hasText(apiKeyName) == false + && Strings.hasText(apiKeyId) == false) { + invalidateListener + .onFailure(new IllegalArgumentException("One of [api key id, api key name, username, realm name] must be specified")); } else { - findApiKeyForApiKeyName(apiKeyName, true, false, ActionListener.wrap(apiKeyIds -> { - if (apiKeyIds.isEmpty()) { - logger.warn("No api key to invalidate for api key name [{}]", apiKeyName); - invalidateListener.onResponse(InvalidateApiKeyResponse.emptyResponse()); - } else { - invalidateAllApiKeys(apiKeyIds.stream().map(apiKey -> apiKey.getId()).collect(Collectors.toSet()), invalidateListener); - } - }, invalidateListener::onFailure)); + findApiKeysForUserRealmApiKeyIdAndNameCombination(realmName, username, apiKeyName, apiKeyId, true, false, + ActionListener.wrap(apiKeyIds -> { + if (apiKeyIds.isEmpty()) { + logger.warn( + "No active api keys to invalidate for realm [{}], username [{}], api key name [{}] and api key id [{}]", + realmName, username, apiKeyName, apiKeyId); + invalidateListener.onResponse(InvalidateApiKeysResult.EMPTY_RESULT); + } else { + invalidateAllApiKeys(apiKeyIds.stream().map(apiKey -> apiKey.getId()).collect(Collectors.toSet()), + invalidateListener); + } + }, invalidateListener::onFailure)); } } - private void findApiKeysForUserAndRealm(String userName, String realmName, boolean filterOutInvalidatedKeys, - boolean filterOutExpiredKeys, ActionListener> listener) { - final SecurityIndexManager frozenSecurityIndex = securityIndex.freeze(); - if (frozenSecurityIndex.indexExists() == false) { - listener.onResponse(Collections.emptyList()); - } else if (frozenSecurityIndex.isAvailable() == false) { - listener.onFailure(frozenSecurityIndex.getUnavailableReason()); - } else { - final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() - .filter(QueryBuilders.termQuery("doc_type", "api_key")); - if (Strings.hasText(userName)) { - boolQuery.filter(QueryBuilders.termQuery("creator.principal", userName)); - } - if (Strings.hasText(realmName)) { - boolQuery.filter(QueryBuilders.termQuery("creator.realm", realmName)); - } - - findApiKeys(boolQuery, filterOutInvalidatedKeys, filterOutExpiredKeys, listener); - } + private void invalidateAllApiKeys(Collection apiKeyIds, ActionListener invalidateListener) { + indexInvalidation(apiKeyIds, invalidateListener, null); } private void findApiKeys(final BoolQueryBuilder boolQuery, boolean filterOutInvalidatedKeys, boolean filterOutExpiredKeys, @@ -743,58 +685,53 @@ private void findApiKeys(final BoolQueryBuilder boolQuery, boolean filterOutInva expiredQuery.should(QueryBuilders.boolQuery().mustNot(QueryBuilders.existsQuery("expiration_time"))); boolQuery.filter(expiredQuery); } - final SearchRequest request = client.prepareSearch(SECURITY_MAIN_ALIAS) - .setScroll(DEFAULT_KEEPALIVE_SETTING.get(settings)) - .setQuery(boolQuery) - .setVersion(false) - .setSize(1000) - .setFetchSource(true) - .request(); - securityIndex.checkIndexVersionThenExecute(listener::onFailure, - () -> ScrollHelper.fetchAllByEntity(client, request, listener, - (SearchHit hit) -> { - Map source = hit.getSourceAsMap(); - String name = (String) source.get("name"); - String id = hit.getId(); - Long creation = (Long) source.get("creation_time"); - Long expiration = (Long) source.get("expiration_time"); - Boolean invalidated = (Boolean) source.get("api_key_invalidated"); - String username = (String) ((Map) source.get("creator")).get("principal"); - String realm = (String) ((Map) source.get("creator")).get("realm"); - return new ApiKey(name, id, Instant.ofEpochMilli(creation), - (expiration != null) ? Instant.ofEpochMilli(expiration) : null, invalidated, username, realm); - })); - } - - private void findApiKeyForApiKeyName(String apiKeyName, boolean filterOutInvalidatedKeys, boolean filterOutExpiredKeys, - ActionListener> listener) { + try (ThreadContext.StoredContext ignore = client.threadPool().getThreadContext().stashWithOrigin(SECURITY_ORIGIN)) { + final SearchRequest request = client.prepareSearch(SECURITY_MAIN_ALIAS) + .setScroll(DEFAULT_KEEPALIVE_SETTING.get(settings)) + .setQuery(boolQuery) + .setVersion(false) + .setSize(1000) + .setFetchSource(true) + .request(); + securityIndex.checkIndexVersionThenExecute(listener::onFailure, + () -> ScrollHelper.fetchAllByEntity(client, request, listener, + (SearchHit hit) -> { + Map source = hit.getSourceAsMap(); + String name = (String) source.get("name"); + String id = hit.getId(); + Long creation = (Long) source.get("creation_time"); + Long expiration = (Long) source.get("expiration_time"); + Boolean invalidated = (Boolean) source.get("api_key_invalidated"); + String username = (String) ((Map) source.get("creator")).get("principal"); + String realm = (String) ((Map) source.get("creator")).get("realm"); + return new ApiKey(name, id, Instant.ofEpochMilli(creation), + (expiration != null) ? Instant.ofEpochMilli(expiration) : null, invalidated, username, realm); + })); + } + } + + private void findApiKeysForUserRealmApiKeyIdAndNameCombination(String realmName, String userName, String apiKeyName, String apiKeyId, + boolean filterOutInvalidatedKeys, boolean filterOutExpiredKeys, + ActionListener> listener) { final SecurityIndexManager frozenSecurityIndex = securityIndex.freeze(); if (frozenSecurityIndex.indexExists() == false) { listener.onResponse(Collections.emptyList()); } else if (frozenSecurityIndex.isAvailable() == false) { listener.onFailure(frozenSecurityIndex.getUnavailableReason()); } else { - final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() - .filter(QueryBuilders.termQuery("doc_type", "api_key")); + final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery().filter(QueryBuilders.termQuery("doc_type", "api_key")); + if (Strings.hasText(realmName)) { + boolQuery.filter(QueryBuilders.termQuery("creator.realm", realmName)); + } + if (Strings.hasText(userName)) { + boolQuery.filter(QueryBuilders.termQuery("creator.principal", userName)); + } if (Strings.hasText(apiKeyName)) { boolQuery.filter(QueryBuilders.termQuery("name", apiKeyName)); } - - findApiKeys(boolQuery, filterOutInvalidatedKeys, filterOutExpiredKeys, listener); - } - } - - private void findApiKeysForApiKeyId(String apiKeyId, boolean filterOutInvalidatedKeys, boolean filterOutExpiredKeys, - ActionListener> listener) { - final SecurityIndexManager frozenSecurityIndex = securityIndex.freeze(); - if (frozenSecurityIndex.indexExists() == false) { - listener.onResponse(Collections.emptyList()); - } else if (frozenSecurityIndex.isAvailable() == false) { - listener.onFailure(frozenSecurityIndex.getUnavailableReason()); - } else { - final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() - .filter(QueryBuilders.termQuery("doc_type", "api_key")) - .filter(QueryBuilders.termQuery("_id", apiKeyId)); + if (Strings.hasText(apiKeyId)) { + boolQuery.filter(QueryBuilders.termQuery("_id", apiKeyId)); + } findApiKeys(boolQuery, filterOutInvalidatedKeys, filterOutExpiredKeys, listener); } @@ -808,7 +745,7 @@ private void findApiKeysForApiKeyId(String apiKeyId, boolean filterOutInvalidate * @param previousResult if this not the initial attempt for invalidation, it contains the result of invalidating * api keys up to the point of the retry. This result is added to the result of the current attempt */ - private void indexInvalidation(Collection apiKeyIds, ActionListener listener, + private void indexInvalidation(Collection apiKeyIds, ActionListener listener, @Nullable InvalidateApiKeyResponse previousResult) { maybeStartApiKeyRemover(); if (apiKeyIds.isEmpty()) { @@ -850,7 +787,7 @@ private void indexInvalidation(Collection apiKeyIds, ActionListener { @@ -923,69 +860,30 @@ private void maybeStartApiKeyRemover() { } /** - * Get API keys for given realm and user name. + * Get API key information for given realm, user, API key name and id combination * @param realmName realm name - * @param userName user name - * @param listener listener for {@link GetApiKeyResponse} - */ - public void getApiKeysForRealmAndUser(String realmName, String userName, ActionListener listener) { - ensureEnabled(); - if (Strings.hasText(realmName) == false && Strings.hasText(userName) == false) { - logger.trace("No realm name or username provided"); - listener.onFailure(new IllegalArgumentException("realm name or username must be provided")); - } else { - findApiKeysForUserAndRealm(userName, realmName, false, false, ActionListener.wrap(apiKeyInfos -> { - if (apiKeyInfos.isEmpty()) { - logger.warn("No active api keys found for realm [{}] and username [{}]", realmName, userName); - listener.onResponse(GetApiKeyResponse.emptyResponse()); - } else { - listener.onResponse(new GetApiKeyResponse(apiKeyInfos)); - } - }, listener::onFailure)); - } - } - - /** - * Get API key for given API key id - * @param apiKeyId API key id - * @param listener listener for {@link GetApiKeyResponse} - */ - public void getApiKeyForApiKeyId(String apiKeyId, ActionListener listener) { - ensureEnabled(); - if (Strings.hasText(apiKeyId) == false) { - logger.trace("No api key id provided"); - listener.onFailure(new IllegalArgumentException("api key id must be provided")); - } else { - findApiKeysForApiKeyId(apiKeyId, false, false, ActionListener.wrap(apiKeyInfos -> { - if (apiKeyInfos.isEmpty()) { - logger.warn("No api key found for api key id [{}]", apiKeyId); - listener.onResponse(GetApiKeyResponse.emptyResponse()); - } else { - listener.onResponse(new GetApiKeyResponse(apiKeyInfos)); - } - }, listener::onFailure)); - } - } - - /** - * Get API key for given API key name + * @param username user name * @param apiKeyName API key name - * @param listener listener for {@link GetApiKeyResponse} + * @param apiKeyId API key id + * @param listener listener for {@link GetApiKeysResult} */ - public void getApiKeyForApiKeyName(String apiKeyName, ActionListener listener) { + public void getApiKeys(String realmName, String username, String apiKeyName, String apiKeyId, + ActionListener listener) { ensureEnabled(); - if (Strings.hasText(apiKeyName) == false) { - logger.trace("No api key name provided"); - listener.onFailure(new IllegalArgumentException("api key name must be provided")); + if (Strings.hasText(realmName) == false && Strings.hasText(username) == false && Strings.hasText(apiKeyName) == false + && Strings.hasText(apiKeyId) == false) { + listener.onFailure(new IllegalArgumentException("One of [api key id, api key name, username, realm name] must be specified")); } else { - findApiKeyForApiKeyName(apiKeyName, false, false, ActionListener.wrap(apiKeyInfos -> { - if (apiKeyInfos.isEmpty()) { - logger.warn("No api key found for api key name [{}]", apiKeyName); - listener.onResponse(GetApiKeyResponse.emptyResponse()); - } else { - listener.onResponse(new GetApiKeyResponse(apiKeyInfos)); - } - }, listener::onFailure)); + findApiKeysForUserRealmApiKeyIdAndNameCombination(realmName, username, apiKeyName, apiKeyId, false, false, + ActionListener.wrap(apiKeyInfos -> { + if (apiKeyInfos.isEmpty()) { + logger.warn("No active api keys found for realm [{}], user [{}], api key name [{}] and api key id [{}]", + realmName, username, apiKeyName, apiKeyId); + listener.onResponse(GetApiKeysResult.EMPTY_RESULT); + } else { + listener.onResponse(new GetApiKeysResult(apiKeyInfos)); + } + }, listener::onFailure)); } } @@ -1002,4 +900,45 @@ private boolean verify(SecureString password) { return hash != null && cacheHasher.verify(password, hash); } } + + public static final class GetApiKeysResult { + static final GetApiKeysResult EMPTY_RESULT = new GetApiKeysResult(Collections.emptyList()); + private final Collection foundApiKeysInfo; + + public GetApiKeysResult(Collection foundApiKeysInfo) { + this.foundApiKeysInfo = foundApiKeysInfo; + } + + public Collection getApiKeyInfos() { + return foundApiKeysInfo; + } + } + + public static final class InvalidateApiKeysResult { + static final InvalidateApiKeysResult EMPTY_RESULT = new InvalidateApiKeysResult(Collections.emptyList(), Collections.emptyList(), + null); + private final List invalidatedApiKeys; + private final List previouslyInvalidatedApiKeys; + private final List errors; + + public InvalidateApiKeysResult(List invalidatedApiKeys, List previouslyInvalidatedApiKeys, + List errors) { + this.invalidatedApiKeys = (invalidatedApiKeys == null) ? List.of() : List.copyOf(invalidatedApiKeys); + this.previouslyInvalidatedApiKeys = (previouslyInvalidatedApiKeys == null) ? List.of() + : List.copyOf(previouslyInvalidatedApiKeys); + this.errors = (errors == null) ? List.of() : List.copyOf(errors); + } + + public List getInvalidatedApiKeys() { + return invalidatedApiKeys; + } + + public List getPreviouslyInvalidatedApiKeys() { + return previouslyInvalidatedApiKeys; + } + + public List getErrors() { + return errors; + } + } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java index afbe7a1231bd7..734afdac214d8 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java @@ -54,7 +54,7 @@ import org.elasticsearch.xpack.core.security.authz.ResolvedIndices; import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl; import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor; -import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilegeResolver; import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege; import org.elasticsearch.xpack.core.security.user.AnonymousUser; import org.elasticsearch.xpack.core.security.user.SystemUser; @@ -231,7 +231,7 @@ private void authorizeAction(final RequestInfo requestInfo, final String request final TransportRequest request = requestInfo.getRequest(); final String action = requestInfo.getAction(); final AuthorizationEngine authzEngine = getAuthorizationEngine(authentication); - if (ClusterPrivilege.ACTION_MATCHER.test(action)) { + if (ClusterPrivilegeResolver.ACTION_MATCHER.test(action)) { final ActionListener clusterAuthzListener = wrapPreservingContext(new AuthorizationResultListener<>(result -> { putTransientIfNonExisting(AuthorizationServiceField.INDICES_PERMISSIONS_KEY, IndicesAccessControl.ALLOW_ALL); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java index e2824e74ecafe..a763c4616f6aa 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java @@ -28,11 +28,12 @@ import org.elasticsearch.cluster.metadata.AliasOrIndex; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesReference; -import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.transport.TransportActionProxy; import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.core.security.action.GetApiKeyAction; +import org.elasticsearch.xpack.core.security.action.GetApiKeyRequest; import org.elasticsearch.xpack.core.security.action.user.AuthenticateAction; import org.elasticsearch.xpack.core.security.action.user.ChangePasswordAction; import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesAction; @@ -57,7 +58,8 @@ import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor; 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.ClusterPrivilegeResolver; +import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.Privilege; import org.elasticsearch.xpack.core.security.support.Automatons; import org.elasticsearch.xpack.core.security.user.User; @@ -85,7 +87,7 @@ public class RBACEngine implements AuthorizationEngine { private static final Predicate SAME_USER_PRIVILEGE = Automatons.predicate( - ChangePasswordAction.NAME, AuthenticateAction.NAME, HasPrivilegesAction.NAME, GetUserPrivilegesAction.NAME); + ChangePasswordAction.NAME, AuthenticateAction.NAME, HasPrivilegesAction.NAME, GetUserPrivilegesAction.NAME, GetApiKeyAction.NAME); private static final String INDEX_SUB_REQUEST_PRIMARY = IndexAction.NAME + "[p]"; private static final String INDEX_SUB_REQUEST_REPLICA = IndexAction.NAME + "[r]"; private static final String DELETE_SUB_REQUEST_PRIMARY = DeleteAction.NAME + "[p]"; @@ -136,7 +138,7 @@ public void authorizeClusterAction(RequestInfo requestInfo, AuthorizationInfo au ActionListener listener) { if (authorizationInfo instanceof RBACAuthorizationInfo) { final Role role = ((RBACAuthorizationInfo) authorizationInfo).getRole(); - if (role.checkClusterAction(requestInfo.getAction(), requestInfo.getRequest())) { + if (role.checkClusterAction(requestInfo.getAction(), requestInfo.getRequest(), requestInfo.getAuthentication())) { listener.onResponse(AuthorizationResult.granted()); } else if (checkSameUserPermissions(requestInfo.getAction(), requestInfo.getRequest(), requestInfo.getAuthentication())) { listener.onResponse(AuthorizationResult.granted()); @@ -153,26 +155,36 @@ public void authorizeClusterAction(RequestInfo requestInfo, AuthorizationInfo au boolean checkSameUserPermissions(String action, TransportRequest request, Authentication authentication) { final boolean actionAllowed = SAME_USER_PRIVILEGE.test(action); if (actionAllowed) { - if (request instanceof UserRequest == false) { - assert false : "right now only a user request should be allowed"; - return false; - } - UserRequest userRequest = (UserRequest) request; - String[] usernames = userRequest.usernames(); - if (usernames == null || usernames.length != 1 || usernames[0] == null) { - assert false : "this role should only be used for actions to apply to a single user"; + if (request instanceof UserRequest) { + UserRequest userRequest = (UserRequest) request; + String[] usernames = userRequest.usernames(); + if (usernames == null || usernames.length != 1 || usernames[0] == null) { + assert false : "this role should only be used for actions to apply to a single user"; + return false; + } + final String username = usernames[0]; + final boolean sameUsername = authentication.getUser().principal().equals(username); + if (sameUsername && ChangePasswordAction.NAME.equals(action)) { + return checkChangePasswordAction(authentication); + } + + assert AuthenticateAction.NAME.equals(action) || HasPrivilegesAction.NAME.equals(action) + || GetUserPrivilegesAction.NAME.equals(action) || sameUsername == false + : "Action '" + action + "' should not be possible when sameUsername=" + sameUsername; + return sameUsername; + } else if (request instanceof GetApiKeyRequest) { + GetApiKeyRequest getApiKeyRequest = (GetApiKeyRequest) request; + if (authentication.getAuthenticatedBy().getType().equals("_es_api_key")) { + // if authenticated by API key id then the request must also contain same API key id + String authenticatedApiKeyId = (String) authentication.getMetadata().get("_security_api_key_id"); + if (Strings.hasText(getApiKeyRequest.getApiKeyId())) { + return getApiKeyRequest.getApiKeyId().equals(authenticatedApiKeyId); + } + } + } else { + assert false : "right now only a user request or get api key request for API key should be allowed"; return false; } - final String username = usernames[0]; - final boolean sameUsername = authentication.getUser().principal().equals(username); - if (sameUsername && ChangePasswordAction.NAME.equals(action)) { - return checkChangePasswordAction(authentication); - } - - assert AuthenticateAction.NAME.equals(action) || HasPrivilegesAction.NAME.equals(action) - || GetUserPrivilegesAction.NAME.equals(action) || sameUsername == false - : "Action '" + action + "' should not be possible when sameUsername=" + sameUsername; - return sameUsername; } return false; } @@ -360,7 +372,10 @@ public void checkPrivileges(Authentication authentication, AuthorizationInfo aut Map cluster = new HashMap<>(); for (String checkAction : request.clusterPrivileges()) { - final ClusterPrivilege checkPrivilege = ClusterPrivilege.get(Collections.singleton(checkAction)); + ClusterPrivilege checkPrivilege = ClusterPrivilegeResolver + .resolve(Collections.singleton(checkAction)); + // here we just check if the action would be allowed by the underlying cluster privilege without considering request as the + // request is not available for evaluation of conditional cluster privilege. cluster.put(checkAction, userRole.grants(checkPrivilege)); } boolean allMatch = cluster.values().stream().allMatch(Boolean::booleanValue); @@ -413,14 +428,12 @@ GetUserPrivilegesResponse buildUserPrivilegesResponseObject(Role userRole) { // We use sorted sets for Strings because they will typically be small, and having a predictable order allows for simpler testing final Set cluster = new TreeSet<>(); // But we don't have a meaningful ordering for objects like ConditionalClusterPrivilege, so the tests work with "random" ordering - final Set conditionalCluster = new HashSet<>(); - for (Tuple tup : userRole.cluster().privileges()) { - if (tup.v2() == null) { - if (ClusterPrivilege.NONE.equals(tup.v1()) == false) { - cluster.addAll(tup.v1().name()); - } + final Set conditionalCluster = new HashSet<>(); + for (ClusterPrivilege privilege : userRole.cluster().privileges()) { + if (privilege instanceof GlobalClusterPrivilege) { + conditionalCluster.add((GlobalClusterPrivilege) privilege); } else { - conditionalCluster.add(tup.v2()); + cluster.addAll(privilege.name()); } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java index 7454ec59da55f..2aa8d59836567 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java @@ -34,7 +34,7 @@ import org.elasticsearch.xpack.core.security.authz.permission.LimitedRole; 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.ConditionalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivilege; 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; @@ -350,7 +350,7 @@ public static void buildRoleFromDescriptors(Collection roleDescr } Set clusterPrivileges = new HashSet<>(); - final List conditionalClusterPrivileges = new ArrayList<>(); + final List globalClusterPrivileges = new ArrayList<>(); Set runAs = new HashSet<>(); final Map, MergeableIndicesPrivilege> restrictedIndicesPrivilegesMap = new HashMap<>(); final Map, MergeableIndicesPrivilege> indicesPrivilegesMap = new HashMap<>(); @@ -365,7 +365,7 @@ public static void buildRoleFromDescriptors(Collection roleDescr clusterPrivileges.addAll(Arrays.asList(descriptor.getClusterPrivileges())); } if (descriptor.getConditionalClusterPrivileges() != null) { - conditionalClusterPrivileges.addAll(Arrays.asList(descriptor.getConditionalClusterPrivileges())); + globalClusterPrivileges.addAll(Arrays.asList(descriptor.getConditionalClusterPrivileges())); } if (descriptor.getRunAs() != null) { runAs.addAll(Arrays.asList(descriptor.getRunAs())); @@ -387,7 +387,7 @@ public static void buildRoleFromDescriptors(Collection roleDescr 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(clusterPrivileges, conditionalClusterPrivileges) + .cluster(clusterPrivileges, globalClusterPrivileges) .runAs(runAsPrivilege); indicesPrivilegesMap.entrySet().forEach((entry) -> { MergeableIndicesPrivilege privilege = entry.getValue(); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/user/RestGetUserPrivilegesAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/user/RestGetUserPrivilegesAction.java index 1f32c866cb3c8..274621d149bf4 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/user/RestGetUserPrivilegesAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/user/RestGetUserPrivilegesAction.java @@ -24,8 +24,8 @@ import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesRequestBuilder; import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesResponse; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges; +import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivileges; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler; @@ -81,8 +81,8 @@ public RestResponse buildResponse(GetUserPrivilegesResponse response, XContentBu builder.field(RoleDescriptor.Fields.CLUSTER.getPreferredName(), response.getClusterPrivileges()); builder.startArray(RoleDescriptor.Fields.GLOBAL.getPreferredName()); - for (ConditionalClusterPrivilege ccp : response.getConditionalClusterPrivileges()) { - ConditionalClusterPrivileges.toXContent(builder, ToXContent.EMPTY_PARAMS, Collections.singleton(ccp)); + for (GlobalClusterPrivilege ccp : response.getConditionalClusterPrivileges()) { + GlobalClusterPrivileges.toXContent(builder, ToXContent.EMPTY_PARAMS, Collections.singleton(ccp)); } builder.endArray(); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyIntegTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyIntegTests.java index 5c6c04b6ad491..bd3a2ca681be0 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyIntegTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyIntegTests.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.security.authc; import com.google.common.collect.Sets; + import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.action.DocWriteResponse; import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; @@ -61,6 +62,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.isIn; import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; public class ApiKeyIntegTests extends SecurityIntegTestCase { @@ -88,6 +90,38 @@ public void wipeSecurityIndex() throws InterruptedException { deleteSecurityIndex(); } + @Override + public String configRoles() { + return super.configRoles() + "\n" + + "manage_api_key_role:\n" + + " cluster: [\"manage_api_key\"]\n" + + "manage_own_api_key_role:\n" + + " cluster: [\"manage_own_api_key\"]\n" + + "no_manage_api_key_role:\n" + + " indices:\n" + + " - names: '*'\n" + + " privileges:\n" + + " - all\n"; + } + + @Override + public String configUsers() { + final String usersPasswdHashed = new String( + getFastStoredHashAlgoForTests().hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)); + return super.configUsers() + + "user_with_manage_api_key_role:" + usersPasswdHashed + "\n" + + "user_with_owner_manage_api_key_role:" + usersPasswdHashed + "\n" + + "user_with_no_manage_api_key_role:" + usersPasswdHashed + "\n"; + } + + @Override + public String configUsersRoles() { + return super.configUsersRoles() + + "manage_api_key_role:user_with_manage_api_key_role\n" + + "manage_own_api_key_role:user_with_owner_manage_api_key_role\n" + + "no_manage_api_key_role:user_with_no_manage_api_key_role"; + } + private void awaitApiKeysRemoverCompletion() throws InterruptedException { for (ApiKeyService apiKeyService : internalCluster().getInstances(ApiKeyService.class)) { final boolean done = awaitBusy(() -> apiKeyService.isExpirationInProgress() == false); @@ -501,12 +535,149 @@ private void verifyGetResponse(int noOfApiKeys, List respo } + public void testApiKeyWithMinimalRoleCanGetApiKeyInformation() { + final RoleDescriptor descriptor = new RoleDescriptor("role", new String[] { "monitor" }, null, null); + Client client = client().filterWithHeader(Collections.singletonMap("Authorization", + UsernamePasswordToken.basicAuthHeaderValue(SecuritySettingsSource.TEST_SUPERUSER, + SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); + + final CreateApiKeyResponse response = new CreateApiKeyRequestBuilder(client) + .setName("test key") + .setExpiration(TimeValue.timeValueHours(TimeUnit.DAYS.toHours(7L))) + .setRoleDescriptors(Collections.singletonList(descriptor)) + .get(); + + assertEquals("test key", response.getName()); + assertNotNull(response.getId()); + assertNotNull(response.getKey()); + + // use the first ApiKey for authorized action + final String base64ApiKeyKeyValue = Base64.getEncoder().encodeToString( + (response.getId() + ":" + response.getKey().toString()).getBytes(StandardCharsets.UTF_8)); + client = client().filterWithHeader(Collections.singletonMap("Authorization", "ApiKey " + base64ApiKeyKeyValue)); + + PlainActionFuture listener = new PlainActionFuture<>(); + GetApiKeyRequest request = GetApiKeyRequest.usingApiKeyId(response.getId()); + client.execute(GetApiKeyAction.INSTANCE, request, listener); + + GetApiKeyResponse apiKeyResponse = listener.actionGet(); + assertThat(apiKeyResponse.getApiKeyInfos().length, is(1)); + assertThat(apiKeyResponse.getApiKeyInfos()[0].getId(), is(response.getId())); + assertThat(apiKeyResponse.getApiKeyInfos()[0].getName(), is(response.getName())); + assertThat(apiKeyResponse.getApiKeyInfos()[0].getExpiration(), is(response.getExpiration())); + + // request where API key id is missing must fail + request = GetApiKeyRequest.usingApiKeyName("test key"); + PlainActionFuture failureListener = new PlainActionFuture<>(); + client.execute(GetApiKeyAction.INSTANCE, request, failureListener); + ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class, () -> failureListener.actionGet()); + assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/get", SecuritySettingsSource.TEST_SUPERUSER); + } + + public void testCreateApiKeyAuthorization() { + // user_with_manage_api_key_role should be able to create API key + List responses = createApiKeys("user_with_manage_api_key_role", 1, null); + assertThat(responses.get(0).getKey(), is(notNullValue())); + + // user_with_no_manage_api_key_role should not be able to create API key + Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken + .basicAuthHeaderValue("user_with_no_manage_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); + + RoleDescriptor roleDescriptor = new RoleDescriptor("role", new String[] { "monitor" }, null, null); + ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class, + () -> new CreateApiKeyRequestBuilder(client).setName("test-key-" + randomAlphaOfLengthBetween(5, 9)) + .setRoleDescriptors(Collections.singletonList(roleDescriptor)).get()); + assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/create", "user_with_no_manage_api_key_role"); + } + + public void testGetApiKeyAuthorization() throws InterruptedException, ExecutionException { + List userWithManageApiKeyRoleApiKeys = createApiKeys("user_with_manage_api_key_role", 2, null); + List userWithOwnerManageApiKeyRoleApiKeys = createApiKeys("user_with_owner_manage_api_key_role", 2, null); + + // user_with_manage_api_key_role should be able to get any user's API Key + { + final Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken + .basicAuthHeaderValue("user_with_manage_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); + + PlainActionFuture listener = new PlainActionFuture<>(); + client.execute(GetApiKeyAction.INSTANCE, GetApiKeyRequest.usingApiKeyId(userWithManageApiKeyRoleApiKeys.get(0).getId()), + listener); + GetApiKeyResponse response = listener.actionGet(); + assertThat(response.getApiKeyInfos().length, is(1)); + assertThat(response.getApiKeyInfos()[0].getId(), is(userWithManageApiKeyRoleApiKeys.get(0).getId())); + + listener = new PlainActionFuture<>(); + client.execute(GetApiKeyAction.INSTANCE, GetApiKeyRequest.usingApiKeyId(userWithOwnerManageApiKeyRoleApiKeys.get(0).getId()), + listener); + response = listener.actionGet(); + assertThat(response.getApiKeyInfos().length, is(1)); + assertThat(response.getApiKeyInfos()[0].getId(), is(userWithOwnerManageApiKeyRoleApiKeys.get(0).getId())); + } + + // user_with_owner_manage_api_key_role should be able to get its own API key but not any other + // user's API key + { + final Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken + .basicAuthHeaderValue("user_with_owner_manage_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); + + PlainActionFuture listener = new PlainActionFuture<>(); + GetApiKeyRequest getApiKeyRequest = new GetApiKeyRequest("file", "user_with_owner_manage_api_key_role", null, null); + client.execute(GetApiKeyAction.INSTANCE, getApiKeyRequest, listener); + GetApiKeyResponse response = listener.actionGet(); + assertThat(response.getApiKeyInfos().length, is(2)); + + final PlainActionFuture getApiKeyOfOtherUserListener = new PlainActionFuture<>(); + client.execute(GetApiKeyAction.INSTANCE, GetApiKeyRequest.usingApiKeyId(userWithManageApiKeyRoleApiKeys.get(0).getId()), + getApiKeyOfOtherUserListener); + final ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class, + () -> getApiKeyOfOtherUserListener.actionGet()); + assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/get", "user_with_owner_manage_api_key_role"); + } + + } + + public void testInvalidateApiKeyAuthorization() throws InterruptedException, ExecutionException { + List userWithManageApiKeyRoleApiKeys = createApiKeys("user_with_manage_api_key_role", 2, null); + List userWithOwnerManageApiKeyRoleApiKeys = createApiKeys("user_with_owner_manage_api_key_role", 2, null); + + // user_with_manage_api_key_role should be able to invalidate any user's API Key + InvalidateApiKeyResponse invalidateApiKeyResponse = invalidateApiKey("user_with_manage_api_key_role", null, null, + userWithManageApiKeyRoleApiKeys.get(0).getName(), null); + verifyInvalidateResponse(1, Collections.singletonList(userWithManageApiKeyRoleApiKeys.get(0)), invalidateApiKeyResponse); + invalidateApiKeyResponse = invalidateApiKey("user_with_manage_api_key_role", null, null, + userWithOwnerManageApiKeyRoleApiKeys.get(0).getName(), null); + verifyInvalidateResponse(1, Collections.singletonList(userWithOwnerManageApiKeyRoleApiKeys.get(0)), invalidateApiKeyResponse); + + // user_with_owner_manage_api_key_role should be able to invalidate its own API key but not any other user's API key + { + final Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken + .basicAuthHeaderValue("user_with_owner_manage_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); + + final PlainActionFuture listener = new PlainActionFuture<>(); + final InvalidateApiKeyRequest invalidateApiKeyRequest = new InvalidateApiKeyRequest("file", + "user_with_owner_manage_api_key_role", null, userWithOwnerManageApiKeyRoleApiKeys.get(1).getName()); + client.execute(InvalidateApiKeyAction.INSTANCE, invalidateApiKeyRequest, listener); + invalidateApiKeyResponse = listener.actionGet(); + verifyInvalidateResponse(1, Collections.singletonList(userWithOwnerManageApiKeyRoleApiKeys.get(1)), invalidateApiKeyResponse); + + final ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class, + () -> invalidateApiKey("user_with_owner_manage_api_key_role", null, "user_with_manage_api_key_role", + userWithManageApiKeyRoleApiKeys.get(1).getName(), null)); + assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/invalidate", "user_with_owner_manage_api_key_role"); + } + + } + private List createApiKeys(int noOfApiKeys, TimeValue expiration) { + return createApiKeys(SecuritySettingsSource.TEST_SUPERUSER, noOfApiKeys, expiration); + } + + private List createApiKeys(String user, int noOfApiKeys, TimeValue expiration) { List responses = new ArrayList<>(); for (int i = 0; i < noOfApiKeys; i++) { final RoleDescriptor descriptor = new RoleDescriptor("role", new String[] { "monitor" }, null, null); Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken - .basicAuthHeaderValue(SecuritySettingsSource.TEST_SUPERUSER, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); + .basicAuthHeaderValue(user, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); final CreateApiKeyResponse response = new CreateApiKeyRequestBuilder(client) .setName("test-key-" + randomAlphaOfLengthBetween(5, 9) + i).setExpiration(expiration) .setRoleDescriptors(Collections.singletonList(descriptor)).get(); @@ -517,4 +688,27 @@ private List createApiKeys(int noOfApiKeys, TimeValue expi assertThat(responses.size(), is(noOfApiKeys)); return responses; } + + private void assertErrorMessage(final ElasticsearchSecurityException ese, String action, String userName) { + assertThat(ese.getMessage(), is("action [" + action + "] is unauthorized for user [" + userName + "]")); + } + + private InvalidateApiKeyResponse invalidateApiKey(String executeActionAsUser, String realmName, String userName, String apiKeyName, + String apiKeyId) { + Client client = client().filterWithHeader(Collections.singletonMap("Authorization", + UsernamePasswordToken.basicAuthHeaderValue(executeActionAsUser, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); + PlainActionFuture listener = new PlainActionFuture<>(); + if (Strings.hasText(realmName) && Strings.hasText(userName)) { + client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingRealmAndUserName(realmName, userName), listener); + } else if (Strings.hasText(realmName)) { + client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingRealmName(realmName), listener); + } else if (Strings.hasText(userName)) { + client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingUserName(userName), listener); + } else if (Strings.hasText(apiKeyName)) { + client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingApiKeyName(apiKeyName), listener); + } else if (Strings.hasText(apiKeyId)) { + client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingApiKeyId(apiKeyId), listener); + } + return listener.actionGet(); + } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java index 36c9b79538272..4de6d36fa6c74 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java @@ -49,6 +49,7 @@ import org.elasticsearch.xpack.core.security.action.user.GetUsersResponse; import org.elasticsearch.xpack.core.security.action.user.PutUserRequestBuilder; import org.elasticsearch.xpack.core.security.action.user.SetEnabledRequestBuilder; +import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; import org.elasticsearch.xpack.core.security.authz.permission.Role; @@ -368,11 +369,12 @@ public void testCreateAndUpdateRole() { assertThat(e.status(), is(RestStatus.FORBIDDEN)); } } else { + final Authentication authentication = mock(Authentication.class); final TransportRequest request = mock(TransportRequest.class); GetRolesResponse getRolesResponse = new GetRolesRequestBuilder(client()).names("test_role").get(); assertTrue("test_role does not exist!", getRolesResponse.hasRoles()); assertTrue("any cluster permission should be authorized", - Role.builder(getRolesResponse.roles()[0], null).build().cluster().check("cluster:admin/foo", request)); + Role.builder(getRolesResponse.roles()[0], null).build().cluster().check("cluster:admin/foo", request, authentication)); preparePutRole("test_role") .cluster("none") @@ -383,7 +385,7 @@ public void testCreateAndUpdateRole() { assertTrue("test_role does not exist!", getRolesResponse.hasRoles()); assertFalse("no cluster permission should be authorized", - Role.builder(getRolesResponse.roles()[0], null).build().cluster().check("cluster:admin/bar", request)); + Role.builder(getRolesResponse.roles()[0], null).build().cluster().check("cluster:admin/bar", request, authentication)); } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java index 2b085c6834581..6a3a7648b4dc0 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java @@ -114,8 +114,8 @@ 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.ApplicationPrivilegeDescriptor; -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.DefaultClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore; import org.elasticsearch.xpack.core.security.user.AnonymousUser; import org.elasticsearch.xpack.core.security.user.ElasticUser; @@ -149,7 +149,7 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.CountDownLatch; -import java.util.function.Predicate; +import java.util.function.BiPredicate; import static java.util.Arrays.asList; import static org.elasticsearch.test.SecurityTestsUtils.assertAuthenticationException; @@ -315,11 +315,12 @@ public void testAuthorizeUsingConditionalPrivileges() throws IOException { final DeletePrivilegesRequest request = new DeletePrivilegesRequest(); final Authentication authentication = createAuthentication(new User("user1", "role1")); - final ConditionalClusterPrivilege conditionalClusterPrivilege = Mockito.mock(ConditionalClusterPrivilege.class); - final Predicate requestPredicate = r -> r == request; + final GlobalClusterPrivilege conditionalClusterPrivilege = Mockito.mock(GlobalClusterPrivilege.class); + final BiPredicate requestPredicate = (r, a) -> r == request; Mockito.when(conditionalClusterPrivilege.getRequestPredicate()).thenReturn(requestPredicate); - Mockito.when(conditionalClusterPrivilege.getPrivilege()).thenReturn(ClusterPrivilege.MANAGE_SECURITY); - final ConditionalClusterPrivilege[] conditionalClusterPrivileges = new ConditionalClusterPrivilege[] { + Mockito.when(conditionalClusterPrivilege.predicate()) + .thenReturn(DefaultClusterPrivilege.MANAGE_SECURITY.clusterPrivilege().predicate()); + final GlobalClusterPrivilege[] conditionalClusterPrivileges = new GlobalClusterPrivilege[] { conditionalClusterPrivilege }; final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); @@ -336,11 +337,12 @@ public void testAuthorizationDeniedWhenConditionalPrivilegesDoNotMatch() throws final DeletePrivilegesRequest request = new DeletePrivilegesRequest(); final Authentication authentication = createAuthentication(new User("user1", "role1")); - final ConditionalClusterPrivilege conditionalClusterPrivilege = Mockito.mock(ConditionalClusterPrivilege.class); - final Predicate requestPredicate = r -> false; + final GlobalClusterPrivilege conditionalClusterPrivilege = Mockito.mock(GlobalClusterPrivilege.class); + final BiPredicate requestPredicate = (r, a) -> false; Mockito.when(conditionalClusterPrivilege.getRequestPredicate()).thenReturn(requestPredicate); - Mockito.when(conditionalClusterPrivilege.getPrivilege()).thenReturn(ClusterPrivilege.MANAGE_SECURITY); - final ConditionalClusterPrivilege[] conditionalClusterPrivileges = new ConditionalClusterPrivilege[] { + Mockito.when(conditionalClusterPrivilege.predicate()) + .thenReturn(DefaultClusterPrivilege.MANAGE_SECURITY.clusterPrivilege().predicate()); + final GlobalClusterPrivilege[] conditionalClusterPrivileges = new GlobalClusterPrivilege[] { conditionalClusterPrivilege }; final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizedIndicesTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizedIndicesTests.java index 51dba4e4c2334..7bb1f21a2fcb4 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizedIndicesTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizedIndicesTests.java @@ -19,18 +19,19 @@ import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissions; import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsCache; import org.elasticsearch.xpack.core.security.authz.permission.Role; -import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.DefaultClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege; import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore; import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames; import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore; +import java.util.Collections; import java.util.List; import java.util.Set; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.contains; public class AuthorizedIndicesTests extends ESTestCase { @@ -88,7 +89,9 @@ public void testAuthorizedIndicesUserWithSomeRolesEmptyMetaData() { } public void testSecurityIndicesAreRemovedFromRegularUser() { - Role role = Role.builder("user_role").add(IndexPrivilege.ALL, "*").cluster(ClusterPrivilege.ALL).build(); + Role role = Role.builder("user_role").add(IndexPrivilege.ALL, "*") + .cluster(Set.of(DefaultClusterPrivilege.ALL.privilegeName()), Collections.emptySet()) + .build(); List authorizedIndices = RBACEngine.resolveAuthorizedIndicesFromRole(role, SearchAction.NAME, MetaData.EMPTY_META_DATA.getAliasAndIndexLookup()); assertTrue(authorizedIndices.isEmpty()); @@ -97,7 +100,7 @@ public void testSecurityIndicesAreRemovedFromRegularUser() { public void testSecurityIndicesAreRestrictedForDefaultRole() { Role role = Role.builder(randomFrom("user_role", ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName())) .add(IndexPrivilege.ALL, "*") - .cluster(ClusterPrivilege.ALL) + .cluster(Set.of(DefaultClusterPrivilege.ALL.privilegeName()), Collections.emptySet()) .build(); Settings indexSettings = Settings.builder().put("index.version.created", Version.CURRENT).build(); final String internalSecurityIndex = randomFrom(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_6, @@ -124,7 +127,7 @@ public void testSecurityIndicesAreRestrictedForDefaultRole() { public void testSecurityIndicesAreNotRemovedFromUnrestrictedRole() { Role role = Role.builder(randomAlphaOfLength(8)) .add(FieldPermissions.DEFAULT, null, IndexPrivilege.ALL, true, "*") - .cluster(ClusterPrivilege.ALL) + .cluster(Set.of(DefaultClusterPrivilege.ALL.privilegeName()), Collections.emptySet()) .build(); Settings indexSettings = Settings.builder().put("index.version.created", Version.CURRENT).build(); final String internalSecurityIndex = randomFrom(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_6, diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RBACEngineTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RBACEngineTests.java index 5c2e964c743c6..b18cd254f0101 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RBACEngineTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RBACEngineTests.java @@ -46,11 +46,11 @@ 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.ApplicationPrivilegeDescriptor; -import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges.ManageApplicationPrivileges; -import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames; +import org.elasticsearch.xpack.core.security.authz.privilege.DefaultClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.ManageApplicationPrivileges; import org.elasticsearch.xpack.core.security.authz.privilege.Privilege; +import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; import org.elasticsearch.xpack.security.authz.RBACEngine.RBACAuthorizationInfo; @@ -284,7 +284,7 @@ public void testMatchSubsetOfPrivileges() throws Exception { Authentication authentication = mock(Authentication.class); when(authentication.getUser()).thenReturn(user); Role role = Role.builder("test2") - .cluster(ClusterPrivilege.MONITOR) + .cluster(Set.of(DefaultClusterPrivilege.MONITOR.privilegeName()), Collections.emptySet()) .add(IndexPrivilege.INDEX, "academy") .add(IndexPrivilege.WRITE, "initiative") .build(); @@ -343,7 +343,7 @@ public void testMatchNothing() throws Exception { Authentication authentication = mock(Authentication.class); when(authentication.getUser()).thenReturn(user); Role role = Role.builder("test3") - .cluster(ClusterPrivilege.MONITOR) + .cluster(Set.of(DefaultClusterPrivilege.MONITOR.privilegeName()), Collections.emptySet()) .build(); RBACAuthorizationInfo authzInfo = new RBACAuthorizationInfo(role, null); @@ -721,6 +721,23 @@ public void testCheckingApplicationPrivilegesWithComplexNames() throws Exception )); } + public void testCheckingHasPrivilegesWithConditionalClusterPrivilege() throws Exception { + User user = new User(randomAlphaOfLengthBetween(4, 12)); + Authentication authentication = mock(Authentication.class); + when(authentication.getUser()).thenReturn(user); + Role role = Role.builder("test-write") + .cluster(Set.of("none", "manage_own_api_key"), Collections.emptySet()) + .build(); + RBACAuthorizationInfo authzInfo = new RBACAuthorizationInfo(role, null); + + final HasPrivilegesResponse response = hasPrivileges(new RoleDescriptor.IndicesPrivileges[0], + new RoleDescriptor.ApplicationResourcePrivileges[0], authentication, authzInfo, Collections.emptyList(), "monitor", + "cluster:admin/xpack/security/api_key/invalidate"); + assertThat(response.isCompleteMatch(), is(false)); + assertThat(response.getClusterPrivileges().get("monitor"), is(false)); + assertThat(response.getClusterPrivileges().get("cluster:admin/xpack/security/api_key/invalidate"), is(true)); + } + public void testIsCompleteMatch() throws Exception { final List privs = new ArrayList<>(); final ApplicationPrivilege kibanaRead = defineApplicationPrivilege(privs, "kibana", "read", "data:read/*"); @@ -729,7 +746,7 @@ public void testIsCompleteMatch() throws Exception { Authentication authentication = mock(Authentication.class); when(authentication.getUser()).thenReturn(user); Role role = Role.builder("test-write") - .cluster(ClusterPrivilege.MONITOR) + .cluster(Set.of(DefaultClusterPrivilege.MONITOR.privilegeName()), Collections.emptySet()) .add(IndexPrivilege.READ, "read-*") .add(IndexPrivilege.ALL, "all-*") .addApplicationPrivilege(kibanaRead, Collections.singleton("*")) diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RoleDescriptorTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RoleDescriptorTests.java index a2d828cf92284..e848c227070d4 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RoleDescriptorTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RoleDescriptorTests.java @@ -22,8 +22,8 @@ import org.elasticsearch.test.VersionUtils; import org.elasticsearch.xpack.core.XPackClientPlugin; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges; +import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.ManageApplicationPrivileges; import org.elasticsearch.xpack.core.security.support.MetadataUtils; import org.hamcrest.Matchers; @@ -72,8 +72,8 @@ public void testToString() { .build() }; - final ConditionalClusterPrivilege[] conditionalClusterPrivileges = new ConditionalClusterPrivilege[]{ - new ConditionalClusterPrivileges.ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02"))) + final GlobalClusterPrivilege[] conditionalClusterPrivileges = new GlobalClusterPrivilege[]{ + new ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02"))) }; RoleDescriptor descriptor = new RoleDescriptor("test", new String[] { "all", "none" }, groups, applicationPrivileges, @@ -104,8 +104,8 @@ public void testToXContent() throws Exception { .resources("*") .build() }; - final ConditionalClusterPrivilege[] conditionalClusterPrivileges = { - new ConditionalClusterPrivileges.ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02"))) + final GlobalClusterPrivilege[] conditionalClusterPrivileges = { + new ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02"))) }; Map metadata = randomBoolean() ? MetadataUtils.DEFAULT_RESERVED_METADATA : null; @@ -189,10 +189,10 @@ public void testParse() throws Exception { assertThat(rd.getApplicationPrivileges()[1].getApplication(), equalTo("app2")); assertThat(rd.getConditionalClusterPrivileges(), Matchers.arrayWithSize(1)); - final ConditionalClusterPrivilege conditionalPrivilege = rd.getConditionalClusterPrivileges()[0]; - assertThat(conditionalPrivilege.getCategory(), equalTo(ConditionalClusterPrivilege.Category.APPLICATION)); - assertThat(conditionalPrivilege, instanceOf(ConditionalClusterPrivileges.ManageApplicationPrivileges.class)); - assertThat(((ConditionalClusterPrivileges.ManageApplicationPrivileges) conditionalPrivilege).getApplicationNames(), + final GlobalClusterPrivilege conditionalPrivilege = rd.getConditionalClusterPrivileges()[0]; + assertThat(conditionalPrivilege.getCategory(), equalTo(GlobalClusterPrivilege.Category.APPLICATION)); + assertThat(conditionalPrivilege, instanceOf(ManageApplicationPrivileges.class)); + assertThat(((ManageApplicationPrivileges) conditionalPrivilege).getApplicationNames(), containsInAnyOrder("kibana", "logstash")); q = "{\"applications\": [{\"application\": \"myapp\", \"resources\": [\"*\"], \"privileges\": [\"login\" ]}] }"; @@ -233,8 +233,8 @@ public void testSerializationForCurrentVersion() throws Exception { .resources("*") .build() }; - final ConditionalClusterPrivilege[] conditionalClusterPrivileges = { - new ConditionalClusterPrivileges.ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02"))) + final GlobalClusterPrivilege[] conditionalClusterPrivileges = { + new ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02"))) }; Map metadata = randomBoolean() ? MetadataUtils.DEFAULT_RESERVED_METADATA : null; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java index b4e0a6a22cf81..87d7ffe03d2d8 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java @@ -41,8 +41,8 @@ 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.ApplicationPrivilegeDescriptor; -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.DefaultClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege; import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore; import org.elasticsearch.xpack.core.security.authz.store.RoleRetrievalResult; @@ -75,11 +75,11 @@ import static org.elasticsearch.mock.orig.Mockito.times; import static org.elasticsearch.mock.orig.Mockito.verifyNoMoreInteractions; import static org.hamcrest.Matchers.anyOf; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anySetOf; import static org.mockito.Matchers.eq; @@ -538,13 +538,15 @@ public void testMergingRolesWithFls() { } public void testMergingBasicRoles() { + final Authentication authentication = mock(Authentication.class); final TransportRequest request1 = mock(TransportRequest.class); final TransportRequest request2 = mock(TransportRequest.class); final TransportRequest request3 = mock(TransportRequest.class); - ConditionalClusterPrivilege ccp1 = mock(ConditionalClusterPrivilege.class); - when(ccp1.getPrivilege()).thenReturn(ClusterPrivilege.MANAGE_SECURITY); - when(ccp1.getRequestPredicate()).thenReturn(req -> req == request1); + GlobalClusterPrivilege ccp1 = mock(GlobalClusterPrivilege.class); + when(ccp1.predicate()).thenReturn(DefaultClusterPrivilege.MANAGE_SECURITY.clusterPrivilege().predicate()); + when(ccp1.getAutomaton()).thenReturn(DefaultClusterPrivilege.MANAGE_SECURITY.clusterPrivilege().getAutomaton()); + when(ccp1.getRequestPredicate()).thenReturn((req, authn) -> req == request1); RoleDescriptor role1 = new RoleDescriptor("r1", new String[]{"monitor"}, new IndicesPrivileges[]{ IndicesPrivileges.builder() .indices("abc-*", "xyz-*") @@ -565,12 +567,13 @@ public void testMergingBasicRoles() { .resources("settings/*") .privileges("read") .build() - }, new ConditionalClusterPrivilege[] { ccp1 }, + }, new GlobalClusterPrivilege[] { ccp1 }, new String[]{"app-user-1"}, null, null); - ConditionalClusterPrivilege ccp2 = mock(ConditionalClusterPrivilege.class); - when(ccp2.getPrivilege()).thenReturn(ClusterPrivilege.MANAGE_SECURITY); - when(ccp2.getRequestPredicate()).thenReturn(req -> req == request2); + GlobalClusterPrivilege ccp2 = mock(GlobalClusterPrivilege.class); + when(ccp2.predicate()).thenReturn(DefaultClusterPrivilege.MANAGE_SECURITY.clusterPrivilege().predicate()); + when(ccp2.getAutomaton()).thenReturn(DefaultClusterPrivilege.MANAGE_SECURITY.clusterPrivilege().getAutomaton()); + when(ccp2.getRequestPredicate()).thenReturn((req, authn) -> req == request2); RoleDescriptor role2 = new RoleDescriptor("r2", new String[]{"manage_saml"}, new IndicesPrivileges[]{ IndicesPrivileges.builder() .indices("abc-*", "ind-2-*") @@ -587,7 +590,7 @@ public void testMergingBasicRoles() { .resources("*") .privileges("read") .build() - }, new ConditionalClusterPrivilege[] { ccp2 }, + }, new GlobalClusterPrivilege[] { ccp2 }, new String[]{"app-user-2"}, null, null); FieldPermissionsCache cache = new FieldPermissionsCache(Settings.EMPTY); @@ -609,12 +612,14 @@ public void testMergingBasicRoles() { CompositeRolesStore.buildRoleFromDescriptors(Sets.newHashSet(role1, role2), cache, privilegeStore, future); Role role = future.actionGet(); - assertThat(role.cluster().check(ClusterStateAction.NAME, randomFrom(request1, request2, request3)), equalTo(true)); - assertThat(role.cluster().check(SamlAuthenticateAction.NAME, randomFrom(request1, request2, request3)), equalTo(true)); - assertThat(role.cluster().check(ClusterUpdateSettingsAction.NAME, randomFrom(request1, request2, request3)), equalTo(false)); + assertThat(role.cluster().check(ClusterStateAction.NAME, randomFrom(request1, request2, request3), authentication), equalTo(true)); + assertThat(role.cluster().check(SamlAuthenticateAction.NAME, randomFrom(request1, request2, request3), authentication), + equalTo(true)); + assertThat(role.cluster().check(ClusterUpdateSettingsAction.NAME, randomFrom(request1, request2, request3), authentication), + equalTo(false)); - assertThat(role.cluster().check(PutUserAction.NAME, randomFrom(request1, request2)), equalTo(true)); - assertThat(role.cluster().check(PutUserAction.NAME, request3), equalTo(false)); + assertThat(role.cluster().check(PutUserAction.NAME, randomFrom(request1, request2), authentication), equalTo(true)); + assertThat(role.cluster().check(PutUserAction.NAME, request3, authentication), equalTo(false)); final Predicate allowedRead = role.indices().allowedIndicesMatcher(GetAction.NAME); assertThat(allowedRead.test("abc-123"), equalTo(true)); @@ -1059,7 +1064,7 @@ public void testApiKeyAuthUsesApiKeyServiceWithScopedRole() throws IOException { PlainActionFuture roleFuture = new PlainActionFuture<>(); compositeRolesStore.getRoles(authentication.getUser(), authentication, roleFuture); Role role = roleFuture.actionGet(); - assertThat(role.checkClusterAction("cluster:admin/foo", Empty.INSTANCE), is(false)); + assertThat(role.checkClusterAction("cluster:admin/foo", Empty.INSTANCE, authentication), is(false)); assertThat(effectiveRoleDescriptors.get(), is(nullValue())); verify(apiKeyService).getRoleForApiKey(eq(authentication), any(ActionListener.class)); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/FileRolesStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/FileRolesStoreTests.java index 0763ff65ec562..d440e989fac5d 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/FileRolesStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/FileRolesStoreTests.java @@ -20,12 +20,13 @@ import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.security.audit.logfile.CapturingLogger; +import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; import org.elasticsearch.xpack.core.security.authz.permission.ClusterPermission; import org.elasticsearch.xpack.core.security.authz.permission.IndicesPermission; import org.elasticsearch.xpack.core.security.authz.permission.Role; import org.elasticsearch.xpack.core.security.authz.permission.RunAsPermission; -import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.DefaultClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege; import java.io.BufferedWriter; @@ -74,7 +75,7 @@ public void testParseFile() throws Exception { assertThat(role, notNullValue()); assertThat(role.names(), equalTo(new String[] { "role1" })); assertThat(role.cluster(), notNullValue()); - assertThat(role.cluster().privilege(), is(ClusterPrivilege.ALL)); + assertThat(role.cluster().privilege(), is(DefaultClusterPrivilege.ALL.clusterPrivilege())); assertThat(role.indices(), notNullValue()); assertThat(role.indices().groups(), notNullValue()); assertThat(role.indices().groups().length, is(2)); @@ -102,7 +103,7 @@ public void testParseFile() throws Exception { assertThat(role, notNullValue()); assertThat(role.names(), equalTo(new String[] { "role1.ab" })); assertThat(role.cluster(), notNullValue()); - assertThat(role.cluster().privilege(), is(ClusterPrivilege.ALL)); + assertThat(role.cluster().privilege(), is(DefaultClusterPrivilege.ALL.clusterPrivilege())); assertThat(role.indices(), notNullValue()); assertThat(role.indices().groups(), notNullValue()); assertThat(role.indices().groups().length, is(0)); @@ -114,7 +115,8 @@ public void testParseFile() throws Exception { assertThat(role, notNullValue()); assertThat(role.names(), equalTo(new String[] { "role2" })); assertThat(role.cluster(), notNullValue()); - assertTrue(Operations.sameLanguage(role.cluster().privilege().getAutomaton(), ClusterPrivilege.ALL.getAutomaton())); + assertTrue(Operations.sameLanguage(role.cluster().privilege().getAutomaton(), + DefaultClusterPrivilege.ALL.clusterPrivilege().getAutomaton())); assertThat(role.indices(), notNullValue()); assertThat(role.indices(), is(IndicesPermission.NONE)); assertThat(role.runAs(), is(RunAsPermission.NONE)); @@ -350,6 +352,7 @@ public void testAutoReload() throws Exception { assertEquals(1, modifiedRoles.size()); assertTrue(modifiedRoles.contains("role5")); + final Authentication authentication = mock(Authentication.class); final TransportRequest request = mock(TransportRequest.class); descriptors = store.roleDescriptors(Collections.singleton("role5")); assertThat(descriptors, notNullValue()); @@ -357,8 +360,8 @@ public void testAutoReload() throws Exception { Role role = Role.builder(descriptors.iterator().next(), null).build(); assertThat(role, notNullValue()); assertThat(role.names(), equalTo(new String[] { "role5" })); - assertThat(role.cluster().check("cluster:monitor/foo/bar", request), is(true)); - assertThat(role.cluster().check("cluster:admin/foo/bar", request), is(false)); + assertThat(role.cluster().check("cluster:monitor/foo/bar", request, authentication), is(true)); + assertThat(role.cluster().check("cluster:admin/foo/bar", request, authentication), is(false)); // truncate to remove some final Set truncatedFileRolesModified = new HashSet<>(); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/user/RestGetUserPrivilegesActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/user/RestGetUserPrivilegesActionTests.java index 41c5c39765199..6f15f229e1fe6 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/user/RestGetUserPrivilegesActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/user/RestGetUserPrivilegesActionTests.java @@ -22,8 +22,8 @@ import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesResponse; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor.ApplicationResourcePrivileges; import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsDefinition; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges; +import org.elasticsearch.xpack.core.security.authz.privilege.ManageApplicationPrivileges; +import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivilege; import java.util.Arrays; import java.util.Collections; @@ -55,8 +55,8 @@ public void testBasicLicense() throws Exception { public void testBuildResponse() throws Exception { final RestGetUserPrivilegesAction.RestListener listener = new RestGetUserPrivilegesAction.RestListener(null); final Set cluster = new LinkedHashSet<>(Arrays.asList("monitor", "manage_ml", "manage_watcher")); - final Set conditionalCluster = Collections.singleton( - new ConditionalClusterPrivileges.ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02")))); + final Set conditionalCluster = Collections.singleton( + new ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02")))); final Set index = new LinkedHashSet<>(Arrays.asList( new GetUserPrivilegesResponse.Indices(Arrays.asList("index-1", "index-2", "index-3-*"), Arrays.asList("read", "write"), new LinkedHashSet<>(Arrays.asList(