Explorar o código

Update selected reserved roles for remote indices privileges (#93540)

The new RCS model requires remote indices privileges to be specified
separately from local indices privileges. This PR updates selected
reserved roles with necessary remote indices privileges to ensure they
work with both legacy and new RCS remote clusters.
Yang Wang %!s(int64=2) %!d(string=hai) anos
pai
achega
87fdbb8749

+ 17 - 3
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/Role.java

@@ -341,9 +341,6 @@ public interface Role {
         final RestrictedIndices restrictedIndices,
         final Collection<ApplicationPrivilegeDescriptor> storedApplicationPrivilegeDescriptors
     ) {
-        // TODO handle this when we introduce remote index privileges for built-in users and roles. That's the only production code
-        // using this builder
-        assert false == roleDescriptor.hasRemoteIndicesPrivileges();
         Objects.requireNonNull(fieldPermissionsCache);
 
         final Builder builder = builder(restrictedIndices, roleDescriptor.getName());
@@ -365,6 +362,23 @@ public interface Role {
             );
         }
 
+        for (RoleDescriptor.RemoteIndicesPrivileges remoteIndicesPrivileges : roleDescriptor.getRemoteIndicesPrivileges()) {
+            final String[] clusterAliases = remoteIndicesPrivileges.remoteClusters();
+            assert Arrays.equals(new String[] { "*" }, clusterAliases)
+                : "reserved role should not define remote indices privileges for specific clusters";
+            final RoleDescriptor.IndicesPrivileges indicesPrivileges = remoteIndicesPrivileges.indicesPrivileges();
+            builder.addRemoteGroup(
+                Set.of(clusterAliases),
+                fieldPermissionsCache.getFieldPermissions(
+                    new FieldPermissionsDefinition(indicesPrivileges.getGrantedFields(), indicesPrivileges.getDeniedFields())
+                ),
+                indicesPrivileges.getQuery() == null ? null : Collections.singleton(indicesPrivileges.getQuery()),
+                IndexPrivilege.get(Set.of(indicesPrivileges.getPrivileges())),
+                indicesPrivileges.allowRestrictedIndices(),
+                indicesPrivileges.getIndices()
+            );
+        }
+
         for (RoleDescriptor.ApplicationResourcePrivileges applicationPrivilege : roleDescriptor.getApplicationPrivileges()) {
             ApplicationPrivilege.get(
                 applicationPrivilege.getApplication(),

+ 44 - 3
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java

@@ -15,6 +15,7 @@ import org.elasticsearch.action.admin.indices.mapping.put.PutMappingAction;
 import org.elasticsearch.action.admin.indices.rollover.RolloverAction;
 import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsAction;
 import org.elasticsearch.common.collect.MapBuilder;
+import org.elasticsearch.transport.TcpTransport;
 import org.elasticsearch.xpack.core.ilm.action.GetLifecycleAction;
 import org.elasticsearch.xpack.core.ilm.action.PutLifecycleAction;
 import org.elasticsearch.xpack.core.monitoring.action.MonitoringBulkAction;
@@ -81,10 +82,36 @@ public class ReservedRolesStore implements BiConsumer<Set<String>, ActionListene
         null,
         new String[] { "*" },
         MetadataUtils.DEFAULT_RESERVED_METADATA,
-        Collections.emptyMap()
+        Collections.emptyMap(),
+        TcpTransport.isUntrustedRemoteClusterEnabled()
+            ? new RoleDescriptor.RemoteIndicesPrivileges[] {
+                new RoleDescriptor.RemoteIndicesPrivileges(
+                    RoleDescriptor.IndicesPrivileges.builder().indices("*").privileges("all").allowRestrictedIndices(false).build(),
+                    "*"
+                ),
+                new RoleDescriptor.RemoteIndicesPrivileges(
+                    RoleDescriptor.IndicesPrivileges.builder()
+                        .indices("*")
+                        .privileges("monitor", "read", "view_index_metadata", "read_cross_cluster")
+                        .allowRestrictedIndices(true)
+                        .build(),
+                    "*"
+                ) }
+            : null
     );
     private static final Map<String, RoleDescriptor> RESERVED_ROLES = initializeReservedRoles();
 
+    private static RoleDescriptor.RemoteIndicesPrivileges getRemoteIndicesReadPrivileges(String indexPattern) {
+        return new RoleDescriptor.RemoteIndicesPrivileges(
+            RoleDescriptor.IndicesPrivileges.builder()
+                .indices(indexPattern)
+                .privileges("read", "read_cross_cluster")
+                .allowRestrictedIndices(false)
+                .build(),
+            "*"
+        );
+    }
+
     private static Map<String, RoleDescriptor> initializeReservedRoles() {
         return MapBuilder.<String, RoleDescriptor>newMapBuilder()
             .put("superuser", SUPERUSER_ROLE_DESCRIPTOR)
@@ -126,7 +153,12 @@ public class ReservedRolesStore implements BiConsumer<Set<String>, ActionListene
                     null,
                     null,
                     MetadataUtils.DEFAULT_RESERVED_METADATA,
-                    null
+                    null,
+                    TcpTransport.isUntrustedRemoteClusterEnabled()
+                        ? new RoleDescriptor.RemoteIndicesPrivileges[] {
+                            getRemoteIndicesReadPrivileges(".monitoring-*"),
+                            getRemoteIndicesReadPrivileges("metricbeat-*") }
+                        : null
                 )
             )
             .put(
@@ -854,7 +886,16 @@ public class ReservedRolesStore implements BiConsumer<Set<String>, ActionListene
                 new WriteProfileDataPrivileges(Set.of("kibana*")) },
             null,
             MetadataUtils.DEFAULT_RESERVED_METADATA,
-            null
+            null,
+            TcpTransport.isUntrustedRemoteClusterEnabled()
+                ? new RoleDescriptor.RemoteIndicesPrivileges[] {
+                    getRemoteIndicesReadPrivileges(".monitoring-*"),
+                    getRemoteIndicesReadPrivileges("apm-*"),
+                    getRemoteIndicesReadPrivileges("logs-apm.*"),
+                    getRemoteIndicesReadPrivileges("metrics-apm.*"),
+                    getRemoteIndicesReadPrivileges("traces-apm.*"),
+                    getRemoteIndicesReadPrivileges("traces-apm-*") }
+                : null
         );
     }
 

