diff --git a/x-pack/plugin/ilm/qa/with-security/src/test/java/org/elasticsearch/xpack/security/PermissionsIT.java b/x-pack/plugin/ilm/qa/with-security/src/test/java/org/elasticsearch/xpack/security/PermissionsIT.java index 78fc2700f860e..5503e12cb8b0e 100644 --- a/x-pack/plugin/ilm/qa/with-security/src/test/java/org/elasticsearch/xpack/security/PermissionsIT.java +++ b/x-pack/plugin/ilm/qa/with-security/src/test/java/org/elasticsearch/xpack/security/PermissionsIT.java @@ -7,9 +7,12 @@ import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; +import org.elasticsearch.client.Node; import org.elasticsearch.client.Request; import org.elasticsearch.client.Response; import org.elasticsearch.client.ResponseException; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestClientBuilder; import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Settings; @@ -19,6 +22,7 @@ import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.json.JsonXContent; +import org.elasticsearch.common.xcontent.support.XContentMapValues; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.test.rest.ESRestTestCase; import org.elasticsearch.xpack.core.indexlifecycle.DeleteAction; @@ -26,6 +30,7 @@ import org.elasticsearch.xpack.core.indexlifecycle.LifecyclePolicy; import org.elasticsearch.xpack.core.indexlifecycle.LifecycleSettings; import org.elasticsearch.xpack.core.indexlifecycle.Phase; +import org.elasticsearch.xpack.core.indexlifecycle.RolloverAction; import org.junit.Before; import java.io.IOException; @@ -36,8 +41,10 @@ import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; public class PermissionsIT extends ESRestTestCase { + private static final String jsonDoc = "{ \"name\" : \"elasticsearch\", \"body\": \"foo bar\" }"; private String deletePolicy = "deletePolicy"; private Settings indexSettingsWithPolicy; @@ -74,7 +81,7 @@ public void init() throws Exception { .put("number_of_shards", 1) .put("number_of_replicas", 0) .build(); - createNewSingletonPolicy(deletePolicy,"delete", new DeleteAction()); + createNewSingletonPolicy(client(), deletePolicy,"delete", new DeleteAction()); } /** @@ -126,7 +133,62 @@ public void testCanViewExplainOnUnmanagedIndex() throws Exception { assertOK(client().performRequest(request)); } - private void createNewSingletonPolicy(String policy, String phaseName, LifecycleAction action) throws IOException { + /** + * Tests when the user is limited by alias of an index is able to write to index + * which was rolled over by an ILM policy. + */ + public void testWhenUserLimitedByOnlyAliasOfIndexCanWriteToIndexWhichWasRolledoverByILMPolicy() + throws IOException, InterruptedException { + /* + * Setup: + * - ILM policy to rollover index when max docs condition is met + * - Index template to which the ILM policy applies and create Index + * - Create role with just write and manage privileges on alias + * - Create user and assign newly created role. + */ + createNewSingletonPolicy(adminClient(), "foo-policy", "hot", new RolloverAction(null, null, 2L)); + createIndexTemplate("foo-template", "foo-logs-*", "foo_alias", "foo-policy"); + createIndexAsAdmin("foo-logs-000001", "foo_alias", randomBoolean()); + createRole("foo_alias_role", "foo_alias"); + createUser("test_user", "x-pack-test-password", "foo_alias_role"); + + // test_user: index docs using alias in the newly created index + indexDocs("test_user", "x-pack-test-password", "foo_alias", 2); + refresh("foo_alias"); + + // wait so the ILM policy triggers rollover action, verify that the new index exists + assertThat(awaitBusy(() -> { + Request request = new Request("HEAD", "/" + "foo-logs-000002"); + int status; + try { + status = adminClient().performRequest(request).getStatusLine().getStatusCode(); + } catch (IOException e) { + throw new RuntimeException(e); + } + return status == 200; + }), is(true)); + + // test_user: index docs using alias, now should be able write to new index + indexDocs("test_user", "x-pack-test-password", "foo_alias", 1); + refresh("foo_alias"); + + // verify that the doc has been indexed into new write index + awaitBusy(() -> { + Request request = new Request("GET", "/foo-logs-000002/_search"); + Response response; + try { + response = adminClient().performRequest(request); + try (InputStream content = response.getEntity().getContent()) { + Map map = XContentHelper.convertToMap(JsonXContent.jsonXContent, content, false); + return ((Integer) XContentMapValues.extractValue("hits.total.value", map)) == 1; + } + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + + private void createNewSingletonPolicy(RestClient client, String policy, String phaseName, LifecycleAction action) throws IOException { Phase phase = new Phase(phaseName, TimeValue.ZERO, singletonMap(action.getWriteableName(), action)); LifecyclePolicy lifecyclePolicy = new LifecyclePolicy(policy, singletonMap(phase.getName(), phase)); XContentBuilder builder = jsonBuilder(); @@ -135,7 +197,7 @@ private void createNewSingletonPolicy(String policy, String phaseName, Lifecycle "{ \"policy\":" + Strings.toString(builder) + "}", ContentType.APPLICATION_JSON); Request request = new Request("PUT", "_ilm/policy/" + policy); request.setEntity(entity); - client().performRequest(request); + assertOK(client.performRequest(request)); } private void createIndexAsAdmin(String name, Settings settings, String mapping) throws IOException { @@ -144,4 +206,59 @@ private void createIndexAsAdmin(String name, Settings settings, String mapping) + ", \"mappings\" : {" + mapping + "} }"); assertOK(adminClient().performRequest(request)); } + + private void createIndexAsAdmin(String name, String alias, boolean isWriteIndex) throws IOException { + Request request = new Request("PUT", "/" + name); + request.setJsonEntity("{ \"aliases\": { \""+alias+"\": {" + ((isWriteIndex) ? "\"is_write_index\" : true" : "") + + "} } }"); + assertOK(adminClient().performRequest(request)); + } + + private void createIndexTemplate(String name, String pattern, String alias, String policy) throws IOException { + Request request = new Request("PUT", "/_template/" + name); + request.setJsonEntity("{\n" + + " \"index_patterns\": [\""+pattern+"\"],\n" + + " \"settings\": {\n" + + " \"number_of_shards\": 1,\n" + + " \"number_of_replicas\": 0,\n" + + " \"index.lifecycle.name\": \""+policy+"\",\n" + + " \"index.lifecycle.rollover_alias\": \""+alias+"\"\n" + + " }\n" + + " }"); + assertOK(adminClient().performRequest(request)); + } + + private void createUser(String name, String password, String role) throws IOException { + Request request = new Request("PUT", "/_security/user/" + name); + request.setJsonEntity("{ \"password\": \""+password+"\", \"roles\": [ \""+ role+"\"] }"); + assertOK(adminClient().performRequest(request)); + } + + private void createRole(String name, String alias) throws IOException { + Request request = new Request("PUT", "/_security/role/" + name); + request.setJsonEntity("{ \"indices\": [ { \"names\" : [ \""+ alias+"\"], \"privileges\": [ \"write\", \"manage\" ] } ] }"); + assertOK(adminClient().performRequest(request)); + } + + private void indexDocs(String user, String passwd, String index, int noOfDocs) throws IOException { + RestClientBuilder builder = RestClient.builder(adminClient().getNodes().toArray(new Node[0])); + String token = basicAuthHeaderValue(user, new SecureString(passwd.toCharArray())); + configureClient(builder, Settings.builder() + .put(ThreadContext.PREFIX + ".Authorization", token) + .build()); + builder.setStrictDeprecationMode(true); + try (RestClient userClient = builder.build();) { + + for (int cnt = 0; cnt < noOfDocs; cnt++) { + Request request = new Request("POST", "/" + index + "/_doc"); + request.setJsonEntity(jsonDoc); + assertOK(userClient.performRequest(request)); + } + } + } + + private void refresh(String index) throws IOException { + Request request = new Request("POST", "/" + index + "/_refresh"); + assertOK(adminClient().performRequest(request)); + } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolver.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolver.java index e5d4609c13fb1..6e0c2ed0bb101 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolver.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolver.java @@ -249,7 +249,17 @@ static String getPutMappingIndexOrAlias(PutMappingRequest request, List Optional foundAlias = aliasMetaData.stream() .map(AliasMetaData::alias) .filter(authorizedIndicesList::contains) - .filter(aliasName -> metaData.getAliasAndIndexLookup().get(aliasName).getIndices().size() == 1) + .filter(aliasName -> { + AliasOrIndex alias = metaData.getAliasAndIndexLookup().get(aliasName); + List indexMetadata = alias.getIndices(); + if (indexMetadata.size() == 1) { + return true; + } else { + assert alias instanceof AliasOrIndex.Alias; + IndexMetaData idxMeta = ((AliasOrIndex.Alias) alias).getWriteIndex(); + return idxMeta != null && idxMeta.getIndex().getName().equals(concreteIndexName); + } + }) .findFirst(); resolvedAliasOrIndex = foundAlias.orElse(concreteIndexName); } else { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolverTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolverTests.java index 2f09b74ac3d53..dc32580980e02 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolverTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolverTests.java @@ -72,6 +72,7 @@ import org.junit.Before; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -104,7 +105,6 @@ public class IndicesAndAliasesResolverTests extends ESTestCase { private IndicesAndAliasesResolver defaultIndicesResolver; private IndexNameExpressionResolver indexNameExpressionResolver; private Map roleMap; - private FieldPermissionsCache fieldPermissionsCache; @Before public void setup() { @@ -138,13 +138,15 @@ public void setup() { .put(indexBuilder("-index11").settings(settings)) .put(indexBuilder("-index20").settings(settings)) .put(indexBuilder("-index21").settings(settings)) + .put(indexBuilder("logs-00001").putAlias(AliasMetaData.builder("logs-alias").writeIndex(false)).settings(settings)) + .put(indexBuilder("logs-00002").putAlias(AliasMetaData.builder("logs-alias").writeIndex(false)).settings(settings)) + .put(indexBuilder("logs-00003").putAlias(AliasMetaData.builder("logs-alias").writeIndex(true)).settings(settings)) .put(indexBuilder(securityIndexName).settings(settings)).build(); if (withAlias) { metaData = SecurityTestUtils.addAliasToMetaData(metaData, securityIndexName); } this.metaData = metaData; - this.fieldPermissionsCache = new FieldPermissionsCache(settings); user = new User("user", "role"); userDashIndices = new User("dash", "dash"); @@ -1355,6 +1357,29 @@ public void testDynamicPutMappingRequestFromAlias() { request = new PutMappingRequest(Strings.EMPTY_ARRAY).setConcreteIndex(new Index(index, UUIDs.base64UUID())); putMappingIndexOrAlias = IndicesAndAliasesResolver.getPutMappingIndexOrAlias(request, authorizedIndices, metaData); assertEquals(index, putMappingIndexOrAlias); + + } + + public void testWhenAliasToMultipleIndicesAndUserIsAuthorizedUsingAliasReturnsAliasNameForDynamicPutMappingRequestOnWriteIndex() { + String index = "logs-00003"; // write index + PutMappingRequest request = new PutMappingRequest(Strings.EMPTY_ARRAY).setConcreteIndex(new Index(index, UUIDs.base64UUID())); + List authorizedIndices = Collections.singletonList("logs-alias"); + assert metaData.getAliasAndIndexLookup().get("logs-alias").getIndices().size() == 3; + String putMappingIndexOrAlias = IndicesAndAliasesResolver.getPutMappingIndexOrAlias(request, authorizedIndices, metaData); + String message = "user is authorized to access `logs-alias` and the put mapping request is for a write index" + + "so this should have returned the alias name"; + assertEquals(message, "logs-alias", putMappingIndexOrAlias); + } + + public void testWhenAliasToMultipleIndicesAndUserIsAuthorizedUsingAliasReturnsIndexNameForDynamicPutMappingRequestOnReadIndex() { + String index = "logs-00002"; // read index + PutMappingRequest request = new PutMappingRequest(Strings.EMPTY_ARRAY).setConcreteIndex(new Index(index, UUIDs.base64UUID())); + List authorizedIndices = Collections.singletonList("logs-alias"); + assert metaData.getAliasAndIndexLookup().get("logs-alias").getIndices().size() == 3; + String putMappingIndexOrAlias = IndicesAndAliasesResolver.getPutMappingIndexOrAlias(request, authorizedIndices, metaData); + String message = "user is authorized to access `logs-alias` and the put mapping request is for a read index" + + "so this should have returned the concrete index as fallback"; + assertEquals(message, index, putMappingIndexOrAlias); } // TODO with the removal of DeleteByQuery is there another way to test resolving a write action?