Skip to content

[Backport] put mapping authorization for alias with write-index and multiple read indices (#40834) #41288

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -19,13 +22,15 @@
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;
import org.elasticsearch.xpack.core.indexlifecycle.LifecycleAction;
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;
Expand All @@ -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;
Expand Down Expand Up @@ -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());
}

/**
Expand Down Expand Up @@ -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<String, Object> 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();
Expand All @@ -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 {
Expand All @@ -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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,17 @@ static String getPutMappingIndexOrAlias(PutMappingRequest request, List<String>
Optional<String> 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> 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -104,7 +105,6 @@ public class IndicesAndAliasesResolverTests extends ESTestCase {
private IndicesAndAliasesResolver defaultIndicesResolver;
private IndexNameExpressionResolver indexNameExpressionResolver;
private Map<String, RoleDescriptor> roleMap;
private FieldPermissionsCache fieldPermissionsCache;

@Before
public void setup() {
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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<String> 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<String> 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?
Expand Down