+ 75 - 0
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java

@@ -64,6 +64,7 @@ import org.elasticsearch.cluster.metadata.Metadata;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.util.set.Sets;
 import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.transport.TcpTransport;
 import org.elasticsearch.transport.TransportRequest;
 import org.elasticsearch.xpack.core.XPackPlugin;
 import org.elasticsearch.xpack.core.action.XPackInfoAction;
@@ -180,6 +181,7 @@ import org.elasticsearch.xpack.core.security.authc.AuthenticationTestHelper;
 import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
 import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
 import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsCache;
+import org.elasticsearch.xpack.core.security.authz.permission.RemoteIndicesPermission;
 import org.elasticsearch.xpack.core.security.authz.permission.Role;
 import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor;
 import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeTests;
@@ -213,6 +215,7 @@ import org.elasticsearch.xpack.core.watcher.transport.actions.service.WatcherSer
 import org.elasticsearch.xpack.core.watcher.transport.actions.stats.WatcherStatsAction;
 import org.elasticsearch.xpack.core.watcher.watch.Watch;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -222,6 +225,10 @@ import java.util.Set;
 import java.util.SortedMap;
 
 import static org.elasticsearch.xpack.core.security.test.TestRestrictedIndices.RESTRICTED_INDICES;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.emptyArray;
+import static org.hamcrest.Matchers.emptyIterable;
+import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.hasEntry;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.not;
@@ -2963,6 +2970,74 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 20)), is(false));
     }
 
