diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java index b7261b2dd95..fa10de4fe4c 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java @@ -638,8 +638,8 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase { List roles = response.getRoles(); assertNotNull(response); - // 23 system roles plus the three we created - assertThat(roles.size(), equalTo(26)); + // 24 system roles plus the three we created + assertThat(roles.size(), equalTo(27)); } { 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 fba595e7a09..f3822dac4e4 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,6 +6,10 @@ 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.MapBuilder; @@ -48,6 +52,8 @@ public final class ClusterPrivilege extends Privilege { 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); @@ -73,6 +79,7 @@ public final class ClusterPrivilege extends Privilege { 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); @@ -98,6 +105,7 @@ public final class ClusterPrivilege extends Privilege { .put("manage_rollup", MANAGE_ROLLUP) .put("manage_ccr", MANAGE_CCR) .put("read_ccr", READ_CCR) + .put("create_snapshot", CREATE_SNAPSHOT) .put("manage_ilm", MANAGE_ILM) .put("read_ilm", READ_ILM) .immutableMap(); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/IndexPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/IndexPrivilege.java index 3a92c08704e..d24863d6d53 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/IndexPrivilege.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/IndexPrivilege.java @@ -64,8 +64,8 @@ public final class IndexPrivilege extends Privilege { CloseIndexAction.NAME + "*"); private static final Automaton MANAGE_ILM_AUTOMATON = patterns("indices:admin/ilm/*"); - public static final IndexPrivilege NONE = new IndexPrivilege("none", Automatons.EMPTY); - public static final IndexPrivilege ALL = new IndexPrivilege("all", ALL_AUTOMATON); + public static final IndexPrivilege NONE = new IndexPrivilege("none", Automatons.EMPTY); + public static final IndexPrivilege ALL = new IndexPrivilege("all", ALL_AUTOMATON); public static final IndexPrivilege READ = new IndexPrivilege("read", READ_AUTOMATON); public static final IndexPrivilege READ_CROSS_CLUSTER = new IndexPrivilege("read_cross_cluster", READ_CROSS_CLUSTER_AUTOMATON); public static final IndexPrivilege CREATE = new IndexPrivilege("create", CREATE_AUTOMATON); 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 8cb151da4e2..2c30b5fe1af 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 @@ -6,6 +6,7 @@ package org.elasticsearch.xpack.core.security.authz.store; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.admin.cluster.repositories.get.GetRepositoriesAction; import org.elasticsearch.common.collect.MapBuilder; import org.elasticsearch.xpack.core.monitoring.action.MonitoringBulkAction; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; @@ -179,6 +180,12 @@ public class ReservedRolesStore implements BiConsumer, ActionListene RoleDescriptor.IndicesPrivileges.builder() .indices(".code-*").privileges("read").build() }, null, MetadataUtils.DEFAULT_RESERVED_METADATA)) + .put("snapshot_user", new RoleDescriptor("snapshot_user", new String[] { "create_snapshot", GetRepositoriesAction.NAME }, + new RoleDescriptor.IndicesPrivileges[] { RoleDescriptor.IndicesPrivileges.builder() + .indices("*") + .privileges("view_index_metadata") + .allowRestrictedIndices(true) + .build() }, null, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA, null)) .immutableMap(); } 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 dc077a17e5c..e6e1dd1d068 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 @@ -7,8 +7,13 @@ package org.elasticsearch.xpack.core.security.authz.store; import org.elasticsearch.Version; import org.elasticsearch.action.admin.cluster.health.ClusterHealthAction; +import org.elasticsearch.action.admin.cluster.repositories.get.GetRepositoriesAction; +import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryAction; import org.elasticsearch.action.admin.cluster.reroute.ClusterRerouteAction; import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsAction; +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.action.admin.cluster.stats.ClusterStatsAction; import org.elasticsearch.action.admin.indices.create.CreateIndexAction; @@ -173,9 +178,54 @@ public class ReservedRolesStoreTests extends ESTestCase { assertThat(ReservedRolesStore.isReserved(APMSystemUser.ROLE_NAME), is(true)); assertThat(ReservedRolesStore.isReserved(RemoteMonitoringUser.COLLECTION_ROLE_NAME), is(true)); assertThat(ReservedRolesStore.isReserved(RemoteMonitoringUser.INDEXING_ROLE_NAME), is(true)); + assertThat(ReservedRolesStore.isReserved("snapshot_user"), is(true)); assertThat(ReservedRolesStore.isReserved("code_admin"), is(true)); assertThat(ReservedRolesStore.isReserved("code_user"), is(true)); + } + public void testSnapshotUserRole() { + final TransportRequest request = mock(TransportRequest.class); + + RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("snapshot_user"); + assertNotNull(roleDescriptor); + 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.indices().allowedIndicesMatcher(IndexAction.NAME).test(randomAlphaOfLengthBetween(8, 24)), is(false)); + assertThat(snapshotUserRole.indices().allowedIndicesMatcher("indices:foo").test(randomAlphaOfLengthBetween(8, 24)), is(false)); + assertThat(snapshotUserRole.indices().allowedIndicesMatcher(GetAction.NAME).test(randomAlphaOfLengthBetween(8, 24)), is(false)); + assertThat(snapshotUserRole.indices().allowedIndicesMatcher(GetAction.NAME).test(randomAlphaOfLengthBetween(8, 24)), is(false)); + + assertThat(snapshotUserRole.indices().allowedIndicesMatcher(GetIndexAction.NAME) + .test(randomAlphaOfLengthBetween(8, 24)), is(true)); + assertThat(snapshotUserRole.indices().allowedIndicesMatcher(GetIndexAction.NAME) + .test(RestrictedIndicesNames.INTERNAL_SECURITY_INDEX), is(true)); + assertThat(snapshotUserRole.indices().allowedIndicesMatcher(GetIndexAction.NAME) + .test(RestrictedIndicesNames.SECURITY_INDEX_NAME), is(true)); + + assertNoAccessAllowed(snapshotUserRole, RestrictedIndicesNames.NAMES_SET); } public void testIngestAdminRole() { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/ClusterPrivilegeTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/ClusterPrivilegeTests.java index bf81fd77dc5..3b30982784b 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/ClusterPrivilegeTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/ClusterPrivilegeTests.java @@ -32,12 +32,15 @@ public class ClusterPrivilegeTests extends AbstractPrivilegeTestCase { "role_c:\n" + " indices:\n" + " - names: 'someindex'\n" + - " privileges: [ all ]\n"; + " privileges: [ all ]\n" + + "role_d:\n" + + " cluster: [ create_snapshot ]\n"; private static final String USERS_ROLES = "role_a:user_a\n" + "role_b:user_b\n" + - "role_c:user_c\n"; + "role_c:user_c\n" + + "role_d:user_d\n"; private static Path repositoryLocation; @@ -75,8 +78,8 @@ public class ClusterPrivilegeTests extends AbstractPrivilegeTestCase { return super.configUsers() + "user_a:" + usersPasswdHashed + "\n" + "user_b:" + usersPasswdHashed + "\n" + - "user_c:" + usersPasswdHashed + "\n"; - + "user_c:" + usersPasswdHashed + "\n" + + "user_d:" + usersPasswdHashed + "\n"; } @Override @@ -122,6 +125,18 @@ public class ClusterPrivilegeTests extends AbstractPrivilegeTestCase { assertAccessIsDenied("user_c", "GET", "/_nodes/infos"); assertAccessIsDenied("user_c", "POST", "/_cluster/reroute"); assertAccessIsDenied("user_c", "PUT", "/_cluster/settings", "{ \"transient\" : { \"search.default_search_timeout\": \"1m\" } }"); + + // user_d can view repos and create and view snapshots on existings repos, everything else is DENIED + assertAccessIsDenied("user_d", "GET", "/_cluster/state"); + assertAccessIsDenied("user_d", "GET", "/_cluster/health"); + assertAccessIsDenied("user_d", "GET", "/_cluster/settings"); + assertAccessIsDenied("user_d", "GET", "/_cluster/stats"); + assertAccessIsDenied("user_d", "GET", "/_cluster/pending_tasks"); + assertAccessIsDenied("user_d", "GET", "/_nodes/stats"); + assertAccessIsDenied("user_d", "GET", "/_nodes/hot_threads"); + assertAccessIsDenied("user_d", "GET", "/_nodes/infos"); + assertAccessIsDenied("user_d", "POST", "/_cluster/reroute"); + assertAccessIsDenied("user_d", "PUT", "/_cluster/settings", "{ \"transient\" : { \"search.default_search_timeout\": \"1m\" } }"); } public void testThatSnapshotAndRestore() throws Exception { @@ -129,6 +144,7 @@ public class ClusterPrivilegeTests extends AbstractPrivilegeTestCase { repositoryLocation.toString()).endObject().endObject()); assertAccessIsDenied("user_b", "PUT", "/_snapshot/my-repo", repoJson); assertAccessIsDenied("user_c", "PUT", "/_snapshot/my-repo", repoJson); + assertAccessIsDenied("user_d", "PUT", "/_snapshot/my-repo", repoJson); assertAccessIsAllowed("user_a", "PUT", "/_snapshot/my-repo", repoJson); Request createBar = new Request("PUT", "/someindex/bar/1"); @@ -136,6 +152,7 @@ public class ClusterPrivilegeTests extends AbstractPrivilegeTestCase { createBar.addParameter("refresh", "true"); assertAccessIsDenied("user_a", createBar); assertAccessIsDenied("user_b", createBar); + assertAccessIsDenied("user_d", createBar); assertAccessIsAllowed("user_c", createBar); assertAccessIsDenied("user_b", "PUT", "/_snapshot/my-repo/my-snapshot", "{ \"indices\": \"someindex\" }"); @@ -145,30 +162,38 @@ public class ClusterPrivilegeTests extends AbstractPrivilegeTestCase { assertAccessIsDenied("user_b", "GET", "/_snapshot/my-repo/my-snapshot/_status"); assertAccessIsDenied("user_c", "GET", "/_snapshot/my-repo/my-snapshot/_status"); assertAccessIsAllowed("user_a", "GET", "/_snapshot/my-repo/my-snapshot/_status"); + assertAccessIsAllowed("user_d", "GET", "/_snapshot/my-repo/my-snapshot/_status"); // This snapshot needs to be finished in order to be restored waitForSnapshotToFinish("my-repo", "my-snapshot"); + // user_d can create snapshots, but not concurrently + assertAccessIsAllowed("user_d", "PUT", "/_snapshot/my-repo/my-snapshot-d", "{ \"indices\": \"someindex\" }"); assertAccessIsDenied("user_a", "DELETE", "/someindex"); assertAccessIsDenied("user_b", "DELETE", "/someindex"); + assertAccessIsDenied("user_d", "DELETE", "/someindex"); assertAccessIsAllowed("user_c", "DELETE", "/someindex"); Request restoreSnapshotRequest = new Request("POST", "/_snapshot/my-repo/my-snapshot/_restore"); restoreSnapshotRequest.addParameter("wait_for_completion", "true"); assertAccessIsDenied("user_b", restoreSnapshotRequest); assertAccessIsDenied("user_c", restoreSnapshotRequest); + assertAccessIsDenied("user_d", restoreSnapshotRequest); assertAccessIsAllowed("user_a", restoreSnapshotRequest); assertAccessIsDenied("user_a", "GET", "/someindex/bar/1"); assertAccessIsDenied("user_b", "GET", "/someindex/bar/1"); + assertAccessIsDenied("user_d", "GET", "/someindex/bar/1"); assertAccessIsAllowed("user_c", "GET", "/someindex/bar/1"); assertAccessIsDenied("user_b", "DELETE", "/_snapshot/my-repo/my-snapshot"); assertAccessIsDenied("user_c", "DELETE", "/_snapshot/my-repo/my-snapshot"); + assertAccessIsDenied("user_d", "DELETE", "/_snapshot/my-repo/my-snapshot"); assertAccessIsAllowed("user_a", "DELETE", "/_snapshot/my-repo/my-snapshot"); assertAccessIsDenied("user_b", "DELETE", "/_snapshot/my-repo"); assertAccessIsDenied("user_c", "DELETE", "/_snapshot/my-repo"); + assertAccessIsDenied("user_d", "DELETE", "/_snapshot/my-repo"); assertAccessIsAllowed("user_a", "DELETE", "/_snapshot/my-repo"); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/SnapshotUserRoleIntegTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/SnapshotUserRoleIntegTests.java new file mode 100644 index 00000000000..9a6909aad26 --- /dev/null +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/SnapshotUserRoleIntegTests.java @@ -0,0 +1,131 @@ +/* + * 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.security.authz; + +import org.elasticsearch.action.admin.cluster.repositories.get.GetRepositoriesResponse; +import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse; +import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse; +import org.elasticsearch.action.admin.indices.get.GetIndexResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.snapshots.SnapshotState; +import org.elasticsearch.test.NativeRealmIntegTestCase; +import org.elasticsearch.xpack.core.security.authc.support.Hasher; +import org.junit.Before; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Locale; + +import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.INTERNAL_SECURITY_INDEX; +import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_INDEX_NAME; + +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; +import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.elasticsearch.test.SecurityTestsUtils.assertThrowsAuthorizationException; + +public class SnapshotUserRoleIntegTests extends NativeRealmIntegTestCase { + + private Client client; + private String ordinaryIndex; + + @Before + public void setupClusterBeforeSnapshot() { + logger.info("--> creating repository"); + assertAcked(client().admin().cluster().preparePutRepository("repo") + .setType("fs") + .setSettings(Settings.builder().put("location", randomRepoPath()))); + + logger.info("--> creating ordinary index"); + final int shards = between(1, 10); + ordinaryIndex = randomAlphaOfLength(4).toLowerCase(Locale.ROOT); + assertAcked(prepareCreate(ordinaryIndex, 0, Settings.builder().put("number_of_shards", shards).put("number_of_replicas", 0))); + ensureGreen(); + + logger.info("--> creating snapshot_user user"); + final String user = "snapshot_user"; + final char[] password = new char[] {'p', 'a', 's', 's', 'w', 'o', 'r', 'd'}; + final String snapshotUserToken = basicAuthHeaderValue(user, new SecureString(password)); + client = client().filterWithHeader(Collections.singletonMap("Authorization", snapshotUserToken)); + securityClient().preparePutUser(user, password, Hasher.BCRYPT, "snapshot_user").get(); + ensureGreen(INTERNAL_SECURITY_INDEX); + } + + public void testSnapshotUserRoleCanSnapshotAndSeeAllIndices() { + // view repositories + final GetRepositoriesResponse getRepositoriesResponse = client.admin().cluster().prepareGetRepositories(randomFrom("*", "_all")) + .get(); + assertThat(getRepositoriesResponse.repositories().size(), is(1)); + assertThat(getRepositoriesResponse.repositories().get(0).name(), is("repo")); + // view all indices, including restricted ones + final GetIndexResponse getIndexResponse = client.admin().indices().prepareGetIndex().setIndices(randomFrom("_all", "*")).get(); + assertThat(Arrays.asList(getIndexResponse.indices()), containsInAnyOrder(INTERNAL_SECURITY_INDEX, ordinaryIndex)); + // create snapshot that includes restricted indices + final CreateSnapshotResponse snapshotResponse = client.admin().cluster().prepareCreateSnapshot("repo", "snap") + .setIndices(randomFrom("_all", "*")).setWaitForCompletion(true).get(); + assertThat(snapshotResponse.getSnapshotInfo().state(), is(SnapshotState.SUCCESS)); + assertThat(snapshotResponse.getSnapshotInfo().indices(), containsInAnyOrder(INTERNAL_SECURITY_INDEX, ordinaryIndex)); + // view snapshots for repo + final GetSnapshotsResponse getSnapshotResponse = client.admin().cluster().prepareGetSnapshots("repo").get(); + assertThat(getSnapshotResponse.getSnapshots().size(), is(1)); + assertThat(getSnapshotResponse.getSnapshots().get(0).snapshotId().getName(), is("snap")); + assertThat(getSnapshotResponse.getSnapshots().get(0).indices(), containsInAnyOrder(INTERNAL_SECURITY_INDEX, ordinaryIndex)); + } + + public void testSnapshotUserRoleIsReserved() { + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, + () -> securityClient().preparePutRole("snapshot_user").get()); + assertThat(e.getMessage(), containsString("role [snapshot_user] is reserved and cannot be modified")); + e = expectThrows(IllegalArgumentException.class, + () -> securityClient().prepareDeleteRole("snapshot_user").get()); + assertThat(e.getMessage(), containsString("role [snapshot_user] is reserved and cannot be deleted")); + } + + public void testSnapshotUserRoleUnathorizedForDestructiveActions() { + // try search all + assertThrowsAuthorizationException(() -> client.prepareSearch(randomFrom("_all", "*")).get(), "indices:data/read/search", + "snapshot_user"); + // try create index + assertThrowsAuthorizationException(() -> client.admin().indices().prepareCreate(ordinaryIndex + "2").get(), "indices:admin/create", + "snapshot_user"); + // try create another repo + assertThrowsAuthorizationException( + () -> client.admin().cluster().preparePutRepository("some_other_repo").setType("fs") + .setSettings(Settings.builder().put("location", randomRepoPath())).get(), + "cluster:admin/repository/put", "snapshot_user"); + // try delete repo + assertThrowsAuthorizationException(() -> client.admin().cluster().prepareDeleteRepository("repo").get(), + "cluster:admin/repository/delete", "snapshot_user"); + // try fumble with snapshots + assertThrowsAuthorizationException( + () -> client.admin().cluster().prepareRestoreSnapshot("repo", randomAlphaOfLength(4).toLowerCase(Locale.ROOT)).get(), + "cluster:admin/snapshot/restore", "snapshot_user"); + assertThrowsAuthorizationException( + () -> client.admin().cluster().prepareDeleteSnapshot("repo", randomAlphaOfLength(4).toLowerCase(Locale.ROOT)).get(), + "cluster:admin/snapshot/delete", "snapshot_user"); + // try destructive/revealing actions on all indices + for (final String indexToTest : Arrays.asList(INTERNAL_SECURITY_INDEX, SECURITY_INDEX_NAME, ordinaryIndex)) { + assertThrowsAuthorizationException(() -> client.prepareSearch(indexToTest).get(), "indices:data/read/search", "snapshot_user"); + assertThrowsAuthorizationException(() -> client.prepareGet(indexToTest, "doc", "1").get(), "indices:data/read/get", + "snapshot_user"); + assertThrowsAuthorizationException(() -> client.prepareIndex(indexToTest, "doc").setSource("term", "val").get(), + "indices:data/write/index", "snapshot_user"); + assertThrowsAuthorizationException(() -> client.prepareUpdate(indexToTest, "doc", "1").setDoc("term", "val").get(), + "indices:data/write/update", "snapshot_user"); + assertThrowsAuthorizationException(() -> client.prepareDelete(indexToTest, "doc", "1").get(), "indices:data/write/delete", + "snapshot_user"); + + assertThrowsAuthorizationException(() -> client.admin().indices().prepareDelete(indexToTest).get(), "indices:admin/delete", + "snapshot_user"); + } + } + +}