From f58d5e098984ef8f1f0614acedcaa79c1bdaa61a Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Thu, 9 May 2019 09:22:35 +1000 Subject: [PATCH 01/25] Add granular privileges for API keys In the current implementation of API keys, to create/get/invalidate API keys one needs to be super user which limits the usage of API keys. We would want to have fine grained privileges rather than system wide privileges for using API keys. - `manage_api_key` cluster privilege which allows users to create, retrieve and invalidate any API keys in the system. This allows for limited access than `manage_security` or `all` privileges. To support scenario's where we want to give granular privileges this commit adds support for conditional cluster privileges where we can create different combinations of `create`, `get`, `invalidate` API key privileges for restricting actions to API keys: - owned by the user - owned by group of users - owned by group of users under specified realms - any user from any realm This commit does not:- - define any new API for ease, this will be taken up later after we decide if this approach works - HLRC changes --- .../client/security/user/privileges/Role.java | 3 +- .../xpack/core/XPackClientPlugin.java | 10 +- .../security/action/GetApiKeyRequest.java | 10 - .../action/InvalidateApiKeyRequest.java | 10 - .../authz/permission/ClusterPermission.java | 13 +- .../authz/permission/LimitedRole.java | 5 +- .../core/security/authz/permission/Role.java | 6 +- .../authz/privilege/ClusterPrivilege.java | 5 + .../ConditionalClusterPrivilege.java | 10 +- .../ConditionalClusterPrivileges.java | 155 +---- .../ManageApiKeyConditionalPrivileges.java | 241 ++++++++ .../ManageApplicationPrivileges.java | 157 +++++ .../authz/store/ReservedRolesStore.java | 2 +- .../org/elasticsearch/test/TestMatchers.java | 14 + .../action/GetApiKeyRequestTests.java | 15 +- .../action/InvalidateApiKeyRequestTests.java | 15 +- .../action/role/PutRoleRequestTests.java | 4 +- .../user/GetUserPrivilegesResponseTests.java | 2 +- .../authz/permission/LimitedRoleTests.java | 28 +- .../ConditionalClusterPrivilegesTests.java | 3 +- ...anageApiKeyConditionalPrivilegesTests.java | 269 +++++++++ .../ManageApplicationPrivilegesTests.java | 27 +- .../authz/store/ReservedRolesStoreTests.java | 562 +++++++++--------- .../action/TransportGetApiKeyAction.java | 14 +- .../TransportInvalidateApiKeyAction.java | 13 +- .../xpack/security/authc/ApiKeyService.java | 314 ++++------ .../xpack/security/authz/RBACEngine.java | 52 +- .../security/authc/ApiKeyIntegTests.java | 246 +++++++- .../authc/esnative/NativeRealmIntegTests.java | 6 +- .../authz/AuthorizationServiceTests.java | 6 +- .../xpack/security/authz/RBACEngineTests.java | 2 +- .../security/authz/RoleDescriptorTests.java | 12 +- .../authz/store/CompositeRolesStoreTests.java | 19 +- .../authz/store/FileRolesStoreTests.java | 6 +- .../RestGetUserPrivilegesActionTests.java | 4 +- 35 files changed, 1519 insertions(+), 741 deletions(-) create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivileges.java create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApplicationPrivileges.java create mode 100644 x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivilegesTests.java 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 c6dc6910d97b0..bb4016570210b 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 @@ -312,6 +312,7 @@ public static class ClusterPrivilegeName { public static final String MANAGE_SECURITY = "manage_security"; public static final String MANAGE_SAML = "manage_saml"; 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"; @@ -319,7 +320,7 @@ 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_TOKEN, MANAGE_PIPELINE, MANAGE_CCR, READ_CCR, MANAGE_ILM, READ_ILM }; + MANAGE_SECURITY, MANAGE_SAML, MANAGE_TOKEN, MANAGE_API_KEY, MANAGE_PIPELINE, MANAGE_CCR, READ_CCR, MANAGE_ILM, READ_ILM }; } /** 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 a145569898ee6..d0f91acc3d277 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 @@ -177,7 +177,8 @@ 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.ManageApiKeyConditionalPrivileges; +import org.elasticsearch.xpack.core.security.authz.privilege.ManageApplicationPrivileges; import org.elasticsearch.xpack.core.security.transport.netty4.SecurityNetty4Transport; import org.elasticsearch.xpack.core.sql.SqlFeatureSetUsage; import org.elasticsearch.xpack.core.ssl.SSLService; @@ -414,8 +415,11 @@ public List getNamedWriteables() { 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), + ManageApplicationPrivileges.WRITEABLE_NAME, + ManageApplicationPrivileges::createFrom), + new NamedWriteableRegistry.Entry(ConditionalClusterPrivilege.class, + ManageApiKeyConditionalPrivileges.WRITEABLE_NAME, + ManageApiKeyConditionalPrivileges::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/authz/permission/ClusterPermission.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermission.java index 687798971399f..f05d3ea1577eb 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 @@ -8,6 +8,7 @@ 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; @@ -32,7 +33,7 @@ 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()); @@ -55,7 +56,7 @@ public static class SimpleClusterPermission extends ClusterPermission { } @Override - public boolean check(String action, TransportRequest request) { + public boolean check(String action, TransportRequest request, Authentication authentication) { return predicate.test(action); } @@ -77,8 +78,8 @@ public ConditionalClusterPermission(ConditionalClusterPrivilege conditionalPrivi } @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 super.privilege.predicate().test(action) && conditionalPrivilege.getRequestPredicate().test(request, authentication); } @Override @@ -113,8 +114,8 @@ public List> privileges() { } @Override - public boolean check(String action, TransportRequest request) { - return children.stream().anyMatch(p -> p.check(action, request)); + public boolean check(String action, TransportRequest request, Authentication authentication) { + return children.stream().anyMatch(p -> p.check(action, request, authentication)); } @Override 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..04776df28b132 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,6 +12,7 @@ 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; @@ -124,10 +125,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); } /** 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..b37cc8f597107 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 @@ -39,6 +39,9 @@ public final class ClusterPrivilege extends Privilege { 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/*"); @@ -74,6 +77,7 @@ public final class ClusterPrivilege extends Privilege { 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_API_KEY = new ClusterPrivilege("manage_api_key", MANAGE_API_KEY_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 = @@ -105,6 +109,7 @@ public final class ClusterPrivilege extends Privilege { entry("manage_ml", MANAGE_ML), entry("manage_data_frame_transforms", MANAGE_DATA_FRAME), entry("manage_token", MANAGE_TOKEN), + entry("manage_api_key", MANAGE_API_KEY), entry("manage_watcher", MANAGE_WATCHER), entry("manage_index_templates", MANAGE_IDX_TEMPLATES), entry("manage_ingest_pipelines", MANAGE_INGEST_PIPELINES), 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..3d66aa19d2fa9 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 @@ -11,9 +11,11 @@ import org.elasticsearch.common.xcontent.ToXContentFragment; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.core.security.authc.Authentication; import java.io.IOException; import java.util.Collection; +import java.util.function.BiPredicate; import java.util.function.Predicate; /** @@ -34,9 +36,10 @@ public interface ConditionalClusterPrivilege extends NamedWriteable, ToXContentF ClusterPrivilege getPrivilege(); /** - * The request-level privilege (as a {@link Predicate}) that is required by this conditional privilege. + * 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. */ - Predicate getRequestPredicate(); + BiPredicate getRequestPredicate(); /** * A {@link ConditionalClusterPrivilege} should generate a fragment of {@code XContent}, which consists of @@ -52,7 +55,8 @@ public interface ConditionalClusterPrivilege extends NamedWriteable, ToXContentF * from the categories. */ enum Category { - APPLICATION(new ParseField("application")); + APPLICATION(new ParseField("application")), + API_KEYS(new ParseField("api_keys")); public final ParseField field; 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 index e5cfd2448aaad..853db47d64989 100644 --- 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 @@ -15,21 +15,13 @@ 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 @@ -68,13 +60,19 @@ public static XContentBuilder toXContent(XContentBuilder builder, ToXContent.Par Collection privileges) throws IOException { builder.startObject(); for (Category category : Category.values()) { - builder.startObject(category.field.getPreferredName()); + boolean startObject = false; for (ConditionalClusterPrivilege privilege : privileges) { if (category == privilege.getCategory()) { + if (startObject == false) { + builder.startObject(category.field.getPreferredName()); + startObject = true; + } privilege.toXContent(builder, params); } } - builder.endObject(); + if (startObject) { + builder.endObject(); + } } return builder.endObject(); } @@ -90,13 +88,25 @@ public static List parse(XContentParser parser) thr 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); + String fieldName = parser.currentName(); + if (Category.APPLICATION.field.match(fieldName, parser.getDeprecationHandler())) { + 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); + } else if (Category.API_KEYS.field.match(fieldName, parser.getDeprecationHandler())) { + expectedToken(parser.nextToken(), parser, XContentParser.Token.START_OBJECT); + expectedToken(parser.nextToken(), parser, XContentParser.Token.FIELD_NAME); + + expectFieldName(parser, ManageApiKeyConditionalPrivileges.Fields.MANAGE); + privileges.add(ManageApiKeyConditionalPrivileges.parse(parser)); + expectedToken(parser.nextToken(), parser, XContentParser.Token.END_OBJECT); + } else { + throw new XContentParseException(parser.getTokenLocation(), "failed to parse privilege. expected one from " + + Arrays.asList(Category.values()) + " but found [" + fieldName + "] instead"); + } } return privileges; @@ -118,115 +128,4 @@ private static void expectFieldName(XContentParser parser, ParseField... fields) } } - /** - * 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/ManageApiKeyConditionalPrivileges.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivileges.java new file mode 100644 index 0000000000000..00fd0a7d5589d --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivileges.java @@ -0,0 +1,241 @@ +/* + * 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.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 org.elasticsearch.xpack.core.security.xcontent.XContentUtils; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.BiPredicate; +import java.util.function.Predicate; + +public final class ManageApiKeyConditionalPrivileges implements ConditionalClusterPrivilege { + + 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"; + + public static final String WRITEABLE_NAME = "manage-api-key-privileges"; + + private final Set realms; + private final Predicate realmsPredicate; + private final Set users; + private final Predicate usersPredicate; + private final ClusterPrivilege privilege; + private final BiPredicate requestPredicate; + + interface Fields { + ParseField MANAGE = new ParseField("manage"); + + ParseField CREATE = new ParseField("create"); + ParseField GET = new ParseField("get"); + ParseField INVALIDATE = new ParseField("invalidate"); + + ParseField USERS = new ParseField("users"); + ParseField REALMS = new ParseField("realms"); + } + + public ManageApiKeyConditionalPrivileges(boolean createAllowed, boolean getAllowed, boolean invalidateAllowed, Set realms, + Set users) { + final Set patterns = new HashSet<>(); + if (createAllowed) { + patterns.add(CREATE_API_KEY_PATTERN); + } + if (getAllowed) { + patterns.add(GET_API_KEY_PATTERN); + } + if (invalidateAllowed) { + patterns.add(INVALIDATE_API_KEY_PATTERN); + } + this.privilege = ClusterPrivilege.get(patterns); + + this.realms = (realms == null) ? Collections.emptySet() : Set.copyOf(realms); + this.realmsPredicate = Automatons.predicate(this.realms); + this.users = (users == null) ? Collections.emptySet() : Set.copyOf(users); + this.usersPredicate = Automatons.predicate(this.users); + + this.requestPredicate = (request, authentication) -> { + if (request instanceof CreateApiKeyRequest && privilege.predicate().test(CREATE_API_KEY_PATTERN)) { + return true; + } else if (request instanceof GetApiKeyRequest && privilege.predicate().test(GET_API_KEY_PATTERN)) { + final GetApiKeyRequest getApiKeyRequest = (GetApiKeyRequest) request; + if (this.realms.isEmpty() && this.users.isEmpty()) { + return checkIfUserIsOwnerOfApiKeys(authentication, getApiKeyRequest.getApiKeyId(), getApiKeyRequest.getUserName()); + } else { + return checkIfAccessAllowed(realms, getApiKeyRequest.getRealmName(), realmsPredicate) + && checkIfAccessAllowed(users, getApiKeyRequest.getUserName(), usersPredicate); + } + } else if (request instanceof InvalidateApiKeyRequest && privilege.predicate().test(INVALIDATE_API_KEY_PATTERN)) { + final InvalidateApiKeyRequest invalidateApiKeyRequest = (InvalidateApiKeyRequest) request; + if (this.realms.isEmpty() && this.users.isEmpty()) { + return checkIfUserIsOwnerOfApiKeys(authentication, invalidateApiKeyRequest.getId(), + invalidateApiKeyRequest.getUserName()); + } else { + return checkIfAccessAllowed(realms, invalidateApiKeyRequest.getRealmName(), realmsPredicate) + && checkIfAccessAllowed(users, invalidateApiKeyRequest.getUserName(), usersPredicate); + } + } + return false; + }; + } + + private boolean checkIfUserIsOwnerOfApiKeys(Authentication authentication, String apiKeyId, String username) { + 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(); + if (Strings.hasText(username)) { + return username.equals(authenticatedUserPrincipal); + } + } + return false; + } + + private static boolean checkIfAccessAllowed(Set names, String requestName, Predicate predicate) { + return (Strings.hasText(requestName) == false) ? names.contains("*") : predicate.test(requestName); + } + + @Override + public String getWriteableName() { + return WRITEABLE_NAME; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeBoolean(privilege.predicate().test(CREATE_API_KEY_PATTERN)); + out.writeBoolean(privilege.predicate().test(GET_API_KEY_PATTERN)); + out.writeBoolean(privilege.predicate().test(INVALIDATE_API_KEY_PATTERN)); + out.writeCollection(this.realms, StreamOutput::writeString); + out.writeCollection(this.users, StreamOutput::writeString); + } + + public static ManageApiKeyConditionalPrivileges createFrom(StreamInput in) throws IOException { + final boolean allowCreate = in.readBoolean(); + final boolean allowGet = in.readBoolean(); + final boolean allowInvalidate = in.readBoolean(); + final Set realms = in.readSet(StreamInput::readString); + final Set users = in.readSet(StreamInput::readString); + return new ManageApiKeyConditionalPrivileges(allowCreate, allowGet, allowInvalidate, realms, users); + } + + @Override + public Category getCategory() { + return Category.API_KEYS; + } + + @Override + public ClusterPrivilege getPrivilege() { + return privilege; + } + + @Override + public BiPredicate getRequestPredicate() { + return requestPredicate; + } + + @Override + public int hashCode() { + return Objects.hash(privilege, users, realms); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final ManageApiKeyConditionalPrivileges that = (ManageApiKeyConditionalPrivileges) o; + return Objects.equals(this.privilege, that.privilege) && Objects.equals(this.realms, that.realms) + && Objects.equals(this.users, that.users); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + + builder.field(Fields.MANAGE.getPreferredName(), + Map.of(Fields.CREATE.getPreferredName(), privilege.predicate().test(CREATE_API_KEY_PATTERN), Fields.GET.getPreferredName(), + privilege.predicate().test(GET_API_KEY_PATTERN), Fields.INVALIDATE.getPreferredName(), + privilege.predicate().test(INVALIDATE_API_KEY_PATTERN), Fields.REALMS.getPreferredName(), this.realms, + Fields.USERS.getPreferredName(), this.users)); + + return builder; + } + + public static ManageApiKeyConditionalPrivileges 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); + + boolean createAllowed = false; + boolean getAllowed = false; + boolean invalidateAllowed = false; + String[] realms = null; + String[] users = null; + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + expectedToken(parser.currentToken(), parser, XContentParser.Token.FIELD_NAME); + String fieldName = parser.currentName(); + if (Fields.CREATE.match(fieldName, parser.getDeprecationHandler())) { + parser.nextToken(); + createAllowed = parser.booleanValue(); + } else if (Fields.GET.match(fieldName, parser.getDeprecationHandler())) { + parser.nextToken(); + getAllowed = parser.booleanValue(); + } else if (Fields.INVALIDATE.match(fieldName, parser.getDeprecationHandler())) { + parser.nextToken(); + invalidateAllowed = parser.booleanValue(); + } else if (Fields.REALMS.match(fieldName, parser.getDeprecationHandler())) { + expectedToken(parser.nextToken(), parser, XContentParser.Token.START_ARRAY); + realms = XContentUtils.readStringArray(parser, false); + } else if (Fields.USERS.match(fieldName, parser.getDeprecationHandler())) { + expectedToken(parser.nextToken(), parser, XContentParser.Token.START_ARRAY); + users = XContentUtils.readStringArray(parser, false); + } + + } + return new ManageApiKeyConditionalPrivileges(createAllowed, getAllowed, invalidateAllowed, Set.of(realms), Set.of(users)); + } + + 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/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..597e840f6c463 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApplicationPrivileges.java @@ -0,0 +1,157 @@ +/* + * 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 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 final 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 BiPredicate requestPredicate; + + public ManageApplicationPrivileges(Set applicationNames) { + 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 ClusterPrivilege getPrivilege() { + return PRIVILEGE; + } + + @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/store/ReservedRolesStore.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java index b767b56086159..eca63f8226fed 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 @@ -12,7 +12,7 @@ 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.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; 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..2a3f47def7bfd 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 @@ -22,7 +22,7 @@ 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.ManageApplicationPrivileges; import java.io.IOException; import java.util.ArrayList; 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..45ffdd9d2a017 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,6 +18,7 @@ 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; @@ -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)); } } 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/ConditionalClusterPrivilegesTests.java index ebcd70869cb02..cba49860c50a8 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/ConditionalClusterPrivilegesTests.java @@ -67,7 +67,8 @@ private ConditionalClusterPrivilege[] buildSecurityPrivileges() { private ConditionalClusterPrivilege[] buildSecurityPrivileges(int applicationNameLength) { return new ConditionalClusterPrivilege[] { - ManageApplicationPrivilegesTests.buildPrivileges(applicationNameLength) + ManageApplicationPrivilegesTests.buildPrivileges(applicationNameLength), + ManageApiKeyConditionalPrivilegesTests.ManageApiKeyConditionalPrivilegesBuilder.manageApiKeysOnlyForOwner() }; } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivilegesTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivilegesTests.java new file mode 100644 index 0000000000000..f4700a10bf166 --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivilegesTests.java @@ -0,0 +1,269 @@ +/* + * 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 java.util.Set; + +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ManageApiKeyConditionalPrivilegesTests 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 testManageAllPrivilege() { + final ManageApiKeyConditionalPrivileges condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.manageApiKeysUnrestricted(); + + boolean accessAllowed = checkAccess(condPrivilege, CREATE_ACTION, new CreateApiKeyRequest(), authentication); + assertThat(accessAllowed, is(true)); + + accessAllowed = checkAccess(condPrivilege, GET_ACTION, + GetApiKeyRequest.usingRealmAndUserName(randomAlphaOfLength(5), randomAlphaOfLength(5)), authentication); + assertThat(accessAllowed, is(true)); + + accessAllowed = checkAccess(condPrivilege, INVALIDATE_ACTION, + InvalidateApiKeyRequest.usingRealmAndUserName(randomAlphaOfLength(5), randomAlphaOfLength(5)), authentication); + assertThat(accessAllowed, is(true)); + + // When request does not have user or realm name and conditional api key privileges for manage is used then it should be denied. + accessAllowed = checkAccess(condPrivilege, GET_ACTION, GetApiKeyRequest.usingApiKeyName(randomAlphaOfLength(5)), authentication); + assertThat(accessAllowed, is(true)); + accessAllowed = checkAccess(condPrivilege, INVALIDATE_ACTION, InvalidateApiKeyRequest.usingApiKeyId(randomAlphaOfLength(5)), + authentication); + assertThat(accessAllowed, is(true)); + } + + public void testManagePrivilegeRestrictedForRealmsAndUsers() { + final ManageApiKeyConditionalPrivileges condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.builder().allowCreate().allowGet() + .allowInvalidate().forRealms("realm1", "realm2").forUsers("user1", "user2").build(); + + boolean accessAllowed = checkAccess(condPrivilege, CREATE_ACTION, new CreateApiKeyRequest(), authentication); + assertThat(accessAllowed, is(true)); + + accessAllowed = checkAccess(condPrivilege, GET_ACTION, GetApiKeyRequest.usingRealmAndUserName("realm1", "user1"), authentication); + assertThat(accessAllowed, is(true)); + + accessAllowed = checkAccess(condPrivilege, GET_ACTION, + GetApiKeyRequest.usingRealmAndUserName(randomAlphaOfLength(7), randomAlphaOfLength(7)), authentication); + assertThat(accessAllowed, is(false)); + + accessAllowed = checkAccess(condPrivilege, INVALIDATE_ACTION, InvalidateApiKeyRequest.usingRealmAndUserName("realm2", "user2"), + authentication); + assertThat(accessAllowed, is(true)); + + accessAllowed = checkAccess(condPrivilege, INVALIDATE_ACTION, + InvalidateApiKeyRequest.usingRealmAndUserName(randomAlphaOfLength(7), randomAlphaOfLength(7)), authentication); + assertThat(accessAllowed, is(false)); + + } + + public void testManagePrivilegeRestrictedReadOnlyForRealmsAndUsers() { + final ManageApiKeyConditionalPrivileges condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.builder().allowGet() + .forRealms("realm1", "realm2").forUsers("user1", "user2").build(); + + boolean accessAllowed = checkAccess(condPrivilege, CREATE_ACTION, new CreateApiKeyRequest(), authentication); + assertThat(accessAllowed, is(false)); + + accessAllowed = checkAccess(condPrivilege, GET_ACTION, GetApiKeyRequest.usingRealmAndUserName("realm1", "user1"), authentication); + assertThat(accessAllowed, is(true)); + + accessAllowed = checkAccess(condPrivilege, GET_ACTION, + GetApiKeyRequest.usingRealmAndUserName(randomAlphaOfLength(7), randomAlphaOfLength(7)), authentication); + assertThat(accessAllowed, is(false)); + + accessAllowed = checkAccess(condPrivilege, INVALIDATE_ACTION, InvalidateApiKeyRequest.usingRealmAndUserName("realm2", "user2"), + authentication); + assertThat(accessAllowed, is(false)); + + accessAllowed = checkAccess(condPrivilege, INVALIDATE_ACTION, + InvalidateApiKeyRequest.usingRealmAndUserName(randomAlphaOfLength(7), randomAlphaOfLength(7)), authentication); + assertThat(accessAllowed, is(false)); + + } + + public void testManagePrivilegeOwnerOnly() { + final ManageApiKeyConditionalPrivileges condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.manageApiKeysOnlyForOwner(); + + boolean accessAllowed = checkAccess(condPrivilege, CREATE_ACTION, new CreateApiKeyRequest(), authentication); + assertThat(accessAllowed, is(true)); + + // Username 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, 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)); + } + + public void testManagePrivilegeOwnerAndReadOnly() { + final ManageApiKeyConditionalPrivileges condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.builder().allowGet().forRealms() + .forUsers().build(); + + boolean accessAllowed = checkAccess(condPrivilege, CREATE_ACTION, new CreateApiKeyRequest(), authentication); + assertThat(accessAllowed, is(false)); + + // Username 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, INVALIDATE_ACTION, InvalidateApiKeyRequest.usingRealmAndUserName("realm1", "user1"), + authentication); + assertThat(accessAllowed, is(false)); + 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(false)); + 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(ManageApiKeyConditionalPrivileges privilege, String action, TransportRequest request, + Authentication authentication) { + return privilege.getPrivilege().predicate().test(action) && privilege.getRequestPredicate().test(request, authentication); + } + + public static class ManageApiKeyConditionalPrivilegesBuilder { + private boolean createAllowed; + private boolean getAllowed; + private boolean invalidateAllowed; + private Set realms; + private Set users; + + public ManageApiKeyConditionalPrivilegesBuilder allowCreate() { + this.createAllowed = true; + return this; + } + + public ManageApiKeyConditionalPrivilegesBuilder allowGet() { + this.getAllowed = true; + return this; + } + + public ManageApiKeyConditionalPrivilegesBuilder allowInvalidate() { + this.invalidateAllowed = true; + return this; + } + + public ManageApiKeyConditionalPrivilegesBuilder allowAllRealms() { + this.realms = Set.of("*"); + return this; + } + + public ManageApiKeyConditionalPrivilegesBuilder allowAllUsers() { + this.users = Set.of("*"); + return this; + } + + public ManageApiKeyConditionalPrivilegesBuilder forRealms(String... realms) { + this.realms = Set.of(realms); + return this; + } + + public ManageApiKeyConditionalPrivilegesBuilder forUsers(String... users) { + this.users = Set.of(users); + return this; + } + + public static ManageApiKeyConditionalPrivilegesBuilder builder() { + return new ManageApiKeyConditionalPrivilegesBuilder(); + } + + public static ManageApiKeyConditionalPrivileges manageApiKeysUnrestricted() { + return new ManageApiKeyConditionalPrivileges(true, true, true, Set.of("*"), Set.of("*")); + } + + public static ManageApiKeyConditionalPrivileges manageApiKeysOnlyForOwner() { + return new ManageApiKeyConditionalPrivileges(true, true, true, null, null); + } + + public ManageApiKeyConditionalPrivileges build() { + return new ManageApiKeyConditionalPrivileges(createAllowed, getAllowed, invalidateAllowed, realms, users); + } + } +} 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..e37cc9d946b91 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,10 +42,11 @@ 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; +import static org.mockito.Mockito.mock; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; @@ -113,21 +114,22 @@ public void testPrivilege() { } 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/store/ReservedRolesStoreTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java index 78f9623f4fbb8..eb6ceb062cd6a 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"); @@ -1099,6 +1117,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"); @@ -1106,13 +1125,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"); @@ -1123,6 +1142,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"); @@ -1130,14 +1150,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)); @@ -1152,6 +1172,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"); @@ -1159,14 +1180,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)); @@ -1224,6 +1245,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"); @@ -1231,10 +1253,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..f95a938454ae0 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,44 @@ 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; + this.previouslyInvalidatedApiKeys = previouslyInvalidatedApiKeys; + this.errors = 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/RBACEngine.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java index e2824e74ecafe..be9c93ce67282 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 @@ -33,6 +33,8 @@ 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; @@ -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; } 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 f6849cae4c1cd..881575167782a 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 @@ -60,6 +60,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 { @@ -87,6 +88,49 @@ 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" + + " global: { \"api_keys\":{\"manage\":{\"get\":true,\"invalidate\":true,\"create\":true," + + "\"users\":[],\"realms\":[]}} }\n" + + "only_create_api_key_role:\n" + + " global: { \"api_keys\":{\"manage\":{\"get\":false,\"invalidate\":false,\"create\":true," + + "\"users\":[],\"realms\":[]}} }\n" + + "only_create_get_own_api_key_role:\n" + + " global: { \"api_keys\":{\"manage\":{\"get\":true,\"invalidate\":false,\"create\":true," + + "\"users\":[],\"realms\":[]}} }\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_only_create_api_key_role:" + usersPasswdHashed + "\n" + + "user_with_only_create_get_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" + + "only_create_api_key_role:user_with_only_create_api_key_role\n" + + "only_create_get_own_api_key_role:user_with_only_create_get_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); @@ -517,12 +561,188 @@ 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))); + SecurityClient securityClient = new SecurityClient(client); + final CreateApiKeyResponse response = securityClient.prepareCreateApiKey() + .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)); + securityClient = new SecurityClient(client); + PlainActionFuture listener = new PlainActionFuture<>(); + GetApiKeyRequest request = GetApiKeyRequest.usingApiKeyId(response.getId()); + securityClient.getApiKey(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<>(); + securityClient.getApiKey(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_only_create_api_key_role should be able to create API key + responses = createApiKeys("user_with_only_create_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))); + SecurityClient securityClient = new SecurityClient(client); + RoleDescriptor roleDescriptor = new RoleDescriptor("role", new String[] { "monitor" }, null, null); + ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class, + () -> securityClient.prepareCreateApiKey().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); + List userWithCreateApiKeyRoleApiKeys = createApiKeys("user_with_only_create_api_key_role", 1, 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))); + final SecurityClient securityClient = new SecurityClient(client); + PlainActionFuture listener = new PlainActionFuture<>(); + securityClient.getApiKey(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<>(); + securityClient.getApiKey(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))); + final SecurityClient securityClient = new SecurityClient(client); + PlainActionFuture listener = new PlainActionFuture<>(); + securityClient.getApiKey(GetApiKeyRequest.usingUserName("user_with_owner_manage_api_key_role"), listener); + GetApiKeyResponse response = listener.actionGet(); + assertThat(response.getApiKeyInfos().length, is(2)); + + final PlainActionFuture getApiKeyOfOtherUserListener = new PlainActionFuture<>(); + securityClient.getApiKey(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"); + } + + // user_with_only_create_api_key_role should not be allowed to get it's own API key but not any other user's API key + { + final Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken + .basicAuthHeaderValue("user_with_only_create_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); + final SecurityClient securityClient = new SecurityClient(client); + PlainActionFuture listener = new PlainActionFuture<>(); + GetApiKeyRequest getApiKeyRequest = new GetApiKeyRequest(null, "user_with_only_create_api_key_role", + userWithCreateApiKeyRoleApiKeys.get(0).getId(), userWithCreateApiKeyRoleApiKeys.get(0).getName()); + securityClient.getApiKey(getApiKeyRequest, listener); + ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class, + () -> listener.actionGet()); + assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/get", "user_with_only_create_api_key_role"); + + final PlainActionFuture getApiKeyOfOtherUserListener = new PlainActionFuture<>(); + securityClient.getApiKey(GetApiKeyRequest.usingApiKeyId(userWithManageApiKeyRoleApiKeys.get(0).getId()), + getApiKeyOfOtherUserListener); + ese = expectThrows(ElasticsearchSecurityException.class, + () -> getApiKeyOfOtherUserListener.actionGet()); + assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/get", "user_with_only_create_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); + List userWithCreateApiKeyRoleApiKeys = createApiKeys("user_with_only_create_api_key_role", 1, 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 SecurityClient securityClient = new SecurityClient(client); + final PlainActionFuture listener = new PlainActionFuture<>(); + InvalidateApiKeyRequest invalidateApiKeyRequest = new InvalidateApiKeyRequest(null, "user_with_owner_manage_api_key_role", null, + userWithOwnerManageApiKeyRoleApiKeys.get(1).getName()); + securityClient.invalidateApiKey(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_only_create_api_key_role", + userWithManageApiKeyRoleApiKeys.get(1).getName(), null)); + assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/invalidate", "user_with_owner_manage_api_key_role"); + } + + // user_with_only_create_api_key_role should not be allowed to invalidate it's own API keys or any other users API keys + { + final Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken + .basicAuthHeaderValue("user_with_only_create_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); + final SecurityClient securityClient = new SecurityClient(client); + + ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class, + () -> invalidateApiKey("user_with_only_create_api_key_role", null, "user_with_only_create_api_key_role", + userWithManageApiKeyRoleApiKeys.get(1).getName(), null)); + assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/invalidate", "user_with_only_create_api_key_role"); + + final PlainActionFuture listener = new PlainActionFuture<>(); + securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyName(userWithCreateApiKeyRoleApiKeys.get(0).getName()), + listener); + ese = expectThrows(ElasticsearchSecurityException.class, () -> listener.actionGet()); + assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/invalidate", "user_with_only_create_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))); SecurityClient securityClient = new SecurityClient(client); final CreateApiKeyResponse response = securityClient.prepareCreateApiKey() .setName("test-key-" + randomAlphaOfLengthBetween(5, 9) + i).setExpiration(expiration) @@ -534,4 +754,28 @@ 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))); + SecurityClient securityClient = new SecurityClient(client); + PlainActionFuture listener = new PlainActionFuture<>(); + if (Strings.hasText(realmName) && Strings.hasText(userName)) { + securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingRealmAndUserName(realmName, userName), listener); + } else if (Strings.hasText(realmName)) { + securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingRealmName(realmName), listener); + } else if (Strings.hasText(userName)) { + securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingUserName(userName), listener); + } else if (Strings.hasText(apiKeyName)) { + securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyName(apiKeyName), listener); + } else if (Strings.hasText(apiKeyId)) { + securityClient.invalidateApiKey(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 7021873a02878..7c6056035b38b 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 @@ -39,6 +39,7 @@ import org.elasticsearch.xpack.core.security.action.user.ChangePasswordResponse; import org.elasticsearch.xpack.core.security.action.user.DeleteUserResponse; import org.elasticsearch.xpack.core.security.action.user.GetUsersResponse; +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; @@ -367,11 +368,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 = c.prepareGetRoles().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)); c.preparePutRole("test_role") .cluster("none") @@ -382,7 +384,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 b370c8e2b6b0f..0d01a01bcf1d7 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 @@ -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; @@ -316,7 +316,7 @@ public void testAuthorizeUsingConditionalPrivileges() throws IOException { final Authentication authentication = createAuthentication(new User("user1", "role1")); final ConditionalClusterPrivilege conditionalClusterPrivilege = Mockito.mock(ConditionalClusterPrivilege.class); - final Predicate requestPredicate = r -> r == request; + 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[] { @@ -337,7 +337,7 @@ public void testAuthorizationDeniedWhenConditionalPrivilegesDoNotMatch() throws final Authentication authentication = createAuthentication(new User("user1", "role1")); final ConditionalClusterPrivilege conditionalClusterPrivilege = Mockito.mock(ConditionalClusterPrivilege.class); - final Predicate requestPredicate = r -> false; + 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[] { 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..dc93940741a49 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 @@ -47,9 +47,9 @@ 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.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.user.User; import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; 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..13e4c3d788094 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 @@ -23,7 +23,7 @@ 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.ManageApplicationPrivileges; import org.elasticsearch.xpack.core.security.support.MetadataUtils; import org.hamcrest.Matchers; @@ -73,7 +73,7 @@ public void testToString() { }; final ConditionalClusterPrivilege[] conditionalClusterPrivileges = new ConditionalClusterPrivilege[]{ - new ConditionalClusterPrivileges.ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02"))) + new ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02"))) }; RoleDescriptor descriptor = new RoleDescriptor("test", new String[] { "all", "none" }, groups, applicationPrivileges, @@ -105,7 +105,7 @@ public void testToXContent() throws Exception { .build() }; final ConditionalClusterPrivilege[] conditionalClusterPrivileges = { - new ConditionalClusterPrivileges.ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02"))) + new ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02"))) }; Map metadata = randomBoolean() ? MetadataUtils.DEFAULT_RESERVED_METADATA : null; @@ -191,8 +191,8 @@ public void testParse() throws Exception { final ConditionalClusterPrivilege conditionalPrivilege = rd.getConditionalClusterPrivileges()[0]; assertThat(conditionalPrivilege.getCategory(), equalTo(ConditionalClusterPrivilege.Category.APPLICATION)); - assertThat(conditionalPrivilege, instanceOf(ConditionalClusterPrivileges.ManageApplicationPrivileges.class)); - assertThat(((ConditionalClusterPrivileges.ManageApplicationPrivileges) conditionalPrivilege).getApplicationNames(), + assertThat(conditionalPrivilege, instanceOf(ManageApplicationPrivileges.class)); + assertThat(((ManageApplicationPrivileges) conditionalPrivilege).getApplicationNames(), containsInAnyOrder("kibana", "logstash")); q = "{\"applications\": [{\"application\": \"myapp\", \"resources\": [\"*\"], \"privileges\": [\"login\" ]}] }"; @@ -234,7 +234,7 @@ public void testSerializationForCurrentVersion() throws Exception { .build() }; final ConditionalClusterPrivilege[] conditionalClusterPrivileges = { - new ConditionalClusterPrivileges.ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02"))) + 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 a39545f3a9b3a..0247cc3525bec 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 @@ -538,13 +538,14 @@ 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); + when(ccp1.getRequestPredicate()).thenReturn((req, authn) -> req == request1); RoleDescriptor role1 = new RoleDescriptor("r1", new String[]{"monitor"}, new IndicesPrivileges[]{ IndicesPrivileges.builder() .indices("abc-*", "xyz-*") @@ -570,7 +571,7 @@ public void testMergingBasicRoles() { ConditionalClusterPrivilege ccp2 = mock(ConditionalClusterPrivilege.class); when(ccp2.getPrivilege()).thenReturn(ClusterPrivilege.MANAGE_SECURITY); - when(ccp2.getRequestPredicate()).thenReturn(req -> req == request2); + 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-*") @@ -609,12 +610,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)); @@ -1058,7 +1061,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..ebd662f9eafc0 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,6 +20,7 @@ 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; @@ -350,6 +351,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 +359,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..023819b94691f 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 @@ -23,7 +23,7 @@ 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 java.util.Arrays; import java.util.Collections; @@ -56,7 +56,7 @@ 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")))); + 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( From 167b98e2a4a6b872ee15e3626eb9d4c32db297b8 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Fri, 10 May 2019 01:15:30 +1000 Subject: [PATCH 02/25] add check for realm name in conditional predicate --- .../privilege/ManageApiKeyConditionalPrivileges.java | 12 +++++++----- .../ManageApiKeyConditionalPrivilegesTests.java | 11 +++++++++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivileges.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivileges.java index 00fd0a7d5589d..b3d4c15fa91a3 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivileges.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivileges.java @@ -82,7 +82,8 @@ public ManageApiKeyConditionalPrivileges(boolean createAllowed, boolean getAllow } else if (request instanceof GetApiKeyRequest && privilege.predicate().test(GET_API_KEY_PATTERN)) { final GetApiKeyRequest getApiKeyRequest = (GetApiKeyRequest) request; if (this.realms.isEmpty() && this.users.isEmpty()) { - return checkIfUserIsOwnerOfApiKeys(authentication, getApiKeyRequest.getApiKeyId(), getApiKeyRequest.getUserName()); + return checkIfUserIsOwnerOfApiKeys(authentication, getApiKeyRequest.getApiKeyId(), getApiKeyRequest.getUserName(), + getApiKeyRequest.getRealmName()); } else { return checkIfAccessAllowed(realms, getApiKeyRequest.getRealmName(), realmsPredicate) && checkIfAccessAllowed(users, getApiKeyRequest.getUserName(), usersPredicate); @@ -91,7 +92,7 @@ public ManageApiKeyConditionalPrivileges(boolean createAllowed, boolean getAllow final InvalidateApiKeyRequest invalidateApiKeyRequest = (InvalidateApiKeyRequest) request; if (this.realms.isEmpty() && this.users.isEmpty()) { return checkIfUserIsOwnerOfApiKeys(authentication, invalidateApiKeyRequest.getId(), - invalidateApiKeyRequest.getUserName()); + invalidateApiKeyRequest.getUserName(), invalidateApiKeyRequest.getRealmName()); } else { return checkIfAccessAllowed(realms, invalidateApiKeyRequest.getRealmName(), realmsPredicate) && checkIfAccessAllowed(users, invalidateApiKeyRequest.getUserName(), usersPredicate); @@ -101,7 +102,7 @@ public ManageApiKeyConditionalPrivileges(boolean createAllowed, boolean getAllow }; } - private boolean checkIfUserIsOwnerOfApiKeys(Authentication authentication, String apiKeyId, String username) { + 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"); @@ -110,8 +111,9 @@ private boolean checkIfUserIsOwnerOfApiKeys(Authentication authentication, Strin } } else { String authenticatedUserPrincipal = authentication.getUser().principal(); - if (Strings.hasText(username)) { - return username.equals(authenticatedUserPrincipal); + String authenticatedUserRealm = authentication.getAuthenticatedBy().getName(); + if (Strings.hasText(username) && Strings.hasText(realmName)) { + return username.equals(authenticatedUserPrincipal) && realmName.equals(authenticatedUserRealm); } } return false; diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivilegesTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivilegesTests.java index f4700a10bf166..d9fa178a6de69 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivilegesTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivilegesTests.java @@ -116,12 +116,16 @@ public void testManagePrivilegeOwnerOnly() { boolean accessAllowed = checkAccess(condPrivilege, CREATE_ACTION, new CreateApiKeyRequest(), authentication); assertThat(accessAllowed, is(true)); - // Username is always required to evaluate condition if authenticated by user + // 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)); @@ -163,12 +167,15 @@ public void testManagePrivilegeOwnerAndReadOnly() { boolean accessAllowed = checkAccess(condPrivilege, CREATE_ACTION, new CreateApiKeyRequest(), authentication); assertThat(accessAllowed, is(false)); - // Username is always required to evaluate condition if authenticated by user + // 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(false)); From 08e1163a22c551b3e054e51920faca18c20daec8 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Fri, 10 May 2019 09:50:46 +1000 Subject: [PATCH 03/25] fix test --- .../elasticsearch/xpack/security/authc/ApiKeyIntegTests.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 881575167782a..4a65c1b53833b 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 @@ -649,7 +649,8 @@ public void testGetApiKeyAuthorization() throws InterruptedException, ExecutionE .basicAuthHeaderValue("user_with_owner_manage_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); final SecurityClient securityClient = new SecurityClient(client); PlainActionFuture listener = new PlainActionFuture<>(); - securityClient.getApiKey(GetApiKeyRequest.usingUserName("user_with_owner_manage_api_key_role"), listener); + GetApiKeyRequest getApiKeyRequest = new GetApiKeyRequest("file", "user_with_owner_manage_api_key_role", null, null); + securityClient.getApiKey(getApiKeyRequest, listener); GetApiKeyResponse response = listener.actionGet(); assertThat(response.getApiKeyInfos().length, is(2)); @@ -702,7 +703,7 @@ public void testInvalidateApiKeyAuthorization() throws InterruptedException, Exe .basicAuthHeaderValue("user_with_owner_manage_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); final SecurityClient securityClient = new SecurityClient(client); final PlainActionFuture listener = new PlainActionFuture<>(); - InvalidateApiKeyRequest invalidateApiKeyRequest = new InvalidateApiKeyRequest(null, "user_with_owner_manage_api_key_role", null, + InvalidateApiKeyRequest invalidateApiKeyRequest = new InvalidateApiKeyRequest("file", "user_with_owner_manage_api_key_role", null, userWithOwnerManageApiKeyRoleApiKeys.get(1).getName()); securityClient.invalidateApiKey(invalidateApiKeyRequest, listener); invalidateApiKeyResponse = listener.actionGet(); From 25e97abfc7e35366cec363b051721b0c5cdd0539 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Fri, 10 May 2019 13:12:04 +1000 Subject: [PATCH 04/25] style fix --- .../elasticsearch/xpack/security/authc/ApiKeyIntegTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 9e587bf863e6c..2d34945c0aba4 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 @@ -704,8 +704,8 @@ public void testInvalidateApiKeyAuthorization() throws InterruptedException, Exe .basicAuthHeaderValue("user_with_owner_manage_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); final SecurityClient securityClient = new SecurityClient(client); final PlainActionFuture listener = new PlainActionFuture<>(); - InvalidateApiKeyRequest invalidateApiKeyRequest = new InvalidateApiKeyRequest("file", "user_with_owner_manage_api_key_role", null, - userWithOwnerManageApiKeyRoleApiKeys.get(1).getName()); + final InvalidateApiKeyRequest invalidateApiKeyRequest = new InvalidateApiKeyRequest("file", + "user_with_owner_manage_api_key_role", null, userWithOwnerManageApiKeyRoleApiKeys.get(1).getName()); securityClient.invalidateApiKey(invalidateApiKeyRequest, listener); invalidateApiKeyResponse = listener.actionGet(); verifyInvalidateResponse(1, Collections.singletonList(userWithOwnerManageApiKeyRoleApiKeys.get(1)), invalidateApiKeyResponse); From 7cbb19e5350e9abaeb85616baebf0d1b5a42107f Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Fri, 17 May 2019 14:17:27 -0400 Subject: [PATCH 05/25] add action instead of flags for conditional privilege --- .../ManageApiKeyConditionalPrivileges.java | 90 +++++++------------ ...anageApiKeyConditionalPrivilegesTests.java | 22 ++--- 2 files changed, 42 insertions(+), 70 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivileges.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivileges.java index b3d4c15fa91a3..9abde69e053c5 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivileges.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivileges.java @@ -24,7 +24,7 @@ import java.io.IOException; import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -36,6 +36,8 @@ public final class ManageApiKeyConditionalPrivileges implements ConditionalClust 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(CREATE_API_KEY_PATTERN, GET_API_KEY_PATTERN, + INVALIDATE_API_KEY_PATTERN); public static final String WRITEABLE_NAME = "manage-api-key-privileges"; @@ -43,33 +45,27 @@ public final class ManageApiKeyConditionalPrivileges implements ConditionalClust private final Predicate realmsPredicate; private final Set users; private final Predicate usersPredicate; + private final Set actions; private final ClusterPrivilege privilege; private final BiPredicate requestPredicate; interface Fields { ParseField MANAGE = new ParseField("manage"); - - ParseField CREATE = new ParseField("create"); - ParseField GET = new ParseField("get"); - ParseField INVALIDATE = new ParseField("invalidate"); - + ParseField ACTION = new ParseField("action"); ParseField USERS = new ParseField("users"); ParseField REALMS = new ParseField("realms"); } - public ManageApiKeyConditionalPrivileges(boolean createAllowed, boolean getAllowed, boolean invalidateAllowed, Set realms, - Set users) { - final Set patterns = new HashSet<>(); - if (createAllowed) { - patterns.add(CREATE_API_KEY_PATTERN); - } - if (getAllowed) { - patterns.add(GET_API_KEY_PATTERN); - } - if (invalidateAllowed) { - patterns.add(INVALIDATE_API_KEY_PATTERN); + public ManageApiKeyConditionalPrivileges(Set actions, Set realms, Set users) { + this.actions = Collections.unmodifiableSet(actions); + // validate allowed actions + for (String action : actions) { + if (ClusterPrivilege.MANAGE_API_KEY.predicate().test(action) == false) { + throw new IllegalArgumentException("invalid action [ " + action + " ] specified, expected API key privilege actions [ " + + API_KEY_ACTION_PATTERNS + " ]"); + } } - this.privilege = ClusterPrivilege.get(patterns); + this.privilege = ClusterPrivilege.get(actions); this.realms = (realms == null) ? Collections.emptySet() : Set.copyOf(realms); this.realmsPredicate = Automatons.predicate(this.realms); @@ -81,7 +77,7 @@ public ManageApiKeyConditionalPrivileges(boolean createAllowed, boolean getAllow return true; } else if (request instanceof GetApiKeyRequest && privilege.predicate().test(GET_API_KEY_PATTERN)) { final GetApiKeyRequest getApiKeyRequest = (GetApiKeyRequest) request; - if (this.realms.isEmpty() && this.users.isEmpty()) { + if (this.realms.contains("_self") && this.users.contains("_self")) { return checkIfUserIsOwnerOfApiKeys(authentication, getApiKeyRequest.getApiKeyId(), getApiKeyRequest.getUserName(), getApiKeyRequest.getRealmName()); } else { @@ -90,7 +86,7 @@ public ManageApiKeyConditionalPrivileges(boolean createAllowed, boolean getAllow } } else if (request instanceof InvalidateApiKeyRequest && privilege.predicate().test(INVALIDATE_API_KEY_PATTERN)) { final InvalidateApiKeyRequest invalidateApiKeyRequest = (InvalidateApiKeyRequest) request; - if (this.realms.isEmpty() && this.users.isEmpty()) { + if (this.realms.contains("_self") && this.users.contains("_self")) { return checkIfUserIsOwnerOfApiKeys(authentication, invalidateApiKeyRequest.getId(), invalidateApiKeyRequest.getUserName(), invalidateApiKeyRequest.getRealmName()); } else { @@ -130,20 +126,16 @@ public String getWriteableName() { @Override public void writeTo(StreamOutput out) throws IOException { - out.writeBoolean(privilege.predicate().test(CREATE_API_KEY_PATTERN)); - out.writeBoolean(privilege.predicate().test(GET_API_KEY_PATTERN)); - out.writeBoolean(privilege.predicate().test(INVALIDATE_API_KEY_PATTERN)); + out.writeCollection(this.actions, StreamOutput::writeString); out.writeCollection(this.realms, StreamOutput::writeString); out.writeCollection(this.users, StreamOutput::writeString); } public static ManageApiKeyConditionalPrivileges createFrom(StreamInput in) throws IOException { - final boolean allowCreate = in.readBoolean(); - final boolean allowGet = in.readBoolean(); - final boolean allowInvalidate = in.readBoolean(); + final Set actions = in.readSet(StreamInput::readString); final Set realms = in.readSet(StreamInput::readString); final Set users = in.readSet(StreamInput::readString); - return new ManageApiKeyConditionalPrivileges(allowCreate, allowGet, allowInvalidate, realms, users); + return new ManageApiKeyConditionalPrivileges(actions, realms, users); } @Override @@ -183,11 +175,9 @@ public boolean equals(Object o) { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.field(Fields.MANAGE.getPreferredName(), - Map.of(Fields.CREATE.getPreferredName(), privilege.predicate().test(CREATE_API_KEY_PATTERN), Fields.GET.getPreferredName(), - privilege.predicate().test(GET_API_KEY_PATTERN), Fields.INVALIDATE.getPreferredName(), - privilege.predicate().test(INVALIDATE_API_KEY_PATTERN), Fields.REALMS.getPreferredName(), this.realms, - Fields.USERS.getPreferredName(), this.users)); - + Map.of(Fields.ACTION.getPreferredName(), this.actions, + Fields.REALMS.getPreferredName(), this.realms, + Fields.USERS.getPreferredName(), this.users)); return builder; } @@ -196,33 +186,15 @@ public static ManageApiKeyConditionalPrivileges parse(XContentParser parser) thr expectFieldName(parser, Fields.MANAGE); expectedToken(parser.nextToken(), parser, XContentParser.Token.START_OBJECT); - boolean createAllowed = false; - boolean getAllowed = false; - boolean invalidateAllowed = false; - String[] realms = null; - String[] users = null; - while (parser.nextToken() != XContentParser.Token.END_OBJECT) { - expectedToken(parser.currentToken(), parser, XContentParser.Token.FIELD_NAME); - String fieldName = parser.currentName(); - if (Fields.CREATE.match(fieldName, parser.getDeprecationHandler())) { - parser.nextToken(); - createAllowed = parser.booleanValue(); - } else if (Fields.GET.match(fieldName, parser.getDeprecationHandler())) { - parser.nextToken(); - getAllowed = parser.booleanValue(); - } else if (Fields.INVALIDATE.match(fieldName, parser.getDeprecationHandler())) { - parser.nextToken(); - invalidateAllowed = parser.booleanValue(); - } else if (Fields.REALMS.match(fieldName, parser.getDeprecationHandler())) { - expectedToken(parser.nextToken(), parser, XContentParser.Token.START_ARRAY); - realms = XContentUtils.readStringArray(parser, false); - } else if (Fields.USERS.match(fieldName, parser.getDeprecationHandler())) { - expectedToken(parser.nextToken(), parser, XContentParser.Token.START_ARRAY); - users = XContentUtils.readStringArray(parser, false); - } - - } - return new ManageApiKeyConditionalPrivileges(createAllowed, getAllowed, invalidateAllowed, Set.of(realms), Set.of(users)); + expectedToken(parser.nextToken(), parser, XContentParser.Token.FIELD_NAME); + expectFieldName(parser, Fields.ACTION); + expectedToken(parser.nextToken(), parser, XContentParser.Token.START_ARRAY); + String[] actions = XContentUtils.readStringArray(parser, false); + expectedToken(parser.nextToken(), parser, XContentParser.Token.START_ARRAY); + String[] realms = XContentUtils.readStringArray(parser, false); + expectedToken(parser.nextToken(), parser, XContentParser.Token.START_ARRAY); + String[] users = XContentUtils.readStringArray(parser, false); + return new ManageApiKeyConditionalPrivileges(Set.of(actions), Set.of(realms), Set.of(users)); } private static void expectedToken(XContentParser.Token read, XContentParser parser, XContentParser.Token expected) { diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivilegesTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivilegesTests.java index d9fa178a6de69..dc1a3237ae560 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivilegesTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivilegesTests.java @@ -15,6 +15,7 @@ import org.elasticsearch.xpack.core.security.user.User; import org.junit.Before; +import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -161,8 +162,8 @@ public void testManagePrivilegeOwnerOnly() { } public void testManagePrivilegeOwnerAndReadOnly() { - final ManageApiKeyConditionalPrivileges condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.builder().allowGet().forRealms() - .forUsers().build(); + final ManageApiKeyConditionalPrivileges condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.builder().allowGet().forRealms("_self") + .forUsers("_self").build(); boolean accessAllowed = checkAccess(condPrivilege, CREATE_ACTION, new CreateApiKeyRequest(), authentication); assertThat(accessAllowed, is(false)); @@ -216,24 +217,22 @@ private boolean checkAccess(ManageApiKeyConditionalPrivileges privilege, String } public static class ManageApiKeyConditionalPrivilegesBuilder { - private boolean createAllowed; - private boolean getAllowed; - private boolean invalidateAllowed; + private Set actions = new HashSet<>(); private Set realms; private Set users; public ManageApiKeyConditionalPrivilegesBuilder allowCreate() { - this.createAllowed = true; + actions.add(CREATE_ACTION); return this; } public ManageApiKeyConditionalPrivilegesBuilder allowGet() { - this.getAllowed = true; + actions.add(GET_ACTION); return this; } public ManageApiKeyConditionalPrivilegesBuilder allowInvalidate() { - this.invalidateAllowed = true; + actions.add(INVALIDATE_ACTION); return this; } @@ -262,15 +261,16 @@ public static ManageApiKeyConditionalPrivilegesBuilder builder() { } public static ManageApiKeyConditionalPrivileges manageApiKeysUnrestricted() { - return new ManageApiKeyConditionalPrivileges(true, true, true, Set.of("*"), Set.of("*")); + return new ManageApiKeyConditionalPrivileges(Set.of("cluster:admin/xpack/security/api_key/*"), Set.of("*"), Set.of("*")); } public static ManageApiKeyConditionalPrivileges manageApiKeysOnlyForOwner() { - return new ManageApiKeyConditionalPrivileges(true, true, true, null, null); + return new ManageApiKeyConditionalPrivileges(Set.of("cluster:admin/xpack/security/api_key/*"), Set.of("_self"), + Set.of("_self")); } public ManageApiKeyConditionalPrivileges build() { - return new ManageApiKeyConditionalPrivileges(createAllowed, getAllowed, invalidateAllowed, realms, users); + return new ManageApiKeyConditionalPrivileges(actions, realms, users); } } } From a125018bfff11cd4393a0fe345aaa93b8989f36f Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Fri, 17 May 2019 15:23:01 -0400 Subject: [PATCH 06/25] fix line length --- .../privilege/ManageApiKeyConditionalPrivilegesTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivilegesTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivilegesTests.java index dc1a3237ae560..60f86deeed02f 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivilegesTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivilegesTests.java @@ -162,8 +162,8 @@ public void testManagePrivilegeOwnerOnly() { } public void testManagePrivilegeOwnerAndReadOnly() { - final ManageApiKeyConditionalPrivileges condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.builder().allowGet().forRealms("_self") - .forUsers("_self").build(); + final ManageApiKeyConditionalPrivileges condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.builder().allowGet() + .forRealms("_self").forUsers("_self").build(); boolean accessAllowed = checkAccess(condPrivilege, CREATE_ACTION, new CreateApiKeyRequest(), authentication); assertThat(accessAllowed, is(false)); From cccdb969d273dd57224b8ed98f54f820079fad88 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Fri, 17 May 2019 16:17:42 -0400 Subject: [PATCH 07/25] fix parser --- .../ManageApiKeyConditionalPrivileges.java | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivileges.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivileges.java index 9abde69e053c5..ea87d518e7e3c 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivileges.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivileges.java @@ -185,15 +185,24 @@ public static ManageApiKeyConditionalPrivileges parse(XContentParser parser) thr expectedToken(parser.currentToken(), parser, XContentParser.Token.FIELD_NAME); expectFieldName(parser, Fields.MANAGE); expectedToken(parser.nextToken(), parser, XContentParser.Token.START_OBJECT); + String[] actions = Strings.EMPTY_ARRAY; + String[] users = Strings.EMPTY_ARRAY; + String[] realms = Strings.EMPTY_ARRAY; + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + expectedToken(parser.currentToken(), parser, XContentParser.Token.FIELD_NAME); + String fieldName = parser.currentName(); + if (Fields.ACTION.match(fieldName, parser.getDeprecationHandler())) { + expectedToken(parser.nextToken(), parser, XContentParser.Token.START_ARRAY); + actions = XContentUtils.readStringArray(parser, false); + } else if (Fields.USERS.match(fieldName, parser.getDeprecationHandler())) { + expectedToken(parser.nextToken(), parser, XContentParser.Token.START_ARRAY); + realms = XContentUtils.readStringArray(parser, false); + } else if (Fields.REALMS.match(fieldName, parser.getDeprecationHandler())) { + expectedToken(parser.nextToken(), parser, XContentParser.Token.START_ARRAY); + users = XContentUtils.readStringArray(parser, false); + } + } - expectedToken(parser.nextToken(), parser, XContentParser.Token.FIELD_NAME); - expectFieldName(parser, Fields.ACTION); - expectedToken(parser.nextToken(), parser, XContentParser.Token.START_ARRAY); - String[] actions = XContentUtils.readStringArray(parser, false); - expectedToken(parser.nextToken(), parser, XContentParser.Token.START_ARRAY); - String[] realms = XContentUtils.readStringArray(parser, false); - expectedToken(parser.nextToken(), parser, XContentParser.Token.START_ARRAY); - String[] users = XContentUtils.readStringArray(parser, false); return new ManageApiKeyConditionalPrivileges(Set.of(actions), Set.of(realms), Set.of(users)); } From 9062acbb2a045cbfed611f9a4ce9434e65eda578 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Fri, 17 May 2019 20:27:08 -0400 Subject: [PATCH 08/25] fix integ test --- .../xpack/security/authc/ApiKeyIntegTests.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) 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 2d34945c0aba4..e2fd9ef15c179 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 @@ -94,14 +94,16 @@ public String configRoles() { "manage_api_key_role:\n" + " cluster: [\"manage_api_key\"]\n" + "manage_own_api_key_role:\n" + - " global: { \"api_keys\":{\"manage\":{\"get\":true,\"invalidate\":true,\"create\":true," + - "\"users\":[],\"realms\":[]}} }\n" + + " global: { \"api_keys\":{\"manage\":{\"action\": [ \"cluster:admin/xpack/security/api_key/*\" ], " + + "\"users\": [ \"_self\" ], " + + "\"realms\": [ \"_self\" ]}} }\n" + "only_create_api_key_role:\n" + - " global: { \"api_keys\":{\"manage\":{\"get\":false,\"invalidate\":false,\"create\":true," + - "\"users\":[],\"realms\":[]}} }\n" + + " global: { \"api_keys\":{\"manage\":{\"action\": [ \"cluster:admin/xpack/security/api_key/create\" ], " + + "\"users\":[ \"_self\" ], \"realms\":[ \"_self\" ]}} }\n" + "only_create_get_own_api_key_role:\n" + - " global: { \"api_keys\":{\"manage\":{\"get\":true,\"invalidate\":false,\"create\":true," + - "\"users\":[],\"realms\":[]}} }\n" + + " global: { \"api_keys\":{\"manage\":{\"action\": [ \"cluster:admin/xpack/security/api_key/create\", " + + " \"cluster:admin/xpack/security/api_key/get\" ], " + + "\"users\": [ \"_self\" ], \"realms\": [ \"_self\" ]}} }\n" + "no_manage_api_key_role:\n" + " indices:\n" + " - names: '*'\n" + @@ -663,7 +665,7 @@ public void testGetApiKeyAuthorization() throws InterruptedException, ExecutionE assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/get", "user_with_owner_manage_api_key_role"); } - // user_with_only_create_api_key_role should not be allowed to get it's own API key but not any other user's API key + // user_with_only_create_api_key_role should not be allowed to get it's own API key or not any other user's API key { final Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken .basicAuthHeaderValue("user_with_only_create_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); From 06c3c665a983f54e35a7c750f0fed9d8190e78d2 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Wed, 29 May 2019 16:22:20 +1000 Subject: [PATCH 09/25] remove unwanted check and add check that realms and users contain only _self when restricting API keys to owner --- .../ManageApiKeyConditionalPrivileges.java | 22 ++++++++-- ...anageApiKeyConditionalPrivilegesTests.java | 41 +++++++++++++++++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivileges.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivileges.java index ea87d518e7e3c..33f469096d1a8 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivileges.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivileges.java @@ -6,6 +6,7 @@ package org.elasticsearch.xpack.core.security.authz.privilege; +import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.StreamInput; @@ -73,9 +74,9 @@ public ManageApiKeyConditionalPrivileges(Set actions, Set realms this.usersPredicate = Automatons.predicate(this.users); this.requestPredicate = (request, authentication) -> { - if (request instanceof CreateApiKeyRequest && privilege.predicate().test(CREATE_API_KEY_PATTERN)) { + if (request instanceof CreateApiKeyRequest) { return true; - } else if (request instanceof GetApiKeyRequest && privilege.predicate().test(GET_API_KEY_PATTERN)) { + } else if (request instanceof GetApiKeyRequest) { final GetApiKeyRequest getApiKeyRequest = (GetApiKeyRequest) request; if (this.realms.contains("_self") && this.users.contains("_self")) { return checkIfUserIsOwnerOfApiKeys(authentication, getApiKeyRequest.getApiKeyId(), getApiKeyRequest.getUserName(), @@ -84,7 +85,7 @@ public ManageApiKeyConditionalPrivileges(Set actions, Set realms return checkIfAccessAllowed(realms, getApiKeyRequest.getRealmName(), realmsPredicate) && checkIfAccessAllowed(users, getApiKeyRequest.getUserName(), usersPredicate); } - } else if (request instanceof InvalidateApiKeyRequest && privilege.predicate().test(INVALIDATE_API_KEY_PATTERN)) { + } else if (request instanceof InvalidateApiKeyRequest) { final InvalidateApiKeyRequest invalidateApiKeyRequest = (InvalidateApiKeyRequest) request; if (this.realms.contains("_self") && this.users.contains("_self")) { return checkIfUserIsOwnerOfApiKeys(authentication, invalidateApiKeyRequest.getId(), @@ -202,6 +203,21 @@ public static ManageApiKeyConditionalPrivileges parse(XContentParser parser) thr users = XContentUtils.readStringArray(parser, false); } } + final Set realmNames = Set.of(realms); + final Set userNames = Set.of(users); + if (realmNames.contains("_self") && userNames.contains("_self") == false + || userNames.contains("_self") && realmNames.contains("_self") == false) { + throw new ElasticsearchParseException( + "could not parse, both fields [{}], [{}] must contain only `_self` when restricting access of API keys to owner", + Fields.USERS.getPreferredName(), Fields.REALMS.getPreferredName()); + } + if (realmNames.contains("_self") && userNames.contains("_self")) { + if (realmNames.size() > 1 || userNames.size() > 1) { + throw new ElasticsearchParseException( + "could not parse, both fields [{}], [{}] must contain only `_self` when restricting access of API keys to owner", + Fields.USERS.getPreferredName(), Fields.REALMS.getPreferredName()); + } + } return new ManageApiKeyConditionalPrivileges(Set.of(actions), Set.of(realms), Set.of(users)); } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivilegesTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivilegesTests.java index 60f86deeed02f..12bfe8a585a3b 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivilegesTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivilegesTests.java @@ -6,15 +6,20 @@ package org.elasticsearch.xpack.core.security.authz.privilege; +import org.elasticsearch.ElasticsearchParseException; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; 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.authz.privilege.ManageApiKeyConditionalPrivileges.Fields; import org.elasticsearch.xpack.core.security.user.User; import org.junit.Before; +import java.io.IOException; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -211,6 +216,42 @@ public void testManagePrivilegeOwnerAndReadOnly() { assertThat(accessAllowed, is(false)); } + public void testParsingThrowsErrorWhenUsersAndRealmsFieldValuesAreInvalid() throws IOException { + { + String json = "{" + + "\"manage\": {" + + " \"action\": [ \"cluster:admin/xpack/security/api_key/create\" ], " + + " \"realms\": [ \"_self\" ], " + + " \"users\": [ \"some-user\" ] " + + "}" + + "}"; + final XContentParser parser = createParser(XContentType.JSON.xContent(), json); + parser.nextToken(); + parser.nextToken(); + ElasticsearchParseException epe = expectThrows(ElasticsearchParseException.class, + () -> ManageApiKeyConditionalPrivileges.parse(parser)); + assertThat(epe.getMessage(), is("could not parse, both fields [" + Fields.USERS.getPreferredName() + "], [" + + Fields.REALMS.getPreferredName() + "] must contain only `_self` when restricting access of API keys to owner")); + } + + { + String json = "{" + + "\"manage\": {" + + " \"action\": [ \"cluster:admin/xpack/security/api_key/create\" ], " + + " \"realms\": [ \"_self\" ], " + + " \"users\": [ \"_self\", \"some-user\" ] " + + "}" + + "}"; + final XContentParser parser = createParser(XContentType.JSON.xContent(), json); + parser.nextToken(); + parser.nextToken(); + ElasticsearchParseException epe = expectThrows(ElasticsearchParseException.class, + () -> ManageApiKeyConditionalPrivileges.parse(parser)); + assertThat(epe.getMessage(), is("could not parse, both fields [" + Fields.USERS.getPreferredName() + "], [" + + Fields.REALMS.getPreferredName() + "] must contain only `_self` when restricting access of API keys to owner")); + } + } + private boolean checkAccess(ManageApiKeyConditionalPrivileges privilege, String action, TransportRequest request, Authentication authentication) { return privilege.getPrivilege().predicate().test(action) && privilege.getRequestPredicate().test(request, authentication); From be7a093d850be553953b273d095858ed5e7b19b2 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Mon, 3 Jun 2019 16:32:26 +1000 Subject: [PATCH 10/25] provide default conditional cluster privileges for API keys --- .../xpack/core/XPackClientPlugin.java | 8 +- .../security/action/role/PutRoleRequest.java | 8 +- .../user/GetUserPrivilegesResponse.java | 8 +- .../core/security/authz/RoleDescriptor.java | 18 +- .../authz/permission/ClusterPermission.java | 2 +- .../core/security/authz/permission/Role.java | 6 +- .../authz/privilege/ClusterPrivilege.java | 85 ++++++- .../ConditionalClusterPrivilege.java | 36 +-- .../ConditionalClusterPrivileges.java | 68 ++--- ...nageApiKeyConditionalClusterPrivilege.java | 144 +++++++++++ .../ManageApiKeyConditionalPrivileges.java | 240 ------------------ .../ManageApplicationPrivileges.java | 6 +- ...RenderableConditionalClusterPrivilege.java | 49 ++++ .../authz/store/ReservedRolesStore.java | 4 +- .../user/GetUserPrivilegesResponseTests.java | 10 +- .../ConditionalClusterPrivilegesTests.java | 17 +- ...iKeyConditionalClusterPrivilegeTests.java} | 71 ++---- .../authz/privilege/PrivilegeTests.java | 16 +- .../xpack/security/authz/RBACEngine.java | 10 +- .../user/RestGetUserPrivilegesAction.java | 4 +- .../security/authc/ApiKeyIntegTests.java | 58 +---- .../authz/AuthorizationServiceTests.java | 10 +- .../security/authz/RoleDescriptorTests.java | 12 +- .../authz/store/CompositeRolesStoreTests.java | 10 +- .../RestGetUserPrivilegesActionTests.java | 4 +- 25 files changed, 388 insertions(+), 516 deletions(-) create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalClusterPrivilege.java delete mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivileges.java create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/RenderableConditionalClusterPrivilege.java rename x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/{ManageApiKeyConditionalPrivilegesTests.java => ManageApiKeyConditionalClusterPrivilegeTests.java} (77%) 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 70bcccad482cc..4e48e67503a6a 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,9 +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.ManageApiKeyConditionalPrivileges; import org.elasticsearch.xpack.core.security.authz.privilege.ManageApplicationPrivileges; +import org.elasticsearch.xpack.core.security.authz.privilege.RenderableConditionalClusterPrivilege; import org.elasticsearch.xpack.core.sql.SqlFeatureSetUsage; import org.elasticsearch.xpack.core.ssl.action.GetCertificateInfoAction; import org.elasticsearch.xpack.core.upgrade.actions.IndexUpgradeAction; @@ -386,12 +385,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, + new NamedWriteableRegistry.Entry(RenderableConditionalClusterPrivilege.class, ManageApplicationPrivileges.WRITEABLE_NAME, ManageApplicationPrivileges::createFrom), - new NamedWriteableRegistry.Entry(ConditionalClusterPrivilege.class, - ManageApiKeyConditionalPrivileges.WRITEABLE_NAME, - ManageApiKeyConditionalPrivileges::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/role/PutRoleRequest.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/role/PutRoleRequest.java index e19d9cebb64c1..f379d608726b6 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,7 +15,7 @@ import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.RenderableConditionalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges; import org.elasticsearch.xpack.core.security.support.MetadataUtils; @@ -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(RenderableConditionalClusterPrivilege... conditionalClusterPrivileges) { this.conditionalClusterPrivileges = conditionalClusterPrivileges; } @@ -145,7 +145,7 @@ public List applicationPrivileges( return Collections.unmodifiableList(applicationPrivileges); } - public ConditionalClusterPrivilege[] conditionalClusterPrivileges() { + public RenderableConditionalClusterPrivilege[] conditionalClusterPrivileges() { return conditionalClusterPrivileges; } 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..c75b648d8376b 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,7 +15,7 @@ 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.RenderableConditionalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges; import java.io.IOException; @@ -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; } 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..08be89299c5e0 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,7 +23,7 @@ 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.RenderableConditionalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges; 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 RenderableConditionalClusterPrivilege[] 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)} + * RenderableConditionalClusterPrivilege[], 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)} + * RenderableConditionalClusterPrivilege[], String[], Map, Map)} */ @Deprecated public RoleDescriptor(String name, @@ -93,7 +93,7 @@ public RoleDescriptor(String name, @Nullable String[] clusterPrivileges, @Nullable IndicesPrivileges[] indicesPrivileges, @Nullable ApplicationResourcePrivileges[] applicationPrivileges, - @Nullable ConditionalClusterPrivilege[] conditionalClusterPrivileges, + @Nullable RenderableConditionalClusterPrivilege[] conditionalClusterPrivileges, @Nullable String[] runAs, @Nullable Map metadata, @Nullable Map transientMetadata) { @@ -133,7 +133,7 @@ public String[] getClusterPrivileges() { return this.clusterPrivileges; } - public ConditionalClusterPrivilege[] getConditionalClusterPrivileges() { + public RenderableConditionalClusterPrivilege[] getConditionalClusterPrivileges() { return this.conditionalClusterPrivileges; } @@ -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; @@ -329,8 +329,8 @@ public static RoleDescriptor parse(String name, XContentParser parser, boolean a } } return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, applicationPrivileges, - conditionalClusterPrivileges.toArray(new ConditionalClusterPrivilege[conditionalClusterPrivileges.size()]), runAsUsers, - metadata, null); + conditionalClusterPrivileges.toArray(new RenderableConditionalClusterPrivilege[conditionalClusterPrivileges.size()]), + runAsUsers, metadata, null); } private static String[] readStringArray(String roleName, XContentParser parser, boolean allowNull) throws IOException { 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 f05d3ea1577eb..532befd6dc7ae 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 @@ -105,7 +105,7 @@ private static ClusterPrivilege buildPrivilege(Collection chi .map(ClusterPrivilege::name) .flatMap(Set::stream) .collect(Collectors.toSet()); - return ClusterPrivilege.get(names); + return ClusterPrivilege.get(names).v1(); } @Override 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 04776df28b132..1e765d35978fa 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 @@ -214,7 +214,11 @@ private Builder(RoleDescriptor rd, @Nullable FieldPermissionsCache fieldPermissi public Builder cluster(Set privilegeNames, Iterable conditionalClusterPrivileges) { List clusterPermissions = new ArrayList<>(); if (privilegeNames.isEmpty() == false) { - clusterPermissions.add(new ClusterPermission.SimpleClusterPermission(ClusterPrivilege.get(privilegeNames))); + Tuple> privileges = ClusterPrivilege.get(privilegeNames); + clusterPermissions.add(new ClusterPermission.SimpleClusterPermission(privileges.v1())); + for (ConditionalClusterPrivilege ccp : privileges.v2()) { + clusterPermissions.add(new ClusterPermission.ConditionalClusterPermission(ccp)); + } } for (ConditionalClusterPrivilege ccp : conditionalClusterPrivileges) { clusterPermissions.add(new ClusterPermission.ConditionalClusterPermission(ccp)); 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 b37cc8f597107..bb97167919412 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 @@ -12,6 +12,7 @@ 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.common.collect.Tuple; 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; @@ -19,13 +20,16 @@ import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesAction; import org.elasticsearch.xpack.core.security.support.Automatons; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; +import java.util.stream.Collectors; import static java.util.Map.entry; import static org.elasticsearch.xpack.core.security.support.Automatons.minusAndMinimize; @@ -97,6 +101,59 @@ public final class ClusterPrivilege extends Privilege { public static final Predicate ACTION_MATCHER = ClusterPrivilege.ALL.predicate(); + private static final ConcurrentHashMap, Tuple>> CACHE = + new ConcurrentHashMap<>(); + + /* Conditional Cluster Privileges */ + public enum DefaultConditionalClusterPrivilege { + MANAGE_OWN_API_KEY("manage_own_api_key", new ManageApiKeyConditionalClusterPrivilege( + Set.of("cluster:admin/xpack/security/api_key/*"), + Set.of("_self"), + Set.of("_self"))), + CREATE_GET_OWN_API_KEY("create_get_own_api_key", new ManageApiKeyConditionalClusterPrivilege( + Set.of("cluster:admin/xpack/security/api_key/create", "cluster:admin/xpack/security/api_key/get"), + Set.of("_self"), + Set.of("_self"))), + GET_API_KEY("get_api_key", new ManageApiKeyConditionalClusterPrivilege( + Set.of("cluster:admin/xpack/security/api_key/get"), + Set.of("*"), + Set.of("*"))); + + private final ConditionalClusterPrivilege conditionalClusterPrivilege; + private final String privilegeName; + + DefaultConditionalClusterPrivilege(String privilegeName, ConditionalClusterPrivilege conditionalClusterPrivilege) { + this.privilegeName = privilegeName; + this.conditionalClusterPrivilege = conditionalClusterPrivilege; + } + + public ConditionalClusterPrivilege conditionalClusterPrivilege() { + return conditionalClusterPrivilege; + } + + public static DefaultConditionalClusterPrivilege fromString(String privilegeName) { + Optional value = Arrays.stream(values()) + .filter(dccp -> dccp.privilegeName.equals(privilegeName)).findAny(); + if (value.isEmpty()) { + return null; + } + return value.get(); + } + + public static String name(ConditionalClusterPrivilege ccp) { + Optional value = Arrays.stream(values()) + .filter(dccp -> dccp.conditionalClusterPrivilege.equals(ccp)).findAny(); + if (value.isEmpty()) { + return null; + } + return value.get().privilegeName; + } + + public static Set names() { + return Arrays.stream(values()).map(dccp -> dccp.privilegeName).collect(Collectors.toSet()); + } + } + private static final Map VALUES = Map.ofEntries( entry("none", NONE), entry("all", ALL), @@ -125,8 +182,6 @@ public final class ClusterPrivilege extends Privilege { entry("manage_ilm", MANAGE_ILM), entry("read_ilm", READ_ILM)); - private static final ConcurrentHashMap, ClusterPrivilege> CACHE = new ConcurrentHashMap<>(); - private ClusterPrivilege(String name, String... patterns) { super(name, patterns); } @@ -139,14 +194,14 @@ private ClusterPrivilege(Set name, Automaton automaton) { super(name, automaton); } - public static ClusterPrivilege get(Set name) { + public static Tuple> get(final Set name) { if (name == null || name.isEmpty()) { - return NONE; + return new Tuple>(NONE, Collections.emptySet()); } return CACHE.computeIfAbsent(name, ClusterPrivilege::resolve); } - private static ClusterPrivilege resolve(Set name) { + private static Tuple> resolve(Set name) { final int size = name.size(); if (size == 0) { throw new IllegalArgumentException("empty set should not be used"); @@ -154,6 +209,7 @@ private static ClusterPrivilege resolve(Set name) { Set actions = new HashSet<>(); Set automata = new HashSet<>(); + Set conditionalClusterPrivileges = new HashSet<>(); for (String part : name) { part = part.toLowerCase(Locale.ROOT); if (ACTION_MATCHER.test(part)) { @@ -161,14 +217,20 @@ private static ClusterPrivilege resolve(Set name) { } else { ClusterPrivilege privilege = VALUES.get(part); if (privilege != null && size == 1) { - return privilege; + return new Tuple>(privilege, Collections.emptySet()); } 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"); + DefaultConditionalClusterPrivilege dccp = DefaultConditionalClusterPrivilege.fromString(part); + if (dccp != null) { + conditionalClusterPrivileges.add(dccp.conditionalClusterPrivilege); + } else { + throw new IllegalArgumentException("unknown cluster privilege [" + name + "]. a privilege must be either " + + "one of the predefined fixed cluster privileges [" + + Strings.collectionToCommaDelimitedString(VALUES.entrySet()) + "], fixed conditional cluster privileges [" + + Strings.collectionToCommaDelimitedString(DefaultConditionalClusterPrivilege.names()) + + "] or a pattern over one of the available cluster actions"); + } } } } @@ -176,6 +238,7 @@ private static ClusterPrivilege resolve(Set name) { if (actions.isEmpty() == false) { automata.add(patterns(actions)); } - return new ClusterPrivilege(name, Automatons.unionAndMinimize(automata)); + return new Tuple>( + new ClusterPrivilege(name, Automatons.unionAndMinimize(automata)), conditionalClusterPrivileges); } } 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 3d66aa19d2fa9..bbac369f02df8 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,15 +6,9 @@ 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.elasticsearch.xpack.core.security.authc.Authentication; -import java.io.IOException; -import java.util.Collection; import java.util.function.BiPredicate; import java.util.function.Predicate; @@ -23,12 +17,7 @@ * with a {@link Predicate} for a {@link TransportRequest} (that determines which requests may be executed). * The a given execution of an action is considered to be permitted if both the action and the request are permitted. */ -public interface ConditionalClusterPrivilege extends NamedWriteable, ToXContentFragment { - - /** - * The category under which this privilege should be rendered when output as XContent. - */ - Category getCategory(); +public interface ConditionalClusterPrivilege { /** * The action-level privilege that is required by this conditional privilege. @@ -41,27 +30,4 @@ public interface ConditionalClusterPrivilege extends NamedWriteable, ToXContentF */ BiPredicate 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")), - API_KEYS(new ParseField("api_keys")); - - 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/ConditionalClusterPrivileges.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ConditionalClusterPrivileges.java index 853db47d64989..e86920470a3b3 100644 --- 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 @@ -15,7 +15,7 @@ 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.ConditionalClusterPrivilege.Category; +import org.elasticsearch.xpack.core.security.authz.privilege.RenderableConditionalClusterPrivilege.Category; import java.io.IOException; import java.util.ArrayList; @@ -24,55 +24,49 @@ import java.util.List; /** - * Static utility class for working with {@link ConditionalClusterPrivilege} instances + * Static utility class for working with {@link RenderableConditionalClusterPrivilege} instances */ public final class ConditionalClusterPrivileges { - public static final ConditionalClusterPrivilege[] EMPTY_ARRAY = new ConditionalClusterPrivilege[0]; + public static final RenderableConditionalClusterPrivilege[] EMPTY_ARRAY = new RenderableConditionalClusterPrivilege[0]; - public static final Writeable.Reader READER = - in1 -> in1.readNamedWriteable(ConditionalClusterPrivilege.class); - public static final Writeable.Writer WRITER = + public static final Writeable.Reader READER = + in1 -> in1.readNamedWriteable(RenderableConditionalClusterPrivilege.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} + * Utility method to read an array of {@link RenderableConditionalClusterPrivilege} objects from a {@link StreamInput} */ - public static ConditionalClusterPrivilege[] readArray(StreamInput in) throws IOException { - return in.readArray(READER, ConditionalClusterPrivilege[]::new); + public static RenderableConditionalClusterPrivilege[] readArray(StreamInput in) throws IOException { + return in.readArray(READER, RenderableConditionalClusterPrivilege[]::new); } /** - * Utility method to write an array of {@link ConditionalClusterPrivilege} objects to a {@link StreamOutput} + * Utility method to write an array of {@link RenderableConditionalClusterPrivilege} objects to a {@link StreamOutput} */ - public static void writeArray(StreamOutput out, ConditionalClusterPrivilege[] privileges) throws IOException { + public static void writeArray(StreamOutput out, RenderableConditionalClusterPrivilege[] 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} + * The privileges are grouped according to their {@link RenderableConditionalClusterPrivilege#getCategory() categories} */ public static XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params, - Collection privileges) throws IOException { + Collection privileges) throws IOException { builder.startObject(); for (Category category : Category.values()) { - boolean startObject = false; - for (ConditionalClusterPrivilege privilege : privileges) { + builder.startObject(category.field.getPreferredName()); + for (RenderableConditionalClusterPrivilege privilege : privileges) { if (category == privilege.getCategory()) { - if (startObject == false) { - builder.startObject(category.field.getPreferredName()); - startObject = true; - } privilege.toXContent(builder, params); } } - if (startObject) { - builder.endObject(); - } + builder.endObject(); } return builder.endObject(); } @@ -81,32 +75,20 @@ public static XContentBuilder toXContent(XContentBuilder builder, ToXContent.Par * 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<>(); + 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); - String fieldName = parser.currentName(); - if (Category.APPLICATION.field.match(fieldName, parser.getDeprecationHandler())) { - 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); - } else if (Category.API_KEYS.field.match(fieldName, parser.getDeprecationHandler())) { - expectedToken(parser.nextToken(), parser, XContentParser.Token.START_OBJECT); - expectedToken(parser.nextToken(), parser, XContentParser.Token.FIELD_NAME); - - expectFieldName(parser, ManageApiKeyConditionalPrivileges.Fields.MANAGE); - privileges.add(ManageApiKeyConditionalPrivileges.parse(parser)); - expectedToken(parser.nextToken(), parser, XContentParser.Token.END_OBJECT); - } else { - throw new XContentParseException(parser.getTokenLocation(), "failed to parse privilege. expected one from " - + Arrays.asList(Category.values()) + " but found [" + fieldName + "] instead"); - } + 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; 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..3802791da4d70 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalClusterPrivilege.java @@ -0,0 +1,144 @@ +/* + * 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.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.BiPredicate; +import java.util.function.Predicate; + +/** + * Conditional cluster privilege for managing API keys + */ +public final class ManageApiKeyConditionalClusterPrivilege implements ConditionalClusterPrivilege { + + 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(CREATE_API_KEY_PATTERN, GET_API_KEY_PATTERN, + INVALIDATE_API_KEY_PATTERN); + + private final Set realms; + private final Predicate realmsPredicate; + private final Set users; + private final Predicate usersPredicate; + private final ClusterPrivilege privilege; + private final BiPredicate requestPredicate; + + public ManageApiKeyConditionalClusterPrivilege(Set actions, Set realms, Set users) { + // validate allowed actions + for (String action : actions) { + if (ClusterPrivilege.MANAGE_API_KEY.predicate().test(action) == false) { + throw new IllegalArgumentException("invalid action [ " + action + " ] specified, expected API key privilege actions [ " + + API_KEY_ACTION_PATTERNS + " ]"); + } + } + this.privilege = ClusterPrivilege.get(actions).v1(); + + this.realms = (realms == null) ? Collections.emptySet() : Set.copyOf(realms); + this.realmsPredicate = Automatons.predicate(this.realms); + this.users = (users == null) ? Collections.emptySet() : Set.copyOf(users); + this.usersPredicate = Automatons.predicate(this.users); + + if (this.realms.contains("_self") && this.users.contains("_self") == false + || this.users.contains("_self") && this.realms.contains("_self") == false) { + throw new IllegalArgumentException( + "both realms and users must contain only `_self` when restricting access of API keys to owner"); + } + if (this.realms.contains("_self") && this.users.contains("_self")) { + if (this.realms.size() > 1 || this.users.size() > 1) { + throw new IllegalArgumentException( + "both realms and users must contain only `_self` when restricting access of API keys to owner"); + } + } + + this.requestPredicate = (request, authentication) -> { + if (request instanceof CreateApiKeyRequest) { + return true; + } else if (request instanceof GetApiKeyRequest) { + final GetApiKeyRequest getApiKeyRequest = (GetApiKeyRequest) request; + if (this.realms.contains("_self") && this.users.contains("_self")) { + return checkIfUserIsOwnerOfApiKeys(authentication, getApiKeyRequest.getApiKeyId(), getApiKeyRequest.getUserName(), + getApiKeyRequest.getRealmName()); + } else { + return checkIfAccessAllowed(realms, getApiKeyRequest.getRealmName(), realmsPredicate) + && checkIfAccessAllowed(users, getApiKeyRequest.getUserName(), usersPredicate); + } + } else if (request instanceof InvalidateApiKeyRequest) { + final InvalidateApiKeyRequest invalidateApiKeyRequest = (InvalidateApiKeyRequest) request; + if (this.realms.contains("_self") && this.users.contains("_self")) { + return checkIfUserIsOwnerOfApiKeys(authentication, invalidateApiKeyRequest.getId(), + invalidateApiKeyRequest.getUserName(), invalidateApiKeyRequest.getRealmName()); + } else { + return checkIfAccessAllowed(realms, invalidateApiKeyRequest.getRealmName(), realmsPredicate) + && checkIfAccessAllowed(users, invalidateApiKeyRequest.getUserName(), usersPredicate); + } + } + return false; + }; + } + + 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; + } + + private static boolean checkIfAccessAllowed(Set names, String requestName, Predicate predicate) { + return (Strings.hasText(requestName) == false) ? names.contains("*") : predicate.test(requestName); + } + + @Override + public ClusterPrivilege getPrivilege() { + return privilege; + } + + @Override + public BiPredicate getRequestPredicate() { + return requestPredicate; + } + + @Override + public int hashCode() { + return Objects.hash(privilege, users, realms); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final ManageApiKeyConditionalClusterPrivilege that = (ManageApiKeyConditionalClusterPrivilege) o; + return Objects.equals(this.privilege, that.privilege) && Objects.equals(this.realms, that.realms) + && Objects.equals(this.users, that.users); + } + +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivileges.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivileges.java deleted file mode 100644 index 33f469096d1a8..0000000000000 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivileges.java +++ /dev/null @@ -1,240 +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.ElasticsearchParseException; -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.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 org.elasticsearch.xpack.core.security.xcontent.XContentUtils; - -import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.function.BiPredicate; -import java.util.function.Predicate; - -public final class ManageApiKeyConditionalPrivileges implements ConditionalClusterPrivilege { - - 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(CREATE_API_KEY_PATTERN, GET_API_KEY_PATTERN, - INVALIDATE_API_KEY_PATTERN); - - public static final String WRITEABLE_NAME = "manage-api-key-privileges"; - - private final Set realms; - private final Predicate realmsPredicate; - private final Set users; - private final Predicate usersPredicate; - private final Set actions; - private final ClusterPrivilege privilege; - private final BiPredicate requestPredicate; - - interface Fields { - ParseField MANAGE = new ParseField("manage"); - ParseField ACTION = new ParseField("action"); - ParseField USERS = new ParseField("users"); - ParseField REALMS = new ParseField("realms"); - } - - public ManageApiKeyConditionalPrivileges(Set actions, Set realms, Set users) { - this.actions = Collections.unmodifiableSet(actions); - // validate allowed actions - for (String action : actions) { - if (ClusterPrivilege.MANAGE_API_KEY.predicate().test(action) == false) { - throw new IllegalArgumentException("invalid action [ " + action + " ] specified, expected API key privilege actions [ " - + API_KEY_ACTION_PATTERNS + " ]"); - } - } - this.privilege = ClusterPrivilege.get(actions); - - this.realms = (realms == null) ? Collections.emptySet() : Set.copyOf(realms); - this.realmsPredicate = Automatons.predicate(this.realms); - this.users = (users == null) ? Collections.emptySet() : Set.copyOf(users); - this.usersPredicate = Automatons.predicate(this.users); - - this.requestPredicate = (request, authentication) -> { - if (request instanceof CreateApiKeyRequest) { - return true; - } else if (request instanceof GetApiKeyRequest) { - final GetApiKeyRequest getApiKeyRequest = (GetApiKeyRequest) request; - if (this.realms.contains("_self") && this.users.contains("_self")) { - return checkIfUserIsOwnerOfApiKeys(authentication, getApiKeyRequest.getApiKeyId(), getApiKeyRequest.getUserName(), - getApiKeyRequest.getRealmName()); - } else { - return checkIfAccessAllowed(realms, getApiKeyRequest.getRealmName(), realmsPredicate) - && checkIfAccessAllowed(users, getApiKeyRequest.getUserName(), usersPredicate); - } - } else if (request instanceof InvalidateApiKeyRequest) { - final InvalidateApiKeyRequest invalidateApiKeyRequest = (InvalidateApiKeyRequest) request; - if (this.realms.contains("_self") && this.users.contains("_self")) { - return checkIfUserIsOwnerOfApiKeys(authentication, invalidateApiKeyRequest.getId(), - invalidateApiKeyRequest.getUserName(), invalidateApiKeyRequest.getRealmName()); - } else { - return checkIfAccessAllowed(realms, invalidateApiKeyRequest.getRealmName(), realmsPredicate) - && checkIfAccessAllowed(users, invalidateApiKeyRequest.getUserName(), usersPredicate); - } - } - return false; - }; - } - - 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; - } - - private static boolean checkIfAccessAllowed(Set names, String requestName, Predicate predicate) { - return (Strings.hasText(requestName) == false) ? names.contains("*") : predicate.test(requestName); - } - - @Override - public String getWriteableName() { - return WRITEABLE_NAME; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeCollection(this.actions, StreamOutput::writeString); - out.writeCollection(this.realms, StreamOutput::writeString); - out.writeCollection(this.users, StreamOutput::writeString); - } - - public static ManageApiKeyConditionalPrivileges createFrom(StreamInput in) throws IOException { - final Set actions = in.readSet(StreamInput::readString); - final Set realms = in.readSet(StreamInput::readString); - final Set users = in.readSet(StreamInput::readString); - return new ManageApiKeyConditionalPrivileges(actions, realms, users); - } - - @Override - public Category getCategory() { - return Category.API_KEYS; - } - - @Override - public ClusterPrivilege getPrivilege() { - return privilege; - } - - @Override - public BiPredicate getRequestPredicate() { - return requestPredicate; - } - - @Override - public int hashCode() { - return Objects.hash(privilege, users, realms); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - final ManageApiKeyConditionalPrivileges that = (ManageApiKeyConditionalPrivileges) o; - return Objects.equals(this.privilege, that.privilege) && Objects.equals(this.realms, that.realms) - && Objects.equals(this.users, that.users); - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - - builder.field(Fields.MANAGE.getPreferredName(), - Map.of(Fields.ACTION.getPreferredName(), this.actions, - Fields.REALMS.getPreferredName(), this.realms, - Fields.USERS.getPreferredName(), this.users)); - return builder; - } - - public static ManageApiKeyConditionalPrivileges 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); - String[] actions = Strings.EMPTY_ARRAY; - String[] users = Strings.EMPTY_ARRAY; - String[] realms = Strings.EMPTY_ARRAY; - while (parser.nextToken() != XContentParser.Token.END_OBJECT) { - expectedToken(parser.currentToken(), parser, XContentParser.Token.FIELD_NAME); - String fieldName = parser.currentName(); - if (Fields.ACTION.match(fieldName, parser.getDeprecationHandler())) { - expectedToken(parser.nextToken(), parser, XContentParser.Token.START_ARRAY); - actions = XContentUtils.readStringArray(parser, false); - } else if (Fields.USERS.match(fieldName, parser.getDeprecationHandler())) { - expectedToken(parser.nextToken(), parser, XContentParser.Token.START_ARRAY); - realms = XContentUtils.readStringArray(parser, false); - } else if (Fields.REALMS.match(fieldName, parser.getDeprecationHandler())) { - expectedToken(parser.nextToken(), parser, XContentParser.Token.START_ARRAY); - users = XContentUtils.readStringArray(parser, false); - } - } - final Set realmNames = Set.of(realms); - final Set userNames = Set.of(users); - if (realmNames.contains("_self") && userNames.contains("_self") == false - || userNames.contains("_self") && realmNames.contains("_self") == false) { - throw new ElasticsearchParseException( - "could not parse, both fields [{}], [{}] must contain only `_self` when restricting access of API keys to owner", - Fields.USERS.getPreferredName(), Fields.REALMS.getPreferredName()); - } - if (realmNames.contains("_self") && userNames.contains("_self")) { - if (realmNames.size() > 1 || userNames.size() > 1) { - throw new ElasticsearchParseException( - "could not parse, both fields [{}], [{}] must contain only `_self` when restricting access of API keys to owner", - Fields.USERS.getPreferredName(), Fields.REALMS.getPreferredName()); - } - } - - return new ManageApiKeyConditionalPrivileges(Set.of(actions), Set.of(realms), Set.of(users)); - } - - 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/ManageApplicationPrivileges.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApplicationPrivileges.java index 597e840f6c463..f1c21f3fbd5e6 100644 --- 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 @@ -29,15 +29,15 @@ import java.util.function.Predicate; /** - * The {@code ManageApplicationPrivileges} privilege is a {@link ConditionalClusterPrivilege} that grants the + * The {@code ManageApplicationPrivileges} privilege is a {@link RenderableConditionalClusterPrivilege} 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 implements ConditionalClusterPrivilege { +public final class ManageApplicationPrivileges implements RenderableConditionalClusterPrivilege { private static final ClusterPrivilege PRIVILEGE = ClusterPrivilege.get( Collections.singleton("cluster:admin/xpack/security/privilege/*") - ); + ).v1(); public static final String WRITEABLE_NAME = "manage-application-privileges"; private final Set applicationNames; diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/RenderableConditionalClusterPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/RenderableConditionalClusterPrivilege.java new file mode 100644 index 0000000000000..055186d76ad2b --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/RenderableConditionalClusterPrivilege.java @@ -0,0 +1,49 @@ +/* + * 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.io.stream.NamedWriteable; +import org.elasticsearch.common.xcontent.ToXContentFragment; +import org.elasticsearch.common.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.Collection; + +/** + * Conditional Cluster privileges that can be serialized / rendered as `XContent` + */ +public interface RenderableConditionalClusterPrivilege extends NamedWriteable, ToXContentFragment, ConditionalClusterPrivilege { + + /** + * The category under which this privilege should be rendered when output as XContent. + */ + Category getCategory(); + + /** + * 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; + } + } +} 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 4569f7430418d..b2c4c8ce193b0 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,7 +11,7 @@ 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.RenderableConditionalClusterPrivilege; 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; @@ -123,7 +123,7 @@ private static Map initializeReservedRoles() { .indices(".code-*", ".code_internal-*").privileges("all").build(), }, null, - new ConditionalClusterPrivilege[] { new ManageApplicationPrivileges(Collections.singleton("kibana-*")) }, + new RenderableConditionalClusterPrivilege[] { 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/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 2a3f47def7bfd..1182164649aec 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,7 +21,7 @@ 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.RenderableConditionalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.ManageApplicationPrivileges; import java.io.IOException; @@ -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,8 @@ 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, RenderableConditionalClusterPrivilege[]::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/authz/privilege/ConditionalClusterPrivilegesTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ConditionalClusterPrivilegesTests.java index cba49860c50a8..39d718101b3d4 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/ConditionalClusterPrivilegesTests.java @@ -30,12 +30,12 @@ public class ConditionalClusterPrivilegesTests extends ESTestCase { public void testSerialization() throws Exception { - final ConditionalClusterPrivilege[] original = buildSecurityPrivileges(); + final RenderableConditionalClusterPrivilege[] original = buildSecurityPrivileges(); try (BytesStreamOutput out = new BytesStreamOutput()) { ConditionalClusterPrivileges.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 RenderableConditionalClusterPrivilege[] copy = ConditionalClusterPrivileges.readArray(in); assertThat(copy, equalTo(original)); assertThat(original, equalTo(copy)); } @@ -47,28 +47,27 @@ public void testGenerateAndParseXContent() throws Exception { try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { final XContentBuilder builder = new XContentBuilder(xContent, out); - final List original = Arrays.asList(buildSecurityPrivileges()); + final List original = Arrays.asList(buildSecurityPrivileges()); ConditionalClusterPrivileges.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 = ConditionalClusterPrivileges.parse(parser); assertThat(clone, equalTo(original)); assertThat(original, equalTo(clone)); } } } - private ConditionalClusterPrivilege[] buildSecurityPrivileges() { + private RenderableConditionalClusterPrivilege[] buildSecurityPrivileges() { return buildSecurityPrivileges(randomIntBetween(4, 7)); } - private ConditionalClusterPrivilege[] buildSecurityPrivileges(int applicationNameLength) { - return new ConditionalClusterPrivilege[] { - ManageApplicationPrivilegesTests.buildPrivileges(applicationNameLength), - ManageApiKeyConditionalPrivilegesTests.ManageApiKeyConditionalPrivilegesBuilder.manageApiKeysOnlyForOwner() + private RenderableConditionalClusterPrivilege[] buildSecurityPrivileges(int applicationNameLength) { + return new RenderableConditionalClusterPrivilege[] { + ManageApplicationPrivilegesTests.buildPrivileges(applicationNameLength) }; } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivilegesTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalClusterPrivilegeTests.java similarity index 77% rename from x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivilegesTests.java rename to x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalClusterPrivilegeTests.java index 12bfe8a585a3b..013d8a25eaed2 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalPrivilegesTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalClusterPrivilegeTests.java @@ -6,20 +6,15 @@ package org.elasticsearch.xpack.core.security.authz.privilege; -import org.elasticsearch.ElasticsearchParseException; -import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.common.xcontent.XContentType; 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.authz.privilege.ManageApiKeyConditionalPrivileges.Fields; import org.elasticsearch.xpack.core.security.user.User; import org.junit.Before; -import java.io.IOException; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -28,7 +23,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class ManageApiKeyConditionalPrivilegesTests extends ESTestCase { +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"; @@ -47,7 +42,7 @@ public void setup() { } public void testManageAllPrivilege() { - final ManageApiKeyConditionalPrivileges condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.manageApiKeysUnrestricted(); + final ManageApiKeyConditionalClusterPrivilege condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.manageApiKeysUnrestricted(); boolean accessAllowed = checkAccess(condPrivilege, CREATE_ACTION, new CreateApiKeyRequest(), authentication); assertThat(accessAllowed, is(true)); @@ -69,8 +64,8 @@ public void testManageAllPrivilege() { } public void testManagePrivilegeRestrictedForRealmsAndUsers() { - final ManageApiKeyConditionalPrivileges condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.builder().allowCreate().allowGet() - .allowInvalidate().forRealms("realm1", "realm2").forUsers("user1", "user2").build(); + final ManageApiKeyConditionalClusterPrivilege condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.builder().allowCreate() + .allowGet().allowInvalidate().forRealms("realm1", "realm2").forUsers("user1", "user2").build(); boolean accessAllowed = checkAccess(condPrivilege, CREATE_ACTION, new CreateApiKeyRequest(), authentication); assertThat(accessAllowed, is(true)); @@ -93,7 +88,7 @@ public void testManagePrivilegeRestrictedForRealmsAndUsers() { } public void testManagePrivilegeRestrictedReadOnlyForRealmsAndUsers() { - final ManageApiKeyConditionalPrivileges condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.builder().allowGet() + final ManageApiKeyConditionalClusterPrivilege condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.builder().allowGet() .forRealms("realm1", "realm2").forUsers("user1", "user2").build(); boolean accessAllowed = checkAccess(condPrivilege, CREATE_ACTION, new CreateApiKeyRequest(), authentication); @@ -117,7 +112,7 @@ public void testManagePrivilegeRestrictedReadOnlyForRealmsAndUsers() { } public void testManagePrivilegeOwnerOnly() { - final ManageApiKeyConditionalPrivileges condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.manageApiKeysOnlyForOwner(); + final ManageApiKeyConditionalClusterPrivilege condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.manageApiKeysOnlyForOwner(); boolean accessAllowed = checkAccess(condPrivilege, CREATE_ACTION, new CreateApiKeyRequest(), authentication); assertThat(accessAllowed, is(true)); @@ -167,7 +162,7 @@ public void testManagePrivilegeOwnerOnly() { } public void testManagePrivilegeOwnerAndReadOnly() { - final ManageApiKeyConditionalPrivileges condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.builder().allowGet() + final ManageApiKeyConditionalClusterPrivilege condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.builder().allowGet() .forRealms("_self").forUsers("_self").build(); boolean accessAllowed = checkAccess(condPrivilege, CREATE_ACTION, new CreateApiKeyRequest(), authentication); @@ -216,43 +211,7 @@ public void testManagePrivilegeOwnerAndReadOnly() { assertThat(accessAllowed, is(false)); } - public void testParsingThrowsErrorWhenUsersAndRealmsFieldValuesAreInvalid() throws IOException { - { - String json = "{" + - "\"manage\": {" + - " \"action\": [ \"cluster:admin/xpack/security/api_key/create\" ], " + - " \"realms\": [ \"_self\" ], " + - " \"users\": [ \"some-user\" ] " + - "}" + - "}"; - final XContentParser parser = createParser(XContentType.JSON.xContent(), json); - parser.nextToken(); - parser.nextToken(); - ElasticsearchParseException epe = expectThrows(ElasticsearchParseException.class, - () -> ManageApiKeyConditionalPrivileges.parse(parser)); - assertThat(epe.getMessage(), is("could not parse, both fields [" + Fields.USERS.getPreferredName() + "], [" - + Fields.REALMS.getPreferredName() + "] must contain only `_self` when restricting access of API keys to owner")); - } - - { - String json = "{" + - "\"manage\": {" + - " \"action\": [ \"cluster:admin/xpack/security/api_key/create\" ], " + - " \"realms\": [ \"_self\" ], " + - " \"users\": [ \"_self\", \"some-user\" ] " + - "}" + - "}"; - final XContentParser parser = createParser(XContentType.JSON.xContent(), json); - parser.nextToken(); - parser.nextToken(); - ElasticsearchParseException epe = expectThrows(ElasticsearchParseException.class, - () -> ManageApiKeyConditionalPrivileges.parse(parser)); - assertThat(epe.getMessage(), is("could not parse, both fields [" + Fields.USERS.getPreferredName() + "], [" - + Fields.REALMS.getPreferredName() + "] must contain only `_self` when restricting access of API keys to owner")); - } - } - - private boolean checkAccess(ManageApiKeyConditionalPrivileges privilege, String action, TransportRequest request, + private boolean checkAccess(ManageApiKeyConditionalClusterPrivilege privilege, String action, TransportRequest request, Authentication authentication) { return privilege.getPrivilege().predicate().test(action) && privilege.getRequestPredicate().test(request, authentication); } @@ -301,17 +260,17 @@ public static ManageApiKeyConditionalPrivilegesBuilder builder() { return new ManageApiKeyConditionalPrivilegesBuilder(); } - public static ManageApiKeyConditionalPrivileges manageApiKeysUnrestricted() { - return new ManageApiKeyConditionalPrivileges(Set.of("cluster:admin/xpack/security/api_key/*"), Set.of("*"), Set.of("*")); + public static ManageApiKeyConditionalClusterPrivilege manageApiKeysUnrestricted() { + return new ManageApiKeyConditionalClusterPrivilege(Set.of("cluster:admin/xpack/security/api_key/*"), Set.of("*"), Set.of("*")); } - public static ManageApiKeyConditionalPrivileges manageApiKeysOnlyForOwner() { - return new ManageApiKeyConditionalPrivileges(Set.of("cluster:admin/xpack/security/api_key/*"), Set.of("_self"), - Set.of("_self")); + public static ManageApiKeyConditionalClusterPrivilege manageApiKeysOnlyForOwner() { + return (ManageApiKeyConditionalClusterPrivilege) ClusterPrivilege.DefaultConditionalClusterPrivilege.MANAGE_OWN_API_KEY + .conditionalClusterPrivilege(); } - public ManageApiKeyConditionalPrivileges build() { - return new ManageApiKeyConditionalPrivileges(actions, realms, users); + public ManageApiKeyConditionalClusterPrivilege build() { + return new ManageApiKeyConditionalClusterPrivilege(actions, realms, users); } } } 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..e9150f6830717 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); + ClusterPrivilege cluster = ClusterPrivilege.get(name).v1(); assertThat(cluster, is(ClusterPrivilege.MONITOR)); // since "all" implies "monitor", this should be the same language as All name = Sets.newHashSet("monitor", "all"); - cluster = ClusterPrivilege.get(name); + cluster = ClusterPrivilege.get(name).v1(); assertTrue(Operations.sameLanguage(ClusterPrivilege.ALL.automaton, cluster.automaton)); name = Sets.newHashSet("monitor", "none"); - cluster = ClusterPrivilege.get(name); + cluster = ClusterPrivilege.get(name).v1(); assertTrue(Operations.sameLanguage(ClusterPrivilege.MONITOR.automaton, cluster.automaton)); Set name2 = Sets.newHashSet("none", "monitor"); - ClusterPrivilege cluster2 = ClusterPrivilege.get(name2); + ClusterPrivilege cluster2 = ClusterPrivilege.get(name2).v1(); assertThat(cluster, is(cluster2)); } public void testClusterTemplateActions() throws Exception { Set name = Sets.newHashSet("indices:admin/template/delete"); - ClusterPrivilege cluster = ClusterPrivilege.get(name); + ClusterPrivilege cluster = ClusterPrivilege.get(name).v1(); 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 = ClusterPrivilege.get(name).v1(); 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 = ClusterPrivilege.get(name).v1(); assertThat(cluster, notNullValue()); assertThat(cluster.predicate().test("indices:admin/template/put"), is(true)); } @@ -74,7 +74,7 @@ public void testClusterInvalidName() throws Exception { public void testClusterAction() throws Exception { Set actionName = Sets.newHashSet("cluster:admin/snapshot/delete"); - ClusterPrivilege cluster = ClusterPrivilege.get(actionName); + ClusterPrivilege cluster = ClusterPrivilege.get(actionName).v1(); assertThat(cluster, notNullValue()); assertThat(cluster.predicate().test("cluster:admin/snapshot/delete"), is(true)); assertThat(cluster.predicate().test("cluster:admin/snapshot/dele"), is(false)); 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 be9c93ce67282..7ddf0133d23be 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 @@ -61,6 +61,7 @@ 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.Privilege; +import org.elasticsearch.xpack.core.security.authz.privilege.RenderableConditionalClusterPrivilege; import org.elasticsearch.xpack.core.security.support.Automatons; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; @@ -372,7 +373,8 @@ public void checkPrivileges(Authentication authentication, AuthorizationInfo aut Map cluster = new HashMap<>(); for (String checkAction : request.clusterPrivileges()) { - final ClusterPrivilege checkPrivilege = ClusterPrivilege.get(Collections.singleton(checkAction)); + final ClusterPrivilege checkPrivilege = ClusterPrivilege.get(Collections.singleton(checkAction)).v1(); + // should we check for conditional as well? cluster.put(checkAction, userRole.grants(checkPrivilege)); } boolean allMatch = cluster.values().stream().allMatch(Boolean::booleanValue); @@ -425,14 +427,16 @@ 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<>(); + 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()); } + } else if (tup.v2() instanceof RenderableConditionalClusterPrivilege) { + conditionalCluster.add((RenderableConditionalClusterPrivilege) tup.v2()); } else { - conditionalCluster.add(tup.v2()); + cluster.add(ClusterPrivilege.DefaultConditionalClusterPrivilege.name(tup.v2())); } } 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 b073349d842a3..e0a53f33f1d73 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.RenderableConditionalClusterPrivilege; import org.elasticsearch.xpack.core.security.client.SecurityClient; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler; @@ -82,7 +82,7 @@ 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()) { + for (RenderableConditionalClusterPrivilege ccp : response.getConditionalClusterPrivileges()) { ConditionalClusterPrivileges.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 e2fd9ef15c179..10e21cf70676e 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 @@ -94,16 +94,9 @@ public String configRoles() { "manage_api_key_role:\n" + " cluster: [\"manage_api_key\"]\n" + "manage_own_api_key_role:\n" + - " global: { \"api_keys\":{\"manage\":{\"action\": [ \"cluster:admin/xpack/security/api_key/*\" ], " + - "\"users\": [ \"_self\" ], " + - "\"realms\": [ \"_self\" ]}} }\n" + - "only_create_api_key_role:\n" + - " global: { \"api_keys\":{\"manage\":{\"action\": [ \"cluster:admin/xpack/security/api_key/create\" ], " + - "\"users\":[ \"_self\" ], \"realms\":[ \"_self\" ]}} }\n" + + " cluster: [\"manage_own_api_key\"]\n" + "only_create_get_own_api_key_role:\n" + - " global: { \"api_keys\":{\"manage\":{\"action\": [ \"cluster:admin/xpack/security/api_key/create\", " + - " \"cluster:admin/xpack/security/api_key/get\" ], " + - "\"users\": [ \"_self\" ], \"realms\": [ \"_self\" ]}} }\n" + + " cluster: [\"create_get_own_api_key\"]\n" + "no_manage_api_key_role:\n" + " indices:\n" + " - names: '*'\n" + @@ -117,7 +110,6 @@ public String configUsers() { getFastStoredHashAlgoForTests().hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)); return super.configUsers() + "user_with_manage_api_key_role:" + usersPasswdHashed + "\n" + - "user_with_only_create_api_key_role:" + usersPasswdHashed + "\n" + "user_with_only_create_get_api_key_role:" + usersPasswdHashed + "\n" + "user_with_owner_manage_api_key_role:" + usersPasswdHashed + "\n" + "user_with_no_manage_api_key_role:" + usersPasswdHashed + "\n"; @@ -127,7 +119,6 @@ public String configUsers() { public String configUsersRoles() { return super.configUsersRoles() + "manage_api_key_role:user_with_manage_api_key_role\n" + - "only_create_api_key_role:user_with_only_create_api_key_role\n" + "only_create_get_own_api_key_role:user_with_only_create_get_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"; @@ -607,10 +598,6 @@ public void testCreateApiKeyAuthorization() { List responses = createApiKeys("user_with_manage_api_key_role", 1, null); assertThat(responses.get(0).getKey(), is(notNullValue())); - // user_with_only_create_api_key_role should be able to create API key - responses = createApiKeys("user_with_only_create_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))); @@ -625,7 +612,6 @@ public void testCreateApiKeyAuthorization() { 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); - List userWithCreateApiKeyRoleApiKeys = createApiKeys("user_with_only_create_api_key_role", 1, null); // user_with_manage_api_key_role should be able to get any user's API Key { @@ -665,32 +651,11 @@ public void testGetApiKeyAuthorization() throws InterruptedException, ExecutionE assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/get", "user_with_owner_manage_api_key_role"); } - // user_with_only_create_api_key_role should not be allowed to get it's own API key or not any other user's API key - { - final Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken - .basicAuthHeaderValue("user_with_only_create_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); - final SecurityClient securityClient = new SecurityClient(client); - PlainActionFuture listener = new PlainActionFuture<>(); - GetApiKeyRequest getApiKeyRequest = new GetApiKeyRequest(null, "user_with_only_create_api_key_role", - userWithCreateApiKeyRoleApiKeys.get(0).getId(), userWithCreateApiKeyRoleApiKeys.get(0).getName()); - securityClient.getApiKey(getApiKeyRequest, listener); - ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class, - () -> listener.actionGet()); - assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/get", "user_with_only_create_api_key_role"); - - final PlainActionFuture getApiKeyOfOtherUserListener = new PlainActionFuture<>(); - securityClient.getApiKey(GetApiKeyRequest.usingApiKeyId(userWithManageApiKeyRoleApiKeys.get(0).getId()), - getApiKeyOfOtherUserListener); - ese = expectThrows(ElasticsearchSecurityException.class, - () -> getApiKeyOfOtherUserListener.actionGet()); - assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/get", "user_with_only_create_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); - List userWithCreateApiKeyRoleApiKeys = createApiKeys("user_with_only_create_api_key_role", 1, 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, @@ -713,28 +678,11 @@ public void testInvalidateApiKeyAuthorization() throws InterruptedException, Exe 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_only_create_api_key_role", + () -> 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"); } - // user_with_only_create_api_key_role should not be allowed to invalidate it's own API keys or any other users API keys - { - final Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken - .basicAuthHeaderValue("user_with_only_create_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); - final SecurityClient securityClient = new SecurityClient(client); - - ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class, - () -> invalidateApiKey("user_with_only_create_api_key_role", null, "user_with_only_create_api_key_role", - userWithManageApiKeyRoleApiKeys.get(1).getName(), null)); - assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/invalidate", "user_with_only_create_api_key_role"); - - final PlainActionFuture listener = new PlainActionFuture<>(); - securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyName(userWithCreateApiKeyRoleApiKeys.get(0).getName()), - listener); - ese = expectThrows(ElasticsearchSecurityException.class, () -> listener.actionGet()); - assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/invalidate", "user_with_only_create_api_key_role"); - } } private List createApiKeys(int noOfApiKeys, TimeValue expiration) { 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 0d01a01bcf1d7..dd653bb423e7a 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 @@ -115,7 +115,7 @@ 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.RenderableConditionalClusterPrivilege; 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; @@ -315,11 +315,11 @@ 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 RenderableConditionalClusterPrivilege conditionalClusterPrivilege = Mockito.mock(RenderableConditionalClusterPrivilege.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[] { + final RenderableConditionalClusterPrivilege[] conditionalClusterPrivileges = new RenderableConditionalClusterPrivilege[] { conditionalClusterPrivilege }; final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); @@ -336,11 +336,11 @@ 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 RenderableConditionalClusterPrivilege conditionalClusterPrivilege = Mockito.mock(RenderableConditionalClusterPrivilege.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[] { + final RenderableConditionalClusterPrivilege[] conditionalClusterPrivileges = new RenderableConditionalClusterPrivilege[] { conditionalClusterPrivilege }; final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); 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 13e4c3d788094..38bc716a96b3f 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,7 +22,7 @@ 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.RenderableConditionalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.ManageApplicationPrivileges; import org.elasticsearch.xpack.core.security.support.MetadataUtils; import org.hamcrest.Matchers; @@ -72,7 +72,7 @@ public void testToString() { .build() }; - final ConditionalClusterPrivilege[] conditionalClusterPrivileges = new ConditionalClusterPrivilege[]{ + final RenderableConditionalClusterPrivilege[] conditionalClusterPrivileges = new RenderableConditionalClusterPrivilege[]{ new ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02"))) }; @@ -104,7 +104,7 @@ public void testToXContent() throws Exception { .resources("*") .build() }; - final ConditionalClusterPrivilege[] conditionalClusterPrivileges = { + final RenderableConditionalClusterPrivilege[] conditionalClusterPrivileges = { new ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02"))) }; @@ -189,8 +189,8 @@ 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)); + final RenderableConditionalClusterPrivilege conditionalPrivilege = rd.getConditionalClusterPrivileges()[0]; + assertThat(conditionalPrivilege.getCategory(), equalTo(RenderableConditionalClusterPrivilege.Category.APPLICATION)); assertThat(conditionalPrivilege, instanceOf(ManageApplicationPrivileges.class)); assertThat(((ManageApplicationPrivileges) conditionalPrivilege).getApplicationNames(), containsInAnyOrder("kibana", "logstash")); @@ -233,7 +233,7 @@ public void testSerializationForCurrentVersion() throws Exception { .resources("*") .build() }; - final ConditionalClusterPrivilege[] conditionalClusterPrivileges = { + final RenderableConditionalClusterPrivilege[] conditionalClusterPrivileges = { new ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02"))) }; 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 0247cc3525bec..271f454b7e11c 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 @@ -42,7 +42,7 @@ 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.RenderableConditionalClusterPrivilege; 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; @@ -543,7 +543,7 @@ public void testMergingBasicRoles() { final TransportRequest request2 = mock(TransportRequest.class); final TransportRequest request3 = mock(TransportRequest.class); - ConditionalClusterPrivilege ccp1 = mock(ConditionalClusterPrivilege.class); + RenderableConditionalClusterPrivilege ccp1 = mock(RenderableConditionalClusterPrivilege.class); when(ccp1.getPrivilege()).thenReturn(ClusterPrivilege.MANAGE_SECURITY); when(ccp1.getRequestPredicate()).thenReturn((req, authn) -> req == request1); RoleDescriptor role1 = new RoleDescriptor("r1", new String[]{"monitor"}, new IndicesPrivileges[]{ @@ -566,10 +566,10 @@ public void testMergingBasicRoles() { .resources("settings/*") .privileges("read") .build() - }, new ConditionalClusterPrivilege[] { ccp1 }, + }, new RenderableConditionalClusterPrivilege[] { ccp1 }, new String[]{"app-user-1"}, null, null); - ConditionalClusterPrivilege ccp2 = mock(ConditionalClusterPrivilege.class); + RenderableConditionalClusterPrivilege ccp2 = mock(RenderableConditionalClusterPrivilege.class); when(ccp2.getPrivilege()).thenReturn(ClusterPrivilege.MANAGE_SECURITY); when(ccp2.getRequestPredicate()).thenReturn((req, authn) -> req == request2); RoleDescriptor role2 = new RoleDescriptor("r2", new String[]{"manage_saml"}, new IndicesPrivileges[]{ @@ -588,7 +588,7 @@ public void testMergingBasicRoles() { .resources("*") .privileges("read") .build() - }, new ConditionalClusterPrivilege[] { ccp2 }, + }, new RenderableConditionalClusterPrivilege[] { ccp2 }, new String[]{"app-user-2"}, null, null); FieldPermissionsCache cache = new FieldPermissionsCache(Settings.EMPTY); 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 023819b94691f..3a285d906d851 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.ManageApplicationPrivileges; +import org.elasticsearch.xpack.core.security.authz.privilege.RenderableConditionalClusterPrivilege; import java.util.Arrays; import java.util.Collections; @@ -55,7 +55,7 @@ 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( + 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"), From 9958b819f2791886ffe324ce7639f32fe1a94e4d Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Mon, 3 Jun 2019 17:21:06 +1000 Subject: [PATCH 11/25] fix error message and compilation issue --- .../org/elasticsearch/example/CustomAuthorizationEngine.java | 4 ++-- .../xpack/core/security/authz/privilege/ClusterPrivilege.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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..fb343abe6d655 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.RenderableConditionalClusterPrivilege; 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/security/authz/privilege/ClusterPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilege.java index bb97167919412..60785d4ce4a13 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 @@ -227,7 +227,7 @@ private static Tuple> resolve } else { throw new IllegalArgumentException("unknown cluster privilege [" + name + "]. a privilege must be either " + "one of the predefined fixed cluster privileges [" + - Strings.collectionToCommaDelimitedString(VALUES.entrySet()) + "], fixed conditional cluster privileges [" + + Strings.collectionToCommaDelimitedString(VALUES.entrySet()) + "], default conditional cluster privileges [" + Strings.collectionToCommaDelimitedString(DefaultConditionalClusterPrivilege.names()) + "] or a pattern over one of the available cluster actions"); } From d69224e1d0b27a9d97fec9ebf6d4c104788c027c Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Mon, 3 Jun 2019 18:14:02 +1000 Subject: [PATCH 12/25] keep the conditional cluster privileges same and introduce plain conditional cluster privileges without rendering support --- .../example/CustomAuthorizationEngine.java | 4 +- .../xpack/core/XPackClientPlugin.java | 4 +- .../security/action/role/PutRoleRequest.java | 8 +-- .../user/GetUserPrivilegesResponse.java | 8 +-- .../core/security/authz/RoleDescriptor.java | 16 +++--- .../authz/permission/ClusterPermission.java | 14 +++--- .../core/security/authz/permission/Role.java | 10 ++-- .../authz/privilege/ClusterPrivilege.java | 22 ++++----- .../ConditionalClusterPrivilege.java | 42 +++++++++++----- .../ConditionalClusterPrivileges.java | 32 ++++++------ ...nageApiKeyConditionalClusterPrivilege.java | 2 +- .../ManageApplicationPrivileges.java | 4 +- .../PlainConditionalClusterPrivilege.java | 33 +++++++++++++ ...RenderableConditionalClusterPrivilege.java | 49 ------------------- .../authz/store/ReservedRolesStore.java | 4 +- .../user/GetUserPrivilegesResponseTests.java | 8 +-- .../ConditionalClusterPrivilegesTests.java | 14 +++--- .../xpack/security/authz/RBACEngine.java | 12 ++--- .../authz/store/CompositeRolesStore.java | 4 +- .../user/RestGetUserPrivilegesAction.java | 4 +- .../security/authc/ApiKeyIntegTests.java | 43 ++++++++-------- .../authz/AuthorizationServiceTests.java | 10 ++-- .../security/authz/RoleDescriptorTests.java | 12 ++--- .../authz/store/CompositeRolesStoreTests.java | 10 ++-- .../RestGetUserPrivilegesActionTests.java | 4 +- 25 files changed, 187 insertions(+), 186 deletions(-) create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/PlainConditionalClusterPrivilege.java delete mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/RenderableConditionalClusterPrivilege.java 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 fb343abe6d655..9916eb5dfed37 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.RenderableConditionalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; 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 4e48e67503a6a..4fdf0962fa98f 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 @@ -169,7 +169,7 @@ 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.ManageApplicationPrivileges; -import org.elasticsearch.xpack.core.security.authz.privilege.RenderableConditionalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; import org.elasticsearch.xpack.core.sql.SqlFeatureSetUsage; import org.elasticsearch.xpack.core.ssl.action.GetCertificateInfoAction; import org.elasticsearch.xpack.core.upgrade.actions.IndexUpgradeAction; @@ -385,7 +385,7 @@ 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(RenderableConditionalClusterPrivilege.class, + new NamedWriteableRegistry.Entry(ConditionalClusterPrivilege.class, ManageApplicationPrivileges.WRITEABLE_NAME, ManageApplicationPrivileges::createFrom), // security : role-mappings 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 f379d608726b6..e19d9cebb64c1 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,7 +15,7 @@ import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege; -import org.elasticsearch.xpack.core.security.authz.privilege.RenderableConditionalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges; import org.elasticsearch.xpack.core.security.support.MetadataUtils; @@ -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(RenderableConditionalClusterPrivilege... conditionalClusterPrivileges) { + void conditionalCluster(ConditionalClusterPrivilege... conditionalClusterPrivileges) { this.conditionalClusterPrivileges = conditionalClusterPrivileges; } @@ -145,7 +145,7 @@ public List applicationPrivileges( return Collections.unmodifiableList(applicationPrivileges); } - public RenderableConditionalClusterPrivilege[] conditionalClusterPrivileges() { + public ConditionalClusterPrivilege[] conditionalClusterPrivileges() { return conditionalClusterPrivileges; } 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 c75b648d8376b..7c47b700cc0b5 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,7 +15,7 @@ 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.RenderableConditionalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges; import java.io.IOException; @@ -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; } 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 08be89299c5e0..07056864fd33c 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,7 +23,7 @@ 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.RenderableConditionalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges; import org.elasticsearch.xpack.core.security.support.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 RenderableConditionalClusterPrivilege[] conditionalClusterPrivileges; + private final ConditionalClusterPrivilege[] 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[], - * RenderableConditionalClusterPrivilege[], String[], Map, Map)} + * ConditionalClusterPrivilege[], String[], Map, Map)} */ @Deprecated public RoleDescriptor(String name, @@ -77,7 +77,7 @@ public RoleDescriptor(String name, /** * @deprecated Use {@link #RoleDescriptor(String, String[], IndicesPrivileges[], ApplicationResourcePrivileges[], - * RenderableConditionalClusterPrivilege[], String[], Map, Map)} + * ConditionalClusterPrivilege[], String[], Map, Map)} */ @Deprecated public RoleDescriptor(String name, @@ -93,7 +93,7 @@ public RoleDescriptor(String name, @Nullable String[] clusterPrivileges, @Nullable IndicesPrivileges[] indicesPrivileges, @Nullable ApplicationResourcePrivileges[] applicationPrivileges, - @Nullable RenderableConditionalClusterPrivilege[] conditionalClusterPrivileges, + @Nullable ConditionalClusterPrivilege[] conditionalClusterPrivileges, @Nullable String[] runAs, @Nullable Map metadata, @Nullable Map transientMetadata) { @@ -133,7 +133,7 @@ public String[] getClusterPrivileges() { return this.clusterPrivileges; } - public RenderableConditionalClusterPrivilege[] getConditionalClusterPrivileges() { + public ConditionalClusterPrivilege[] getConditionalClusterPrivileges() { return this.conditionalClusterPrivileges; } @@ -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; @@ -329,7 +329,7 @@ public static RoleDescriptor parse(String name, XContentParser parser, boolean a } } return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, applicationPrivileges, - conditionalClusterPrivileges.toArray(new RenderableConditionalClusterPrivilege[conditionalClusterPrivileges.size()]), + conditionalClusterPrivileges.toArray(new ConditionalClusterPrivilege[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 532befd6dc7ae..100087ab3e662 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 @@ -10,7 +10,7 @@ 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.PlainConditionalClusterPrivilege; import java.util.Collection; import java.util.Collections; @@ -39,7 +39,7 @@ 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 @@ -61,7 +61,7 @@ public boolean check(String action, TransportRequest request, Authentication aut } @Override - public List> privileges() { + public List> privileges() { return Collections.singletonList(new Tuple<>(super.privilege, null)); } } @@ -70,9 +70,9 @@ 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; + private final PlainConditionalClusterPrivilege conditionalPrivilege; - public ConditionalClusterPermission(ConditionalClusterPrivilege conditionalPrivilege) { + public ConditionalClusterPermission(PlainConditionalClusterPrivilege conditionalPrivilege) { super(conditionalPrivilege.getPrivilege()); this.conditionalPrivilege = conditionalPrivilege; } @@ -83,7 +83,7 @@ public boolean check(String action, TransportRequest request, Authentication aut } @Override - public List> privileges() { + public List> privileges() { return Collections.singletonList(new Tuple<>(super.privilege, conditionalPrivilege)); } } @@ -109,7 +109,7 @@ private static ClusterPrivilege buildPrivilege(Collection chi } @Override - public List> privileges() { + public List> privileges() { return children.stream().map(ClusterPermission::privileges).flatMap(List::stream).collect(Collectors.toList()); } 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 1e765d35978fa..8bbee6f1bb2c3 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 @@ -18,7 +18,7 @@ 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.PlainConditionalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.Privilege; @@ -211,16 +211,16 @@ 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) { - Tuple> privileges = ClusterPrivilege.get(privilegeNames); + Tuple> privileges = ClusterPrivilege.get(privilegeNames); clusterPermissions.add(new ClusterPermission.SimpleClusterPermission(privileges.v1())); - for (ConditionalClusterPrivilege ccp : privileges.v2()) { + for (PlainConditionalClusterPrivilege ccp : privileges.v2()) { clusterPermissions.add(new ClusterPermission.ConditionalClusterPermission(ccp)); } } - for (ConditionalClusterPrivilege ccp : conditionalClusterPrivileges) { + for (PlainConditionalClusterPrivilege 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 60785d4ce4a13..5d0f0bd09ebc9 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 @@ -101,7 +101,7 @@ public final class ClusterPrivilege extends Privilege { public static final Predicate ACTION_MATCHER = ClusterPrivilege.ALL.predicate(); - private static final ConcurrentHashMap, Tuple>> CACHE = + private static final ConcurrentHashMap, Tuple>> CACHE = new ConcurrentHashMap<>(); /* Conditional Cluster Privileges */ @@ -119,15 +119,15 @@ public enum DefaultConditionalClusterPrivilege { Set.of("*"), Set.of("*"))); - private final ConditionalClusterPrivilege conditionalClusterPrivilege; + private final PlainConditionalClusterPrivilege conditionalClusterPrivilege; private final String privilegeName; - DefaultConditionalClusterPrivilege(String privilegeName, ConditionalClusterPrivilege conditionalClusterPrivilege) { + DefaultConditionalClusterPrivilege(String privilegeName, PlainConditionalClusterPrivilege conditionalClusterPrivilege) { this.privilegeName = privilegeName; this.conditionalClusterPrivilege = conditionalClusterPrivilege; } - public ConditionalClusterPrivilege conditionalClusterPrivilege() { + public PlainConditionalClusterPrivilege conditionalClusterPrivilege() { return conditionalClusterPrivilege; } @@ -140,7 +140,7 @@ public static DefaultConditionalClusterPrivilege fromString(String privilegeName return value.get(); } - public static String name(ConditionalClusterPrivilege ccp) { + public static String name(PlainConditionalClusterPrivilege ccp) { Optional value = Arrays.stream(values()) .filter(dccp -> dccp.conditionalClusterPrivilege.equals(ccp)).findAny(); if (value.isEmpty()) { @@ -194,14 +194,14 @@ private ClusterPrivilege(Set name, Automaton automaton) { super(name, automaton); } - public static Tuple> get(final Set name) { + public static Tuple> get(final Set name) { if (name == null || name.isEmpty()) { - return new Tuple>(NONE, Collections.emptySet()); + return new Tuple>(NONE, Collections.emptySet()); } return CACHE.computeIfAbsent(name, ClusterPrivilege::resolve); } - private static Tuple> resolve(Set name) { + private static Tuple> resolve(Set name) { final int size = name.size(); if (size == 0) { throw new IllegalArgumentException("empty set should not be used"); @@ -209,7 +209,7 @@ private static Tuple> resolve Set actions = new HashSet<>(); Set automata = new HashSet<>(); - Set conditionalClusterPrivileges = new HashSet<>(); + Set conditionalClusterPrivileges = new HashSet<>(); for (String part : name) { part = part.toLowerCase(Locale.ROOT); if (ACTION_MATCHER.test(part)) { @@ -217,7 +217,7 @@ private static Tuple> resolve } else { ClusterPrivilege privilege = VALUES.get(part); if (privilege != null && size == 1) { - return new Tuple>(privilege, Collections.emptySet()); + return new Tuple>(privilege, Collections.emptySet()); } else if (privilege != null) { automata.add(privilege.automaton); } else { @@ -238,7 +238,7 @@ private static Tuple> resolve if (actions.isEmpty() == false) { automata.add(patterns(actions)); } - return new Tuple>( + return new Tuple>( new ClusterPrivilege(name, Automatons.unionAndMinimize(automata)), conditionalClusterPrivileges); } } 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 bbac369f02df8..36bde6432dfc6 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,28 +6,44 @@ package org.elasticsearch.xpack.core.security.authz.privilege; -import org.elasticsearch.transport.TransportRequest; -import org.elasticsearch.xpack.core.security.authc.Authentication; +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.util.function.BiPredicate; -import java.util.function.Predicate; +import java.io.IOException; +import java.util.Collection; /** - * 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. + * Conditional Cluster privileges that can be serialized / rendered as `XContent` */ -public interface ConditionalClusterPrivilege { +public interface ConditionalClusterPrivilege extends NamedWriteable, ToXContentFragment, PlainConditionalClusterPrivilege { /** - * The action-level privilege that is required by this conditional privilege. + * The category under which this privilege should be rendered when output as XContent. */ - ClusterPrivilege getPrivilege(); + Category getCategory(); /** - * 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. + * A {@link PlainConditionalClusterPrivilege} 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). */ - BiPredicate getRequestPredicate(); + @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 PlainConditionalClusterPrivilege} 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; + } + } } 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 index e86920470a3b3..71ad9fd6ceded 100644 --- 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 @@ -15,7 +15,7 @@ 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.RenderableConditionalClusterPrivilege.Category; +import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege.Category; import java.io.IOException; import java.util.ArrayList; @@ -24,44 +24,44 @@ import java.util.List; /** - * Static utility class for working with {@link RenderableConditionalClusterPrivilege} instances + * Static utility class for working with {@link ConditionalClusterPrivilege} instances */ public final class ConditionalClusterPrivileges { - public static final RenderableConditionalClusterPrivilege[] EMPTY_ARRAY = new RenderableConditionalClusterPrivilege[0]; + public static final ConditionalClusterPrivilege[] EMPTY_ARRAY = new ConditionalClusterPrivilege[0]; - public static final Writeable.Reader READER = - in1 -> in1.readNamedWriteable(RenderableConditionalClusterPrivilege.class); - public static final Writeable.Writer WRITER = + 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 RenderableConditionalClusterPrivilege} objects from a {@link StreamInput} + * Utility method to read an array of {@link ConditionalClusterPrivilege} objects from a {@link StreamInput} */ - public static RenderableConditionalClusterPrivilege[] readArray(StreamInput in) throws IOException { - return in.readArray(READER, RenderableConditionalClusterPrivilege[]::new); + public static ConditionalClusterPrivilege[] readArray(StreamInput in) throws IOException { + return in.readArray(READER, ConditionalClusterPrivilege[]::new); } /** - * Utility method to write an array of {@link RenderableConditionalClusterPrivilege} objects to a {@link StreamOutput} + * Utility method to write an array of {@link ConditionalClusterPrivilege} objects to a {@link StreamOutput} */ - public static void writeArray(StreamOutput out, RenderableConditionalClusterPrivilege[] privileges) throws IOException { + 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 RenderableConditionalClusterPrivilege#getCategory() categories} + * The privileges are grouped according to their {@link ConditionalClusterPrivilege#getCategory() categories} */ public static XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params, - Collection privileges) throws IOException { + Collection privileges) throws IOException { builder.startObject(); for (Category category : Category.values()) { builder.startObject(category.field.getPreferredName()); - for (RenderableConditionalClusterPrivilege privilege : privileges) { + for (ConditionalClusterPrivilege privilege : privileges) { if (category == privilege.getCategory()) { privilege.toXContent(builder, params); } @@ -75,8 +75,8 @@ public static XContentBuilder toXContent(XContentBuilder builder, ToXContent.Par * 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<>(); + 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) { 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 index 3802791da4d70..7e92b004cba87 100644 --- 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 @@ -24,7 +24,7 @@ /** * Conditional cluster privilege for managing API keys */ -public final class ManageApiKeyConditionalClusterPrivilege implements ConditionalClusterPrivilege { +public final class ManageApiKeyConditionalClusterPrivilege implements PlainConditionalClusterPrivilege { 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"; 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 index f1c21f3fbd5e6..6465cdbcfddc9 100644 --- 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 @@ -29,11 +29,11 @@ import java.util.function.Predicate; /** - * The {@code ManageApplicationPrivileges} privilege is a {@link RenderableConditionalClusterPrivilege} that grants the + * 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 final class ManageApplicationPrivileges implements RenderableConditionalClusterPrivilege { +public final class ManageApplicationPrivileges implements ConditionalClusterPrivilege { private static final ClusterPrivilege PRIVILEGE = ClusterPrivilege.get( Collections.singleton("cluster:admin/xpack/security/privilege/*") diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/PlainConditionalClusterPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/PlainConditionalClusterPrivilege.java new file mode 100644 index 0000000000000..0433d94b900f2 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/PlainConditionalClusterPrivilege.java @@ -0,0 +1,33 @@ +/* + * 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; +import java.util.function.Predicate; + +/** + * A ConditionalClusterPrivilege is a composition of a {@link ClusterPrivilege} (that determines which actions may be executed) + * with a {@link Predicate} for a {@link TransportRequest} (that determines which requests may be executed). + * The a given execution of an action is considered to be permitted if both the action and the request are permitted. + */ +public interface PlainConditionalClusterPrivilege { + + /** + * The action-level privilege that is required by this conditional privilege. + */ + ClusterPrivilege getPrivilege(); + + /** + * 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/RenderableConditionalClusterPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/RenderableConditionalClusterPrivilege.java deleted file mode 100644 index 055186d76ad2b..0000000000000 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/RenderableConditionalClusterPrivilege.java +++ /dev/null @@ -1,49 +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.io.stream.NamedWriteable; -import org.elasticsearch.common.xcontent.ToXContentFragment; -import org.elasticsearch.common.xcontent.XContentBuilder; - -import java.io.IOException; -import java.util.Collection; - -/** - * Conditional Cluster privileges that can be serialized / rendered as `XContent` - */ -public interface RenderableConditionalClusterPrivilege extends NamedWriteable, ToXContentFragment, ConditionalClusterPrivilege { - - /** - * The category under which this privilege should be rendered when output as XContent. - */ - Category getCategory(); - - /** - * 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; - } - } -} 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 c217b80fb523f..d51546a0da363 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,7 +11,7 @@ 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.RenderableConditionalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; 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; @@ -123,7 +123,7 @@ private static Map initializeReservedRoles() { .indices(".code-*", ".code_internal-*").privileges("all").build(), }, null, - new RenderableConditionalClusterPrivilege[] { new ManageApplicationPrivileges(Collections.singleton("kibana-*")) }, + new ConditionalClusterPrivilege[] { 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/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 1182164649aec..64d3d9d2510ad 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,7 +21,7 @@ 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.RenderableConditionalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.ManageApplicationPrivileges; import java.io.IOException; @@ -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,8 +103,8 @@ 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, RenderableConditionalClusterPrivilege[]::new, () -> new ManageApplicationPrivileges(randomStringSet(3)))); + final Set conditionalCluster = Sets.newHashSet( + randomArray(3, ConditionalClusterPrivilege[]::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/authz/privilege/ConditionalClusterPrivilegesTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ConditionalClusterPrivilegesTests.java index 39d718101b3d4..ebcd70869cb02 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/ConditionalClusterPrivilegesTests.java @@ -30,12 +30,12 @@ public class ConditionalClusterPrivilegesTests extends ESTestCase { public void testSerialization() throws Exception { - final RenderableConditionalClusterPrivilege[] original = buildSecurityPrivileges(); + final ConditionalClusterPrivilege[] original = buildSecurityPrivileges(); try (BytesStreamOutput out = new BytesStreamOutput()) { ConditionalClusterPrivileges.writeArray(out, original); final NamedWriteableRegistry registry = new NamedWriteableRegistry(new XPackClientPlugin(Settings.EMPTY).getNamedWriteables()); try (StreamInput in = new NamedWriteableAwareStreamInput(out.bytes().streamInput(), registry)) { - final RenderableConditionalClusterPrivilege[] copy = ConditionalClusterPrivileges.readArray(in); + final ConditionalClusterPrivilege[] copy = ConditionalClusterPrivileges.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()); + final List original = Arrays.asList(buildSecurityPrivileges()); ConditionalClusterPrivileges.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 = ConditionalClusterPrivileges.parse(parser); assertThat(clone, equalTo(original)); assertThat(original, equalTo(clone)); } } } - private RenderableConditionalClusterPrivilege[] buildSecurityPrivileges() { + private ConditionalClusterPrivilege[] buildSecurityPrivileges() { return buildSecurityPrivileges(randomIntBetween(4, 7)); } - private RenderableConditionalClusterPrivilege[] buildSecurityPrivileges(int applicationNameLength) { - return new RenderableConditionalClusterPrivilege[] { + private ConditionalClusterPrivilege[] buildSecurityPrivileges(int applicationNameLength) { + return new ConditionalClusterPrivilege[] { ManageApplicationPrivilegesTests.buildPrivileges(applicationNameLength) }; } 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 7ddf0133d23be..c8aae17c9d19f 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 @@ -59,9 +59,9 @@ 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.PlainConditionalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.Privilege; -import org.elasticsearch.xpack.core.security.authz.privilege.RenderableConditionalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; import org.elasticsearch.xpack.core.security.support.Automatons; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; @@ -427,14 +427,14 @@ 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()) { + 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()); } - } else if (tup.v2() instanceof RenderableConditionalClusterPrivilege) { - conditionalCluster.add((RenderableConditionalClusterPrivilege) tup.v2()); + } else if (tup.v2() instanceof ConditionalClusterPrivilege) { + conditionalCluster.add((ConditionalClusterPrivilege) tup.v2()); } else { cluster.add(ClusterPrivilege.DefaultConditionalClusterPrivilege.name(tup.v2())); } 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..8a715208f8864 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.PlainConditionalClusterPrivilege; 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 conditionalClusterPrivileges = new ArrayList<>(); Set runAs = new HashSet<>(); final Map, MergeableIndicesPrivilege> restrictedIndicesPrivilegesMap = new HashMap<>(); final Map, MergeableIndicesPrivilege> indicesPrivilegesMap = new HashMap<>(); 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 3c865c77f3754..865348f4569f6 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 @@ -25,7 +25,7 @@ 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.ConditionalClusterPrivileges; -import org.elasticsearch.xpack.core.security.authz.privilege.RenderableConditionalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler; @@ -81,7 +81,7 @@ public RestResponse buildResponse(GetUserPrivilegesResponse response, XContentBu builder.field(RoleDescriptor.Fields.CLUSTER.getPreferredName(), response.getClusterPrivileges()); builder.startArray(RoleDescriptor.Fields.GLOBAL.getPreferredName()); - for (RenderableConditionalClusterPrivilege ccp : response.getConditionalClusterPrivileges()) { + for (ConditionalClusterPrivilege ccp : response.getConditionalClusterPrivileges()) { ConditionalClusterPrivileges.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 b452bd48284b1..6424b408f77a1 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; @@ -543,8 +544,8 @@ public void testApiKeyWithMinimalRoleCanGetApiKeyInformation() { Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken.basicAuthHeaderValue(SecuritySettingsSource.TEST_SUPERUSER, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); - SecurityClient securityClient = new SecurityClient(client); - final CreateApiKeyResponse response = securityClient.prepareCreateApiKey() + + final CreateApiKeyResponse response = new CreateApiKeyRequestBuilder(client) .setName("test key") .setExpiration(TimeValue.timeValueHours(TimeUnit.DAYS.toHours(7L))) .setRoleDescriptors(Collections.singletonList(descriptor)) @@ -558,10 +559,11 @@ public void testApiKeyWithMinimalRoleCanGetApiKeyInformation() { final String base64ApiKeyKeyValue = Base64.getEncoder().encodeToString( (response.getId() + ":" + response.getKey().toString()).getBytes(StandardCharsets.UTF_8)); client = client().filterWithHeader(Collections.singletonMap("Authorization", "ApiKey " + base64ApiKeyKeyValue)); - securityClient = new SecurityClient(client); + PlainActionFuture listener = new PlainActionFuture<>(); GetApiKeyRequest request = GetApiKeyRequest.usingApiKeyId(response.getId()); - securityClient.getApiKey(request, listener); + client.execute(GetApiKeyAction.INSTANCE, request, listener); + GetApiKeyResponse apiKeyResponse = listener.actionGet(); assertThat(apiKeyResponse.getApiKeyInfos().length, is(1)); assertThat(apiKeyResponse.getApiKeyInfos()[0].getId(), is(response.getId())); @@ -571,7 +573,7 @@ public void testApiKeyWithMinimalRoleCanGetApiKeyInformation() { // request where API key id is missing must fail request = GetApiKeyRequest.usingApiKeyName("test key"); PlainActionFuture failureListener = new PlainActionFuture<>(); - securityClient.getApiKey(request, failureListener); + 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); } @@ -584,10 +586,10 @@ public void testCreateApiKeyAuthorization() { // 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))); - SecurityClient securityClient = new SecurityClient(client); + RoleDescriptor roleDescriptor = new RoleDescriptor("role", new String[] { "monitor" }, null, null); ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class, - () -> securityClient.prepareCreateApiKey().setName("test-key-" + randomAlphaOfLengthBetween(5, 9)) + () -> 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"); } @@ -600,15 +602,15 @@ public void testGetApiKeyAuthorization() throws InterruptedException, ExecutionE { final Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken .basicAuthHeaderValue("user_with_manage_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); - final SecurityClient securityClient = new SecurityClient(client); + PlainActionFuture listener = new PlainActionFuture<>(); - securityClient.getApiKey(GetApiKeyRequest.usingApiKeyId(userWithManageApiKeyRoleApiKeys.get(0).getId()), listener); + 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<>(); - securityClient.getApiKey(GetApiKeyRequest.usingApiKeyId(userWithOwnerManageApiKeyRoleApiKeys.get(0).getId()), listener); + 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())); @@ -619,15 +621,15 @@ public void testGetApiKeyAuthorization() throws InterruptedException, ExecutionE { final Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken .basicAuthHeaderValue("user_with_owner_manage_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); - final SecurityClient securityClient = new SecurityClient(client); + PlainActionFuture listener = new PlainActionFuture<>(); GetApiKeyRequest getApiKeyRequest = new GetApiKeyRequest("file", "user_with_owner_manage_api_key_role", null, null); - securityClient.getApiKey(getApiKeyRequest, listener); + client.execute(GetApiKeyAction.INSTANCE, getApiKeyRequest, listener); GetApiKeyResponse response = listener.actionGet(); assertThat(response.getApiKeyInfos().length, is(2)); final PlainActionFuture getApiKeyOfOtherUserListener = new PlainActionFuture<>(); - securityClient.getApiKey(GetApiKeyRequest.usingApiKeyId(userWithManageApiKeyRoleApiKeys.get(0).getId()), + client.execute(GetApiKeyAction.INSTANCE, GetApiKeyRequest.usingApiKeyId(userWithManageApiKeyRoleApiKeys.get(0).getId()), getApiKeyOfOtherUserListener); final ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class, () -> getApiKeyOfOtherUserListener.actionGet()); @@ -652,11 +654,11 @@ public void testInvalidateApiKeyAuthorization() throws InterruptedException, Exe { final Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken .basicAuthHeaderValue("user_with_owner_manage_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); - final SecurityClient securityClient = new SecurityClient(client); + final PlainActionFuture listener = new PlainActionFuture<>(); final InvalidateApiKeyRequest invalidateApiKeyRequest = new InvalidateApiKeyRequest("file", "user_with_owner_manage_api_key_role", null, userWithOwnerManageApiKeyRoleApiKeys.get(1).getName()); - securityClient.invalidateApiKey(invalidateApiKeyRequest, listener); + client.execute(InvalidateApiKeyAction.INSTANCE, invalidateApiKeyRequest, listener); invalidateApiKeyResponse = listener.actionGet(); verifyInvalidateResponse(1, Collections.singletonList(userWithOwnerManageApiKeyRoleApiKeys.get(1)), invalidateApiKeyResponse); @@ -697,18 +699,17 @@ private InvalidateApiKeyResponse invalidateApiKey(String executeActionAsUser, St String apiKeyId) { Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken.basicAuthHeaderValue(executeActionAsUser, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); - SecurityClient securityClient = new SecurityClient(client); PlainActionFuture listener = new PlainActionFuture<>(); if (Strings.hasText(realmName) && Strings.hasText(userName)) { - securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingRealmAndUserName(realmName, userName), listener); + client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingRealmAndUserName(realmName, userName), listener); } else if (Strings.hasText(realmName)) { - securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingRealmName(realmName), listener); + client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingRealmName(realmName), listener); } else if (Strings.hasText(userName)) { - securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingUserName(userName), listener); + client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingUserName(userName), listener); } else if (Strings.hasText(apiKeyName)) { - securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyName(apiKeyName), listener); + client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingApiKeyName(apiKeyName), listener); } else if (Strings.hasText(apiKeyId)) { - securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyId(apiKeyId), listener); + 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/authz/AuthorizationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java index dd653bb423e7a..0d01a01bcf1d7 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 @@ -115,7 +115,7 @@ 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.RenderableConditionalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; 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; @@ -315,11 +315,11 @@ public void testAuthorizeUsingConditionalPrivileges() throws IOException { final DeletePrivilegesRequest request = new DeletePrivilegesRequest(); final Authentication authentication = createAuthentication(new User("user1", "role1")); - final RenderableConditionalClusterPrivilege conditionalClusterPrivilege = Mockito.mock(RenderableConditionalClusterPrivilege.class); + final ConditionalClusterPrivilege conditionalClusterPrivilege = Mockito.mock(ConditionalClusterPrivilege.class); final BiPredicate requestPredicate = (r, a) -> r == request; Mockito.when(conditionalClusterPrivilege.getRequestPredicate()).thenReturn(requestPredicate); Mockito.when(conditionalClusterPrivilege.getPrivilege()).thenReturn(ClusterPrivilege.MANAGE_SECURITY); - final RenderableConditionalClusterPrivilege[] conditionalClusterPrivileges = new RenderableConditionalClusterPrivilege[] { + final ConditionalClusterPrivilege[] conditionalClusterPrivileges = new ConditionalClusterPrivilege[] { conditionalClusterPrivilege }; final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); @@ -336,11 +336,11 @@ public void testAuthorizationDeniedWhenConditionalPrivilegesDoNotMatch() throws final DeletePrivilegesRequest request = new DeletePrivilegesRequest(); final Authentication authentication = createAuthentication(new User("user1", "role1")); - final RenderableConditionalClusterPrivilege conditionalClusterPrivilege = Mockito.mock(RenderableConditionalClusterPrivilege.class); + final ConditionalClusterPrivilege conditionalClusterPrivilege = Mockito.mock(ConditionalClusterPrivilege.class); final BiPredicate requestPredicate = (r, a) -> false; Mockito.when(conditionalClusterPrivilege.getRequestPredicate()).thenReturn(requestPredicate); Mockito.when(conditionalClusterPrivilege.getPrivilege()).thenReturn(ClusterPrivilege.MANAGE_SECURITY); - final RenderableConditionalClusterPrivilege[] conditionalClusterPrivileges = new RenderableConditionalClusterPrivilege[] { + final ConditionalClusterPrivilege[] conditionalClusterPrivileges = new ConditionalClusterPrivilege[] { conditionalClusterPrivilege }; final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); 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 38bc716a96b3f..13e4c3d788094 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,7 +22,7 @@ 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.RenderableConditionalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.ManageApplicationPrivileges; import org.elasticsearch.xpack.core.security.support.MetadataUtils; import org.hamcrest.Matchers; @@ -72,7 +72,7 @@ public void testToString() { .build() }; - final RenderableConditionalClusterPrivilege[] conditionalClusterPrivileges = new RenderableConditionalClusterPrivilege[]{ + final ConditionalClusterPrivilege[] conditionalClusterPrivileges = new ConditionalClusterPrivilege[]{ new ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02"))) }; @@ -104,7 +104,7 @@ public void testToXContent() throws Exception { .resources("*") .build() }; - final RenderableConditionalClusterPrivilege[] conditionalClusterPrivileges = { + final ConditionalClusterPrivilege[] conditionalClusterPrivileges = { new ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02"))) }; @@ -189,8 +189,8 @@ public void testParse() throws Exception { assertThat(rd.getApplicationPrivileges()[1].getApplication(), equalTo("app2")); assertThat(rd.getConditionalClusterPrivileges(), Matchers.arrayWithSize(1)); - final RenderableConditionalClusterPrivilege conditionalPrivilege = rd.getConditionalClusterPrivileges()[0]; - assertThat(conditionalPrivilege.getCategory(), equalTo(RenderableConditionalClusterPrivilege.Category.APPLICATION)); + final ConditionalClusterPrivilege conditionalPrivilege = rd.getConditionalClusterPrivileges()[0]; + assertThat(conditionalPrivilege.getCategory(), equalTo(ConditionalClusterPrivilege.Category.APPLICATION)); assertThat(conditionalPrivilege, instanceOf(ManageApplicationPrivileges.class)); assertThat(((ManageApplicationPrivileges) conditionalPrivilege).getApplicationNames(), containsInAnyOrder("kibana", "logstash")); @@ -233,7 +233,7 @@ public void testSerializationForCurrentVersion() throws Exception { .resources("*") .build() }; - final RenderableConditionalClusterPrivilege[] conditionalClusterPrivileges = { + final ConditionalClusterPrivilege[] conditionalClusterPrivileges = { new ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02"))) }; 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 1e29422388c60..5d9b7d84f40b9 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 @@ -42,7 +42,7 @@ 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.RenderableConditionalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege; import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore; import org.elasticsearch.xpack.core.security.authz.store.RoleRetrievalResult; @@ -543,7 +543,7 @@ public void testMergingBasicRoles() { final TransportRequest request2 = mock(TransportRequest.class); final TransportRequest request3 = mock(TransportRequest.class); - RenderableConditionalClusterPrivilege ccp1 = mock(RenderableConditionalClusterPrivilege.class); + ConditionalClusterPrivilege ccp1 = mock(ConditionalClusterPrivilege.class); when(ccp1.getPrivilege()).thenReturn(ClusterPrivilege.MANAGE_SECURITY); when(ccp1.getRequestPredicate()).thenReturn((req, authn) -> req == request1); RoleDescriptor role1 = new RoleDescriptor("r1", new String[]{"monitor"}, new IndicesPrivileges[]{ @@ -566,10 +566,10 @@ public void testMergingBasicRoles() { .resources("settings/*") .privileges("read") .build() - }, new RenderableConditionalClusterPrivilege[] { ccp1 }, + }, new ConditionalClusterPrivilege[] { ccp1 }, new String[]{"app-user-1"}, null, null); - RenderableConditionalClusterPrivilege ccp2 = mock(RenderableConditionalClusterPrivilege.class); + ConditionalClusterPrivilege ccp2 = mock(ConditionalClusterPrivilege.class); when(ccp2.getPrivilege()).thenReturn(ClusterPrivilege.MANAGE_SECURITY); when(ccp2.getRequestPredicate()).thenReturn((req, authn) -> req == request2); RoleDescriptor role2 = new RoleDescriptor("r2", new String[]{"manage_saml"}, new IndicesPrivileges[]{ @@ -588,7 +588,7 @@ public void testMergingBasicRoles() { .resources("*") .privileges("read") .build() - }, new RenderableConditionalClusterPrivilege[] { ccp2 }, + }, new ConditionalClusterPrivilege[] { ccp2 }, new String[]{"app-user-2"}, null, null); FieldPermissionsCache cache = new FieldPermissionsCache(Settings.EMPTY); 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 3a285d906d851..285ef707b4257 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 @@ -23,7 +23,7 @@ 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.ManageApplicationPrivileges; -import org.elasticsearch.xpack.core.security.authz.privilege.RenderableConditionalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; import java.util.Arrays; import java.util.Collections; @@ -55,7 +55,7 @@ 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( + 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"), From 8f6caadeaaf709d448a13b2596de4273dfc2aa1a Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Mon, 3 Jun 2019 18:20:49 +1000 Subject: [PATCH 13/25] revert unwanted change --- .../xpack/core/security/authz/RoleDescriptor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 07056864fd33c..15304ff85dbd9 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 @@ -329,8 +329,8 @@ public static RoleDescriptor parse(String name, XContentParser parser, boolean a } } return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, applicationPrivileges, - conditionalClusterPrivileges.toArray(new ConditionalClusterPrivilege[conditionalClusterPrivileges.size()]), - runAsUsers, metadata, null); + conditionalClusterPrivileges.toArray(new ConditionalClusterPrivilege[conditionalClusterPrivileges.size()]), runAsUsers, + metadata, null); } private static String[] readStringArray(String roleName, XContentParser parser, boolean allowNull) throws IOException { From 04fe24d8c48d4877579cc7aa1b6df6ff80e8890e Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Mon, 3 Jun 2019 19:47:28 +1000 Subject: [PATCH 14/25] fix checkstyle errors --- .../core/security/authz/privilege/ClusterPrivilege.java | 7 ++++--- .../xpack/security/authc/ApiKeyIntegTests.java | 6 ++++-- 2 files changed, 8 insertions(+), 5 deletions(-) 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 5d0f0bd09ebc9..5f5fa8c782ae6 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 @@ -227,9 +227,10 @@ private static Tuple> re } else { throw new IllegalArgumentException("unknown cluster privilege [" + name + "]. a privilege must be either " + "one of the predefined fixed cluster privileges [" + - Strings.collectionToCommaDelimitedString(VALUES.entrySet()) + "], default conditional cluster privileges [" + - Strings.collectionToCommaDelimitedString(DefaultConditionalClusterPrivilege.names()) + - "] or a pattern over one of the available cluster actions"); + Strings.collectionToCommaDelimitedString(VALUES.entrySet()) + "], " + + "predefined fixed conditional cluster privileges [" + + Strings.collectionToCommaDelimitedString(DefaultConditionalClusterPrivilege.names()) + "] " + + "or a pattern over one of the available cluster actions"); } } } 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 6424b408f77a1..1498d4d0ef55c 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 @@ -604,13 +604,15 @@ public void testGetApiKeyAuthorization() throws InterruptedException, ExecutionE .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); + 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); + 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())); From 8952ffdaec85b0cf65309903d445f58695ab81c7 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Tue, 4 Jun 2019 12:25:11 +1000 Subject: [PATCH 15/25] add test, javadocs, cleanup --- .../authz/privilege/ClusterPrivilege.java | 16 ++++++++++---- .../ConditionalClusterPrivilege.java | 2 +- ...nageApiKeyConditionalClusterPrivilege.java | 20 +++++++++++++---- .../PlainConditionalClusterPrivilege.java | 8 +++---- .../user/GetUserPrivilegesResponseTests.java | 6 +++-- .../authz/privilege/PrivilegeTests.java | 22 +++++++++++++++++++ .../xpack/security/authc/ApiKeyService.java | 7 +++--- .../xpack/security/authz/RBACEngine.java | 3 ++- .../user/RestGetUserPrivilegesAction.java | 2 +- 9 files changed, 66 insertions(+), 20 deletions(-) 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 5f5fa8c782ae6..8cbec8700a0f3 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 @@ -122,7 +122,7 @@ public enum DefaultConditionalClusterPrivilege { private final PlainConditionalClusterPrivilege conditionalClusterPrivilege; private final String privilegeName; - DefaultConditionalClusterPrivilege(String privilegeName, PlainConditionalClusterPrivilege conditionalClusterPrivilege) { + DefaultConditionalClusterPrivilege(final String privilegeName, final PlainConditionalClusterPrivilege conditionalClusterPrivilege) { this.privilegeName = privilegeName; this.conditionalClusterPrivilege = conditionalClusterPrivilege; } @@ -140,7 +140,7 @@ public static DefaultConditionalClusterPrivilege fromString(String privilegeName return value.get(); } - public static String name(PlainConditionalClusterPrivilege ccp) { + public static String privilegeName(PlainConditionalClusterPrivilege ccp) { Optional value = Arrays.stream(values()) .filter(dccp -> dccp.conditionalClusterPrivilege.equals(ccp)).findAny(); if (value.isEmpty()) { @@ -194,6 +194,14 @@ private ClusterPrivilege(Set name, Automaton automaton) { super(name, automaton); } + /** + * For given set of privilege names returns a tuple of {@link ClusterPrivilege} and set of predefined fixed conditional cluster + * privileges {@link PlainConditionalClusterPrivilege} + * + * @param name set of predefined names in {@link #VALUES} or {@link DefaultConditionalClusterPrivilege} or a valid cluster action + * @return a {@link Tuple} of {@link ClusterPrivilege} and set of predefined fixed conditional cluster privileges + * {@link PlainConditionalClusterPrivilege} + */ public static Tuple> get(final Set name) { if (name == null || name.isEmpty()) { return new Tuple>(NONE, Collections.emptySet()); @@ -239,7 +247,7 @@ private static Tuple> re if (actions.isEmpty() == false) { automata.add(patterns(actions)); } - return new Tuple>( - new ClusterPrivilege(name, Automatons.unionAndMinimize(automata)), conditionalClusterPrivileges); + final ClusterPrivilege clusterPrivilege = new ClusterPrivilege(name, Automatons.unionAndMinimize(automata)); + return new Tuple>(clusterPrivilege, conditionalClusterPrivileges); } } 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 36bde6432dfc6..4808c8435548a 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 @@ -15,7 +15,7 @@ import java.util.Collection; /** - * Conditional Cluster privileges that can be serialized / rendered as `XContent` + * A ConditionalClusterPrivilege is a {@link PlainConditionalClusterPrivilege} that can be serialized / rendered as `XContent`. */ public interface ConditionalClusterPrivilege extends NamedWriteable, ToXContentFragment, PlainConditionalClusterPrivilege { 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 index 7e92b004cba87..256ef58562eea 100644 --- 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 @@ -26,10 +26,11 @@ */ public final class ManageApiKeyConditionalClusterPrivilege implements PlainConditionalClusterPrivilege { + 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(CREATE_API_KEY_PATTERN, GET_API_KEY_PATTERN, + 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 Set realms; @@ -39,11 +40,22 @@ public final class ManageApiKeyConditionalClusterPrivilege implements PlainCondi private final ClusterPrivilege privilege; private final BiPredicate requestPredicate; + /** + * Constructor for {@link ManageApiKeyConditionalClusterPrivilege} + * + * @param actions set of API key cluster actions + * @param realms set of realm names such that the privilege to manage API keys is restricted for given realm names.
+ * `_self` can be used to restrict access to the authenticated user's realm.
+ * `*` can be used to allow all realms. + * @param users set of user names such that the privilege to manage API keys is restricted for given users.
+ * `_self` can be used to restrict access to the authenticated user.
+ * `*` can be used to allow all users. + */ public ManageApiKeyConditionalClusterPrivilege(Set actions, Set realms, Set users) { // validate allowed actions for (String action : actions) { if (ClusterPrivilege.MANAGE_API_KEY.predicate().test(action) == false) { - throw new IllegalArgumentException("invalid action [ " + action + " ] specified, expected API key privilege actions [ " + throw new IllegalArgumentException("invalid action [ " + action + " ] specified, expected API key privilege actions from [ " + API_KEY_ACTION_PATTERNS + " ]"); } } @@ -57,12 +69,12 @@ public ManageApiKeyConditionalClusterPrivilege(Set actions, Set if (this.realms.contains("_self") && this.users.contains("_self") == false || this.users.contains("_self") && this.realms.contains("_self") == false) { throw new IllegalArgumentException( - "both realms and users must contain only `_self` when restricting access of API keys to owner"); + "both realms and users must contain only `_self` when restricting access of API keys to the owner"); } if (this.realms.contains("_self") && this.users.contains("_self")) { if (this.realms.size() > 1 || this.users.size() > 1) { throw new IllegalArgumentException( - "both realms and users must contain only `_self` when restricting access of API keys to owner"); + "both realms and users must contain only `_self` when restricting access of API keys to the owner"); } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/PlainConditionalClusterPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/PlainConditionalClusterPrivilege.java index 0433d94b900f2..8ab3084fd0dc5 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/PlainConditionalClusterPrivilege.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/PlainConditionalClusterPrivilege.java @@ -10,12 +10,12 @@ import org.elasticsearch.xpack.core.security.authc.Authentication; import java.util.function.BiPredicate; -import java.util.function.Predicate; /** - * A ConditionalClusterPrivilege is a composition of a {@link ClusterPrivilege} (that determines which actions may be executed) - * with a {@link Predicate} for a {@link TransportRequest} (that determines which requests may be executed). - * The a given execution of an action is considered to be permitted if both the action and the request are permitted. + * A PlainConditionalClusterPrivilege is a composition of a {@link ClusterPrivilege} (that determines which actions may be executed) with a + * {@link BiPredicate} for a {@link TransportRequest} (that determines which requests may be executed) and a {@link Authentication} (for + * current authenticated user). The a given execution of an action is considered to be permitted if both the action and the request are + * permitted in the context of given authentication. */ public interface PlainConditionalClusterPrivilege { 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 64d3d9d2510ad..2300235bd85fb 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 @@ -103,8 +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, ConditionalClusterPrivilege[]::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/authz/privilege/PrivilegeTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/PrivilegeTests.java index e9150f6830717..7511cbe78a706 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 @@ -6,6 +6,7 @@ package org.elasticsearch.xpack.core.security.authz.privilege; import org.apache.lucene.util.automaton.Operations; +import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.core.security.support.Automatons; @@ -204,4 +205,25 @@ public void testIlmPrivileges() { assertThat(predicate.test("indices:admin/whatever"), is(false)); } } + + public void testClusterPrivilegeAndPlainConditionalClusterPrivilege() { + Set actionName = Sets.newHashSet("cluster:admin/snapshot/delete", "create_get_own_api_key"); + Tuple> tuple = ClusterPrivilege.get(actionName); + ClusterPrivilege cluster = tuple.v1(); + Set plainConditionalClusterPrivilege = tuple.v2(); + assertThat(cluster, notNullValue()); + assertThat(cluster.predicate().test("cluster:admin/snapshot/delete"), is(true)); + assertThat(cluster.predicate().test("cluster:admin/snapshot/dele"), is(false)); + assertThat(plainConditionalClusterPrivilege, notNullValue()); + assertThat(plainConditionalClusterPrivilege.size(), is(1)); + PlainConditionalClusterPrivilege createGetOwnApiKeyConditionalPrivilege = plainConditionalClusterPrivilege.stream().findFirst() + .get(); + assertThat(createGetOwnApiKeyConditionalPrivilege.getPrivilege().predicate().test("cluster:admin/xpack/security/api_key/create"), + is(true)); + assertThat(createGetOwnApiKeyConditionalPrivilege.getPrivilege().predicate().test("cluster:admin/xpack/security/api_key/get"), + is(true)); + assertThat( + createGetOwnApiKeyConditionalPrivilege.getPrivilege().predicate().test("cluster:admin/xpack/security/api_key/invalidate"), + is(false)); + } } 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 f95a938454ae0..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 @@ -923,9 +923,10 @@ public static final class InvalidateApiKeysResult { public InvalidateApiKeysResult(List invalidatedApiKeys, List previouslyInvalidatedApiKeys, List errors) { - this.invalidatedApiKeys = invalidatedApiKeys; - this.previouslyInvalidatedApiKeys = previouslyInvalidatedApiKeys; - this.errors = 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() { 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 c8aae17c9d19f..99e4781d26492 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 @@ -436,7 +436,8 @@ GetUserPrivilegesResponse buildUserPrivilegesResponseObject(Role userRole) { } else if (tup.v2() instanceof ConditionalClusterPrivilege) { conditionalCluster.add((ConditionalClusterPrivilege) tup.v2()); } else { - cluster.add(ClusterPrivilege.DefaultConditionalClusterPrivilege.name(tup.v2())); + // non renderable predefined conditional cluster privilege names + cluster.add(ClusterPrivilege.DefaultConditionalClusterPrivilege.privilegeName(tup.v2())); } } 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 865348f4569f6..1f32c866cb3c8 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.ConditionalClusterPrivileges; import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler; From 108278cfed2a035695770511e09f54f7604df557 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Tue, 11 Jun 2019 13:36:29 +1000 Subject: [PATCH 16/25] address review comments - remove unwanted api conditional cluster privileges --- .../authz/privilege/ClusterPrivilege.java | 12 +- ...nageApiKeyConditionalClusterPrivilege.java | 55 ++------ ...piKeyConditionalClusterPrivilegeTests.java | 129 +----------------- .../authz/privilege/PrivilegeTests.java | 12 +- .../security/authc/ApiKeyIntegTests.java | 4 - 5 files changed, 24 insertions(+), 188 deletions(-) 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 8cbec8700a0f3..e97b413877d09 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 @@ -108,16 +108,8 @@ public final class ClusterPrivilege extends Privilege { public enum DefaultConditionalClusterPrivilege { MANAGE_OWN_API_KEY("manage_own_api_key", new ManageApiKeyConditionalClusterPrivilege( Set.of("cluster:admin/xpack/security/api_key/*"), - Set.of("_self"), - Set.of("_self"))), - CREATE_GET_OWN_API_KEY("create_get_own_api_key", new ManageApiKeyConditionalClusterPrivilege( - Set.of("cluster:admin/xpack/security/api_key/create", "cluster:admin/xpack/security/api_key/get"), - Set.of("_self"), - Set.of("_self"))), - GET_API_KEY("get_api_key", new ManageApiKeyConditionalClusterPrivilege( - Set.of("cluster:admin/xpack/security/api_key/get"), - Set.of("*"), - Set.of("*"))); + true)) + ; private final PlainConditionalClusterPrivilege conditionalClusterPrivilege; private final String privilegeName; 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 index 256ef58562eea..6ac3465fb8a2f 100644 --- 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 @@ -12,14 +12,11 @@ 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.Collections; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.function.BiPredicate; -import java.util.function.Predicate; /** * Conditional cluster privilege for managing API keys @@ -33,10 +30,7 @@ public final class ManageApiKeyConditionalClusterPrivilege implements PlainCondi 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 Set realms; - private final Predicate realmsPredicate; - private final Set users; - private final Predicate usersPredicate; + private final boolean restrictActionsToAuthenticatedUser; private final ClusterPrivilege privilege; private final BiPredicate requestPredicate; @@ -44,14 +38,9 @@ public final class ManageApiKeyConditionalClusterPrivilege implements PlainCondi * Constructor for {@link ManageApiKeyConditionalClusterPrivilege} * * @param actions set of API key cluster actions - * @param realms set of realm names such that the privilege to manage API keys is restricted for given realm names.
- * `_self` can be used to restrict access to the authenticated user's realm.
- * `*` can be used to allow all realms. - * @param users set of user names such that the privilege to manage API keys is restricted for given users.
- * `_self` can be used to restrict access to the authenticated user.
- * `*` can be used to allow all users. + * @param restrictActionsToAuthenticatedUser if {@code true} privileges will be restricted to current authenticated user. */ - public ManageApiKeyConditionalClusterPrivilege(Set actions, Set realms, Set users) { + public ManageApiKeyConditionalClusterPrivilege(Set actions, boolean restrictActionsToAuthenticatedUser) { // validate allowed actions for (String action : actions) { if (ClusterPrivilege.MANAGE_API_KEY.predicate().test(action) == false) { @@ -60,44 +49,26 @@ public ManageApiKeyConditionalClusterPrivilege(Set actions, Set } } this.privilege = ClusterPrivilege.get(actions).v1(); - - this.realms = (realms == null) ? Collections.emptySet() : Set.copyOf(realms); - this.realmsPredicate = Automatons.predicate(this.realms); - this.users = (users == null) ? Collections.emptySet() : Set.copyOf(users); - this.usersPredicate = Automatons.predicate(this.users); - - if (this.realms.contains("_self") && this.users.contains("_self") == false - || this.users.contains("_self") && this.realms.contains("_self") == false) { - throw new IllegalArgumentException( - "both realms and users must contain only `_self` when restricting access of API keys to the owner"); - } - if (this.realms.contains("_self") && this.users.contains("_self")) { - if (this.realms.size() > 1 || this.users.size() > 1) { - throw new IllegalArgumentException( - "both realms and users must contain only `_self` when restricting access of API keys to the owner"); - } - } + 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.realms.contains("_self") && this.users.contains("_self")) { + if (this.restrictActionsToAuthenticatedUser) { return checkIfUserIsOwnerOfApiKeys(authentication, getApiKeyRequest.getApiKeyId(), getApiKeyRequest.getUserName(), getApiKeyRequest.getRealmName()); } else { - return checkIfAccessAllowed(realms, getApiKeyRequest.getRealmName(), realmsPredicate) - && checkIfAccessAllowed(users, getApiKeyRequest.getUserName(), usersPredicate); + return true; } } else if (request instanceof InvalidateApiKeyRequest) { final InvalidateApiKeyRequest invalidateApiKeyRequest = (InvalidateApiKeyRequest) request; - if (this.realms.contains("_self") && this.users.contains("_self")) { + if (this.restrictActionsToAuthenticatedUser) { return checkIfUserIsOwnerOfApiKeys(authentication, invalidateApiKeyRequest.getId(), invalidateApiKeyRequest.getUserName(), invalidateApiKeyRequest.getRealmName()); } else { - return checkIfAccessAllowed(realms, invalidateApiKeyRequest.getRealmName(), realmsPredicate) - && checkIfAccessAllowed(users, invalidateApiKeyRequest.getUserName(), usersPredicate); + return true; } } return false; @@ -121,10 +92,6 @@ private boolean checkIfUserIsOwnerOfApiKeys(Authentication authentication, Strin return false; } - private static boolean checkIfAccessAllowed(Set names, String requestName, Predicate predicate) { - return (Strings.hasText(requestName) == false) ? names.contains("*") : predicate.test(requestName); - } - @Override public ClusterPrivilege getPrivilege() { return privilege; @@ -137,7 +104,7 @@ public BiPredicate getRequestPredicate() { @Override public int hashCode() { - return Objects.hash(privilege, users, realms); + return Objects.hash(privilege, restrictActionsToAuthenticatedUser); } @Override @@ -149,8 +116,8 @@ public boolean equals(Object o) { return false; } final ManageApiKeyConditionalClusterPrivilege that = (ManageApiKeyConditionalClusterPrivilege) o; - return Objects.equals(this.privilege, that.privilege) && Objects.equals(this.realms, that.realms) - && Objects.equals(this.users, that.users); + return Objects.equals(this.privilege, that.privilege) + && Objects.equals(this.restrictActionsToAuthenticatedUser, that.restrictActionsToAuthenticatedUser); } } 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 index 013d8a25eaed2..d71997d3770ec 100644 --- 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 @@ -63,54 +63,6 @@ public void testManageAllPrivilege() { assertThat(accessAllowed, is(true)); } - public void testManagePrivilegeRestrictedForRealmsAndUsers() { - final ManageApiKeyConditionalClusterPrivilege condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.builder().allowCreate() - .allowGet().allowInvalidate().forRealms("realm1", "realm2").forUsers("user1", "user2").build(); - - boolean accessAllowed = checkAccess(condPrivilege, CREATE_ACTION, new CreateApiKeyRequest(), authentication); - assertThat(accessAllowed, is(true)); - - accessAllowed = checkAccess(condPrivilege, GET_ACTION, GetApiKeyRequest.usingRealmAndUserName("realm1", "user1"), authentication); - assertThat(accessAllowed, is(true)); - - accessAllowed = checkAccess(condPrivilege, GET_ACTION, - GetApiKeyRequest.usingRealmAndUserName(randomAlphaOfLength(7), randomAlphaOfLength(7)), authentication); - assertThat(accessAllowed, is(false)); - - accessAllowed = checkAccess(condPrivilege, INVALIDATE_ACTION, InvalidateApiKeyRequest.usingRealmAndUserName("realm2", "user2"), - authentication); - assertThat(accessAllowed, is(true)); - - accessAllowed = checkAccess(condPrivilege, INVALIDATE_ACTION, - InvalidateApiKeyRequest.usingRealmAndUserName(randomAlphaOfLength(7), randomAlphaOfLength(7)), authentication); - assertThat(accessAllowed, is(false)); - - } - - public void testManagePrivilegeRestrictedReadOnlyForRealmsAndUsers() { - final ManageApiKeyConditionalClusterPrivilege condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.builder().allowGet() - .forRealms("realm1", "realm2").forUsers("user1", "user2").build(); - - boolean accessAllowed = checkAccess(condPrivilege, CREATE_ACTION, new CreateApiKeyRequest(), authentication); - assertThat(accessAllowed, is(false)); - - accessAllowed = checkAccess(condPrivilege, GET_ACTION, GetApiKeyRequest.usingRealmAndUserName("realm1", "user1"), authentication); - assertThat(accessAllowed, is(true)); - - accessAllowed = checkAccess(condPrivilege, GET_ACTION, - GetApiKeyRequest.usingRealmAndUserName(randomAlphaOfLength(7), randomAlphaOfLength(7)), authentication); - assertThat(accessAllowed, is(false)); - - accessAllowed = checkAccess(condPrivilege, INVALIDATE_ACTION, InvalidateApiKeyRequest.usingRealmAndUserName("realm2", "user2"), - authentication); - assertThat(accessAllowed, is(false)); - - accessAllowed = checkAccess(condPrivilege, INVALIDATE_ACTION, - InvalidateApiKeyRequest.usingRealmAndUserName(randomAlphaOfLength(7), randomAlphaOfLength(7)), authentication); - assertThat(accessAllowed, is(false)); - - } - public void testManagePrivilegeOwnerOnly() { final ManageApiKeyConditionalClusterPrivilege condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.manageApiKeysOnlyForOwner(); @@ -161,56 +113,6 @@ public void testManagePrivilegeOwnerOnly() { assertThat(accessAllowed, is(false)); } - public void testManagePrivilegeOwnerAndReadOnly() { - final ManageApiKeyConditionalClusterPrivilege condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.builder().allowGet() - .forRealms("_self").forUsers("_self").build(); - - boolean accessAllowed = checkAccess(condPrivilege, CREATE_ACTION, new CreateApiKeyRequest(), authentication); - assertThat(accessAllowed, is(false)); - - // 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(false)); - 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(false)); - 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.getPrivilege().predicate().test(action) && privilege.getRequestPredicate().test(request, authentication); @@ -218,8 +120,7 @@ private boolean checkAccess(ManageApiKeyConditionalClusterPrivilege privilege, S public static class ManageApiKeyConditionalPrivilegesBuilder { private Set actions = new HashSet<>(); - private Set realms; - private Set users; + private boolean restrictActionsToAuthenticatedUser; public ManageApiKeyConditionalPrivilegesBuilder allowCreate() { actions.add(CREATE_ACTION); @@ -231,28 +132,8 @@ public ManageApiKeyConditionalPrivilegesBuilder allowGet() { return this; } - public ManageApiKeyConditionalPrivilegesBuilder allowInvalidate() { - actions.add(INVALIDATE_ACTION); - return this; - } - - public ManageApiKeyConditionalPrivilegesBuilder allowAllRealms() { - this.realms = Set.of("*"); - return this; - } - - public ManageApiKeyConditionalPrivilegesBuilder allowAllUsers() { - this.users = Set.of("*"); - return this; - } - - public ManageApiKeyConditionalPrivilegesBuilder forRealms(String... realms) { - this.realms = Set.of(realms); - return this; - } - - public ManageApiKeyConditionalPrivilegesBuilder forUsers(String... users) { - this.users = Set.of(users); + public ManageApiKeyConditionalPrivilegesBuilder restrictActionsToAuthenticatedUser() { + this.restrictActionsToAuthenticatedUser = true; return this; } @@ -261,7 +142,7 @@ public static ManageApiKeyConditionalPrivilegesBuilder builder() { } public static ManageApiKeyConditionalClusterPrivilege manageApiKeysUnrestricted() { - return new ManageApiKeyConditionalClusterPrivilege(Set.of("cluster:admin/xpack/security/api_key/*"), Set.of("*"), Set.of("*")); + return new ManageApiKeyConditionalClusterPrivilege(Set.of("cluster:admin/xpack/security/api_key/*"), false); } public static ManageApiKeyConditionalClusterPrivilege manageApiKeysOnlyForOwner() { @@ -270,7 +151,7 @@ public static ManageApiKeyConditionalClusterPrivilege manageApiKeysOnlyForOwner( } public ManageApiKeyConditionalClusterPrivilege build() { - return new ManageApiKeyConditionalClusterPrivilege(actions, realms, users); + return new ManageApiKeyConditionalClusterPrivilege(actions, restrictActionsToAuthenticatedUser); } } } 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 7511cbe78a706..5e6903ed948d5 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 @@ -207,7 +207,7 @@ public void testIlmPrivileges() { } public void testClusterPrivilegeAndPlainConditionalClusterPrivilege() { - Set actionName = Sets.newHashSet("cluster:admin/snapshot/delete", "create_get_own_api_key"); + Set actionName = Sets.newHashSet("cluster:admin/snapshot/delete", "manage_own_api_key"); Tuple> tuple = ClusterPrivilege.get(actionName); ClusterPrivilege cluster = tuple.v1(); Set plainConditionalClusterPrivilege = tuple.v2(); @@ -216,14 +216,14 @@ public void testClusterPrivilegeAndPlainConditionalClusterPrivilege() { assertThat(cluster.predicate().test("cluster:admin/snapshot/dele"), is(false)); assertThat(plainConditionalClusterPrivilege, notNullValue()); assertThat(plainConditionalClusterPrivilege.size(), is(1)); - PlainConditionalClusterPrivilege createGetOwnApiKeyConditionalPrivilege = plainConditionalClusterPrivilege.stream().findFirst() + PlainConditionalClusterPrivilege manageOwnApiKeysConditionalClusterPrivilege = plainConditionalClusterPrivilege.stream().findFirst() .get(); - assertThat(createGetOwnApiKeyConditionalPrivilege.getPrivilege().predicate().test("cluster:admin/xpack/security/api_key/create"), + assertThat(manageOwnApiKeysConditionalClusterPrivilege.getPrivilege().predicate().test("cluster:admin/xpack/security/api_key/create"), is(true)); - assertThat(createGetOwnApiKeyConditionalPrivilege.getPrivilege().predicate().test("cluster:admin/xpack/security/api_key/get"), + assertThat(manageOwnApiKeysConditionalClusterPrivilege.getPrivilege().predicate().test("cluster:admin/xpack/security/api_key/get"), is(true)); assertThat( - createGetOwnApiKeyConditionalPrivilege.getPrivilege().predicate().test("cluster:admin/xpack/security/api_key/invalidate"), - is(false)); + manageOwnApiKeysConditionalClusterPrivilege.getPrivilege().predicate().test("cluster:admin/xpack/security/api_key/invalidate"), + is(true)); } } 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 1498d4d0ef55c..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 @@ -97,8 +97,6 @@ public String configRoles() { " cluster: [\"manage_api_key\"]\n" + "manage_own_api_key_role:\n" + " cluster: [\"manage_own_api_key\"]\n" + - "only_create_get_own_api_key_role:\n" + - " cluster: [\"create_get_own_api_key\"]\n" + "no_manage_api_key_role:\n" + " indices:\n" + " - names: '*'\n" + @@ -112,7 +110,6 @@ public String configUsers() { getFastStoredHashAlgoForTests().hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)); return super.configUsers() + "user_with_manage_api_key_role:" + usersPasswdHashed + "\n" + - "user_with_only_create_get_api_key_role:" + usersPasswdHashed + "\n" + "user_with_owner_manage_api_key_role:" + usersPasswdHashed + "\n" + "user_with_no_manage_api_key_role:" + usersPasswdHashed + "\n"; } @@ -121,7 +118,6 @@ public String configUsers() { public String configUsersRoles() { return super.configUsersRoles() + "manage_api_key_role:user_with_manage_api_key_role\n" + - "only_create_get_own_api_key_role:user_with_only_create_get_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"; } From 07bb1171cfdad975ebe809a3bb9ec688b935ec51 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Tue, 11 Jun 2019 16:01:25 +1000 Subject: [PATCH 17/25] change ConditionalClusterPrivilege to GlobalClusterPrivilege and PlainCCP to just CCP --- .../example/CustomAuthorizationEngine.java | 4 +- .../xpack/core/XPackClientPlugin.java | 4 +- .../security/action/role/PutRoleRequest.java | 8 +-- .../user/GetUserPrivilegesResponse.java | 8 +-- .../core/security/authz/RoleDescriptor.java | 16 +++--- .../authz/permission/ClusterPermission.java | 14 +++--- .../core/security/authz/permission/Role.java | 10 ++-- .../authz/privilege/ClusterPrivilege.java | 26 +++++----- .../ConditionalClusterPrivilege.java | 42 +++++----------- .../ConditionalClusterPrivileges.java | 32 ++++++------ .../privilege/GlobalClusterPrivilege.java | 49 +++++++++++++++++++ ...nageApiKeyConditionalClusterPrivilege.java | 2 +- .../ManageApplicationPrivileges.java | 4 +- .../PlainConditionalClusterPrivilege.java | 33 ------------- .../authz/store/ReservedRolesStore.java | 4 +- .../user/GetUserPrivilegesResponseTests.java | 6 +-- .../ConditionalClusterPrivilegesTests.java | 14 +++--- .../authz/privilege/PrivilegeTests.java | 6 +-- .../xpack/security/authz/RBACEngine.java | 12 ++--- .../authz/store/CompositeRolesStore.java | 4 +- .../user/RestGetUserPrivilegesAction.java | 4 +- .../authz/AuthorizationServiceTests.java | 10 ++-- .../security/authz/RoleDescriptorTests.java | 12 ++--- .../authz/store/CompositeRolesStoreTests.java | 10 ++-- .../RestGetUserPrivilegesActionTests.java | 4 +- 25 files changed, 169 insertions(+), 169 deletions(-) create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/GlobalClusterPrivilege.java delete mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/PlainConditionalClusterPrivilege.java 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 4fdf0962fa98f..adece231cff4b 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 @@ -169,7 +169,7 @@ 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.ManageApplicationPrivileges; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; +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; @@ -385,7 +385,7 @@ 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, + new NamedWriteableRegistry.Entry(GlobalClusterPrivilege.class, ManageApplicationPrivileges.WRITEABLE_NAME, ManageApplicationPrivileges::createFrom), // security : role-mappings 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..f98829fa2ccf0 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,7 +15,7 @@ import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges; import org.elasticsearch.xpack.core.security.support.MetadataUtils; @@ -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; } 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..ff550bab03cbf 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,7 +15,7 @@ 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.GlobalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges; import java.io.IOException; @@ -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; } 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..9bfaaea8924fc 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,7 +23,7 @@ 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.GlobalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges; 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,7 +93,7 @@ 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) { @@ -133,7 +133,7 @@ public String[] getClusterPrivileges() { return this.clusterPrivileges; } - public ConditionalClusterPrivilege[] getConditionalClusterPrivileges() { + public GlobalClusterPrivilege[] getConditionalClusterPrivileges() { return this.conditionalClusterPrivileges; } @@ -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; @@ -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 100087ab3e662..532befd6dc7ae 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 @@ -10,7 +10,7 @@ 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.PlainConditionalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; import java.util.Collection; import java.util.Collections; @@ -39,7 +39,7 @@ 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 @@ -61,7 +61,7 @@ public boolean check(String action, TransportRequest request, Authentication aut } @Override - public List> privileges() { + public List> privileges() { return Collections.singletonList(new Tuple<>(super.privilege, null)); } } @@ -70,9 +70,9 @@ public List> privilege * A permission that makes use of both cluster privileges and request inspection */ public static class ConditionalClusterPermission extends ClusterPermission { - private final PlainConditionalClusterPrivilege conditionalPrivilege; + private final ConditionalClusterPrivilege conditionalPrivilege; - public ConditionalClusterPermission(PlainConditionalClusterPrivilege conditionalPrivilege) { + public ConditionalClusterPermission(ConditionalClusterPrivilege conditionalPrivilege) { super(conditionalPrivilege.getPrivilege()); this.conditionalPrivilege = conditionalPrivilege; } @@ -83,7 +83,7 @@ public boolean check(String action, TransportRequest request, Authentication aut } @Override - public List> privileges() { + public List> privileges() { return Collections.singletonList(new Tuple<>(super.privilege, conditionalPrivilege)); } } @@ -109,7 +109,7 @@ private static ClusterPrivilege buildPrivilege(Collection chi } @Override - public List> privileges() { + public List> privileges() { return children.stream().map(ClusterPermission::privileges).flatMap(List::stream).collect(Collectors.toList()); } 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 8bbee6f1bb2c3..1e765d35978fa 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 @@ -18,7 +18,7 @@ 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.PlainConditionalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.Privilege; @@ -211,16 +211,16 @@ 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) { - Tuple> privileges = ClusterPrivilege.get(privilegeNames); + Tuple> privileges = ClusterPrivilege.get(privilegeNames); clusterPermissions.add(new ClusterPermission.SimpleClusterPermission(privileges.v1())); - for (PlainConditionalClusterPrivilege ccp : privileges.v2()) { + for (ConditionalClusterPrivilege ccp : privileges.v2()) { clusterPermissions.add(new ClusterPermission.ConditionalClusterPermission(ccp)); } } - for (PlainConditionalClusterPrivilege ccp : conditionalClusterPrivileges) { + for (ConditionalClusterPrivilege 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 e97b413877d09..ae7c1836d0975 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 @@ -101,7 +101,7 @@ public final class ClusterPrivilege extends Privilege { public static final Predicate ACTION_MATCHER = ClusterPrivilege.ALL.predicate(); - private static final ConcurrentHashMap, Tuple>> CACHE = + private static final ConcurrentHashMap, Tuple>> CACHE = new ConcurrentHashMap<>(); /* Conditional Cluster Privileges */ @@ -111,15 +111,15 @@ public enum DefaultConditionalClusterPrivilege { true)) ; - private final PlainConditionalClusterPrivilege conditionalClusterPrivilege; + private final ConditionalClusterPrivilege conditionalClusterPrivilege; private final String privilegeName; - DefaultConditionalClusterPrivilege(final String privilegeName, final PlainConditionalClusterPrivilege conditionalClusterPrivilege) { + DefaultConditionalClusterPrivilege(final String privilegeName, final ConditionalClusterPrivilege conditionalClusterPrivilege) { this.privilegeName = privilegeName; this.conditionalClusterPrivilege = conditionalClusterPrivilege; } - public PlainConditionalClusterPrivilege conditionalClusterPrivilege() { + public ConditionalClusterPrivilege conditionalClusterPrivilege() { return conditionalClusterPrivilege; } @@ -132,7 +132,7 @@ public static DefaultConditionalClusterPrivilege fromString(String privilegeName return value.get(); } - public static String privilegeName(PlainConditionalClusterPrivilege ccp) { + public static String privilegeName(ConditionalClusterPrivilege ccp) { Optional value = Arrays.stream(values()) .filter(dccp -> dccp.conditionalClusterPrivilege.equals(ccp)).findAny(); if (value.isEmpty()) { @@ -188,20 +188,20 @@ private ClusterPrivilege(Set name, Automaton automaton) { /** * For given set of privilege names returns a tuple of {@link ClusterPrivilege} and set of predefined fixed conditional cluster - * privileges {@link PlainConditionalClusterPrivilege} + * privileges {@link ConditionalClusterPrivilege} * * @param name set of predefined names in {@link #VALUES} or {@link DefaultConditionalClusterPrivilege} or a valid cluster action * @return a {@link Tuple} of {@link ClusterPrivilege} and set of predefined fixed conditional cluster privileges - * {@link PlainConditionalClusterPrivilege} + * {@link ConditionalClusterPrivilege} */ - public static Tuple> get(final Set name) { + public static Tuple> get(final Set name) { if (name == null || name.isEmpty()) { - return new Tuple>(NONE, Collections.emptySet()); + return new Tuple>(NONE, Collections.emptySet()); } return CACHE.computeIfAbsent(name, ClusterPrivilege::resolve); } - private static Tuple> resolve(Set name) { + private static Tuple> resolve(Set name) { final int size = name.size(); if (size == 0) { throw new IllegalArgumentException("empty set should not be used"); @@ -209,7 +209,7 @@ private static Tuple> re Set actions = new HashSet<>(); Set automata = new HashSet<>(); - Set conditionalClusterPrivileges = new HashSet<>(); + Set conditionalClusterPrivileges = new HashSet<>(); for (String part : name) { part = part.toLowerCase(Locale.ROOT); if (ACTION_MATCHER.test(part)) { @@ -217,7 +217,7 @@ private static Tuple> re } else { ClusterPrivilege privilege = VALUES.get(part); if (privilege != null && size == 1) { - return new Tuple>(privilege, Collections.emptySet()); + return new Tuple>(privilege, Collections.emptySet()); } else if (privilege != null) { automata.add(privilege.automaton); } else { @@ -240,6 +240,6 @@ private static Tuple> re automata.add(patterns(actions)); } final ClusterPrivilege clusterPrivilege = new ClusterPrivilege(name, Automatons.unionAndMinimize(automata)); - return new Tuple>(clusterPrivilege, conditionalClusterPrivileges); + return new Tuple>(clusterPrivilege, conditionalClusterPrivileges); } } 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 4808c8435548a..84ee2ded7112a 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,44 +6,28 @@ 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.elasticsearch.xpack.core.security.authc.Authentication; -import java.io.IOException; -import java.util.Collection; +import java.util.function.BiPredicate; /** - * A ConditionalClusterPrivilege is a {@link PlainConditionalClusterPrivilege} that can be serialized / rendered as `XContent`. + * A ConditionalClusterPrivilege is a composition of a {@link ClusterPrivilege} (that determines which actions may be executed) with a + * {@link BiPredicate} for a {@link TransportRequest} (that determines which requests may be executed) and a {@link Authentication} (for + * current authenticated user). The a given execution of an action is considered to be permitted if both the action and the request are + * permitted in the context of given authentication. */ -public interface ConditionalClusterPrivilege extends NamedWriteable, ToXContentFragment, PlainConditionalClusterPrivilege { +public interface ConditionalClusterPrivilege { /** - * The category under which this privilege should be rendered when output as XContent. + * The action-level privilege that is required by this conditional privilege. */ - Category getCategory(); + ClusterPrivilege getPrivilege(); /** - * A {@link PlainConditionalClusterPrivilege} 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). + * 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 - XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException; + BiPredicate getRequestPredicate(); - /** - * 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 PlainConditionalClusterPrivilege} 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; - } - } } 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 index 71ad9fd6ceded..bb1212324597d 100644 --- 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 @@ -15,7 +15,7 @@ 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.ConditionalClusterPrivilege.Category; +import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivilege.Category; import java.io.IOException; import java.util.ArrayList; @@ -24,44 +24,44 @@ import java.util.List; /** - * Static utility class for working with {@link ConditionalClusterPrivilege} instances + * Static utility class for working with {@link GlobalClusterPrivilege} instances */ public final class ConditionalClusterPrivileges { - public static final ConditionalClusterPrivilege[] EMPTY_ARRAY = new ConditionalClusterPrivilege[0]; + public static final GlobalClusterPrivilege[] EMPTY_ARRAY = new GlobalClusterPrivilege[0]; - public static final Writeable.Reader READER = - in1 -> in1.readNamedWriteable(ConditionalClusterPrivilege.class); - public static final Writeable.Writer WRITER = + public static final Writeable.Reader READER = + in1 -> in1.readNamedWriteable(GlobalClusterPrivilege.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} + * Utility method to read an array of {@link GlobalClusterPrivilege} objects from a {@link StreamInput} */ - public static ConditionalClusterPrivilege[] readArray(StreamInput in) throws IOException { - return in.readArray(READER, ConditionalClusterPrivilege[]::new); + public static GlobalClusterPrivilege[] readArray(StreamInput in) throws IOException { + return in.readArray(READER, GlobalClusterPrivilege[]::new); } /** - * Utility method to write an array of {@link ConditionalClusterPrivilege} objects to a {@link StreamOutput} + * Utility method to write an array of {@link GlobalClusterPrivilege} objects to a {@link StreamOutput} */ - public static void writeArray(StreamOutput out, ConditionalClusterPrivilege[] privileges) throws IOException { + 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 ConditionalClusterPrivilege#getCategory() categories} + * The privileges are grouped according to their {@link GlobalClusterPrivilege#getCategory() categories} */ public static XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params, - Collection privileges) throws IOException { + Collection privileges) throws IOException { builder.startObject(); for (Category category : Category.values()) { builder.startObject(category.field.getPreferredName()); - for (ConditionalClusterPrivilege privilege : privileges) { + for (GlobalClusterPrivilege privilege : privileges) { if (category == privilege.getCategory()) { privilege.toXContent(builder, params); } @@ -75,8 +75,8 @@ public static XContentBuilder toXContent(XContentBuilder builder, ToXContent.Par * 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<>(); + 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) { 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..6b6362aadaba8 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/GlobalClusterPrivilege.java @@ -0,0 +1,49 @@ +/* + * 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.io.stream.NamedWriteable; +import org.elasticsearch.common.xcontent.ToXContentFragment; +import org.elasticsearch.common.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.Collection; + +/** + * A ConditionalClusterPrivilege is a {@link ConditionalClusterPrivilege} that can be serialized / rendered as `XContent`. + */ +public interface GlobalClusterPrivilege extends NamedWriteable, ToXContentFragment, ConditionalClusterPrivilege { + + /** + * The category under which this privilege should be rendered when output as XContent. + */ + Category getCategory(); + + /** + * 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; + } + } +} 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 index 6ac3465fb8a2f..acba90a0cc685 100644 --- 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 @@ -21,7 +21,7 @@ /** * Conditional cluster privilege for managing API keys */ -public final class ManageApiKeyConditionalClusterPrivilege implements PlainConditionalClusterPrivilege { +public final class ManageApiKeyConditionalClusterPrivilege implements 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"; 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 index 6465cdbcfddc9..e8ebbcd4b56b9 100644 --- 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 @@ -29,11 +29,11 @@ import java.util.function.Predicate; /** - * The {@code ManageApplicationPrivileges} privilege is a {@link ConditionalClusterPrivilege} that grants the + * 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 implements ConditionalClusterPrivilege { +public final class ManageApplicationPrivileges implements GlobalClusterPrivilege { private static final ClusterPrivilege PRIVILEGE = ClusterPrivilege.get( Collections.singleton("cluster:admin/xpack/security/privilege/*") diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/PlainConditionalClusterPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/PlainConditionalClusterPrivilege.java deleted file mode 100644 index 8ab3084fd0dc5..0000000000000 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/PlainConditionalClusterPrivilege.java +++ /dev/null @@ -1,33 +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.transport.TransportRequest; -import org.elasticsearch.xpack.core.security.authc.Authentication; - -import java.util.function.BiPredicate; - -/** - * A PlainConditionalClusterPrivilege is a composition of a {@link ClusterPrivilege} (that determines which actions may be executed) with a - * {@link BiPredicate} for a {@link TransportRequest} (that determines which requests may be executed) and a {@link Authentication} (for - * current authenticated user). The a given execution of an action is considered to be permitted if both the action and the request are - * permitted in the context of given authentication. - */ -public interface PlainConditionalClusterPrivilege { - - /** - * The action-level privilege that is required by this conditional privilege. - */ - ClusterPrivilege getPrivilege(); - - /** - * 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/store/ReservedRolesStore.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java index d51546a0da363..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,7 +11,7 @@ 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.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; @@ -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/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 2300235bd85fb..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,7 +21,7 @@ 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.GlobalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.ManageApplicationPrivileges; import java.io.IOException; @@ -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,7 +103,7 @@ 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, + final Set conditionalCluster = Sets.newHashSet(randomArray(3, GlobalClusterPrivilege[]::new, () -> new ManageApplicationPrivileges( randomStringSet(3) ))); 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/ConditionalClusterPrivilegesTests.java index ebcd70869cb02..f94d2a68e35c8 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/ConditionalClusterPrivilegesTests.java @@ -30,12 +30,12 @@ public class ConditionalClusterPrivilegesTests extends ESTestCase { public void testSerialization() throws Exception { - final ConditionalClusterPrivilege[] original = buildSecurityPrivileges(); + final GlobalClusterPrivilege[] original = buildSecurityPrivileges(); try (BytesStreamOutput out = new BytesStreamOutput()) { ConditionalClusterPrivileges.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 = ConditionalClusterPrivileges.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()); + final List original = Arrays.asList(buildSecurityPrivileges()); ConditionalClusterPrivileges.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 = ConditionalClusterPrivileges.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/PrivilegeTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/PrivilegeTests.java index 5e6903ed948d5..1c8779e86233e 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 @@ -208,15 +208,15 @@ public void testIlmPrivileges() { public void testClusterPrivilegeAndPlainConditionalClusterPrivilege() { Set actionName = Sets.newHashSet("cluster:admin/snapshot/delete", "manage_own_api_key"); - Tuple> tuple = ClusterPrivilege.get(actionName); + Tuple> tuple = ClusterPrivilege.get(actionName); ClusterPrivilege cluster = tuple.v1(); - Set plainConditionalClusterPrivilege = tuple.v2(); + Set plainConditionalClusterPrivilege = tuple.v2(); assertThat(cluster, notNullValue()); assertThat(cluster.predicate().test("cluster:admin/snapshot/delete"), is(true)); assertThat(cluster.predicate().test("cluster:admin/snapshot/dele"), is(false)); assertThat(plainConditionalClusterPrivilege, notNullValue()); assertThat(plainConditionalClusterPrivilege.size(), is(1)); - PlainConditionalClusterPrivilege manageOwnApiKeysConditionalClusterPrivilege = plainConditionalClusterPrivilege.stream().findFirst() + ConditionalClusterPrivilege manageOwnApiKeysConditionalClusterPrivilege = plainConditionalClusterPrivilege.stream().findFirst() .get(); assertThat(manageOwnApiKeysConditionalClusterPrivilege.getPrivilege().predicate().test("cluster:admin/xpack/security/api_key/create"), is(true)); 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 99e4781d26492..bc49902d49c93 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 @@ -59,9 +59,9 @@ 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.PlainConditionalClusterPrivilege; -import org.elasticsearch.xpack.core.security.authz.privilege.Privilege; import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.Privilege; +import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivilege; import org.elasticsearch.xpack.core.security.support.Automatons; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; @@ -427,14 +427,14 @@ 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()) { + 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()); } - } else if (tup.v2() instanceof ConditionalClusterPrivilege) { - conditionalCluster.add((ConditionalClusterPrivilege) tup.v2()); + } else if (tup.v2() instanceof GlobalClusterPrivilege) { + conditionalCluster.add((GlobalClusterPrivilege) tup.v2()); } else { // non renderable predefined conditional cluster privilege names cluster.add(ClusterPrivilege.DefaultConditionalClusterPrivilege.privilegeName(tup.v2())); 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 8a715208f8864..7454ec59da55f 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.PlainConditionalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.Privilege; import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore; @@ -350,7 +350,7 @@ public static void buildRoleFromDescriptors(Collection roleDescr } Set clusterPrivileges = new HashSet<>(); - final List conditionalClusterPrivileges = new ArrayList<>(); + final List conditionalClusterPrivileges = new ArrayList<>(); Set runAs = new HashSet<>(); final Map, MergeableIndicesPrivilege> restrictedIndicesPrivilegesMap = new HashMap<>(); final Map, MergeableIndicesPrivilege> indicesPrivilegesMap = new HashMap<>(); 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..9ddda9010d8c5 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,7 +24,7 @@ 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.GlobalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler; @@ -81,7 +81,7 @@ 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()) { + for (GlobalClusterPrivilege ccp : response.getConditionalClusterPrivileges()) { ConditionalClusterPrivileges.toXContent(builder, ToXContent.EMPTY_PARAMS, Collections.singleton(ccp)); } builder.endArray(); 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 24bc096111d8e..6fd2015e7cee8 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 @@ -115,7 +115,7 @@ 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.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; @@ -315,11 +315,11 @@ 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 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[] { + final GlobalClusterPrivilege[] conditionalClusterPrivileges = new GlobalClusterPrivilege[] { conditionalClusterPrivilege }; final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); @@ -336,11 +336,11 @@ 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 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[] { + 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/RoleDescriptorTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RoleDescriptorTests.java index 13e4c3d788094..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,7 +22,7 @@ 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.GlobalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.ManageApplicationPrivileges; import org.elasticsearch.xpack.core.security.support.MetadataUtils; import org.hamcrest.Matchers; @@ -72,7 +72,7 @@ public void testToString() { .build() }; - final ConditionalClusterPrivilege[] conditionalClusterPrivileges = new ConditionalClusterPrivilege[]{ + final GlobalClusterPrivilege[] conditionalClusterPrivileges = new GlobalClusterPrivilege[]{ new ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02"))) }; @@ -104,7 +104,7 @@ public void testToXContent() throws Exception { .resources("*") .build() }; - final ConditionalClusterPrivilege[] conditionalClusterPrivileges = { + final GlobalClusterPrivilege[] conditionalClusterPrivileges = { new ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02"))) }; @@ -189,8 +189,8 @@ 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)); + 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")); @@ -233,7 +233,7 @@ public void testSerializationForCurrentVersion() throws Exception { .resources("*") .build() }; - final ConditionalClusterPrivilege[] conditionalClusterPrivileges = { + final GlobalClusterPrivilege[] conditionalClusterPrivileges = { new ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02"))) }; 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 5d9b7d84f40b9..7c4ce92fcb546 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 @@ -42,7 +42,7 @@ 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.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; @@ -543,7 +543,7 @@ public void testMergingBasicRoles() { final TransportRequest request2 = mock(TransportRequest.class); final TransportRequest request3 = mock(TransportRequest.class); - ConditionalClusterPrivilege ccp1 = mock(ConditionalClusterPrivilege.class); + GlobalClusterPrivilege ccp1 = mock(GlobalClusterPrivilege.class); when(ccp1.getPrivilege()).thenReturn(ClusterPrivilege.MANAGE_SECURITY); when(ccp1.getRequestPredicate()).thenReturn((req, authn) -> req == request1); RoleDescriptor role1 = new RoleDescriptor("r1", new String[]{"monitor"}, new IndicesPrivileges[]{ @@ -566,10 +566,10 @@ 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); + GlobalClusterPrivilege ccp2 = mock(GlobalClusterPrivilege.class); when(ccp2.getPrivilege()).thenReturn(ClusterPrivilege.MANAGE_SECURITY); when(ccp2.getRequestPredicate()).thenReturn((req, authn) -> req == request2); RoleDescriptor role2 = new RoleDescriptor("r2", new String[]{"manage_saml"}, new IndicesPrivileges[]{ @@ -588,7 +588,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); 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 285ef707b4257..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 @@ -23,7 +23,7 @@ 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.ManageApplicationPrivileges; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivilege; import java.util.Arrays; import java.util.Collections; @@ -55,7 +55,7 @@ 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( + 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"), From aa07ee3ae28fae7a11792aba6033676785a42ee5 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Tue, 11 Jun 2019 18:54:30 +1000 Subject: [PATCH 18/25] refactor cluster privilege and extract enum for default cluster privileges --- .../authz/permission/ClusterPermission.java | 6 +- .../core/security/authz/permission/Role.java | 3 +- .../authz/privilege/ClusterPrivilege.java | 223 +----------------- .../privilege/ClusterPrivilegeResolver.java | 97 ++++++++ .../privilege/DefaultClusterPrivilege.java | 136 +++++++++++ .../DefaultConditionalClusterPrivilege.java | 54 +++++ ...nageApiKeyConditionalClusterPrivilege.java | 4 +- .../ManageApplicationPrivileges.java | 2 +- .../user/HasPrivilegesRequestTests.java | 10 +- .../authz/permission/LimitedRoleTests.java | 24 +- ...piKeyConditionalClusterPrivilegeTests.java | 2 +- .../authz/privilege/PrivilegeTests.java | 40 ++-- .../security/authz/AuthorizationService.java | 4 +- .../xpack/security/authz/RBACEngine.java | 8 +- .../authz/AuthorizationServiceTests.java | 6 +- .../authz/AuthorizedIndicesTests.java | 13 +- .../xpack/security/authz/RBACEngineTests.java | 10 +- .../authz/store/CompositeRolesStoreTests.java | 10 +- .../authz/store/FileRolesStoreTests.java | 8 +- 19 files changed, 369 insertions(+), 291 deletions(-) create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolver.java create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/DefaultClusterPrivilege.java create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/DefaultConditionalClusterPrivilege.java 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 532befd6dc7ae..9f5e3427d065f 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 @@ -10,7 +10,9 @@ 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.ClusterPrivilegeResolver; import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; +import org.elasticsearch.xpack.core.security.authz.privilege.DefaultClusterPrivilege; import java.util.Collection; import java.util.Collections; @@ -46,7 +48,7 @@ public boolean grants(ClusterPrivilege clusterPrivilege) { */ public static class SimpleClusterPermission extends ClusterPermission { - public static final SimpleClusterPermission NONE = new SimpleClusterPermission(ClusterPrivilege.NONE); + public static final SimpleClusterPermission NONE = new SimpleClusterPermission(DefaultClusterPrivilege.NONE.clusterPrivilege()); private final Predicate predicate; @@ -105,7 +107,7 @@ private static ClusterPrivilege buildPrivilege(Collection chi .map(ClusterPrivilege::name) .flatMap(Set::stream) .collect(Collectors.toSet()); - return ClusterPrivilege.get(names).v1(); + return ClusterPrivilegeResolver.resolve(names).v1(); } @Override 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 1e765d35978fa..4258e6ddba788 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 @@ -18,6 +18,7 @@ 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.ClusterPrivilegeResolver; import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.Privilege; @@ -214,7 +215,7 @@ private Builder(RoleDescriptor rd, @Nullable FieldPermissionsCache fieldPermissi public Builder cluster(Set privilegeNames, Iterable conditionalClusterPrivileges) { List clusterPermissions = new ArrayList<>(); if (privilegeNames.isEmpty() == false) { - Tuple> privileges = ClusterPrivilege.get(privilegeNames); + Tuple> privileges = ClusterPrivilegeResolver.resolve(privilegeNames); clusterPermissions.add(new ClusterPermission.SimpleClusterPermission(privileges.v1())); for (ConditionalClusterPrivilege ccp : privileges.v2()) { clusterPermissions.add(new ClusterPermission.ConditionalClusterPermission(ccp)); 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 ae7c1836d0975..3f38e84ff9965 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,240 +6,23 @@ 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.common.collect.Tuple; -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.Arrays; import java.util.Collections; -import java.util.HashSet; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -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 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 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); - - 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_API_KEY = new ClusterPrivilege("manage_api_key", MANAGE_API_KEY_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 ConcurrentHashMap, Tuple>> CACHE = - new ConcurrentHashMap<>(); - - /* Conditional Cluster Privileges */ - public enum DefaultConditionalClusterPrivilege { - MANAGE_OWN_API_KEY("manage_own_api_key", new ManageApiKeyConditionalClusterPrivilege( - Set.of("cluster:admin/xpack/security/api_key/*"), - true)) - ; - - private final ConditionalClusterPrivilege conditionalClusterPrivilege; - private final String privilegeName; - - DefaultConditionalClusterPrivilege(final String privilegeName, final ConditionalClusterPrivilege conditionalClusterPrivilege) { - this.privilegeName = privilegeName; - this.conditionalClusterPrivilege = conditionalClusterPrivilege; - } - - public ConditionalClusterPrivilege conditionalClusterPrivilege() { - return conditionalClusterPrivilege; - } - public static DefaultConditionalClusterPrivilege fromString(String privilegeName) { - Optional value = Arrays.stream(values()) - .filter(dccp -> dccp.privilegeName.equals(privilegeName)).findAny(); - if (value.isEmpty()) { - return null; - } - return value.get(); - } - - public static String privilegeName(ConditionalClusterPrivilege ccp) { - Optional value = Arrays.stream(values()) - .filter(dccp -> dccp.conditionalClusterPrivilege.equals(ccp)).findAny(); - if (value.isEmpty()) { - return null; - } - return value.get().privilegeName; - } - - public static Set names() { - return Arrays.stream(values()).map(dccp -> dccp.privilegeName).collect(Collectors.toSet()); - } - } - - 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_api_key", MANAGE_API_KEY), - 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 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); } - /** - * For given set of privilege names returns a tuple of {@link ClusterPrivilege} and set of predefined fixed conditional cluster - * privileges {@link ConditionalClusterPrivilege} - * - * @param name set of predefined names in {@link #VALUES} or {@link DefaultConditionalClusterPrivilege} or a valid cluster action - * @return a {@link Tuple} of {@link ClusterPrivilege} and set of predefined fixed conditional cluster privileges - * {@link ConditionalClusterPrivilege} - */ - public static Tuple> get(final Set name) { - if (name == null || name.isEmpty()) { - return new Tuple>(NONE, Collections.emptySet()); - } - return CACHE.computeIfAbsent(name, ClusterPrivilege::resolve); - } - - private static Tuple> 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<>(); - Set conditionalClusterPrivileges = 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 new Tuple>(privilege, Collections.emptySet()); - } else if (privilege != null) { - automata.add(privilege.automaton); - } else { - DefaultConditionalClusterPrivilege dccp = DefaultConditionalClusterPrivilege.fromString(part); - if (dccp != null) { - conditionalClusterPrivileges.add(dccp.conditionalClusterPrivilege); - } else { - throw new IllegalArgumentException("unknown cluster privilege [" + name + "]. a privilege must be either " + - "one of the predefined fixed cluster privileges [" + - Strings.collectionToCommaDelimitedString(VALUES.entrySet()) + "], " + - "predefined fixed conditional cluster privileges [" + - Strings.collectionToCommaDelimitedString(DefaultConditionalClusterPrivilege.names()) + "] " + - "or a pattern over one of the available cluster actions"); - } - } - } - } - - if (actions.isEmpty() == false) { - automata.add(patterns(actions)); - } - final ClusterPrivilege clusterPrivilege = new ClusterPrivilege(name, Automatons.unionAndMinimize(automata)); - return new Tuple>(clusterPrivilege, conditionalClusterPrivileges); - } } 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..81341f629efec --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolver.java @@ -0,0 +1,97 @@ +/* + * 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.Strings; +import org.elasticsearch.common.collect.Tuple; +import org.elasticsearch.xpack.core.security.support.Automatons; + +import java.util.Collections; +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 Tuple} of {@link ClusterPrivilege} or + * {@link ConditionalClusterPrivilege} + */ +public final class ClusterPrivilegeResolver { + + public static final Predicate ACTION_MATCHER = DefaultClusterPrivilege.ALL.predicate(); + + private static final ConcurrentHashMap, Tuple>> CACHE = + new ConcurrentHashMap<>(); + + /** + * For given set of privilege names returns a tuple of {@link ClusterPrivilege} and set of predefined fixed conditional cluster + * privileges {@link ConditionalClusterPrivilege} + * + * @param name set of predefined names in {@link DefaultClusterPrivilege} or {@link DefaultConditionalClusterPrivilege} or a valid + * cluster action + * @return a {@link Tuple} of {@link ClusterPrivilege} and set of predefined fixed conditional cluster privileges + * {@link ConditionalClusterPrivilege} + */ + public static Tuple> resolve(final Set name) { + if (name == null || name.isEmpty()) { + return new Tuple>(DefaultClusterPrivilege.NONE.clusterPrivilege(), + Collections.emptySet()); + } + return CACHE.computeIfAbsent(name, ClusterPrivilegeResolver::resolveI); + } + + private static Tuple> resolveI(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<>(); + Set conditionalClusterPrivileges = new HashSet<>(); + for (String part : name) { + part = part.toLowerCase(Locale.ROOT); + if (ACTION_MATCHER.test(part)) { + actions.add(actionToPattern(part)); + } else { + DefaultClusterPrivilege privilege = DefaultClusterPrivilege.fromString(part); + if (privilege != null && size == 1) { + return new Tuple>(privilege.clusterPrivilege(), + Collections.emptySet()); + } else if (privilege != null) { + automata.add(privilege.automaton()); + } else { + DefaultConditionalClusterPrivilege dccp = DefaultConditionalClusterPrivilege.fromString(part); + if (dccp != null) { + conditionalClusterPrivileges.add(dccp.conditionalClusterPrivilege); + } else { + throw new IllegalArgumentException("unknown cluster privilege [" + name + "]. a privilege must be either " + + "one of the predefined fixed cluster privileges [" + + Strings.collectionToCommaDelimitedString(DefaultClusterPrivilege.names()) + "], " + + "predefined fixed conditional cluster privileges [" + + Strings.collectionToCommaDelimitedString(DefaultConditionalClusterPrivilege.names()) + "] " + + "or a pattern over one of the available cluster actions"); + } + } + } + } + + if (actions.isEmpty() == false) { + automata.add(patterns(actions)); + } + final ClusterPrivilege clusterPrivilege = new ClusterPrivilege(name, Automatons.unionAndMinimize(automata)); + return new Tuple>(clusterPrivilege, conditionalClusterPrivileges); + } + + static String actionToPattern(String text) { + return text + "*"; + } +} 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..88b619bc1d727 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/DefaultClusterPrivilege.java @@ -0,0 +1,136 @@ +/* + * 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 java.util.function.Predicate; + +import static org.elasticsearch.xpack.core.security.support.Automatons.minusAndMinimize; +import static org.elasticsearch.xpack.core.security.support.Automatons.patterns; + +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), + ; + + 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.clusterPrivilege = new ClusterPrivilege(privilegeName, clusterAction); + this.privilegeName = privilegeName; + } + + DefaultClusterPrivilege(String privilegeName, Automaton automaton) { + this.clusterPrivilege = new ClusterPrivilege(privilegeName, automaton); + this.privilegeName = privilegeName; + } + + public String privilegeName() { + return privilegeName; + } + + public ClusterPrivilege clusterPrivilege() { + return clusterPrivilege; + } + + public Predicate predicate() { + return clusterPrivilege.predicate; + } + + public Automaton automaton() { + return clusterPrivilege.automaton; + } + + 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/DefaultConditionalClusterPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/DefaultConditionalClusterPrivilege.java new file mode 100644 index 0000000000000..a81d9dd3f2dda --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/DefaultConditionalClusterPrivilege.java @@ -0,0 +1,54 @@ +/* + * 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 java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/* Conditional Cluster Privileges */ +public enum DefaultConditionalClusterPrivilege { + MANAGE_OWN_API_KEY("manage_own_api_key", + new ManageApiKeyConditionalClusterPrivilege(Set.of("cluster:admin/xpack/security/api_key/*"), true)); + + final ConditionalClusterPrivilege conditionalClusterPrivilege; + private final String privilegeName; + + private static Map privilegeNameToEnumMap = new HashMap<>(); + private static Map ccpToEnumMap = new HashMap<>(); + static { + for (DefaultConditionalClusterPrivilege privilege : values()) { + privilegeNameToEnumMap.put(privilege.privilegeName, privilege); + ccpToEnumMap.put(privilege.conditionalClusterPrivilege, privilege); + } + } + + DefaultConditionalClusterPrivilege(final String privilegeName, final ConditionalClusterPrivilege conditionalClusterPrivilege) { + this.privilegeName = privilegeName; + this.conditionalClusterPrivilege = conditionalClusterPrivilege; + + } + + public ConditionalClusterPrivilege conditionalClusterPrivilege() { + return conditionalClusterPrivilege; + } + + public static DefaultConditionalClusterPrivilege fromString(String privilegeName) { + return privilegeNameToEnumMap.get(privilegeName); + } + + public static String privilegeName(ConditionalClusterPrivilege ccp) { + if (ccpToEnumMap.containsKey(ccp)) { + return ccpToEnumMap.get(ccp).privilegeName; + } + return null; + } + + public static Set names() { + return privilegeNameToEnumMap.keySet(); + } +} \ No newline at end of file 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 index acba90a0cc685..0d345330c57dd 100644 --- 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 @@ -43,12 +43,12 @@ public final class ManageApiKeyConditionalClusterPrivilege implements Conditiona public ManageApiKeyConditionalClusterPrivilege(Set actions, boolean restrictActionsToAuthenticatedUser) { // validate allowed actions for (String action : actions) { - if (ClusterPrivilege.MANAGE_API_KEY.predicate().test(action) == false) { + 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.privilege = ClusterPrivilege.get(actions).v1(); + this.privilege = ClusterPrivilegeResolver.resolve(actions).v1(); this.restrictActionsToAuthenticatedUser = restrictActionsToAuthenticatedUser; this.requestPredicate = (request, authentication) -> { 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 index e8ebbcd4b56b9..b420ba6ec208e 100644 --- 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 @@ -35,7 +35,7 @@ */ public final class ManageApplicationPrivileges implements GlobalClusterPrivilege { - private static final ClusterPrivilege PRIVILEGE = ClusterPrivilege.get( + private static final ClusterPrivilege PRIVILEGE = ClusterPrivilegeResolver.resolve( Collections.singleton("cluster:admin/xpack/security/privilege/*") ).v1(); public static final String WRITEABLE_NAME = "manage-application-privileges"; 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 45ffdd9d2a017..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 @@ -22,7 +22,7 @@ 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; @@ -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/ManageApiKeyConditionalClusterPrivilegeTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalClusterPrivilegeTests.java index d71997d3770ec..90fd4d69a9a97 100644 --- 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 @@ -146,7 +146,7 @@ public static ManageApiKeyConditionalClusterPrivilege manageApiKeysUnrestricted( } public static ManageApiKeyConditionalClusterPrivilege manageApiKeysOnlyForOwner() { - return (ManageApiKeyConditionalClusterPrivilege) ClusterPrivilege.DefaultConditionalClusterPrivilege.MANAGE_OWN_API_KEY + return (ManageApiKeyConditionalClusterPrivilege) DefaultConditionalClusterPrivilege.MANAGE_OWN_API_KEY .conditionalClusterPrivilege(); } 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 1c8779e86233e..3dd170f349eb0 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 @@ -33,36 +33,36 @@ public void testSubActionPattern() throws Exception { public void testCluster() throws Exception { Set name = Sets.newHashSet("monitor"); - ClusterPrivilege cluster = ClusterPrivilege.get(name).v1(); - assertThat(cluster, is(ClusterPrivilege.MONITOR)); + ClusterPrivilege cluster = ClusterPrivilegeResolver.resolve(name).v1(); + 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).v1(); - assertTrue(Operations.sameLanguage(ClusterPrivilege.ALL.automaton, cluster.automaton)); + cluster = ClusterPrivilegeResolver.resolve(name).v1(); + assertTrue(Operations.sameLanguage(DefaultClusterPrivilege.ALL.automaton(), cluster.automaton)); name = Sets.newHashSet("monitor", "none"); - cluster = ClusterPrivilege.get(name).v1(); - assertTrue(Operations.sameLanguage(ClusterPrivilege.MONITOR.automaton, cluster.automaton)); + cluster = ClusterPrivilegeResolver.resolve(name).v1(); + assertTrue(Operations.sameLanguage(DefaultClusterPrivilege.MONITOR.automaton(), cluster.automaton)); Set name2 = Sets.newHashSet("none", "monitor"); - ClusterPrivilege cluster2 = ClusterPrivilege.get(name2).v1(); + ClusterPrivilege cluster2 = ClusterPrivilegeResolver.resolve(name2).v1(); assertThat(cluster, is(cluster2)); } public void testClusterTemplateActions() throws Exception { Set name = Sets.newHashSet("indices:admin/template/delete"); - ClusterPrivilege cluster = ClusterPrivilege.get(name).v1(); + ClusterPrivilege cluster = ClusterPrivilegeResolver.resolve(name).v1(); assertThat(cluster, notNullValue()); assertThat(cluster.predicate().test("indices:admin/template/delete"), is(true)); name = Sets.newHashSet("indices:admin/template/get"); - cluster = ClusterPrivilege.get(name).v1(); + cluster = ClusterPrivilegeResolver.resolve(name).v1(); assertThat(cluster, notNullValue()); assertThat(cluster.predicate().test("indices:admin/template/get"), is(true)); name = Sets.newHashSet("indices:admin/template/put"); - cluster = ClusterPrivilege.get(name).v1(); + cluster = ClusterPrivilegeResolver.resolve(name).v1(); assertThat(cluster, notNullValue()); assertThat(cluster.predicate().test("indices:admin/template/put"), is(true)); } @@ -70,12 +70,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).v1(); + ClusterPrivilege cluster = ClusterPrivilegeResolver.resolve(actionName).v1(); assertThat(cluster, notNullValue()); assertThat(cluster.predicate().test("cluster:admin/snapshot/delete"), is(true)); assertThat(cluster.predicate().test("cluster:admin/snapshot/dele"), is(false)); @@ -145,7 +145,7 @@ public void testSystem() throws Exception { } public void testManageCcrPrivilege() { - Predicate predicate = ClusterPrivilege.MANAGE_CCR.predicate(); + Predicate predicate = DefaultClusterPrivilege.MANAGE_CCR.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)); @@ -154,7 +154,7 @@ public void testManageCcrPrivilege() { public void testIlmPrivileges() { { - Predicate predicate = ClusterPrivilege.MANAGE_ILM.predicate(); + Predicate predicate = DefaultClusterPrivilege.MANAGE_ILM.predicate(); // check cluster actions assertThat(predicate.test("cluster:admin/ilm/delete"), is(true)); assertThat(predicate.test("cluster:admin/ilm/_move/post"), is(true)); @@ -169,7 +169,7 @@ public void testIlmPrivileges() { } { - Predicate predicate = ClusterPrivilege.READ_ILM.predicate(); + Predicate predicate = DefaultClusterPrivilege.READ_ILM.predicate(); // check cluster actions assertThat(predicate.test("cluster:admin/ilm/delete"), is(false)); assertThat(predicate.test("cluster:admin/ilm/_move/post"), is(false)); @@ -208,7 +208,7 @@ public void testIlmPrivileges() { public void testClusterPrivilegeAndPlainConditionalClusterPrivilege() { Set actionName = Sets.newHashSet("cluster:admin/snapshot/delete", "manage_own_api_key"); - Tuple> tuple = ClusterPrivilege.get(actionName); + Tuple> tuple = ClusterPrivilegeResolver.resolve(actionName); ClusterPrivilege cluster = tuple.v1(); Set plainConditionalClusterPrivilege = tuple.v2(); assertThat(cluster, notNullValue()); @@ -218,12 +218,12 @@ public void testClusterPrivilegeAndPlainConditionalClusterPrivilege() { assertThat(plainConditionalClusterPrivilege.size(), is(1)); ConditionalClusterPrivilege manageOwnApiKeysConditionalClusterPrivilege = plainConditionalClusterPrivilege.stream().findFirst() .get(); - assertThat(manageOwnApiKeysConditionalClusterPrivilege.getPrivilege().predicate().test("cluster:admin/xpack/security/api_key/create"), + assertThat( + manageOwnApiKeysConditionalClusterPrivilege.getPrivilege().predicate().test("cluster:admin/xpack/security/api_key/create"), is(true)); assertThat(manageOwnApiKeysConditionalClusterPrivilege.getPrivilege().predicate().test("cluster:admin/xpack/security/api_key/get"), is(true)); - assertThat( - manageOwnApiKeysConditionalClusterPrivilege.getPrivilege().predicate().test("cluster:admin/xpack/security/api_key/invalidate"), - is(true)); + assertThat(manageOwnApiKeysConditionalClusterPrivilege.getPrivilege().predicate() + .test("cluster:admin/xpack/security/api_key/invalidate"), is(true)); } } 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 bc49902d49c93..11ca3a7b3a181 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 @@ -59,9 +59,11 @@ 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.ClusterPrivilegeResolver; import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege; -import org.elasticsearch.xpack.core.security.authz.privilege.Privilege; +import org.elasticsearch.xpack.core.security.authz.privilege.DefaultConditionalClusterPrivilege; 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; import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; @@ -373,7 +375,7 @@ public void checkPrivileges(Authentication authentication, AuthorizationInfo aut Map cluster = new HashMap<>(); for (String checkAction : request.clusterPrivileges()) { - final ClusterPrivilege checkPrivilege = ClusterPrivilege.get(Collections.singleton(checkAction)).v1(); + final ClusterPrivilege checkPrivilege = ClusterPrivilegeResolver.resolve(Collections.singleton(checkAction)).v1(); // should we check for conditional as well? cluster.put(checkAction, userRole.grants(checkPrivilege)); } @@ -437,7 +439,7 @@ GetUserPrivilegesResponse buildUserPrivilegesResponseObject(Role userRole) { conditionalCluster.add((GlobalClusterPrivilege) tup.v2()); } else { // non renderable predefined conditional cluster privilege names - cluster.add(ClusterPrivilege.DefaultConditionalClusterPrivilege.privilegeName(tup.v2())); + cluster.add(DefaultConditionalClusterPrivilege.privilegeName(tup.v2())); } } 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 6fd2015e7cee8..7df765da74c9e 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,7 +114,7 @@ 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.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; @@ -318,7 +318,7 @@ public void testAuthorizeUsingConditionalPrivileges() throws IOException { 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); + Mockito.when(conditionalClusterPrivilege.getPrivilege()).thenReturn(DefaultClusterPrivilege.MANAGE_SECURITY.clusterPrivilege()); final GlobalClusterPrivilege[] conditionalClusterPrivileges = new GlobalClusterPrivilege[] { conditionalClusterPrivilege }; @@ -339,7 +339,7 @@ public void testAuthorizationDeniedWhenConditionalPrivilegesDoNotMatch() throws 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); + Mockito.when(conditionalClusterPrivilege.getPrivilege()).thenReturn(DefaultClusterPrivilege.MANAGE_SECURITY.clusterPrivilege()); final GlobalClusterPrivilege[] conditionalClusterPrivileges = new GlobalClusterPrivilege[] { conditionalClusterPrivilege }; 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 dc93940741a49..f49fa52e1015d 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.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); @@ -729,7 +729,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/store/CompositeRolesStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java index 7c4ce92fcb546..d3862c3e0ecc7 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,7 +41,7 @@ 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.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; @@ -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; @@ -544,7 +544,7 @@ public void testMergingBasicRoles() { final TransportRequest request3 = mock(TransportRequest.class); GlobalClusterPrivilege ccp1 = mock(GlobalClusterPrivilege.class); - when(ccp1.getPrivilege()).thenReturn(ClusterPrivilege.MANAGE_SECURITY); + when(ccp1.getPrivilege()).thenReturn(DefaultClusterPrivilege.MANAGE_SECURITY.clusterPrivilege()); when(ccp1.getRequestPredicate()).thenReturn((req, authn) -> req == request1); RoleDescriptor role1 = new RoleDescriptor("r1", new String[]{"monitor"}, new IndicesPrivileges[]{ IndicesPrivileges.builder() @@ -570,7 +570,7 @@ public void testMergingBasicRoles() { new String[]{"app-user-1"}, null, null); GlobalClusterPrivilege ccp2 = mock(GlobalClusterPrivilege.class); - when(ccp2.getPrivilege()).thenReturn(ClusterPrivilege.MANAGE_SECURITY); + when(ccp2.getPrivilege()).thenReturn(DefaultClusterPrivilege.MANAGE_SECURITY.clusterPrivilege()); when(ccp2.getRequestPredicate()).thenReturn((req, authn) -> req == request2); RoleDescriptor role2 = new RoleDescriptor("r2", new String[]{"manage_saml"}, new IndicesPrivileges[]{ IndicesPrivileges.builder() 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 ebd662f9eafc0..8e30a0054582e 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 @@ -26,7 +26,7 @@ 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; @@ -75,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)); @@ -103,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)); @@ -115,7 +115,7 @@ 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.automaton())); assertThat(role.indices(), notNullValue()); assertThat(role.indices(), is(IndicesPermission.NONE)); assertThat(role.runAs(), is(RunAsPermission.NONE)); From 8aeeafadfa70cb84af98bfdf08c4e3d54f04c9be Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Tue, 11 Jun 2019 21:29:08 +1000 Subject: [PATCH 19/25] add docs --- .../security/authz/privilege/DefaultClusterPrivilege.java | 3 +++ .../authz/privilege/DefaultConditionalClusterPrivilege.java | 4 +++- .../core/security/authz/privilege/GlobalClusterPrivilege.java | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) 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 index 88b619bc1d727..81051b07b2796 100644 --- 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 @@ -27,6 +27,9 @@ 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), diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/DefaultConditionalClusterPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/DefaultConditionalClusterPrivilege.java index a81d9dd3f2dda..350eaec1d9e83 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/DefaultConditionalClusterPrivilege.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/DefaultConditionalClusterPrivilege.java @@ -10,7 +10,9 @@ import java.util.Map; import java.util.Set; -/* Conditional Cluster Privileges */ +/** + * Enum that defines default conditional cluster privileges + */ public enum DefaultConditionalClusterPrivilege { MANAGE_OWN_API_KEY("manage_own_api_key", new ManageApiKeyConditionalClusterPrivilege(Set.of("cluster:admin/xpack/security/api_key/*"), true)); 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 index 6b6362aadaba8..f9b77ca2a2f24 100644 --- 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 @@ -15,7 +15,7 @@ import java.util.Collection; /** - * A ConditionalClusterPrivilege is a {@link ConditionalClusterPrivilege} that can be serialized / rendered as `XContent`. + * A GlobalClusterPrivilege is a {@link ConditionalClusterPrivilege} that can be serialized / rendered as `XContent`. */ public interface GlobalClusterPrivilege extends NamedWriteable, ToXContentFragment, ConditionalClusterPrivilege { From b20f4c3a58f82bca9a02076ec3150bd4864df065 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Tue, 18 Jun 2019 13:27:10 +1000 Subject: [PATCH 20/25] address review comments --- .../security/action/role/PutRoleRequest.java | 8 +-- .../user/GetUserPrivilegesResponse.java | 6 +- .../core/security/authz/RoleDescriptor.java | 12 ++-- .../authz/privilege/ClusterPrivilege.java | 1 - .../privilege/ClusterPrivilegeResolver.java | 28 ++++++++- .../DefaultConditionalClusterPrivilege.java | 14 ++--- .../privilege/GlobalClusterPrivilege.java | 2 +- ...eges.java => GlobalClusterPrivileges.java} | 4 +- ...nageApiKeyConditionalClusterPrivilege.java | 6 +- ...java => GlobalClusterPrivilegesTests.java} | 10 +-- ...piKeyConditionalClusterPrivilegeTests.java | 62 +------------------ .../authz/privilege/PrivilegeTests.java | 21 +++++++ .../user/RestGetUserPrivilegesAction.java | 4 +- 13 files changed, 81 insertions(+), 97 deletions(-) rename x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/{ConditionalClusterPrivileges.java => GlobalClusterPrivileges.java} (98%) rename x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/{ConditionalClusterPrivilegesTests.java => GlobalClusterPrivilegesTests.java} (87%) 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 f98829fa2ccf0..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 @@ -16,7 +16,7 @@ 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.GlobalClusterPrivilege; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges; +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; @@ -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 ff550bab03cbf..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 @@ -16,7 +16,7 @@ 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.GlobalClusterPrivilege; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges; +import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivileges; import java.io.IOException; import java.util.Collection; @@ -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 9bfaaea8924fc..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 @@ -24,7 +24,7 @@ import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.xpack.core.security.authz.privilege.GlobalClusterPrivilege; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges; +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; @@ -100,7 +100,7 @@ public RoleDescriptor(String name, 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() { @@ -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) @@ -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( 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 3f38e84ff9965..0c72c1e16abdd 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 @@ -12,7 +12,6 @@ public final class ClusterPrivilege extends Privilege { - ClusterPrivilege(String name, String... patterns) { super(name, patterns); } 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 index 81341f629efec..930f6361aac3e 100644 --- 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 @@ -45,10 +45,29 @@ public static Tuple> resolve( return new Tuple>(DefaultClusterPrivilege.NONE.clusterPrivilege(), Collections.emptySet()); } - return CACHE.computeIfAbsent(name, ClusterPrivilegeResolver::resolveI); + return CACHE.computeIfAbsent(name, ClusterPrivilegeResolver::resolvePrivileges); } - private static Tuple> resolveI(Set name) { + + /** + * 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 resolveClusterPrivilege(final Set name) { + if (name == null || name.isEmpty()) { + return DefaultClusterPrivilege.NONE.clusterPrivilege(); + } + Tuple> privileges = CACHE.computeIfAbsent(name, ClusterPrivilegeResolver::resolvePrivileges); + if (privileges.v2().isEmpty() == false) { + throw new IllegalArgumentException("set of names must not contain conditional cluster privileges"); + } + return privileges.v1(); + } + + private static Tuple> resolvePrivileges(Set name) { final int size = name.size(); if (size == 0) { throw new IllegalArgumentException("empty set should not be used"); @@ -57,9 +76,11 @@ private static Tuple> resolve Set actions = new HashSet<>(); Set automata = new HashSet<>(); Set conditionalClusterPrivileges = new HashSet<>(); + Set clusterPrivilegeNames = 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); @@ -67,6 +88,7 @@ private static Tuple> resolve return new Tuple>(privilege.clusterPrivilege(), Collections.emptySet()); } else if (privilege != null) { + clusterPrivilegeNames.add(part); automata.add(privilege.automaton()); } else { DefaultConditionalClusterPrivilege dccp = DefaultConditionalClusterPrivilege.fromString(part); @@ -87,7 +109,7 @@ private static Tuple> resolve if (actions.isEmpty() == false) { automata.add(patterns(actions)); } - final ClusterPrivilege clusterPrivilege = new ClusterPrivilege(name, Automatons.unionAndMinimize(automata)); + final ClusterPrivilege clusterPrivilege = new ClusterPrivilege(clusterPrivilegeNames, Automatons.unionAndMinimize(automata)); return new Tuple>(clusterPrivilege, conditionalClusterPrivileges); } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/DefaultConditionalClusterPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/DefaultConditionalClusterPrivilege.java index 350eaec1d9e83..2f3b000074c13 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/DefaultConditionalClusterPrivilege.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/DefaultConditionalClusterPrivilege.java @@ -13,9 +13,8 @@ /** * Enum that defines default conditional cluster privileges */ -public enum DefaultConditionalClusterPrivilege { - MANAGE_OWN_API_KEY("manage_own_api_key", - new ManageApiKeyConditionalClusterPrivilege(Set.of("cluster:admin/xpack/security/api_key/*"), true)); +enum DefaultConditionalClusterPrivilege { + MANAGE_OWN_API_KEY("manage_own_api_key", ManageApiKeyConditionalClusterPrivilege.createOwnerManageApiKeyConditionalClusterPrivilege()); final ConditionalClusterPrivilege conditionalClusterPrivilege; private final String privilegeName; @@ -32,25 +31,24 @@ public enum DefaultConditionalClusterPrivilege { DefaultConditionalClusterPrivilege(final String privilegeName, final ConditionalClusterPrivilege conditionalClusterPrivilege) { this.privilegeName = privilegeName; this.conditionalClusterPrivilege = conditionalClusterPrivilege; - } - public ConditionalClusterPrivilege conditionalClusterPrivilege() { + ConditionalClusterPrivilege conditionalClusterPrivilege() { return conditionalClusterPrivilege; } - public static DefaultConditionalClusterPrivilege fromString(String privilegeName) { + static DefaultConditionalClusterPrivilege fromString(String privilegeName) { return privilegeNameToEnumMap.get(privilegeName); } - public static String privilegeName(ConditionalClusterPrivilege ccp) { + static String privilegeName(ConditionalClusterPrivilege ccp) { if (ccpToEnumMap.containsKey(ccp)) { return ccpToEnumMap.get(ccp).privilegeName; } return null; } - public static Set names() { + static Set names() { return privilegeNameToEnumMap.keySet(); } } \ No newline at end of file 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 index f9b77ca2a2f24..ba55c2335ce7e 100644 --- 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 @@ -33,7 +33,7 @@ public interface GlobalClusterPrivilege extends NamedWriteable, ToXContentFragme /** * Categories exist for to segment privileges for the purposes of rendering to XContent. - * {@link ConditionalClusterPrivileges#toXContent(XContentBuilder, Params, Collection)} builds one XContent + * {@link GlobalClusterPrivileges#toXContent(XContentBuilder, Params, Collection)} builds one XContent * object for a collection of {@link ConditionalClusterPrivilege} instances, with the top level fields built * from the categories. */ 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/GlobalClusterPrivileges.java similarity index 98% rename from x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ConditionalClusterPrivileges.java rename to x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/GlobalClusterPrivileges.java index bb1212324597d..9e741a6ae8e11 100644 --- 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/GlobalClusterPrivileges.java @@ -26,7 +26,7 @@ /** * Static utility class for working with {@link GlobalClusterPrivilege} instances */ -public final class ConditionalClusterPrivileges { +public final class GlobalClusterPrivileges { public static final GlobalClusterPrivilege[] EMPTY_ARRAY = new GlobalClusterPrivilege[0]; @@ -35,7 +35,7 @@ public final class ConditionalClusterPrivileges { public static final Writeable.Writer WRITER = (out1, value) -> out1.writeNamedWriteable(value); - private ConditionalClusterPrivileges() { + private GlobalClusterPrivileges() { } /** 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 index 0d345330c57dd..20f65fe49a588 100644 --- 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 @@ -40,7 +40,7 @@ public final class ManageApiKeyConditionalClusterPrivilege implements Conditiona * @param actions set of API key cluster actions * @param restrictActionsToAuthenticatedUser if {@code true} privileges will be restricted to current authenticated user. */ - public ManageApiKeyConditionalClusterPrivilege(Set actions, boolean restrictActionsToAuthenticatedUser) { + private ManageApiKeyConditionalClusterPrivilege(Set actions, boolean restrictActionsToAuthenticatedUser) { // validate allowed actions for (String action : actions) { if (DefaultClusterPrivilege.MANAGE_API_KEY.clusterPrivilege().predicate().test(action) == false) { @@ -75,6 +75,10 @@ public ManageApiKeyConditionalClusterPrivilege(Set actions, boolean rest }; } + 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 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 87% 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 f94d2a68e35c8..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 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 GlobalClusterPrivilege[] copy = ConditionalClusterPrivileges.readArray(in); + final GlobalClusterPrivilege[] copy = GlobalClusterPrivileges.readArray(in); assertThat(copy, equalTo(original)); assertThat(original, equalTo(copy)); } @@ -48,13 +48,13 @@ public void testGenerateAndParseXContent() throws Exception { final XContentBuilder builder = new XContentBuilder(xContent, out); final List original = Arrays.asList(buildSecurityPrivileges()); - ConditionalClusterPrivileges.toXContent(builder, ToXContent.EMPTY_PARAMS, original); + 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)); } 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 index 90fd4d69a9a97..78ac08a777fe0 100644 --- 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 @@ -15,9 +15,7 @@ import org.elasticsearch.xpack.core.security.user.User; import org.junit.Before; -import java.util.HashSet; import java.util.Map; -import java.util.Set; import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.mock; @@ -41,30 +39,8 @@ public void setup() { when(authenticatedBy.getType()).thenReturn("kerberos"); } - public void testManageAllPrivilege() { - final ManageApiKeyConditionalClusterPrivilege condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.manageApiKeysUnrestricted(); - - boolean accessAllowed = checkAccess(condPrivilege, CREATE_ACTION, new CreateApiKeyRequest(), authentication); - assertThat(accessAllowed, is(true)); - - accessAllowed = checkAccess(condPrivilege, GET_ACTION, - GetApiKeyRequest.usingRealmAndUserName(randomAlphaOfLength(5), randomAlphaOfLength(5)), authentication); - assertThat(accessAllowed, is(true)); - - accessAllowed = checkAccess(condPrivilege, INVALIDATE_ACTION, - InvalidateApiKeyRequest.usingRealmAndUserName(randomAlphaOfLength(5), randomAlphaOfLength(5)), authentication); - assertThat(accessAllowed, is(true)); - - // When request does not have user or realm name and conditional api key privileges for manage is used then it should be denied. - accessAllowed = checkAccess(condPrivilege, GET_ACTION, GetApiKeyRequest.usingApiKeyName(randomAlphaOfLength(5)), authentication); - assertThat(accessAllowed, is(true)); - accessAllowed = checkAccess(condPrivilege, INVALIDATE_ACTION, InvalidateApiKeyRequest.usingApiKeyId(randomAlphaOfLength(5)), - authentication); - assertThat(accessAllowed, is(true)); - } - public void testManagePrivilegeOwnerOnly() { - final ManageApiKeyConditionalClusterPrivilege condPrivilege = ManageApiKeyConditionalPrivilegesBuilder.manageApiKeysOnlyForOwner(); + final ManageApiKeyConditionalClusterPrivilege condPrivilege = ManageApiKeyConditionalClusterPrivilege.createOwnerManageApiKeyConditionalClusterPrivilege(); boolean accessAllowed = checkAccess(condPrivilege, CREATE_ACTION, new CreateApiKeyRequest(), authentication); assertThat(accessAllowed, is(true)); @@ -118,40 +94,4 @@ private boolean checkAccess(ManageApiKeyConditionalClusterPrivilege privilege, S return privilege.getPrivilege().predicate().test(action) && privilege.getRequestPredicate().test(request, authentication); } - public static class ManageApiKeyConditionalPrivilegesBuilder { - private Set actions = new HashSet<>(); - private boolean restrictActionsToAuthenticatedUser; - - public ManageApiKeyConditionalPrivilegesBuilder allowCreate() { - actions.add(CREATE_ACTION); - return this; - } - - public ManageApiKeyConditionalPrivilegesBuilder allowGet() { - actions.add(GET_ACTION); - return this; - } - - public ManageApiKeyConditionalPrivilegesBuilder restrictActionsToAuthenticatedUser() { - this.restrictActionsToAuthenticatedUser = true; - return this; - } - - public static ManageApiKeyConditionalPrivilegesBuilder builder() { - return new ManageApiKeyConditionalPrivilegesBuilder(); - } - - public static ManageApiKeyConditionalClusterPrivilege manageApiKeysUnrestricted() { - return new ManageApiKeyConditionalClusterPrivilege(Set.of("cluster:admin/xpack/security/api_key/*"), false); - } - - public static ManageApiKeyConditionalClusterPrivilege manageApiKeysOnlyForOwner() { - return (ManageApiKeyConditionalClusterPrivilege) DefaultConditionalClusterPrivilege.MANAGE_OWN_API_KEY - .conditionalClusterPrivilege(); - } - - public ManageApiKeyConditionalClusterPrivilege build() { - return new ManageApiKeyConditionalClusterPrivilege(actions, restrictActionsToAuthenticatedUser); - } - } } 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 3dd170f349eb0..c9f782ee0a258 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 @@ -226,4 +226,25 @@ public void testClusterPrivilegeAndPlainConditionalClusterPrivilege() { assertThat(manageOwnApiKeysConditionalClusterPrivilege.getPrivilege().predicate() .test("cluster:admin/xpack/security/api_key/invalidate"), is(true)); } + + public void testConditionalClusterPrivilegesOnly() { + Set actionName = Sets.newHashSet("manage_own_api_key"); + Tuple> tuple = ClusterPrivilegeResolver.resolve(actionName); + ClusterPrivilege cluster = tuple.v1(); + Set conditionalClusterPrivilege = tuple.v2(); + assertThat(cluster, notNullValue()); + assertThat(cluster.predicate().test("cluster:admin/snapshot/delete"), is(false)); + assertThat(cluster.predicate().test("cluster:admin/snapshot/dele"), is(false)); + assertThat(conditionalClusterPrivilege, notNullValue()); + assertThat(conditionalClusterPrivilege.size(), is(1)); + ConditionalClusterPrivilege manageOwnApiKeysConditionalClusterPrivilege = conditionalClusterPrivilege.stream().findFirst() + .get(); + assertThat( + manageOwnApiKeysConditionalClusterPrivilege.getPrivilege().predicate().test("cluster:admin/xpack/security/api_key/create"), + is(true)); + assertThat(manageOwnApiKeysConditionalClusterPrivilege.getPrivilege().predicate().test("cluster:admin/xpack/security/api_key/get"), + is(true)); + assertThat(manageOwnApiKeysConditionalClusterPrivilege.getPrivilege().predicate() + .test("cluster:admin/xpack/security/api_key/invalidate"), is(true)); + } } 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 9ddda9010d8c5..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 @@ -25,7 +25,7 @@ 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.GlobalClusterPrivilege; -import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges; +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; @@ -82,7 +82,7 @@ public RestResponse buildResponse(GetUserPrivilegesResponse response, XContentBu builder.field(RoleDescriptor.Fields.CLUSTER.getPreferredName(), response.getClusterPrivileges()); builder.startArray(RoleDescriptor.Fields.GLOBAL.getPreferredName()); for (GlobalClusterPrivilege ccp : response.getConditionalClusterPrivileges()) { - ConditionalClusterPrivileges.toXContent(builder, ToXContent.EMPTY_PARAMS, Collections.singleton(ccp)); + GlobalClusterPrivileges.toXContent(builder, ToXContent.EMPTY_PARAMS, Collections.singleton(ccp)); } builder.endArray(); From 3a1c3611846110baf3769cdb1faec9c67bf7f35e Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Tue, 18 Jun 2019 13:52:26 +1000 Subject: [PATCH 21/25] remove unwanted code --- .../privilege/ClusterPrivilegeResolver.java | 33 ++++--------------- 1 file changed, 6 insertions(+), 27 deletions(-) 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 index 930f6361aac3e..d87e6f136bd85 100644 --- 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 @@ -48,31 +48,7 @@ public static Tuple> resolve( return CACHE.computeIfAbsent(name, ClusterPrivilegeResolver::resolvePrivileges); } - - /** - * 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 resolveClusterPrivilege(final Set name) { - if (name == null || name.isEmpty()) { - return DefaultClusterPrivilege.NONE.clusterPrivilege(); - } - Tuple> privileges = CACHE.computeIfAbsent(name, ClusterPrivilegeResolver::resolvePrivileges); - if (privileges.v2().isEmpty() == false) { - throw new IllegalArgumentException("set of names must not contain conditional cluster privileges"); - } - return privileges.v1(); - } - private static Tuple> resolvePrivileges(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<>(); Set conditionalClusterPrivileges = new HashSet<>(); @@ -84,7 +60,7 @@ private static Tuple> resolve actions.add(actionToPattern(part)); } else { DefaultClusterPrivilege privilege = DefaultClusterPrivilege.fromString(part); - if (privilege != null && size == 1) { + if (privilege != null && name.size() == 1) { return new Tuple>(privilege.clusterPrivilege(), Collections.emptySet()); } else if (privilege != null) { @@ -109,11 +85,14 @@ private static Tuple> resolve if (actions.isEmpty() == false) { automata.add(patterns(actions)); } - final ClusterPrivilege clusterPrivilege = new ClusterPrivilege(clusterPrivilegeNames, Automatons.unionAndMinimize(automata)); + ClusterPrivilege clusterPrivilege = DefaultClusterPrivilege.NONE.clusterPrivilege(); + if (automata.isEmpty() == false) { + clusterPrivilege = new ClusterPrivilege(clusterPrivilegeNames, Automatons.unionAndMinimize(automata)); + } return new Tuple>(clusterPrivilege, conditionalClusterPrivileges); } - static String actionToPattern(String text) { + private static String actionToPattern(String text) { return text + "*"; } } From e39e263c9e8c4caeab812872e43019b0f74f0cb9 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Tue, 18 Jun 2019 14:34:16 +1000 Subject: [PATCH 22/25] fix code and tests --- .../authz/permission/ClusterPermission.java | 4 ++- .../DefaultConditionalClusterPrivilege.java | 4 +-- .../authz/privilege/PrivilegeTests.java | 32 ++++++++++++++----- .../xpack/security/authz/RBACEngine.java | 16 ++++++++-- .../xpack/security/authz/RBACEngineTests.java | 17 ++++++++++ 5 files changed, 59 insertions(+), 14 deletions(-) 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 9f5e3427d065f..f27f33f0026f1 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 @@ -107,7 +107,9 @@ private static ClusterPrivilege buildPrivilege(Collection chi .map(ClusterPrivilege::name) .flatMap(Set::stream) .collect(Collectors.toSet()); - return ClusterPrivilegeResolver.resolve(names).v1(); + Tuple> resolvedPrivileges = ClusterPrivilegeResolver.resolve(names); + assert resolvedPrivileges.v2().isEmpty(); + return resolvedPrivileges.v1(); } @Override diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/DefaultConditionalClusterPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/DefaultConditionalClusterPrivilege.java index 2f3b000074c13..113de336365e0 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/DefaultConditionalClusterPrivilege.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/DefaultConditionalClusterPrivilege.java @@ -13,7 +13,7 @@ /** * Enum that defines default conditional cluster privileges */ -enum DefaultConditionalClusterPrivilege { +public enum DefaultConditionalClusterPrivilege { MANAGE_OWN_API_KEY("manage_own_api_key", ManageApiKeyConditionalClusterPrivilege.createOwnerManageApiKeyConditionalClusterPrivilege()); final ConditionalClusterPrivilege conditionalClusterPrivilege; @@ -41,7 +41,7 @@ static DefaultConditionalClusterPrivilege fromString(String privilegeName) { return privilegeNameToEnumMap.get(privilegeName); } - static String privilegeName(ConditionalClusterPrivilege ccp) { + public static String privilegeName(ConditionalClusterPrivilege ccp) { if (ccpToEnumMap.containsKey(ccp)) { return ccpToEnumMap.get(ccp).privilegeName; } 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 c9f782ee0a258..510a89f5f1d93 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 @@ -33,38 +33,52 @@ public void testSubActionPattern() throws Exception { public void testCluster() throws Exception { Set name = Sets.newHashSet("monitor"); - ClusterPrivilege cluster = ClusterPrivilegeResolver.resolve(name).v1(); + Tuple> resolvedPrivileges = ClusterPrivilegeResolver.resolve(name); + ClusterPrivilege cluster = resolvedPrivileges.v1(); assertThat(cluster, is(DefaultClusterPrivilege.MONITOR.clusterPrivilege())); + assertThat(resolvedPrivileges.v2().isEmpty(), is(true)); // since "all" implies "monitor", this should be the same language as All name = Sets.newHashSet("monitor", "all"); - cluster = ClusterPrivilegeResolver.resolve(name).v1(); + resolvedPrivileges = ClusterPrivilegeResolver.resolve(name); + cluster = resolvedPrivileges.v1(); assertTrue(Operations.sameLanguage(DefaultClusterPrivilege.ALL.automaton(), cluster.automaton)); + assertThat(resolvedPrivileges.v2().isEmpty(), is(true)); name = Sets.newHashSet("monitor", "none"); - cluster = ClusterPrivilegeResolver.resolve(name).v1(); + resolvedPrivileges = ClusterPrivilegeResolver.resolve(name); + cluster = resolvedPrivileges.v1(); assertTrue(Operations.sameLanguage(DefaultClusterPrivilege.MONITOR.automaton(), cluster.automaton)); + assertThat(resolvedPrivileges.v2().isEmpty(), is(true)); Set name2 = Sets.newHashSet("none", "monitor"); - ClusterPrivilege cluster2 = ClusterPrivilegeResolver.resolve(name2).v1(); + resolvedPrivileges = ClusterPrivilegeResolver.resolve(name2); + ClusterPrivilege cluster2 = resolvedPrivileges.v1(); assertThat(cluster, is(cluster2)); + assertThat(resolvedPrivileges.v2().isEmpty(), is(true)); } public void testClusterTemplateActions() throws Exception { Set name = Sets.newHashSet("indices:admin/template/delete"); - ClusterPrivilege cluster = ClusterPrivilegeResolver.resolve(name).v1(); + Tuple> resolvedPrivileges = ClusterPrivilegeResolver.resolve(name); + ClusterPrivilege cluster = resolvedPrivileges.v1(); assertThat(cluster, notNullValue()); assertThat(cluster.predicate().test("indices:admin/template/delete"), is(true)); + assertThat(resolvedPrivileges.v2().isEmpty(), is(true)); name = Sets.newHashSet("indices:admin/template/get"); - cluster = ClusterPrivilegeResolver.resolve(name).v1(); + resolvedPrivileges = ClusterPrivilegeResolver.resolve(name); + cluster = resolvedPrivileges.v1(); assertThat(cluster, notNullValue()); assertThat(cluster.predicate().test("indices:admin/template/get"), is(true)); + assertThat(resolvedPrivileges.v2().isEmpty(), is(true)); name = Sets.newHashSet("indices:admin/template/put"); - cluster = ClusterPrivilegeResolver.resolve(name).v1(); + resolvedPrivileges = ClusterPrivilegeResolver.resolve(name); + cluster = resolvedPrivileges.v1(); assertThat(cluster, notNullValue()); assertThat(cluster.predicate().test("indices:admin/template/put"), is(true)); + assertThat(resolvedPrivileges.v2().isEmpty(), is(true)); } public void testClusterInvalidName() throws Exception { @@ -75,10 +89,12 @@ public void testClusterInvalidName() throws Exception { public void testClusterAction() throws Exception { Set actionName = Sets.newHashSet("cluster:admin/snapshot/delete"); - ClusterPrivilege cluster = ClusterPrivilegeResolver.resolve(actionName).v1(); + Tuple> resolvedPrivileges = ClusterPrivilegeResolver.resolve(actionName); + ClusterPrivilege cluster = resolvedPrivileges.v1(); assertThat(cluster, notNullValue()); assertThat(cluster.predicate().test("cluster:admin/snapshot/delete"), is(true)); assertThat(cluster.predicate().test("cluster:admin/snapshot/dele"), is(false)); + assertThat(resolvedPrivileges.v2().isEmpty(), is(true)); } public void testIndexAction() throws Exception { 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 11ca3a7b3a181..bb79633ec63ba 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 @@ -375,9 +375,19 @@ public void checkPrivileges(Authentication authentication, AuthorizationInfo aut Map cluster = new HashMap<>(); for (String checkAction : request.clusterPrivileges()) { - final ClusterPrivilege checkPrivilege = ClusterPrivilegeResolver.resolve(Collections.singleton(checkAction)).v1(); - // should we check for conditional as well? - cluster.put(checkAction, userRole.grants(checkPrivilege)); + Tuple> resolvedPrivileges = ClusterPrivilegeResolver.resolve(Collections.singleton(checkAction)); + final ClusterPrivilege checkPrivilege = resolvedPrivileges.v1(); + final Set conditionalClusterPrivileges = resolvedPrivileges.v2(); + boolean granted = userRole.grants(checkPrivilege); + // 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. + for (ConditionalClusterPrivilege conditionalClusterPrivilege : conditionalClusterPrivileges) { + granted = (granted || userRole.grants(conditionalClusterPrivilege.getPrivilege())); + if (granted) { + break; + } + } + cluster.put(checkAction, granted); } boolean allMatch = cluster.values().stream().allMatch(Boolean::booleanValue); ResourcePrivilegesMap.Builder combineIndicesResourcePrivileges = ResourcePrivilegesMap.builder(); 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 f49fa52e1015d..243ee74159234 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 @@ -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/*"); From 9826c81610aff024ac29e83c40e4ad7bee9aec2a Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Tue, 18 Jun 2019 15:34:37 +1000 Subject: [PATCH 23/25] resolve precommit errors --- .../ManageApiKeyConditionalClusterPrivilegeTests.java | 3 ++- .../org/elasticsearch/xpack/security/authz/RBACEngine.java | 3 ++- .../elasticsearch/xpack/security/authz/RBACEngineTests.java | 6 +++--- 3 files changed, 7 insertions(+), 5 deletions(-) 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 index 78ac08a777fe0..c319ef3d4d45e 100644 --- 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 @@ -40,7 +40,8 @@ public void setup() { } public void testManagePrivilegeOwnerOnly() { - final ManageApiKeyConditionalClusterPrivilege condPrivilege = ManageApiKeyConditionalClusterPrivilege.createOwnerManageApiKeyConditionalClusterPrivilege(); + final ManageApiKeyConditionalClusterPrivilege condPrivilege = ManageApiKeyConditionalClusterPrivilege + .createOwnerManageApiKeyConditionalClusterPrivilege(); boolean accessAllowed = checkAccess(condPrivilege, CREATE_ACTION, new CreateApiKeyRequest(), authentication); assertThat(accessAllowed, is(true)); 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 bb79633ec63ba..c26eaaebff68e 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 @@ -375,7 +375,8 @@ public void checkPrivileges(Authentication authentication, AuthorizationInfo aut Map cluster = new HashMap<>(); for (String checkAction : request.clusterPrivileges()) { - Tuple> resolvedPrivileges = ClusterPrivilegeResolver.resolve(Collections.singleton(checkAction)); + Tuple> resolvedPrivileges = ClusterPrivilegeResolver + .resolve(Collections.singleton(checkAction)); final ClusterPrivilege checkPrivilege = resolvedPrivileges.v1(); final Set conditionalClusterPrivileges = resolvedPrivileges.v2(); boolean granted = userRole.grants(checkPrivilege); 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 243ee74159234..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 @@ -730,9 +730,9 @@ public void testCheckingHasPrivilegesWithConditionalClusterPrivilege() throws Ex .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"); + 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)); From d69db4ed414eb160397f1ece0040b208cf8313e6 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Tue, 18 Jun 2019 17:11:42 +1000 Subject: [PATCH 24/25] address intermittent failure --- .../privilege/ManageApiKeyConditionalClusterPrivilege.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 index 20f65fe49a588..9f61bf7bcec7b 100644 --- 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 @@ -12,6 +12,7 @@ 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.Objects; @@ -48,7 +49,7 @@ private ManageApiKeyConditionalClusterPrivilege(Set actions, boolean res + API_KEY_ACTION_PATTERNS + " ]"); } } - this.privilege = ClusterPrivilegeResolver.resolve(actions).v1(); + this.privilege = new ClusterPrivilege(actions, Automatons.patterns(actions)); this.restrictActionsToAuthenticatedUser = restrictActionsToAuthenticatedUser; this.requestPredicate = (request, authentication) -> { From aa14195dcb26e23656ed9295ed378652f19801d9 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Thu, 20 Jun 2019 00:31:05 +1000 Subject: [PATCH 25/25] refactor - to address review comments --- .../authz/permission/ClusterPermission.java | 52 ++++------ .../core/security/authz/permission/Role.java | 13 +-- .../authz/privilege/ClusterPrivilege.java | 4 +- .../privilege/ClusterPrivilegeResolver.java | 58 ++++------- .../ConditionalClusterPrivilege.java | 37 ++++--- .../authz/privilege/ConditionalPrivilege.java | 26 +++++ .../privilege/DefaultClusterPrivilege.java | 22 ++--- .../DefaultConditionalClusterPrivilege.java | 54 ---------- .../privilege/GlobalClusterPrivilege.java | 27 +++-- ...nageApiKeyConditionalClusterPrivilege.java | 33 +++---- .../ManageApplicationPrivileges.java | 12 +-- .../privilege/MergeableClusterPrivilege.java | 62 ++++++++++++ .../ClusterPrivilegeResolverTests.java | 89 +++++++++++++++++ ...piKeyConditionalClusterPrivilegeTests.java | 2 +- .../ManageApplicationPrivilegesTests.java | 8 +- .../MergeableClusterPrivilegeTests.java | 99 +++++++++++++++++++ .../authz/privilege/PrivilegeTests.java | 82 +++++---------- .../xpack/security/authz/RBACEngine.java | 29 ++---- .../authz/store/CompositeRolesStore.java | 8 +- .../authz/AuthorizationServiceTests.java | 6 +- .../authz/store/CompositeRolesStoreTests.java | 6 +- .../authz/store/FileRolesStoreTests.java | 3 +- 22 files changed, 437 insertions(+), 295 deletions(-) create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ConditionalPrivilege.java delete mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/DefaultConditionalClusterPrivilege.java create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/MergeableClusterPrivilege.java create mode 100644 x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolverTests.java create mode 100644 x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/MergeableClusterPrivilegeTests.java 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 f27f33f0026f1..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,19 +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.ClusterPrivilegeResolver; 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; /** @@ -41,7 +38,7 @@ 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 @@ -50,21 +47,22 @@ public static class SimpleClusterPermission extends ClusterPermission { public static final SimpleClusterPermission NONE = new SimpleClusterPermission(DefaultClusterPrivilege.NONE.clusterPrivilege()); - private final Predicate predicate; - SimpleClusterPermission(ClusterPrivilege privilege) { super(privilege); - this.predicate = privilege.predicate(); } @Override public boolean check(String action, TransportRequest request, Authentication authentication) { - return predicate.test(action); + 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); } } @@ -72,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, Authentication authentication) { - return super.privilege.predicate().test(action) && conditionalPrivilege.getRequestPredicate().test(request, 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()); } } @@ -102,29 +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()); - Tuple> resolvedPrivileges = ClusterPrivilegeResolver.resolve(names); - assert resolvedPrivileges.v2().isEmpty(); - return resolvedPrivileges.v1(); + 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, Authentication authentication) { - return children.stream().anyMatch(p -> p.check(action, request, authentication)); + 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/Role.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/Role.java index 4258e6ddba788..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 @@ -19,7 +19,7 @@ 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.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; @@ -212,16 +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) { - Tuple> privileges = ClusterPrivilegeResolver.resolve(privilegeNames); - clusterPermissions.add(new ClusterPermission.SimpleClusterPermission(privileges.v1())); - for (ConditionalClusterPrivilege ccp : privileges.v2()) { - clusterPermissions.add(new ClusterPermission.ConditionalClusterPermission(ccp)); - } + 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 0c72c1e16abdd..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 @@ -10,7 +10,7 @@ import java.util.Collections; import java.util.Set; -public final class ClusterPrivilege extends Privilege { +public class ClusterPrivilege extends Privilege { ClusterPrivilege(String name, String... patterns) { super(name, patterns); @@ -24,4 +24,4 @@ public final class ClusterPrivilege extends Privilege { super(name, automaton); } -} +} \ 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 index d87e6f136bd85..71e991005daaf 100644 --- 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 @@ -6,12 +6,8 @@ package org.elasticsearch.xpack.core.security.authz.privilege; -import org.apache.lucene.util.automaton.Automaton; import org.elasticsearch.common.Strings; -import org.elasticsearch.common.collect.Tuple; -import org.elasticsearch.xpack.core.security.support.Automatons; -import java.util.Collections; import java.util.HashSet; import java.util.Locale; import java.util.Set; @@ -21,38 +17,33 @@ import static org.elasticsearch.xpack.core.security.support.Automatons.patterns; /** - * This class helps resolving set of cluster privilege names to a {@link Tuple} of {@link ClusterPrivilege} or - * {@link ConditionalClusterPrivilege} + * 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.predicate(); + public static final Predicate ACTION_MATCHER = DefaultClusterPrivilege.ALL.clusterPrivilege().predicate(); - private static final ConcurrentHashMap, Tuple>> CACHE = + private static final ConcurrentHashMap, ClusterPrivilege> CACHE = new ConcurrentHashMap<>(); /** - * For given set of privilege names returns a tuple of {@link ClusterPrivilege} and set of predefined fixed conditional cluster - * privileges {@link ConditionalClusterPrivilege} + * For given set of privilege names returns a {@link ClusterPrivilege} * - * @param name set of predefined names in {@link DefaultClusterPrivilege} or {@link DefaultConditionalClusterPrivilege} or a valid + * @param name set of predefined names in {@link DefaultClusterPrivilege} or a valid * cluster action - * @return a {@link Tuple} of {@link ClusterPrivilege} and set of predefined fixed conditional cluster privileges - * {@link ConditionalClusterPrivilege} + * @return a {@link ClusterPrivilege} */ - public static Tuple> resolve(final Set name) { + public static ClusterPrivilege resolve(final Set name) { if (name == null || name.isEmpty()) { - return new Tuple>(DefaultClusterPrivilege.NONE.clusterPrivilege(), - Collections.emptySet()); + return DefaultClusterPrivilege.NONE.clusterPrivilege(); } return CACHE.computeIfAbsent(name, ClusterPrivilegeResolver::resolvePrivileges); } - private static Tuple> resolvePrivileges(Set name) { + private static ClusterPrivilege resolvePrivileges(Set name) { Set actions = new HashSet<>(); - Set automata = new HashSet<>(); - Set conditionalClusterPrivileges = new HashSet<>(); Set clusterPrivilegeNames = new HashSet<>(); + Set clusterPrivileges = new HashSet<>(); for (String part : name) { part = part.toLowerCase(Locale.ROOT); if (ACTION_MATCHER.test(part)) { @@ -61,35 +52,22 @@ private static Tuple> resolve } else { DefaultClusterPrivilege privilege = DefaultClusterPrivilege.fromString(part); if (privilege != null && name.size() == 1) { - return new Tuple>(privilege.clusterPrivilege(), - Collections.emptySet()); + return privilege.clusterPrivilege(); } else if (privilege != null) { - clusterPrivilegeNames.add(part); - automata.add(privilege.automaton()); + clusterPrivileges.add(privilege.clusterPrivilege()); } else { - DefaultConditionalClusterPrivilege dccp = DefaultConditionalClusterPrivilege.fromString(part); - if (dccp != null) { - conditionalClusterPrivileges.add(dccp.conditionalClusterPrivilege); - } else { - throw new IllegalArgumentException("unknown cluster privilege [" + name + "]. a privilege must be either " + - "one of the predefined fixed cluster privileges [" + - Strings.collectionToCommaDelimitedString(DefaultClusterPrivilege.names()) + "], " + - "predefined fixed conditional cluster privileges [" + - Strings.collectionToCommaDelimitedString(DefaultConditionalClusterPrivilege.names()) + "] " + - "or a pattern over one of the available cluster actions"); - } + 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) { - automata.add(patterns(actions)); + clusterPrivileges.add(new ClusterPrivilege(clusterPrivilegeNames, patterns(actions))); } - ClusterPrivilege clusterPrivilege = DefaultClusterPrivilege.NONE.clusterPrivilege(); - if (automata.isEmpty() == false) { - clusterPrivilege = new ClusterPrivilege(clusterPrivilegeNames, Automatons.unionAndMinimize(automata)); - } - return new Tuple>(clusterPrivilege, conditionalClusterPrivileges); + return MergeableClusterPrivilege.merge(clusterPrivileges); } private static String actionToPattern(String 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 84ee2ded7112a..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,28 +6,27 @@ package org.elasticsearch.xpack.core.security.authz.privilege; -import org.elasticsearch.transport.TransportRequest; -import org.elasticsearch.xpack.core.security.authc.Authentication; +import org.apache.lucene.util.automaton.Automaton; -import java.util.function.BiPredicate; +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 BiPredicate} for a {@link TransportRequest} (that determines which requests may be executed) and a {@link Authentication} (for - * current authenticated user). The a given execution of an action is considered to be permitted if both the action and the request are - * permitted in the context of given authentication. + * A ConditionalClusterPrivilege is a {@link ClusterPrivilege} that determines which cluster actions + * may be executed based on the {@link ConditionalPrivilege#getRequestPredicate()}. */ -public interface ConditionalClusterPrivilege { - - /** - * The action-level privilege that is required by this conditional privilege. - */ - ClusterPrivilege getPrivilege(); - - /** - * 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(); +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/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 index 81051b07b2796..4fc0d03381894 100644 --- 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 @@ -22,7 +22,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; -import java.util.function.Predicate; import static org.elasticsearch.xpack.core.security.support.Automatons.minusAndMinimize; import static org.elasticsearch.xpack.core.security.support.Automatons.patterns; @@ -58,7 +57,9 @@ public enum DefaultClusterPrivilege { 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; @@ -70,12 +71,15 @@ public enum DefaultClusterPrivilege { } DefaultClusterPrivilege(String privilegeName, String clusterAction) { - this.clusterPrivilege = new ClusterPrivilege(privilegeName, clusterAction); - this.privilegeName = privilegeName; + this(privilegeName, new ClusterPrivilege(privilegeName, clusterAction)); } DefaultClusterPrivilege(String privilegeName, Automaton automaton) { - this.clusterPrivilege = new ClusterPrivilege(privilegeName, automaton); + this(privilegeName, new ClusterPrivilege(privilegeName, automaton)); + } + + DefaultClusterPrivilege(String privilegeName, ClusterPrivilege clusterPrivilege) { + this.clusterPrivilege = clusterPrivilege; this.privilegeName = privilegeName; } @@ -87,14 +91,6 @@ public ClusterPrivilege clusterPrivilege() { return clusterPrivilege; } - public Predicate predicate() { - return clusterPrivilege.predicate; - } - - public Automaton automaton() { - return clusterPrivilege.automaton; - } - public static DefaultClusterPrivilege fromString(String privilegeName) { return privilegeNameToEnumMap.get(privilegeName); } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/DefaultConditionalClusterPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/DefaultConditionalClusterPrivilege.java deleted file mode 100644 index 113de336365e0..0000000000000 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/DefaultConditionalClusterPrivilege.java +++ /dev/null @@ -1,54 +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 java.util.HashMap; -import java.util.Map; -import java.util.Set; - -/** - * Enum that defines default conditional cluster privileges - */ -public enum DefaultConditionalClusterPrivilege { - MANAGE_OWN_API_KEY("manage_own_api_key", ManageApiKeyConditionalClusterPrivilege.createOwnerManageApiKeyConditionalClusterPrivilege()); - - final ConditionalClusterPrivilege conditionalClusterPrivilege; - private final String privilegeName; - - private static Map privilegeNameToEnumMap = new HashMap<>(); - private static Map ccpToEnumMap = new HashMap<>(); - static { - for (DefaultConditionalClusterPrivilege privilege : values()) { - privilegeNameToEnumMap.put(privilege.privilegeName, privilege); - ccpToEnumMap.put(privilege.conditionalClusterPrivilege, privilege); - } - } - - DefaultConditionalClusterPrivilege(final String privilegeName, final ConditionalClusterPrivilege conditionalClusterPrivilege) { - this.privilegeName = privilegeName; - this.conditionalClusterPrivilege = conditionalClusterPrivilege; - } - - ConditionalClusterPrivilege conditionalClusterPrivilege() { - return conditionalClusterPrivilege; - } - - static DefaultConditionalClusterPrivilege fromString(String privilegeName) { - return privilegeNameToEnumMap.get(privilegeName); - } - - public static String privilegeName(ConditionalClusterPrivilege ccp) { - if (ccpToEnumMap.containsKey(ccp)) { - return ccpToEnumMap.get(ccp).privilegeName; - } - return null; - } - - static Set names() { - return privilegeNameToEnumMap.keySet(); - } -} \ No newline at end of file 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 index ba55c2335ce7e..4e8596967dada 100644 --- 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 @@ -6,6 +6,7 @@ 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; @@ -13,31 +14,45 @@ 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 interface GlobalClusterPrivilege extends NamedWriteable, ToXContentFragment, ConditionalClusterPrivilege { +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. */ - Category getCategory(); + public abstract Category getCategory(); /** - * A {@link ConditionalClusterPrivilege} should generate a fragment of {@code XContent}, which consists of + * 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 - XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException; + 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 ConditionalClusterPrivilege} instances, with the top level fields built + * object for a collection of {@link GlobalClusterPrivilege} instances, with the top level fields built * from the categories. */ - enum Category { + public enum Category { APPLICATION(new ParseField("application")); public final ParseField field; 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 index 9f61bf7bcec7b..4912779673a41 100644 --- 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 @@ -15,14 +15,13 @@ import org.elasticsearch.xpack.core.security.support.Automatons; import java.util.List; -import java.util.Objects; import java.util.Set; import java.util.function.BiPredicate; /** * Conditional cluster privilege for managing API keys */ -public final class ManageApiKeyConditionalClusterPrivilege implements ConditionalClusterPrivilege { +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"; @@ -32,7 +31,6 @@ public final class ManageApiKeyConditionalClusterPrivilege implements Conditiona INVALIDATE_API_KEY_PATTERN); private final boolean restrictActionsToAuthenticatedUser; - private final ClusterPrivilege privilege; private final BiPredicate requestPredicate; /** @@ -42,6 +40,7 @@ public final class ManageApiKeyConditionalClusterPrivilege implements Conditiona * @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) { @@ -49,7 +48,6 @@ private ManageApiKeyConditionalClusterPrivilege(Set actions, boolean res + API_KEY_ACTION_PATTERNS + " ]"); } } - this.privilege = new ClusterPrivilege(actions, Automatons.patterns(actions)); this.restrictActionsToAuthenticatedUser = restrictActionsToAuthenticatedUser; this.requestPredicate = (request, authentication) -> { @@ -97,11 +95,6 @@ private boolean checkIfUserIsOwnerOfApiKeys(Authentication authentication, Strin return false; } - @Override - public ClusterPrivilege getPrivilege() { - return privilege; - } - @Override public BiPredicate getRequestPredicate() { return requestPredicate; @@ -109,20 +102,24 @@ public BiPredicate getRequestPredicate() { @Override public int hashCode() { - return Objects.hash(privilege, restrictActionsToAuthenticatedUser); + final int prime = 31; + int result = super.hashCode(); + result = prime * result + (restrictActionsToAuthenticatedUser ? 1231 : 1237); + return result; } @Override - public boolean equals(Object o) { - if (this == o) { + public boolean equals(Object obj) { + if (this == obj) return true; - } - if (o == null || getClass() != o.getClass()) { + if (!super.equals(obj)) return false; - } - final ManageApiKeyConditionalClusterPrivilege that = (ManageApiKeyConditionalClusterPrivilege) o; - return Objects.equals(this.privilege, that.privilege) - && Objects.equals(this.restrictActionsToAuthenticatedUser, that.restrictActionsToAuthenticatedUser); + 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 index b420ba6ec208e..3d8b8eeba32f8 100644 --- 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 @@ -33,11 +33,8 @@ * 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 implements GlobalClusterPrivilege { +public final class ManageApplicationPrivileges extends GlobalClusterPrivilege { - private static final ClusterPrivilege PRIVILEGE = ClusterPrivilegeResolver.resolve( - Collections.singleton("cluster:admin/xpack/security/privilege/*") - ).v1(); public static final String WRITEABLE_NAME = "manage-application-privileges"; private final Set applicationNames; @@ -45,6 +42,8 @@ public final class ManageApplicationPrivileges implements GlobalClusterPrivilege 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) -> { @@ -63,11 +62,6 @@ public Category getCategory() { return Category.APPLICATION; } - @Override - public ClusterPrivilege getPrivilege() { - return PRIVILEGE; - } - @Override public BiPredicate getRequestPredicate() { return this.requestPredicate; 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/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/ManageApiKeyConditionalClusterPrivilegeTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApiKeyConditionalClusterPrivilegeTests.java index c319ef3d4d45e..d206f265c0c04 100644 --- 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 @@ -92,7 +92,7 @@ public void testManagePrivilegeOwnerOnly() { private boolean checkAccess(ManageApiKeyConditionalClusterPrivilege privilege, String action, TransportRequest request, Authentication authentication) { - return privilege.getPrivilege().predicate().test(action) && privilege.getRequestPredicate().test(request, 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 e37cc9d946b91..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 @@ -46,12 +46,12 @@ import static org.elasticsearch.common.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION; import static org.elasticsearch.test.TestMatchers.predicateMatches; -import static org.mockito.Mockito.mock; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.Matchers.equalTo; 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 { @@ -103,13 +103,13 @@ 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))); } } 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 510a89f5f1d93..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 @@ -6,7 +6,6 @@ package org.elasticsearch.xpack.core.security.authz.privilege; import org.apache.lucene.util.automaton.Operations; -import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.core.security.support.Automatons; @@ -33,52 +32,38 @@ public void testSubActionPattern() throws Exception { public void testCluster() throws Exception { Set name = Sets.newHashSet("monitor"); - Tuple> resolvedPrivileges = ClusterPrivilegeResolver.resolve(name); - ClusterPrivilege cluster = resolvedPrivileges.v1(); + ClusterPrivilege cluster = ClusterPrivilegeResolver.resolve(name); assertThat(cluster, is(DefaultClusterPrivilege.MONITOR.clusterPrivilege())); - assertThat(resolvedPrivileges.v2().isEmpty(), is(true)); // since "all" implies "monitor", this should be the same language as All name = Sets.newHashSet("monitor", "all"); - resolvedPrivileges = ClusterPrivilegeResolver.resolve(name); - cluster = resolvedPrivileges.v1(); - assertTrue(Operations.sameLanguage(DefaultClusterPrivilege.ALL.automaton(), cluster.automaton)); - assertThat(resolvedPrivileges.v2().isEmpty(), is(true)); + cluster = ClusterPrivilegeResolver.resolve(name); + assertTrue(Operations.sameLanguage(DefaultClusterPrivilege.ALL.clusterPrivilege().getAutomaton(), cluster.automaton)); name = Sets.newHashSet("monitor", "none"); - resolvedPrivileges = ClusterPrivilegeResolver.resolve(name); - cluster = resolvedPrivileges.v1(); - assertTrue(Operations.sameLanguage(DefaultClusterPrivilege.MONITOR.automaton(), cluster.automaton)); - assertThat(resolvedPrivileges.v2().isEmpty(), is(true)); + cluster = ClusterPrivilegeResolver.resolve(name); + assertTrue(Operations.sameLanguage(DefaultClusterPrivilege.MONITOR.clusterPrivilege().getAutomaton(), cluster.automaton)); Set name2 = Sets.newHashSet("none", "monitor"); - resolvedPrivileges = ClusterPrivilegeResolver.resolve(name2); - ClusterPrivilege cluster2 = resolvedPrivileges.v1(); + ClusterPrivilege cluster2 = ClusterPrivilegeResolver.resolve(name2); assertThat(cluster, is(cluster2)); - assertThat(resolvedPrivileges.v2().isEmpty(), is(true)); } public void testClusterTemplateActions() throws Exception { Set name = Sets.newHashSet("indices:admin/template/delete"); - Tuple> resolvedPrivileges = ClusterPrivilegeResolver.resolve(name); - ClusterPrivilege cluster = resolvedPrivileges.v1(); + ClusterPrivilege cluster = ClusterPrivilegeResolver.resolve(name); assertThat(cluster, notNullValue()); assertThat(cluster.predicate().test("indices:admin/template/delete"), is(true)); - assertThat(resolvedPrivileges.v2().isEmpty(), is(true)); name = Sets.newHashSet("indices:admin/template/get"); - resolvedPrivileges = ClusterPrivilegeResolver.resolve(name); - cluster = resolvedPrivileges.v1(); + cluster = ClusterPrivilegeResolver.resolve(name); assertThat(cluster, notNullValue()); assertThat(cluster.predicate().test("indices:admin/template/get"), is(true)); - assertThat(resolvedPrivileges.v2().isEmpty(), is(true)); name = Sets.newHashSet("indices:admin/template/put"); - resolvedPrivileges = ClusterPrivilegeResolver.resolve(name); - cluster = resolvedPrivileges.v1(); + cluster = ClusterPrivilegeResolver.resolve(name); assertThat(cluster, notNullValue()); assertThat(cluster.predicate().test("indices:admin/template/put"), is(true)); - assertThat(resolvedPrivileges.v2().isEmpty(), is(true)); } public void testClusterInvalidName() throws Exception { @@ -89,12 +74,10 @@ public void testClusterInvalidName() throws Exception { public void testClusterAction() throws Exception { Set actionName = Sets.newHashSet("cluster:admin/snapshot/delete"); - Tuple> resolvedPrivileges = ClusterPrivilegeResolver.resolve(actionName); - ClusterPrivilege cluster = resolvedPrivileges.v1(); + 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(resolvedPrivileges.v2().isEmpty(), is(true)); } public void testIndexAction() throws Exception { @@ -161,7 +144,7 @@ public void testSystem() throws Exception { } public void testManageCcrPrivilege() { - Predicate predicate = DefaultClusterPrivilege.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)); @@ -170,7 +153,7 @@ public void testManageCcrPrivilege() { public void testIlmPrivileges() { { - Predicate predicate = DefaultClusterPrivilege.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)); @@ -185,7 +168,7 @@ public void testIlmPrivileges() { } { - Predicate predicate = DefaultClusterPrivilege.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)); @@ -224,43 +207,26 @@ public void testIlmPrivileges() { public void testClusterPrivilegeAndPlainConditionalClusterPrivilege() { Set actionName = Sets.newHashSet("cluster:admin/snapshot/delete", "manage_own_api_key"); - Tuple> tuple = ClusterPrivilegeResolver.resolve(actionName); - ClusterPrivilege cluster = tuple.v1(); - Set plainConditionalClusterPrivilege = tuple.v2(); + 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(plainConditionalClusterPrivilege, notNullValue()); - assertThat(plainConditionalClusterPrivilege.size(), is(1)); - ConditionalClusterPrivilege manageOwnApiKeysConditionalClusterPrivilege = plainConditionalClusterPrivilege.stream().findFirst() - .get(); - assertThat( - manageOwnApiKeysConditionalClusterPrivilege.getPrivilege().predicate().test("cluster:admin/xpack/security/api_key/create"), - is(true)); - assertThat(manageOwnApiKeysConditionalClusterPrivilege.getPrivilege().predicate().test("cluster:admin/xpack/security/api_key/get"), - is(true)); - assertThat(manageOwnApiKeysConditionalClusterPrivilege.getPrivilege().predicate() - .test("cluster:admin/xpack/security/api_key/invalidate"), is(true)); + + 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"); - Tuple> tuple = ClusterPrivilegeResolver.resolve(actionName); - ClusterPrivilege cluster = tuple.v1(); - Set conditionalClusterPrivilege = tuple.v2(); + 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(conditionalClusterPrivilege, notNullValue()); - assertThat(conditionalClusterPrivilege.size(), is(1)); - ConditionalClusterPrivilege manageOwnApiKeysConditionalClusterPrivilege = conditionalClusterPrivilege.stream().findFirst() - .get(); - assertThat( - manageOwnApiKeysConditionalClusterPrivilege.getPrivilege().predicate().test("cluster:admin/xpack/security/api_key/create"), - is(true)); - assertThat(manageOwnApiKeysConditionalClusterPrivilege.getPrivilege().predicate().test("cluster:admin/xpack/security/api_key/get"), - is(true)); - assertThat(manageOwnApiKeysConditionalClusterPrivilege.getPrivilege().predicate() - .test("cluster:admin/xpack/security/api_key/invalidate"), is(true)); + + 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/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 c26eaaebff68e..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,7 +28,6 @@ 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; @@ -60,8 +59,6 @@ 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.ConditionalClusterPrivilege; -import org.elasticsearch.xpack.core.security.authz.privilege.DefaultConditionalClusterPrivilege; 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; @@ -375,20 +372,11 @@ public void checkPrivileges(Authentication authentication, AuthorizationInfo aut Map cluster = new HashMap<>(); for (String checkAction : request.clusterPrivileges()) { - Tuple> resolvedPrivileges = ClusterPrivilegeResolver + ClusterPrivilege checkPrivilege = ClusterPrivilegeResolver .resolve(Collections.singleton(checkAction)); - final ClusterPrivilege checkPrivilege = resolvedPrivileges.v1(); - final Set conditionalClusterPrivileges = resolvedPrivileges.v2(); - boolean granted = userRole.grants(checkPrivilege); // 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. - for (ConditionalClusterPrivilege conditionalClusterPrivilege : conditionalClusterPrivileges) { - granted = (granted || userRole.grants(conditionalClusterPrivilege.getPrivilege())); - if (granted) { - break; - } - } - cluster.put(checkAction, granted); + cluster.put(checkAction, userRole.grants(checkPrivilege)); } boolean allMatch = cluster.values().stream().allMatch(Boolean::booleanValue); ResourcePrivilegesMap.Builder combineIndicesResourcePrivileges = ResourcePrivilegesMap.builder(); @@ -441,16 +429,11 @@ GetUserPrivilegesResponse buildUserPrivilegesResponseObject(Role userRole) { 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()); - } - } else if (tup.v2() instanceof GlobalClusterPrivilege) { - conditionalCluster.add((GlobalClusterPrivilege) tup.v2()); + for (ClusterPrivilege privilege : userRole.cluster().privileges()) { + if (privilege instanceof GlobalClusterPrivilege) { + conditionalCluster.add((GlobalClusterPrivilege) privilege); } else { - // non renderable predefined conditional cluster privilege names - cluster.add(DefaultConditionalClusterPrivilege.privilegeName(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/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 7df765da74c9e..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 @@ -318,7 +318,8 @@ public void testAuthorizeUsingConditionalPrivileges() throws IOException { 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(DefaultClusterPrivilege.MANAGE_SECURITY.clusterPrivilege()); + Mockito.when(conditionalClusterPrivilege.predicate()) + .thenReturn(DefaultClusterPrivilege.MANAGE_SECURITY.clusterPrivilege().predicate()); final GlobalClusterPrivilege[] conditionalClusterPrivileges = new GlobalClusterPrivilege[] { conditionalClusterPrivilege }; @@ -339,7 +340,8 @@ public void testAuthorizationDeniedWhenConditionalPrivilegesDoNotMatch() throws final GlobalClusterPrivilege conditionalClusterPrivilege = Mockito.mock(GlobalClusterPrivilege.class); final BiPredicate requestPredicate = (r, a) -> false; Mockito.when(conditionalClusterPrivilege.getRequestPredicate()).thenReturn(requestPredicate); - Mockito.when(conditionalClusterPrivilege.getPrivilege()).thenReturn(DefaultClusterPrivilege.MANAGE_SECURITY.clusterPrivilege()); + Mockito.when(conditionalClusterPrivilege.predicate()) + .thenReturn(DefaultClusterPrivilege.MANAGE_SECURITY.clusterPrivilege().predicate()); final GlobalClusterPrivilege[] conditionalClusterPrivileges = new GlobalClusterPrivilege[] { conditionalClusterPrivilege }; 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 d3862c3e0ecc7..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 @@ -544,7 +544,8 @@ public void testMergingBasicRoles() { final TransportRequest request3 = mock(TransportRequest.class); GlobalClusterPrivilege ccp1 = mock(GlobalClusterPrivilege.class); - when(ccp1.getPrivilege()).thenReturn(DefaultClusterPrivilege.MANAGE_SECURITY.clusterPrivilege()); + 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() @@ -570,7 +571,8 @@ public void testMergingBasicRoles() { new String[]{"app-user-1"}, null, null); GlobalClusterPrivilege ccp2 = mock(GlobalClusterPrivilege.class); - when(ccp2.getPrivilege()).thenReturn(DefaultClusterPrivilege.MANAGE_SECURITY.clusterPrivilege()); + 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() 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 8e30a0054582e..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 @@ -115,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(), DefaultClusterPrivilege.ALL.automaton())); + 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));