+    public void testRemoteIndicesPrivilegesForSuperuserRole() {
+        final RoleDescriptor superuserRoleDescriptor = new ReservedRolesStore().roleDescriptor("superuser");
+        final Role superuserRole = Role.buildFromRoleDescriptor(
+            superuserRoleDescriptor,
+            new FieldPermissionsCache(Settings.EMPTY),
+            RESTRICTED_INDICES
+        );
+
+        if (TcpTransport.isUntrustedRemoteClusterEnabled()) {
+            assertThat(superuserRoleDescriptor.hasRemoteIndicesPrivileges(), is(true));
+            assertThat(
+                Arrays.stream(superuserRoleDescriptor.getRemoteIndicesPrivileges())
+                    .map(RoleDescriptor.RemoteIndicesPrivileges::indicesPrivileges)
+                    .toArray(RoleDescriptor.IndicesPrivileges[]::new),
+                equalTo(superuserRoleDescriptor.getIndicesPrivileges())
+            );
+            final List<RemoteIndicesPermission.RemoteIndicesGroup> remoteIndicesGroups = superuserRole.remoteIndices()
+                .remoteIndicesGroups();
+            assertThat(remoteIndicesGroups.size(), equalTo(1));
+            assertThat(remoteIndicesGroups.get(0).remoteClusterAliasMatcher().isTotal(), is(true));
+        } else {
+            assertThat(superuserRoleDescriptor.hasRemoteIndicesPrivileges(), is(false));
+            assertThat(superuserRoleDescriptor.getRemoteIndicesPrivileges(), emptyArray());
+            assertThat(superuserRole.remoteIndices(), is(RemoteIndicesPermission.NONE));
+        }
+    }
+
+    public void testRemoteIndicesPrivileges() {
+        final List<String> rolesWithRemoteIndicesPrivileges = new ArrayList<>();
+
+        for (RoleDescriptor roleDescriptor : new ReservedRolesStore().roleDescriptors()) {
+            if (roleDescriptor.getName().equals("superuser")) {
+                continue;  // superuser is tested separately
+            }
+            final Role role = Role.buildFromRoleDescriptor(roleDescriptor, new FieldPermissionsCache(Settings.EMPTY), RESTRICTED_INDICES);
+
+            if (TcpTransport.isUntrustedRemoteClusterEnabled()) {
+                // The assumption here is that any read_cross_cluster indices privileges should be paired with
+                // a corresponding remote indices privileges
+                final var readCrossClusterIndicesPrivileges = Arrays.stream(roleDescriptor.getIndicesPrivileges())
+                    .filter(ip -> Arrays.asList(ip.getPrivileges()).contains("read_cross_cluster"))
+                    .toArray(RoleDescriptor.IndicesPrivileges[]::new);
+                if (readCrossClusterIndicesPrivileges.length == 0) {
+                    assertThat(roleDescriptor.hasRemoteIndicesPrivileges(), is(false));
+                } else {
+                    assertThat(roleDescriptor.hasRemoteIndicesPrivileges(), is(true));
+                    assertThat(
+                        Arrays.stream(roleDescriptor.getRemoteIndicesPrivileges())
+                            .map(RoleDescriptor.RemoteIndicesPrivileges::indicesPrivileges)
+                            .toList(),
+                        containsInAnyOrder(readCrossClusterIndicesPrivileges)
+                    );
+                    rolesWithRemoteIndicesPrivileges.add(roleDescriptor.getName());
+                }
+            } else {
+                assertThat(roleDescriptor.hasRemoteIndicesPrivileges(), is(false));
+                assertThat(roleDescriptor.getRemoteIndicesPrivileges(), emptyArray());
+                assertThat(role.remoteIndices(), is(RemoteIndicesPermission.NONE));
+            }
+        }
+
+        if (TcpTransport.isUntrustedRemoteClusterEnabled()) {
+            assertThat(rolesWithRemoteIndicesPrivileges, containsInAnyOrder("kibana_system", "monitoring_user"));
+        } else {
+            assertThat(rolesWithRemoteIndicesPrivileges, emptyIterable());
+        }
+    }
+
     private void assertAllIndicesAccessAllowed(Role role, String index) {
         logger.info("index name [{}]", index);
         assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(index)), is(true));

+ 113 - 0
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RBACEngineTests.java

@@ -75,6 +75,7 @@ import org.elasticsearch.xpack.core.security.authz.RoleDescriptorsIntersection;
 import org.elasticsearch.xpack.core.security.authz.permission.ApplicationPermission;
 import org.elasticsearch.xpack.core.security.authz.permission.ClusterPermission;
 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.FieldPermissionsDefinition;
 import org.elasticsearch.xpack.core.security.authz.permission.IndicesPermission;
 import org.elasticsearch.xpack.core.security.authz.permission.RemoteIndicesPermission;
@@ -88,6 +89,7 @@ import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivileg
 import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableClusterPrivileges.ManageApplicationPrivileges;
 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;
 import org.elasticsearch.xpack.core.security.support.Automatons;
 import org.elasticsearch.xpack.core.security.test.TestRestrictedIndices;
 import org.elasticsearch.xpack.core.security.user.User;
@@ -1784,6 +1786,117 @@ public class RBACEngineTests extends ESTestCase {
         assertThat(actual, equalTo(RoleDescriptorsIntersection.EMPTY));
     }
 
