From 947ef387a0301d3dbecb9f0732fc5260e9464cb9 Mon Sep 17 00:00:00 2001 From: Jake Landis Date: Fri, 5 Apr 2024 12:26:07 -0500 Subject: [PATCH] more tests --- .../xpack/core/security/SecurityContext.java | 4 +- ...ossClusterApiKeyRoleDescriptorBuilder.java | 8 +- .../security/action/role/PutRoleRequest.java | 16 +- .../core/security/authz/RoleDescriptor.java | 34 ++--- .../RemoteClusterPermissionGroup.java | 5 +- .../permission/RemoteClusterPermissions.java | 22 +-- .../core/security/authz/permission/Role.java | 29 ++-- .../security/authz/permission/SimpleRole.java | 2 +- .../xpack/core/security/user/SystemUser.java | 9 +- .../operator/exchange/ExchangeService.java | 1 - .../RemoteClusterSecurityEsqlIT.java | 142 +++++++++++------- .../authz/store/CompositeRolesStore.java | 2 +- .../SecurityServerTransportInterceptor.java | 2 +- 13 files changed, 154 insertions(+), 122 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityContext.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityContext.java index 6b85a999b13ff..05ef5d3f70fd9 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityContext.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityContext.java @@ -72,9 +72,9 @@ public User requireUser() { @Nullable public User getUser() { Authentication authentication = getAuthentication(); - if(authentication != null ) { + if (authentication != null) { if (authentication.isCrossClusterAccess()) { - authentication = getAuthenticationFromCrossClusterAccessMetadata(authentication); + authentication = getAuthenticationFromCrossClusterAccessMetadata(authentication); } } return authentication == null ? null : authentication.getEffectiveSubject().getUser(); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/apikey/CrossClusterApiKeyRoleDescriptorBuilder.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/apikey/CrossClusterApiKeyRoleDescriptorBuilder.java index 659aeb92e092c..be897f70c47fe 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/apikey/CrossClusterApiKeyRoleDescriptorBuilder.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/apikey/CrossClusterApiKeyRoleDescriptorBuilder.java @@ -24,8 +24,12 @@ public class CrossClusterApiKeyRoleDescriptorBuilder { - public static final String[] CCS_CLUSTER_PRIVILEGE_NAMES = { "cross_cluster_search", "monitor_enrich", - "cluster:data/read/esql/open_exchange", "cluster:data/read/esql/exchange", "cluster:admin/xpack/security/user/has_privileges" }; //TODO: don't add this here ... just a hack for now + public static final String[] CCS_CLUSTER_PRIVILEGE_NAMES = { + "cross_cluster_search", + "monitor_enrich", + "cluster:data/read/esql/open_exchange", + "cluster:data/read/esql/exchange", + "cluster:admin/xpack/security/user/has_privileges" }; // TODO: don't add this here ... just a hack for now public static final String[] CCR_CLUSTER_PRIVILEGE_NAMES = { "cross_cluster_replication" }; public static final String[] CCS_AND_CCR_CLUSTER_PRIVILEGE_NAMES = Stream.concat( Arrays.stream(CCS_CLUSTER_PRIVILEGE_NAMES), 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 1e4a855fbed18..8cd10d79d08d3 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 @@ -126,10 +126,10 @@ public void addRemoteIndex( ); } - //TODO: who calls this ? -// public void addRemoteCluster(final String[] privileges, final String[] remoteClusters) { -// remoteClusterPrivileges.add(new RoleDescriptor.RemoteClusterPrivileges(remoteClusters, privileges)); -// } + // TODO: who calls this ? + // public void addRemoteCluster(final String[] privileges, final String[] remoteClusters) { + // remoteClusterPrivileges.add(new RoleDescriptor.RemoteClusterPrivileges(remoteClusters, privileges)); + // } public void addIndex( String[] indices, @@ -198,10 +198,10 @@ public boolean hasRemoteIndicesPrivileges() { return false == remoteIndicesPrivileges.isEmpty(); } - //TODO: who calls this ? - //public boolean hasRemoteClusterPrivileges() { - // return false == remoteClusterPrivileges.isEmpty(); - // } + // TODO: who calls this ? + // public boolean hasRemoteClusterPrivileges() { + // return false == remoteClusterPrivileges.isEmpty(); + // } public List applicationPrivileges() { return Collections.unmodifiableList(applicationPrivileges); 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 27797e68b93ab..ed9df25262784 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 @@ -259,7 +259,6 @@ public boolean hasClusterPrivileges() { return clusterPrivileges.length != 0; } - public boolean hasApplicationPrivileges() { return applicationPrivileges.length != 0; } @@ -273,10 +272,10 @@ public boolean hasRunAs() { } public boolean hasUnsupportedPrivileges() { -// //this is enforced elsewhere -// assert Arrays.stream(this.getRemoteClusterPermissions().privilegeNames("*")) -// .anyMatch(s -> s.equalsIgnoreCase("monitor_enrich") == false) : "monitor_enrich is the only value allowed"; - //TODO: make this validation configurable + // //this is enforced elsewhere + // assert Arrays.stream(this.getRemoteClusterPermissions().privilegeNames("*")) + // .anyMatch(s -> s.equalsIgnoreCase("monitor_enrich") == false) : "monitor_enrich is the only value allowed"; + // TODO: make this validation configurable return hasConfigurableClusterPrivileges() || hasApplicationPrivileges() @@ -714,8 +713,7 @@ private static RemoteIndicesPrivileges parseRemoteIndex(String roleName, XConten return new RemoteIndicesPrivileges(parsed.indicesPrivileges(), parsed.remoteClusters()); } - private static RemoteClusterPermissions parseRemoteCluster(final String roleName, final XContentParser parser) - throws IOException { + private static RemoteClusterPermissions parseRemoteCluster(final String roleName, final XContentParser parser) throws IOException { if (parser.currentToken() != XContentParser.Token.START_ARRAY) { throw new ElasticsearchParseException( "failed to parse remote_cluster for role [{}]. expected field [{}] value " + "to be an array, but found [{}] instead", @@ -724,7 +722,7 @@ private static RemoteClusterPermissions parseRemoteCluster(final String roleName parser.currentToken() ); } - RemoteClusterPermissions remoteClusterPermissions = new RemoteClusterPermissions(); + RemoteClusterPermissions remoteClusterPermissions = new RemoteClusterPermissions(); String[] privileges = null; String[] clusters = null; while (parser.nextToken() != XContentParser.Token.END_ARRAY) { @@ -735,14 +733,14 @@ private static RemoteClusterPermissions parseRemoteCluster(final String roleName currentFieldName = parser.currentName(); } else if (Fields.PRIVILEGES.match(currentFieldName, parser.getDeprecationHandler())) { privileges = readStringArray(roleName, parser, false); - //TODO: re-enable this validation -// if (privileges.length != 1 || "monitor_enrich".equals(privileges[0].trim()) == false) { -// throw new ElasticsearchParseException( -// "failed to parse remote_cluster for role [{}]. " + "[monitor_enrich] is the only value allowed for [{}]", -// roleName, -// currentFieldName -// ); -// } + // TODO: re-enable this validation + // if (privileges.length != 1 || "monitor_enrich".equals(privileges[0].trim()) == false) { + // throw new ElasticsearchParseException( + // "failed to parse remote_cluster for role [{}]. " + "[monitor_enrich] is the only value allowed for [{}]", + // roleName, + // currentFieldName + // ); + // } } else if (Fields.CLUSTERS.match(currentFieldName, parser.getDeprecationHandler())) { clusters = readStringArray(roleName, parser, false); } else { @@ -753,12 +751,12 @@ private static RemoteClusterPermissions parseRemoteCluster(final String roleName ); } } - if(privileges != null && clusters == null) { + if (privileges != null && clusters == null) { throw new ElasticsearchParseException( "failed to parse remote_cluster for role [{}]. [clusters] must be defined when [privileges] are defined ", roleName ); - } else if( privileges == null && clusters != null) { + } else if (privileges == null && clusters != null) { throw new ElasticsearchParseException( "failed to parse remote_cluster for role [{}]. [privileges] must be defined when [clusters] are defined ", roleName diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/RemoteClusterPermissionGroup.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/RemoteClusterPermissionGroup.java index aafdf74f8decc..eab879de8795b 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/RemoteClusterPermissionGroup.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/RemoteClusterPermissionGroup.java @@ -26,7 +26,7 @@ } * */ -public class RemoteClusterPermissionGroup implements Writeable,ToXContentObject { +public class RemoteClusterPermissionGroup implements Writeable, ToXContentObject { private final String[] clusterPrivileges; private final String[] remoteClusterAliases; @@ -37,8 +37,9 @@ public RemoteClusterPermissionGroup(StreamInput in) throws IOException { remoteClusterAliases = in.readStringArray(); remoteClusterAliasMatcher = StringMatcher.of(remoteClusterAliases); } + public RemoteClusterPermissionGroup(String[] clusterPrivileges, String[] remoteClusterAliases) { - //TODO: throw an exception if using unsupported cluster privilges + // TODO: throw an exception if using unsupported cluster privilges this(clusterPrivileges, remoteClusterAliases, StringMatcher.of(remoteClusterAliases)); } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/RemoteClusterPermissions.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/RemoteClusterPermissions.java index b6aac83f0d644..1edd230a77a10 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/RemoteClusterPermissions.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/RemoteClusterPermissions.java @@ -50,20 +50,21 @@ */ public class RemoteClusterPermissions implements Writeable, ToXContentObject { - private final List remoteClusterPermissionGroups ; + private final List remoteClusterPermissionGroups; public static final RemoteClusterPermissions NONE = new RemoteClusterPermissions(); public RemoteClusterPermissions(StreamInput in) throws IOException { remoteClusterPermissionGroups = in.readCollectionAsList(RemoteClusterPermissionGroup::new); } - public RemoteClusterPermissions(){ + + public RemoteClusterPermissions() { remoteClusterPermissionGroups = new ArrayList<>(); } public RemoteClusterPermissions addGroup(RemoteClusterPermissionGroup remoteClusterPermissionGroup) { Objects.requireNonNull(remoteClusterPermissionGroup, "remoteClusterPermissionGroup must not be null"); - if(this == NONE) { + if (this == NONE) { throw new IllegalArgumentException("Cannot add a group to the `NONE` instance"); } remoteClusterPermissionGroups.add(remoteClusterPermissionGroup); @@ -71,18 +72,19 @@ public RemoteClusterPermissions addGroup(RemoteClusterPermissionGroup remoteClus } public String[] privilegeNames(final String remoteClusterAlias) { - return - remoteClusterPermissionGroups.stream() - .filter(group -> group.hasPrivileges(remoteClusterAlias)) - .flatMap(groups -> Arrays.stream(groups.clusterPrivileges())).distinct().sorted().toArray(String[]::new); + return remoteClusterPermissionGroups.stream() + .filter(group -> group.hasPrivileges(remoteClusterAlias)) + .flatMap(groups -> Arrays.stream(groups.clusterPrivileges())) + .distinct() + .sorted() + .toArray(String[]::new); } public boolean hasPrivileges(final String remoteClusterAlias) { - return remoteClusterPermissionGroups.stream() - .anyMatch(remoteIndicesGroup -> remoteIndicesGroup.hasPrivileges(remoteClusterAlias)); + return remoteClusterPermissionGroups.stream().anyMatch(remoteIndicesGroup -> remoteIndicesGroup.hasPrivileges(remoteClusterAlias)); } - public boolean hasPrivileges(){ + public boolean hasPrivileges() { return remoteClusterPermissionGroups.isEmpty() == false; } 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 a731470799b04..e35b99c507d95 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 @@ -275,24 +275,23 @@ public Builder addRemoteIndicesGroup( return this; } - public Builder addRemoteClusterPermissions(RemoteClusterPermissions remoteClusterPermissions ){ + public Builder addRemoteClusterPermissions(RemoteClusterPermissions remoteClusterPermissions) { Objects.requireNonNull(remoteClusterPermissions, "remoteClusterPermissions must not be null"); - assert this.remoteClusterPermissions == null - : "addRemoteClusterPermissions should only be called once"; + assert this.remoteClusterPermissions == null : "addRemoteClusterPermissions should only be called once"; if (remoteClusterPermissions.hasPrivileges()) { Set namedClusterPrivileges = ClusterPrivilegeResolver.names(); for (RemoteClusterPermissionGroup group : remoteClusterPermissions.groups()) { for (String namedPrivilege : group.clusterPrivileges()) { - //TODO: re-enable this validation (and test) -// if ("monitor_enrich".equals(namedPrivilege) == false) { -// //this should be enforced upstream while defining the role, so the check here too is just in case... -// throw new IllegalArgumentException("Only [monitor_enrich] is supported as a remote cluster privilege"); -// } -// // this can never happen, but if we ever expand the list of remote cluster privileges then we want to ensure that -// // only named cluster privileges are supported -// if (namedClusterPrivileges.contains(namedPrivilege) == false) { -// throw new IllegalArgumentException("Unknown cluster privilege [" + namedPrivilege + "]"); -// } + // TODO: re-enable this validation (and test) + // if ("monitor_enrich".equals(namedPrivilege) == false) { + // //this should be enforced upstream while defining the role, so the check here too is just in case... + // throw new IllegalArgumentException("Only [monitor_enrich] is supported as a remote cluster privilege"); + // } + // // this can never happen, but if we ever expand the list of remote cluster privileges then we want to ensure that + // // only named cluster privileges are supported + // if (namedClusterPrivileges.contains(namedPrivilege) == false) { + // throw new IllegalArgumentException("Unknown cluster privilege [" + namedPrivilege + "]"); + // } } } } @@ -445,9 +444,9 @@ static SimpleRole buildFromRoleDescriptor( } RemoteClusterPermissions remoteClusterPermissions = roleDescriptor.getRemoteClusterPermissions(); - for(RemoteClusterPermissionGroup group : remoteClusterPermissions.groups()){ + for (RemoteClusterPermissionGroup group : remoteClusterPermissions.groups()) { final String[] clusterAliases = group.remoteClusterAliases(); - //note: this validation only occurs from reserved roles, see the builder for additional general validation + // note: this validation only occurs from reserved roles, see the builder for additional general validation assert Arrays.equals(new String[] { "*" }, clusterAliases) : "reserved role should not define remote cluster privileges for specific clusters"; } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/SimpleRole.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/SimpleRole.java index a1fa8d621da20..1308d177a7c72 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/SimpleRole.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/SimpleRole.java @@ -209,7 +209,7 @@ public RoleDescriptorsIntersection getRoleDescriptorsIntersectionForRemoteCluste && remoteClusterPermissions.hasPrivileges(remoteClusterAlias) == false) { return RoleDescriptorsIntersection.EMPTY; } - //TODO: test the case where only remote cluster permissions are present, but no indices permissions are defined (and vice versa) + // TODO: test the case where only remote cluster permissions are present, but no indices permissions are defined (and vice versa) final List indicesPrivileges = new ArrayList<>(); for (RemoteIndicesPermission.RemoteIndicesGroup remoteIndicesGroup : remoteIndicesPermission.remoteIndicesGroups()) { for (IndicesPermission.Group indicesGroup : remoteIndicesGroup.indicesPermissionGroups()) { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/user/SystemUser.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/user/SystemUser.java index bba816262ff6b..e8d1efd7a6314 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/user/SystemUser.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/user/SystemUser.java @@ -31,9 +31,12 @@ public class SystemUser extends InternalUser { private static final RoleDescriptor REMOTE_ACCESS_ROLE_DESCRIPTOR = new RoleDescriptor( ROLE_NAME + "_cross_cluster_access", - //TODO: don't add these esql here... just a hack for now - new String[] { "cross_cluster_search", "cross_cluster_replication", "cluster:data/read/esql/open_exchange" - ,"cluster:data/read/esql/exchange" }, + // TODO: don't add these esql here... just a hack for now + new String[] { + "cross_cluster_search", + "cross_cluster_replication", + "cluster:data/read/esql/open_exchange", + "cluster:data/read/esql/exchange" }, // Needed for CCR background jobs (with system user) new RoleDescriptor.IndicesPrivileges[] { RoleDescriptor.IndicesPrivileges.builder() diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/exchange/ExchangeService.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/exchange/ExchangeService.java index 5f4558519eafd..d0f3560df7dca 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/exchange/ExchangeService.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/exchange/ExchangeService.java @@ -57,7 +57,6 @@ public final class ExchangeService extends AbstractLifecycleComponent { private static final String OPEN_EXCHANGE_ACTION_NAME = "internal:data/read/esql/open_exchange"; private static final String OPEN_EXCHANGE_ACTION_NAME_FOR_CCS = "cluster:internal:data/read/esql/open_exchange"; - /** * The time interval for an exchange sink handler to be considered inactive and subsequently * removed from the exchange service if no sinks are attached (i.e., no computation uses that sink handler). diff --git a/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecurityEsqlIT.java b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecurityEsqlIT.java index c6663564e0bd4..870415fde67fe 100644 --- a/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecurityEsqlIT.java +++ b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecurityEsqlIT.java @@ -370,9 +370,9 @@ public void testCrossClusterQueryAgainstInvalidRemote() throws Exception { randomBoolean() ? Settings.builder().put("cluster.remote.invalid_remote.seeds", fulfillingCluster.getRemoteClusterServerEndpoint(0)).build() : Settings.builder() - .put("cluster.remote.invalid_remote.mode", "proxy") - .put("cluster.remote.invalid_remote.proxy_address", fulfillingCluster.getRemoteClusterServerEndpoint(0)) - .build() + .put("cluster.remote.invalid_remote.mode", "proxy") + .put("cluster.remote.invalid_remote.proxy_address", fulfillingCluster.getRemoteClusterServerEndpoint(0)) + .build() ); // invalid remote with local index should return local results @@ -436,6 +436,36 @@ public void testCrossClusterQueryWithOnlyRemotePrivs() throws Exception { .flatMap(innerList -> innerList instanceof List ? ((List) innerList).stream() : Stream.empty()) .collect(Collectors.toList()); assertThat(flatList, containsInAnyOrder("1", "3", "engineering", "sales")); + + // no local privs at all will fail + final var putRoleNoLocalPrivs = new Request("PUT", "/_security/role/" + REMOTE_SEARCH_ROLE); + putRoleNoLocalPrivs.setJsonEntity(""" + { + "indices": [], + "remote_indices": [ + { + "names": ["employees"], + "privileges": ["read"], + "clusters": ["my_remote_cluster"] + } + ] + }"""); + assertOK(adminClient().performRequest(putRoleNoLocalPrivs)); + + ResponseException error = expectThrows(ResponseException.class, () -> { performRequestWithRemoteSearchUser(esqlRequest(""" + FROM my_remote_cluster:employees + | SORT emp_id ASC + | LIMIT 2 + | KEEP emp_id, department""")); }); + + assertThat(error.getResponse().getStatusLine().getStatusCode(), equalTo(403)); + assertThat( + error.getMessage(), + containsString( + "action [indices:data/read/esql] is unauthorized for user [remote_search_user] with effective roles [remote_search], " + + "this action is granted by the index privileges [read,read_cross_cluster,all]" + ) + ); } @SuppressWarnings("unchecked") @@ -462,48 +492,51 @@ public void testCrossClusterEnrich() throws Exception { .collect(Collectors.toList()); assertThat(flatList, containsInAnyOrder(2, 3, "usa", "canada")); + // Query cluster + final var putRoleRequest = new Request("PUT", "/_security/role/" + REMOTE_SEARCH_ROLE); + + // no remote_cluster privs should fail the request + putRoleRequest.setJsonEntity(""" + { + "indices": [ + { + "names": ["employees"], + "privileges": ["read"] + } + ], + "cluster": [ "monitor_enrich" ], + "remote_indices": [ + { + "names": ["employees"], + "privileges": ["read"], + "clusters": ["my_remote_cluster"] + } + ] + }"""); + assertOK(adminClient().performRequest(putRoleRequest)); -// // ESQL with enrich is denied when user has no access to enrich policies -// final var putLocalSearchRoleRequest = new Request("PUT", "/_security/role/local_search"); -// putLocalSearchRoleRequest.setJsonEntity(""" -// { -// "indices": [ -// { -// "names": ["employees"], -// "privileges": ["read"] -// } -// ], -// "cluster": [ ], -// "remote_indices": [ -// { -// "names": ["employees"], -// "privileges": ["read"], -// "clusters": ["my_remote_cluster"] -// } -// ] -// }"""); -// assertOK(adminClient().performRequest(putLocalSearchRoleRequest)); -// final var putlocalSearchUserRequest = new Request("PUT", "/_security/user/local_search_user"); -// putlocalSearchUserRequest.setJsonEntity(""" -// { -// "password": "x-pack-test-password", -// "roles" : ["local_search"] -// }"""); -// assertOK(adminClient().performRequest(putlocalSearchUserRequest)); -// for (String indices : List.of("my_remote_cluster:employees,employees", "my_remote_cluster:employees")) { -// ResponseException error = expectThrows(ResponseException.class, () -> { -// var q = "FROM " + indices + "| ENRICH countries | STATS size=count(*) by country | SORT size | LIMIT 2"; -// performRequestWithLocalSearchUser(esqlRequest(q)); -// }); -// assertThat(error.getResponse().getStatusLine().getStatusCode(), equalTo(403)); -// assertThat( -// error.getMessage(), -// containsString( -// "action [cluster:monitor/xpack/enrich/esql/resolve_policy] towards remote cluster [my_remote_cluster]" -// + " is unauthorized for user [local_search_user] with effective roles [local_search]" -// ) -// ); -// } + ResponseException error = expectThrows(ResponseException.class, () -> { performRequestWithRemoteSearchUser(esqlRequest(""" + FROM my_remote_cluster:employees,employees + | ENRICH countries + | STATS size=count(*) by country + | SORT size DESC + | LIMIT 2""")); }); + + assertThat(error.getResponse().getStatusLine().getStatusCode(), equalTo(403)); + assertThat( + error.getMessage(), + containsString( + "action [cluster:monitor/xpack/enrich/esql/resolve_policy] towards remote cluster is unauthorized for user " + + "[remote_search_user] with assigned roles [remote_search] authenticated by API key id [" + ) + ); + assertThat( + error.getMessage(), + containsString( + "this action is granted by the cluster privileges " + + "[cross_cluster_search,monitor_enrich,manage_enrich,monitor,manage,all]" + ) + ); } } @@ -515,8 +548,8 @@ public void testCrossClusterEnrichWithOnlyRemotePrivs() throws Exception { // Query cluster final var putRoleRequest = new Request("PUT", "/_security/role/" + REMOTE_SEARCH_ROLE); - //local cross_cluster_search cluster priv is required for enrich - //ideally, remote only enrichment wouldn't need this local privilege, however remote only enrichment is not currently supported + // local cross_cluster_search cluster priv is required for enrich + // ideally, remote only enrichment wouldn't need this local privilege, however remote only enrichment is not currently supported putRoleRequest.setJsonEntity(""" { "indices": [{"names": [""], "privileges": ["read_cross_cluster"]}], @@ -540,11 +573,11 @@ public void testCrossClusterEnrichWithOnlyRemotePrivs() throws Exception { // Query cluster // ESQL with enrich is okay when user has access to enrich polices Response response = performRequestWithRemoteSearchUser(esqlRequest(""" - FROM my_remote_cluster:employees - | ENRICH countries - | STATS size=count(*) by country - | SORT size DESC - | LIMIT 2""")); + FROM my_remote_cluster:employees + | ENRICH countries + | STATS size=count(*) by country + | SORT size DESC + | LIMIT 2""")); assertOK(response); Map responseAsMap = entityAsMap(response); @@ -595,11 +628,4 @@ private Response performRequestWithRemoteSearchUser(final Request request) throw ); return client().performRequest(request); } - - private Response performRequestWithLocalSearchUser(final Request request) throws IOException { - request.setOptions( - RequestOptions.DEFAULT.toBuilder().addHeader("Authorization", headerFromRandomAuthMethod("local_search_user", PASS)) - ); - return client().performRequest(request); - } } 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 80d021bc3c63e..d515a949f905f 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 @@ -515,7 +515,7 @@ public static void buildRoleFromDescriptors( ); }); - if(remoteClusterPermissions.hasPrivileges()) { + if (remoteClusterPermissions.hasPrivileges()) { builder.addRemoteClusterPermissions(remoteClusterPermissions); } else { builder.addRemoteClusterPermissions(RemoteClusterPermissions.NONE); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java index 1f30ee3a27cf0..462b41a519460 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java @@ -82,7 +82,7 @@ public class SecurityServerTransportInterceptor implements TransportInterceptor "cluster:internal:data/read/esql/open_exchange", "internal:data/read/esql/exchange", "cluster:internal:data/read/esql/exchange" - ); + ); private final AuthenticationService authcService; private final AuthorizationService authzService;