disable core caches to ensure users are always authorized

The IndicesTermsFilter Cache in core can leak data by not authorizing users prior to
retrieving data from the cache. We work around this by ensuring that the cache has
a maximum size of 0, effectively disabling it.

A test is also added to ensure that data is not leaked by this cache or the cache used by
the ScriptService in core.

Closes elastic/elasticsearch#854

Original commit: elastic/x-pack-elasticsearch@8a48bdad98
This commit is contained in:
jaymode 2015-06-03 10:27:43 -04:00
parent 0f56bd37d8
commit 7c62e4c82c
2 changed files with 132 additions and 1 deletions

View File

@ -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.integration;
import org.elasticsearch.Version;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchPhaseExecutionException;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.test.ShieldIntegrationTest;
import org.elasticsearch.test.ShieldSettingsSource;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.Collections;
import static org.elasticsearch.shield.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
import static org.hamcrest.Matchers.*;
public class ShieldCachePermissionTests extends ShieldIntegrationTest {
static final String READ_ONE_IDX_USER = "read_user";
@Override
public String configUsers() {
return super.configUsers()
+ READ_ONE_IDX_USER + ":" + ShieldSettingsSource.DEFAULT_PASSWORD_HASHED + "\n";
}
@Override
public String configRoles() {
return super.configRoles()
+ "\nread_one_idx:\n"
+ " indices:\n"
+ " 'data': READ\n";
}
@Override
public String configUsersRoles() {
return super.configUsersRoles()
+ "read_one_idx:" + READ_ONE_IDX_USER + "\n";
}
@BeforeClass
public static void checkVersion() {
assumeTrue("These tests are only valid with elasticsearch 1.6.0+", Version.CURRENT.id >= 1060099);
}
@Before
public void loadData() {
index("data", "a", "1", "{ \"name\": \"John\", \"token\": \"token1\" }");
index("tokens", "tokens", "1", "{ \"group\": \"1\", \"tokens\": [\"token1\", \"token2\"] }");
client().preparePutIndexedScript().setOpType(IndexRequest.OpType.CREATE).setSource("{\n" +
"\"template\": {\n" +
" \"query\": {\n" +
" \"filtered\": {\n" +
" \"filter\": {\n" +
" \"exists\": {\n" +
" \"field\": \"{{name}}\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}")
.setScriptLang("mustache")
.setId("testTemplate")
.execute().actionGet();
refresh();
}
@Test
public void testThatTermsFilterQueryDoesntLeakData() {
SearchResponse response = client().prepareSearch("data").setTypes("a").setQuery(QueryBuilders.filteredQuery(
QueryBuilders.matchAllQuery(),
QueryBuilders.termsLookupQuery("token")
.lookupIndex("tokens")
.lookupType("tokens")
.lookupId("1")
.lookupPath("tokens")))
.execute().actionGet();
assertThat(response.isTimedOut(), is(false));
assertThat(response.getHits().hits().length, is(1));
// Repeat with unauthorized user!!!!
try {
client().prepareSearch("data").setTypes("a").setQuery(QueryBuilders.filteredQuery(
QueryBuilders.matchAllQuery(),
QueryBuilders.termsLookupQuery("token")
.lookupIndex("tokens")
.lookupType("tokens")
.lookupId("1")
.lookupPath("tokens")))
.putHeader("Authorization", basicAuthHeaderValue(READ_ONE_IDX_USER, new SecuredString("changeme".toCharArray())))
.execute().actionGet();
fail("search phase exception should have been thrown");
} catch (SearchPhaseExecutionException e) {
assertThat(e.toString(), containsString("AuthorizationException"));
}
}
@Test
public void testThatScriptServiceDoesntLeakData() {
SearchResponse response = client().prepareSearch("data").setTypes("a")
.setTemplateType(ScriptService.ScriptType.INDEXED)
.setTemplateName("testTemplate")
.setTemplateParams(Collections.<String, Object>singletonMap("name", "token"))
.execute().actionGet();
assertThat(response.isTimedOut(), is(false));
assertThat(response.getHits().hits().length, is(1));
// Repeat with unauthorized user!!!!
try {
client().prepareSearch("data").setTypes("a")
.setTemplateType(ScriptService.ScriptType.INDEXED)
.setTemplateName("testTemplate")
.setTemplateParams(Collections.<String, Object>singletonMap("name", "token"))
.putHeader("Authorization", basicAuthHeaderValue(READ_ONE_IDX_USER, new SecuredString("changeme".toCharArray())))
.execute().actionGet();
fail("search phase exception should have been thrown");
} catch (SearchPhaseExecutionException e) {
assertThat(e.toString(), containsString("AuthorizationException"));
}
}
}

View File

@ -46,7 +46,7 @@ public class ShieldSettingsSource extends ClusterDiscoveryConfiguration.UnicastZ
public static final String DEFAULT_USER_NAME = "test_user";
public static final String DEFAULT_PASSWORD = "changeme";
private static final String DEFAULT_PASSWORD_HASHED = new String(Hasher.BCRYPT.hash(new SecuredString(DEFAULT_PASSWORD.toCharArray())));
public static final String DEFAULT_PASSWORD_HASHED = new String(Hasher.BCRYPT.hash(new SecuredString(DEFAULT_PASSWORD.toCharArray())));
public static final String DEFAULT_ROLE = "user";
public static final String DEFAULT_TRANSPORT_CLIENT_ROLE = "trans_client_user";