+    public void testGetRemoteAccessRoleDescriptorsForReservedRoles() {
+        assumeTrue("untrusted remote cluster feature flag must be enabled", TcpTransport.isUntrustedRemoteClusterEnabled());
+
+        final ReservedRolesStore reservedRolesStore = new ReservedRolesStore();
+        final FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY);
+
+        // superuser
+        {
+            final SimpleRole role = Role.buildFromRoleDescriptor(
+                reservedRolesStore.roleDescriptor("superuser"),
+                fieldPermissionsCache,
+                RESTRICTED_INDICES
+            );
+            final RBACAuthorizationInfo authorizationInfo = mock(RBACAuthorizationInfo.class);
+            when(authorizationInfo.getRole()).thenReturn(role);
+            final PlainActionFuture<RoleDescriptorsIntersection> future = new PlainActionFuture<>();
+            engine.getRemoteAccessRoleDescriptorsIntersection(randomAlphaOfLengthBetween(5, 20), authorizationInfo, future);
+            assertThat(
+                future.actionGet(),
+                equalTo(
+                    new RoleDescriptorsIntersection(
+                        new RoleDescriptor(
+                            Role.REMOTE_USER_ROLE_NAME,
+                            null,
+                            new IndicesPrivileges[] {
+                                IndicesPrivileges.builder().indices("*").privileges("all").allowRestrictedIndices(false).build(),
+                                IndicesPrivileges.builder()
+                                    .indices("*")
+                                    .privileges("monitor", "read", "read_cross_cluster", "view_index_metadata")
+                                    .allowRestrictedIndices(true)
+                                    .build() },
+                            null,
+                            null,
+                            null,
+                            null,
+                            null
+                        )
+                    )
+                )
+            );
+        }
+
+        // kibana_system
+        {
+            final SimpleRole role = Role.buildFromRoleDescriptor(
+                reservedRolesStore.roleDescriptor("kibana_system"),
+                fieldPermissionsCache,
+                RESTRICTED_INDICES
+            );
+            final RBACAuthorizationInfo authorizationInfo = mock(RBACAuthorizationInfo.class);
+            when(authorizationInfo.getRole()).thenReturn(role);
+            final PlainActionFuture<RoleDescriptorsIntersection> future = new PlainActionFuture<>();
+            engine.getRemoteAccessRoleDescriptorsIntersection(randomAlphaOfLengthBetween(5, 20), authorizationInfo, future);
+            assertThat(
+                future.actionGet(),
+                equalTo(
+                    new RoleDescriptorsIntersection(
+                        new RoleDescriptor(
+                            Role.REMOTE_USER_ROLE_NAME,
+                            null,
+                            new IndicesPrivileges[] {
+                                IndicesPrivileges.builder().indices(".monitoring-*").privileges("read", "read_cross_cluster").build(),
+                                IndicesPrivileges.builder().indices("apm-*").privileges("read", "read_cross_cluster").build(),
+                                IndicesPrivileges.builder().indices("logs-apm.*").privileges("read", "read_cross_cluster").build(),
+                                IndicesPrivileges.builder().indices("metrics-apm.*").privileges("read", "read_cross_cluster").build(),
+                                IndicesPrivileges.builder().indices("traces-apm-*").privileges("read", "read_cross_cluster").build(),
+                                IndicesPrivileges.builder().indices("traces-apm.*").privileges("read", "read_cross_cluster").build() },
+                            null,
+                            null,
+                            null,
+                            null,
+                            null
+                        )
+                    )
+                )
+            );
+        }
+
+        // monitoring_user
+        {
+            final SimpleRole role = Role.buildFromRoleDescriptor(
+                reservedRolesStore.roleDescriptor("monitoring_user"),
+                fieldPermissionsCache,
+                RESTRICTED_INDICES
+            );
+            final RBACAuthorizationInfo authorizationInfo = mock(RBACAuthorizationInfo.class);
+            when(authorizationInfo.getRole()).thenReturn(role);
+            final PlainActionFuture<RoleDescriptorsIntersection> future = new PlainActionFuture<>();
+            engine.getRemoteAccessRoleDescriptorsIntersection(randomAlphaOfLengthBetween(5, 20), authorizationInfo, future);
+            assertThat(
+                future.actionGet(),
+                equalTo(
+                    new RoleDescriptorsIntersection(
+                        new RoleDescriptor(
+                            Role.REMOTE_USER_ROLE_NAME,
+                            null,
+                            new IndicesPrivileges[] {
+                                IndicesPrivileges.builder().indices(".monitoring-*").privileges("read", "read_cross_cluster").build(),
+                                IndicesPrivileges.builder().indices("metricbeat-*").privileges("read", "read_cross_cluster").build() },
+                            null,
+                            null,
+                            null,
+                            null,
+                            null
+                        )
+                    )
+                )
+            );
+        }
+    }
+
     public void testChildSearchActionAuthorizationIsSkipped() {
         final String[] indices = { "test-index" };
         final Role role = Mockito.spy(Role.builder(RESTRICTED_INDICES, "test-role").add(IndexPrivilege.READ, indices).build());